From c05cf1d54959311309378364c441619756017aab Mon Sep 17 00:00:00 2001 From: ZhangJian He Date: Sat, 21 Sep 2024 20:02:27 +0800 Subject: [PATCH] feat: support query builder (#121) Signed-off-by: ZhangJian He --- ci/checkstyle/suppressions.xml | 3 + .../client/api/ArithmeticExpression.java | 35 ++++ .../client/api/ArithmeticOperator.java | 19 ++ .../opengemini/client/api/AsExpression.java | 28 +++ .../client/api/ComparisonCondition.java | 33 +++ .../client/api/ComparisonOperator.java | 20 ++ .../client/api/CompositeCondition.java | 24 +++ .../io/opengemini/client/api/Condition.java | 5 + .../client/api/ConstantExpression.java | 21 ++ .../io/opengemini/client/api/Expression.java | 5 + .../client/api/FieldExpression.java | 21 ++ .../opengemini/client/api/FunctionEnum.java | 12 ++ .../client/api/FunctionExpression.java | 39 ++++ .../client/api/LogicalOperator.java | 6 + .../opengemini/client/api/QueryBuilder.java | 124 +++++++++++ .../io/opengemini/client/api/SortOrder.java | 6 + .../opengemini/client/api/StarExpression.java | 11 + .../client/api/QueryBuilderTest.java | 198 ++++++++++++++++++ 18 files changed, 610 insertions(+) create mode 100644 opengemini-client-api/src/main/java/io/opengemini/client/api/ArithmeticExpression.java create mode 100644 opengemini-client-api/src/main/java/io/opengemini/client/api/ArithmeticOperator.java create mode 100644 opengemini-client-api/src/main/java/io/opengemini/client/api/AsExpression.java create mode 100644 opengemini-client-api/src/main/java/io/opengemini/client/api/ComparisonCondition.java create mode 100644 opengemini-client-api/src/main/java/io/opengemini/client/api/ComparisonOperator.java create mode 100644 opengemini-client-api/src/main/java/io/opengemini/client/api/CompositeCondition.java create mode 100644 opengemini-client-api/src/main/java/io/opengemini/client/api/Condition.java create mode 100644 opengemini-client-api/src/main/java/io/opengemini/client/api/ConstantExpression.java create mode 100644 opengemini-client-api/src/main/java/io/opengemini/client/api/Expression.java create mode 100644 opengemini-client-api/src/main/java/io/opengemini/client/api/FieldExpression.java create mode 100644 opengemini-client-api/src/main/java/io/opengemini/client/api/FunctionEnum.java create mode 100644 opengemini-client-api/src/main/java/io/opengemini/client/api/FunctionExpression.java create mode 100644 opengemini-client-api/src/main/java/io/opengemini/client/api/LogicalOperator.java create mode 100644 opengemini-client-api/src/main/java/io/opengemini/client/api/QueryBuilder.java create mode 100644 opengemini-client-api/src/main/java/io/opengemini/client/api/SortOrder.java create mode 100644 opengemini-client-api/src/main/java/io/opengemini/client/api/StarExpression.java create mode 100644 opengemini-client-api/src/test/java/io/opengemini/client/api/QueryBuilderTest.java diff --git a/ci/checkstyle/suppressions.xml b/ci/checkstyle/suppressions.xml index 5583c019..1a74538f 100644 --- a/ci/checkstyle/suppressions.xml +++ b/ci/checkstyle/suppressions.xml @@ -14,4 +14,7 @@ + + + diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/ArithmeticExpression.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/ArithmeticExpression.java new file mode 100644 index 00000000..69f3d3e7 --- /dev/null +++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/ArithmeticExpression.java @@ -0,0 +1,35 @@ +package io.opengemini.client.api; + +/** + * ArithmeticExpression represents an arithmetic operation between two expressions + */ +public class ArithmeticExpression implements Expression { + private final Expression left; + + private final Expression right; + + private final ArithmeticOperator operator; + + public ArithmeticExpression(Expression left, Expression right, ArithmeticOperator operator) { + this.left = left; + this.right = right; + this.operator = operator; + } + + public Expression left() { + return left; + } + + public Expression right() { + return right; + } + + public ArithmeticOperator operator() { + return operator; + } + + @Override + public String build() { + return "(" + left.build() + " " + operator.symbol() + " " + right.build() + ")"; + } +} diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/ArithmeticOperator.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/ArithmeticOperator.java new file mode 100644 index 00000000..c1cc3c87 --- /dev/null +++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/ArithmeticOperator.java @@ -0,0 +1,19 @@ +package io.opengemini.client.api; + +public enum ArithmeticOperator { + ADD("+"), + SUBTRACT("-"), + MULTIPLY("*"), + DIVIDE("/"), + MODULO("%"); + + private final String symbol; + + ArithmeticOperator(String symbol) { + this.symbol = symbol; + } + + public String symbol() { + return symbol; + } +} diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/AsExpression.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/AsExpression.java new file mode 100644 index 00000000..9e097bd1 --- /dev/null +++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/AsExpression.java @@ -0,0 +1,28 @@ +package io.opengemini.client.api; + +/** + * AsExpression represents an alias for an expression (e.g., SELECT field AS alias) + */ +public class AsExpression implements Expression { + private final String alias; + + private final Expression expression; + + public AsExpression(String alias, Expression expression) { + this.alias = alias; + this.expression = expression; + } + + public String alias() { + return alias; + } + + public Expression expression() { + return expression; + } + + @Override + public String build() { + return expression.build() + " AS \"" + alias + "\""; + } +} diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/ComparisonCondition.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/ComparisonCondition.java new file mode 100644 index 00000000..02126cc8 --- /dev/null +++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/ComparisonCondition.java @@ -0,0 +1,33 @@ +package io.opengemini.client.api; + +public class ComparisonCondition implements Condition { + private final String column; + + private final ComparisonOperator operator; + + private final Object value; + + public ComparisonCondition(String column, ComparisonOperator operator, Object value) { + this.column = column; + this.operator = operator; + this.value = value; + } + + @Override + public String build() { + StringBuilder conditionBuilder = new StringBuilder(); + conditionBuilder.append('"'); + conditionBuilder.append(column); + conditionBuilder.append('"'); + conditionBuilder.append(" "); + conditionBuilder.append(operator.symbol()); + conditionBuilder.append(" "); + if (value instanceof String) { + conditionBuilder.append("'").append(value).append("'"); + } else { + conditionBuilder.append(value); + } + + return conditionBuilder.toString(); + } +} diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/ComparisonOperator.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/ComparisonOperator.java new file mode 100644 index 00000000..33604093 --- /dev/null +++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/ComparisonOperator.java @@ -0,0 +1,20 @@ +package io.opengemini.client.api; + +public enum ComparisonOperator { + EQUALS("="), + NOT_EQUALS("<>"), + GREATER_THAN(">"), + LESS_THAN("<"), + GREATER_THAN_OR_EQUALS(">="), + LESS_THAN_OR_EQUALS("<="); + + private final String symbol; + + ComparisonOperator(String symbol) { + this.symbol = symbol; + } + + public String symbol() { + return symbol; + } +} diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/CompositeCondition.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/CompositeCondition.java new file mode 100644 index 00000000..427b6041 --- /dev/null +++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/CompositeCondition.java @@ -0,0 +1,24 @@ +package io.opengemini.client.api; + +import java.util.ArrayList; +import java.util.List; + +public class CompositeCondition implements Condition { + private final LogicalOperator logicalOperator; + + private final Condition[] conditions; + + public CompositeCondition(LogicalOperator logicalOperator, Condition... conditions) { + this.logicalOperator = logicalOperator; + this.conditions = conditions; + } + + @Override + public String build() { + List parts = new ArrayList<>(); + for (Condition condition : conditions) { + parts.add(condition.build()); + } + return "(" + String.join(" " + logicalOperator.name() + " ", parts) + ")"; + } +} diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/Condition.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/Condition.java new file mode 100644 index 00000000..cc568c6e --- /dev/null +++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/Condition.java @@ -0,0 +1,5 @@ +package io.opengemini.client.api; + +public interface Condition { + String build(); +} diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/ConstantExpression.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/ConstantExpression.java new file mode 100644 index 00000000..95e8df04 --- /dev/null +++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/ConstantExpression.java @@ -0,0 +1,21 @@ +package io.opengemini.client.api; + +/** + * ConstantExpression represents a constant value in the query + */ +public class ConstantExpression implements Expression { + private final Object value; + + public ConstantExpression(Object value) { + this.value = value; + } + + public Object value() { + return value; + } + + @Override + public String build() { + return value.toString(); + } +} diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/Expression.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/Expression.java new file mode 100644 index 00000000..01f2f255 --- /dev/null +++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/Expression.java @@ -0,0 +1,5 @@ +package io.opengemini.client.api; + +public interface Expression { + String build(); +} diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/FieldExpression.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/FieldExpression.java new file mode 100644 index 00000000..be5e8ef9 --- /dev/null +++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/FieldExpression.java @@ -0,0 +1,21 @@ +package io.opengemini.client.api; + +/** + * FieldExpression represents a column or field in the query + */ +public class FieldExpression implements Expression { + private final String field; + + public FieldExpression(String field) { + this.field = field; + } + + public String field() { + return field; + } + + @Override + public String build() { + return "\"" + field + "\""; + } +} diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/FunctionEnum.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/FunctionEnum.java new file mode 100644 index 00000000..27742860 --- /dev/null +++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/FunctionEnum.java @@ -0,0 +1,12 @@ +package io.opengemini.client.api; + +public enum FunctionEnum { + MEAN, + COUNT, + SUM, + MIN, + MAX, + TIME, + TOP, + LAST, +} diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/FunctionExpression.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/FunctionExpression.java new file mode 100644 index 00000000..a1f79d36 --- /dev/null +++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/FunctionExpression.java @@ -0,0 +1,39 @@ +package io.opengemini.client.api; + +/** + * FunctionExpression represents a function call with arguments (e.g., SUM, COUNT) + */ +public class FunctionExpression implements Expression { + private final FunctionEnum function; + + private final Expression[] arguments; + + public FunctionExpression(FunctionEnum function, Expression[] arguments) { + this.function = function; + this.arguments = arguments; + } + + public FunctionEnum function() { + return function; + } + + public Expression[] arguments() { + return arguments; + } + + @Override + public String build() { + StringBuilder builder = new StringBuilder(); + builder.append(function.name()).append("("); + + for (int i = 0; i < arguments.length; i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(arguments[i].build()); + } + + builder.append(")"); + return builder.toString(); + } +} diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/LogicalOperator.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/LogicalOperator.java new file mode 100644 index 00000000..7c480f33 --- /dev/null +++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/LogicalOperator.java @@ -0,0 +1,6 @@ +package io.opengemini.client.api; + +public enum LogicalOperator { + AND, + OR, +} diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/QueryBuilder.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/QueryBuilder.java new file mode 100644 index 00000000..30e6809b --- /dev/null +++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/QueryBuilder.java @@ -0,0 +1,124 @@ +package io.opengemini.client.api; + +public class QueryBuilder { + private Expression[] selectExprs; + + private String[] from; + + private Condition where; + + private Expression[] groupByExpressions; + + private SortOrder orderBy; + + private long limit; + + private long offset; + + private String timezone; + + private QueryBuilder() {} + + public static QueryBuilder create() { + return new QueryBuilder(); + } + + public QueryBuilder select(Expression[] selectExprs) { + this.selectExprs = selectExprs; + return this; + } + + public QueryBuilder from(String[] from) { + this.from = from; + return this; + } + + public QueryBuilder where(Condition where) { + this.where = where; + return this; + } + + public QueryBuilder groupBy(Expression[] groupByExpressions) { + this.groupByExpressions = groupByExpressions; + return this; + } + + public QueryBuilder orderBy(SortOrder order) { + this.orderBy = order; + return this; + } + + public QueryBuilder limit(long limit) { + this.limit = limit; + return this; + } + + public QueryBuilder offset(long offset) { + this.offset = offset; + return this; + } + + public QueryBuilder timezone(String timezone) { + this.timezone = timezone; + return this; + } + + public Query build() { + StringBuilder commandBuilder = new StringBuilder(); + + if (selectExprs != null && selectExprs.length > 0) { + commandBuilder.append("SELECT "); + for (int i = 0; i < selectExprs.length; i++) { + if (i > 0) { + commandBuilder.append(", "); + } + commandBuilder.append(selectExprs[i].build()); + } + } else { + commandBuilder.append("SELECT *"); + } + + if (from != null && from.length > 0) { + commandBuilder.append(" FROM "); + String[] quotedTables = new String[from.length]; + for (int i = 0; i < from.length; i++) { + quotedTables[i] = "\"" + from[i] + "\""; + } + commandBuilder.append(String.join(", ", quotedTables)); + } + + if (where != null) { + commandBuilder.append(" WHERE "); + commandBuilder.append(where.build()); + } + + if (groupByExpressions != null && groupByExpressions.length > 0) { + commandBuilder.append(" GROUP BY "); + for (int i = 0; i < groupByExpressions.length; i++) { + if (i > 0) { + commandBuilder.append(", "); + } + commandBuilder.append(groupByExpressions[i].build()); + } + } + + if (orderBy != null) { + commandBuilder.append(" ORDER BY time "); + commandBuilder.append(orderBy.name()); + } + + if (limit > 0) { + commandBuilder.append(" LIMIT ").append(limit); + } + + if (offset > 0) { + commandBuilder.append(" OFFSET ").append(offset); + } + + if (timezone != null) { + commandBuilder.append(" TZ('").append(timezone).append("')"); + } + + return new Query(commandBuilder.toString()); + } +} diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/SortOrder.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/SortOrder.java new file mode 100644 index 00000000..7ce48265 --- /dev/null +++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/SortOrder.java @@ -0,0 +1,6 @@ +package io.opengemini.client.api; + +public enum SortOrder { + ASC, + DESC, +} diff --git a/opengemini-client-api/src/main/java/io/opengemini/client/api/StarExpression.java b/opengemini-client-api/src/main/java/io/opengemini/client/api/StarExpression.java new file mode 100644 index 00000000..a28a6d0f --- /dev/null +++ b/opengemini-client-api/src/main/java/io/opengemini/client/api/StarExpression.java @@ -0,0 +1,11 @@ +package io.opengemini.client.api; + +/** + * StarExpression represents the wildcard (*) for selecting all columns + */ +public class StarExpression implements Expression { + @Override + public String build() { + return "*"; + } +} diff --git a/opengemini-client-api/src/test/java/io/opengemini/client/api/QueryBuilderTest.java b/opengemini-client-api/src/test/java/io/opengemini/client/api/QueryBuilderTest.java new file mode 100644 index 00000000..d71f071b --- /dev/null +++ b/opengemini-client-api/src/test/java/io/opengemini/client/api/QueryBuilderTest.java @@ -0,0 +1,198 @@ +package io.opengemini.client.api; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.TimeZone; + +public class QueryBuilderTest { + + @Test + public void testQueryBuilderSelectAllFromTable() { + Query query = QueryBuilder.create().from(new String[]{"h2o_feet"}).build(); + + String expectedQuery = "SELECT * FROM \"h2o_feet\""; + Assertions.assertEquals(expectedQuery, query.getCommand()); + } + + @Test + public void testQueryBuilderSelectTopFromTable() { + FunctionExpression topFunction = new FunctionExpression(FunctionEnum.TOP, new Expression[]{ + new FieldExpression("water_level"), + new ConstantExpression(5) + }); + + QueryBuilder queryBuilder = QueryBuilder.create(); + Query query = queryBuilder.select(new Expression[]{topFunction}).from(new String[]{"h2o_feet"}).build(); + + String expectedQuery = "SELECT TOP(\"water_level\", 5) FROM \"h2o_feet\""; + Assertions.assertEquals(expectedQuery, query.getCommand()); + } + + @Test + public void testQueryBuilderSelectLastAndTagFromTable() { + FunctionExpression lastFunction = new FunctionExpression(FunctionEnum.LAST, new Expression[]{ + new FieldExpression("water_level") + }); + + Query query = QueryBuilder.create().select(new Expression[]{ + lastFunction, + new FieldExpression("location") + }).from(new String[]{"h2o_feet"}).build(); + + String expectedQuery = "SELECT LAST(\"water_level\"), \"location\" FROM \"h2o_feet\""; + Assertions.assertEquals(expectedQuery, query.getCommand()); + } + + @Test + public void testQueryBuilderSelectWithArithmetic() { + FieldExpression waterLevelField = new FieldExpression("water_level"); + + ArithmeticExpression multipliedByFour = new ArithmeticExpression(waterLevelField, new ConstantExpression(4), ArithmeticOperator.MULTIPLY); + ArithmeticExpression addTwo = new ArithmeticExpression(multipliedByFour, new ConstantExpression(2), ArithmeticOperator.ADD); + + Query query = QueryBuilder.create().select(new Expression[]{addTwo}).from(new String[]{"h2o_feet"}).limit(10).build(); + + String expectedQuery = "SELECT ((\"water_level\" * 4) + 2) FROM \"h2o_feet\" LIMIT 10"; + Assertions.assertEquals(expectedQuery, query.getCommand()); + } + + @Test + public void testQueryBuilderSelectWhereCondition() { + ComparisonCondition condition = new ComparisonCondition("water_level", ComparisonOperator.GREATER_THAN, 8); + + Query query = QueryBuilder.create().from(new String[]{"h2o_feet"}).where(condition).build(); + + String expectedQuery = "SELECT * FROM \"h2o_feet\" WHERE \"water_level\" > 8"; + Assertions.assertEquals(expectedQuery, query.getCommand()); + } + + @Test + public void testQueryBuilderSelectWithComplexWhereCondition() { + ComparisonCondition locationCondition = new ComparisonCondition("location", ComparisonOperator.NOT_EQUALS, "santa_monica"); + ComparisonCondition lowerWaterLevelCondition = new ComparisonCondition("water_level", ComparisonOperator.LESS_THAN, -0.57); + ComparisonCondition higherWaterLevelCondition = new ComparisonCondition("water_level", ComparisonOperator.GREATER_THAN, 9.95); + + CompositeCondition waterLevelCondition = new CompositeCondition(LogicalOperator.OR, lowerWaterLevelCondition, higherWaterLevelCondition); + CompositeCondition finalCondition = new CompositeCondition(LogicalOperator.AND, locationCondition, waterLevelCondition); + + Query query = QueryBuilder.create().select(new Expression[]{new FieldExpression("water_level")}).from(new String[]{"h2o_feet"}).where(finalCondition).build(); + + String expectedQuery = "SELECT \"water_level\" FROM \"h2o_feet\" WHERE (\"location\" <> 'santa_monica' AND (\"water_level\" < -0.57 OR \"water_level\" > 9.95))"; + Assertions.assertEquals(expectedQuery, query.getCommand()); + } + + @Test + public void testQueryBuilderSelectWithGroupBy() { + FunctionExpression meanFunction = new FunctionExpression(FunctionEnum.MEAN, new Expression[]{new FieldExpression("water_level")}); + + Query query = QueryBuilder.create().select(new Expression[]{meanFunction}).from(new String[]{"h2o_feet"}).groupBy(new Expression[]{new FieldExpression("location")}).build(); + + String expectedQuery = "SELECT MEAN(\"water_level\") FROM \"h2o_feet\" GROUP BY \"location\""; + Assertions.assertEquals(expectedQuery, query.getCommand()); + } + + @Test + public void testQueryBuilderSelectWithTimeRangeAndGroupByTime() { + FunctionExpression countFunction = new FunctionExpression(FunctionEnum.COUNT, new Expression[]{new FieldExpression("water_level")}); + + ComparisonCondition startTimeCondition = new ComparisonCondition("time", ComparisonOperator.GREATER_THAN_OR_EQUALS, "2019-08-18T00:00:00Z"); + ComparisonCondition endTimeCondition = new ComparisonCondition("time", ComparisonOperator.LESS_THAN_OR_EQUALS, "2019-08-18T00:30:00Z"); + + CompositeCondition timeRangeCondition = new CompositeCondition(LogicalOperator.AND, startTimeCondition, endTimeCondition); + FunctionExpression groupByTime = new FunctionExpression(FunctionEnum.TIME, new Expression[]{new ConstantExpression("12m")}); + + Query query = QueryBuilder.create().select(new Expression[]{countFunction}).from(new String[]{"h2o_feet"}).where(timeRangeCondition).groupBy(new Expression[]{groupByTime}).build(); + + String expectedQuery = "SELECT COUNT(\"water_level\") FROM \"h2o_feet\" WHERE (\"time\" >= '2019-08-18T00:00:00Z' AND \"time\" <= '2019-08-18T00:30:00Z') GROUP BY TIME(12m)"; + Assertions.assertEquals(expectedQuery, query.getCommand()); + } + + @Test + public void testQueryBuilderSelectWithLimitAndOffset() { + FieldExpression waterLevelField = new FieldExpression("water_level"); + FieldExpression locationField = new FieldExpression("location"); + + Query query = QueryBuilder.create().select(new Expression[]{waterLevelField, locationField}).from(new String[]{"h2o_feet"}).limit(3).offset(3).build(); + + String expectedQuery = "SELECT \"water_level\", \"location\" FROM \"h2o_feet\" LIMIT 3 OFFSET 3"; + Assertions.assertEquals(expectedQuery, query.getCommand()); + } + + @Test + public void testQueryBuilderSelectWithWhereAndTimezone() { + QueryBuilder qb = QueryBuilder.create(); + + FieldExpression waterLevelField = new FieldExpression("water_level"); + + ComparisonCondition locationCondition = new ComparisonCondition("location", ComparisonOperator.EQUALS, "santa_monica"); + ComparisonCondition startTimeCondition = new ComparisonCondition("time", ComparisonOperator.GREATER_THAN_OR_EQUALS, "2019-08-18T00:00:00Z"); + ComparisonCondition endTimeCondition = new ComparisonCondition("time", ComparisonOperator.LESS_THAN_OR_EQUALS, "2019-08-18T00:18:00Z"); + + CompositeCondition finalCondition = new CompositeCondition(LogicalOperator.AND, locationCondition, startTimeCondition, endTimeCondition); + + TimeZone timeZone = TimeZone.getTimeZone("America/Chicago"); + + Query query = qb.select(new Expression[]{waterLevelField}) + .from(new String[]{"h2o_feet"}) + .where(finalCondition) + .timezone(timeZone.getID()) + .build(); + + String expectedQuery = "SELECT \"water_level\" FROM \"h2o_feet\" WHERE (\"location\" = 'santa_monica' AND \"time\" >= '2019-08-18T00:00:00Z' AND \"time\" <= '2019-08-18T00:18:00Z') TZ('America/Chicago')"; + Assertions.assertEquals(expectedQuery, query.getCommand()); + } + + @Test + public void testQueryBuilderSelectWithAsExpression() { + QueryBuilder qb = QueryBuilder.create(); + + FieldExpression waterLevelField = new FieldExpression("water_level"); + + ComparisonCondition locationCondition = new ComparisonCondition("location", ComparisonOperator.EQUALS, "santa_monica"); + ComparisonCondition startTimeCondition = new ComparisonCondition("time", ComparisonOperator.GREATER_THAN_OR_EQUALS, "2019-08-18T00:00:00Z"); + ComparisonCondition endTimeCondition = new ComparisonCondition("time", ComparisonOperator.LESS_THAN_OR_EQUALS, "2019-08-18T00:18:00Z"); + + CompositeCondition finalCondition = new CompositeCondition(LogicalOperator.AND, locationCondition, startTimeCondition, endTimeCondition); + + TimeZone timeZone = TimeZone.getTimeZone("America/Chicago"); + + AsExpression asWL = new AsExpression("WL", waterLevelField); + + Query query = qb.select(new Expression[]{asWL}) + .from(new String[]{"h2o_feet"}) + .where(finalCondition) + .timezone(timeZone.getID()) + .build(); + + String expectedQuery = "SELECT \"water_level\" AS \"WL\" FROM \"h2o_feet\" WHERE (\"location\" = 'santa_monica' AND \"time\" >= '2019-08-18T00:00:00Z' AND \"time\" <= '2019-08-18T00:18:00Z') TZ('America/Chicago')"; + Assertions.assertEquals(expectedQuery, query.getCommand()); + } + + @Test + public void testQueryBuilderSelectWithAggregate() { + QueryBuilder qb = QueryBuilder.create(); + + FieldExpression waterLevelField = new FieldExpression("water_level"); + FunctionExpression countWaterLevelField = new FunctionExpression(FunctionEnum.COUNT, new Expression[]{waterLevelField}); + + ComparisonCondition locationCondition = new ComparisonCondition("location", ComparisonOperator.EQUALS, "santa_monica"); + ComparisonCondition startTimeCondition = new ComparisonCondition("time", ComparisonOperator.GREATER_THAN_OR_EQUALS, "2019-08-18T00:00:00Z"); + ComparisonCondition endTimeCondition = new ComparisonCondition("time", ComparisonOperator.LESS_THAN_OR_EQUALS, "2019-08-18T00:18:00Z"); + + CompositeCondition finalCondition = new CompositeCondition(LogicalOperator.AND, locationCondition, startTimeCondition, endTimeCondition); + + TimeZone timeZone = TimeZone.getTimeZone("America/Chicago"); + + AsExpression asWL = new AsExpression("WL", countWaterLevelField); + + Query query = qb.select(new Expression[]{asWL}) + .from(new String[]{"h2o_feet"}) + .where(finalCondition) + .timezone(timeZone.getID()) + .build(); + + String expectedQuery = "SELECT COUNT(\"water_level\") AS \"WL\" FROM \"h2o_feet\" WHERE (\"location\" = 'santa_monica' AND \"time\" >= '2019-08-18T00:00:00Z' AND \"time\" <= '2019-08-18T00:18:00Z') TZ('America/Chicago')"; + Assertions.assertEquals(expectedQuery, query.getCommand()); + } +}