diff --git a/doc/user_guide/statements/merge.md b/doc/user_guide/statements/merge.md index 19bdd1e0..d97669c9 100644 --- a/doc/user_guide/statements/merge.md +++ b/doc/user_guide/statements/merge.md @@ -47,6 +47,20 @@ merge.whenNotMatched() Check the JavaDoc of class `Merge` for a more information. +### Using a Filter on a `MERGE` Strategy + +All three merge strategies can be narrowed down with a `WHERE` clause, to limit the dataset they work on. + +Here is an example for a `MERGE` statement with a `DELETE` strategy. + +```java +merge.using("src") // + .on(eq(column("src", "c1"), column("dst", "c1"))) // + .whenMatched() // + .thenDelete() // + .where(gt(column("src", "c5"), integerLiteral(1000))); +``` + ### Rendering `MERGE` Statements Use the `MergeRenderer` to render `Merge` objects into SQL strings. diff --git a/pom.xml b/pom.xml index 4311136e..a0bd373a 100644 --- a/pom.xml +++ b/pom.xml @@ -1,9 +1,10 @@ - 4.0.0 com.exasol sql-statement-builder - 2.0.0 + 3.0.0 Exasol SQL Statement Builder This module provides a Builder for SQL statements that helps creating the correct structure and validates variable parts of the statements. https://github.com/exasol/sql-statement-builder @@ -100,6 +101,17 @@ 1.3 test + + org.mockito + mockito-core + 2.24.0 + + + org.mockito + mockito-junit-jupiter + 2.23.4 + test + nl.jqno.equalsverifier equalsverifier @@ -169,7 +181,7 @@ UTF-8 - + true true true diff --git a/src/main/java/com/exasol/sql/dml/insert/AbstractInsertValueTable.java b/src/main/java/com/exasol/sql/dml/insert/AbstractInsertValueTable.java index 259dc6e8..2033522d 100644 --- a/src/main/java/com/exasol/sql/dml/insert/AbstractInsertValueTable.java +++ b/src/main/java/com/exasol/sql/dml/insert/AbstractInsertValueTable.java @@ -13,7 +13,7 @@ public abstract class AbstractInsertValueTablethis for fluent programming */ public synchronized T field(final String... names) { - if (this.insertFields == null) { + if (!hasFields()) { this.insertFields = new InsertFields(this.getRoot()); } this.insertFields.add(names); @@ -49,7 +49,7 @@ public synchronized T field(final String... names) { * @return this for fluent programming */ public synchronized T valueTable(final ValueTable table) { - if (this.insertValueTable != null) { + if (hasValues()) { throw new IllegalStateException("Cannot add a value table to an INSERT command that already has one."); } this.insertValueTable = table; @@ -108,4 +108,22 @@ public synchronized T valuePlaceholders(final int amount) { } return self(); } -} + + /** + * Check if a value table is present. + * + * @return {@code true} if a value table exists + */ + public boolean hasValues() { + return this.insertValueTable != null; + } + + /** + * Check if a insert fields are defined. + * + * @return {@code true} if insert fields are defined + */ + public boolean hasFields() { + return this.insertFields != null; + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/sql/dml/merge/Merge.java b/src/main/java/com/exasol/sql/dml/merge/Merge.java index 071542e9..8c0616aa 100644 --- a/src/main/java/com/exasol/sql/dml/merge/Merge.java +++ b/src/main/java/com/exasol/sql/dml/merge/Merge.java @@ -12,7 +12,6 @@ public class Merge extends AbstractFragment implements SqlStatement, MergeFragme private final Table destinationTable; private UsingClause using; private OnClause on; - private BooleanExpression condition; private MatchedClause matched; private NotMatchedClause notMatched; @@ -60,6 +59,24 @@ public Merge using(final String sourceTable, final String as) { return this; } + /** + * Get the {@code USING} clause of the {@code MERGE} statement. + * + * @return destination table + */ + public UsingClause getUsing() { + return this.using; + } + + /** + * Check if the {@code USING} clause exists. + * + * @return {@code true} if the {@code USING} clause exists. + */ + protected boolean hasUsing() { + return this.using != null; + } + /** * Define the merge criteria. * @@ -71,42 +88,60 @@ public Merge on(final BooleanExpression condition) { return this; } + /** + * Get the merge criteria (i.e. the `ON` clause). + * + * @return criteria that must be met for the rows in source and destination to be considered a match. + */ + public OnClause getOn() { + return this.on; + } + + /** + * Check if the {@code ON} clause exists. + * + * @return {@code true} if the {@code ON} clause exists. + */ + protected boolean hasOn() { + return this.on != null; + } + /** * Define the merge strategy if the match criteria is met. * * @return match strategy */ public MatchedClause whenMatched() { - this.matched = new MatchedClause(this.root); + this.matched = new MatchedClause(this); return this.matched; } /** - * Define the merge strategy if the match criteria is not met. + * Check if the {@code WHEN MATCHED} clause exists. * - * @return not matched strategy + * @return {@code true} if the {@code WHEN MATCHED} clause exists */ - public NotMatchedClause whenNotMatched() { - this.notMatched = new NotMatchedClause(this.root); - return this.notMatched; + protected boolean hasMatched() { + return this.matched != null; } /** - * Get the {@code USING} clause of the {@code MERGE} statement. + * Define the merge strategy if the match criteria is not met. * - * @return destination table + * @return not matched strategy */ - public UsingClause getUsing() { - return this.using; + public NotMatchedClause whenNotMatched() { + this.notMatched = new NotMatchedClause(this); + return this.notMatched; } /** - * Get the merge condition. + * Check if the {@code WHEN NOT MATCHED} clause exists. * - * @return criteria that must be met for the rows in source and destination to be considered a match. + * @return true if the {@code WHEN NOT MATCHED} clause exists */ - public BooleanExpression getCondition() { - return this.condition; + protected boolean hasNotMatched() { + return this.notMatched != null; } @Override @@ -115,16 +150,16 @@ public void accept(final MergeVisitor visitor) { if (this.destinationTable != null) { this.destinationTable.accept(visitor); } - if (this.using != null) { + if (hasUsing()) { this.using.accept(visitor); } - if (this.on != null) { + if (hasOn()) { this.on.accept(visitor); } - if (this.matched != null) { + if (hasMatched()) { this.matched.accept(visitor); } - if (this.notMatched != null) { + if (hasNotMatched()) { this.notMatched.accept(visitor); } } diff --git a/src/main/java/com/exasol/sql/dml/merge/MergeDeleteClause.java b/src/main/java/com/exasol/sql/dml/merge/MergeDeleteClause.java index a90edde7..651c6ff2 100644 --- a/src/main/java/com/exasol/sql/dml/merge/MergeDeleteClause.java +++ b/src/main/java/com/exasol/sql/dml/merge/MergeDeleteClause.java @@ -1,12 +1,11 @@ package com.exasol.sql.dml.merge; -import com.exasol.sql.AbstractFragment; import com.exasol.sql.Fragment; /** * This class represents the {@code MERGE} strategy of deleting matched rows. */ -public class MergeDeleteClause extends AbstractFragment implements MergeFragment { +public class MergeDeleteClause extends MergeMethodDefinition implements MergeFragment { /** * Create a new instance of a {@link MergeDeleteClause}. * @@ -19,5 +18,8 @@ public MergeDeleteClause(final Fragment root) { @Override public void accept(final MergeVisitor visitor) { visitor.visit(this); + if (hasWhere()) { + this.where.accept(visitor); + } } } \ No newline at end of file diff --git a/src/main/java/com/exasol/sql/dml/merge/MergeInsertClause.java b/src/main/java/com/exasol/sql/dml/merge/MergeInsertClause.java index 193269ad..c42cbe0c 100644 --- a/src/main/java/com/exasol/sql/dml/merge/MergeInsertClause.java +++ b/src/main/java/com/exasol/sql/dml/merge/MergeInsertClause.java @@ -2,11 +2,15 @@ import com.exasol.sql.Fragment; import com.exasol.sql.dml.insert.AbstractInsertValueTable; +import com.exasol.sql.dql.select.WhereClause; +import com.exasol.sql.expression.BooleanExpression; /** * Represents the {@code MERGE} strategy of inserting rows from the source that do not match any row in the destination. */ public class MergeInsertClause extends AbstractInsertValueTable implements MergeFragment { + protected WhereClause where = null; + /** * Create a new instance of a {@link MergeInsertClause}. * @@ -21,14 +25,47 @@ protected MergeInsertClause self() { return this; } + /** + * Add a {@code WHERE} clause insertion definition. + * + * @param expression filter expression + * @return parent {@code MERGE} statement + */ + public Merge where(final BooleanExpression expression) { + final Merge merge = (Merge) this.getRoot(); + this.where = new WhereClause(merge, expression); + return merge; + } + + /** + * Get the {@code WHERE} clause of the insert definition. + * + * @return {@code WHERE} clause + */ + public WhereClause getWhere() { + return this.where; + } + + /** + * Check if the {@code WHERE} clause exists. + * + * @return {@code true} if the {@code WHERE} clause exists + */ + public boolean hasWhere() { + return this.where != null; + } + @Override public void accept(final MergeVisitor visitor) { visitor.visit(this); - if (this.insertFields != null) { + if (hasFields()) { this.insertFields.accept(visitor); } - if (this.insertValueTable != null) { + if (hasValues()) { this.insertValueTable.accept(visitor); } + if (hasWhere()) { + this.where.accept(visitor); + } } } \ No newline at end of file diff --git a/src/main/java/com/exasol/sql/dml/merge/MergeMethodDefinition.java b/src/main/java/com/exasol/sql/dml/merge/MergeMethodDefinition.java new file mode 100644 index 00000000..da906920 --- /dev/null +++ b/src/main/java/com/exasol/sql/dml/merge/MergeMethodDefinition.java @@ -0,0 +1,52 @@ +package com.exasol.sql.dml.merge; + +import com.exasol.sql.AbstractFragment; +import com.exasol.sql.Fragment; +import com.exasol.sql.dql.select.WhereClause; +import com.exasol.sql.expression.BooleanExpression; + +/** + * Abstract base class for merge method definitions like {@code WHEN MATCHED THEN UPDATE}. + */ +public abstract class MergeMethodDefinition extends AbstractFragment { + protected WhereClause where = null; + + /** + * Create the abstract base for a merge method definition. + * + * @param root root {@code MERGE} statement + */ + public MergeMethodDefinition(final Fragment root) { + super(root); + } + + /** + * Add a {@code WHERE} clause {@code MERGE} definition. + * + * @param expression filter expression + * @return parent {@code MERGE} statement + */ + public Merge where(final BooleanExpression expression) { + final Merge merge = (Merge) this.getRoot(); + this.where = new WhereClause(merge, expression); + return merge; + } + + /** + * Get the {@code WHERE} clause of the merge method definition. + * + * @return {@code WHERE} clause + */ + public WhereClause getWhere() { + return this.where; + } + + /** + * Check if the {@code WHERE} clause exists. + * + * @return {@code true} if the {@code WHERE} clause exists + */ + public boolean hasWhere() { + return this.where != null; + } +} diff --git a/src/main/java/com/exasol/sql/dml/merge/MergeUpdateClause.java b/src/main/java/com/exasol/sql/dml/merge/MergeUpdateClause.java index 92aa6e9d..e8df6f0a 100644 --- a/src/main/java/com/exasol/sql/dml/merge/MergeUpdateClause.java +++ b/src/main/java/com/exasol/sql/dml/merge/MergeUpdateClause.java @@ -3,14 +3,13 @@ import java.util.ArrayList; import java.util.List; -import com.exasol.sql.AbstractFragment; import com.exasol.sql.Fragment; import com.exasol.sql.expression.*; /** * Represents the {@code MERGE} strategy of updating matched rows. */ -public class MergeUpdateClause extends AbstractFragment implements MergeFragment { +public class MergeUpdateClause extends MergeMethodDefinition implements MergeFragment { private final List columnUpdates = new ArrayList<>(); /** @@ -67,5 +66,8 @@ public void accept(final MergeVisitor visitor) { for (final MergeColumnUpdate columnUpdate : this.columnUpdates) { columnUpdate.accept(visitor); } + if (hasWhere()) { + this.where.accept(visitor); + } } } \ No newline at end of file diff --git a/src/main/java/com/exasol/sql/dml/merge/MergeVisitor.java b/src/main/java/com/exasol/sql/dml/merge/MergeVisitor.java index ad263aad..e9aee734 100644 --- a/src/main/java/com/exasol/sql/dml/merge/MergeVisitor.java +++ b/src/main/java/com/exasol/sql/dml/merge/MergeVisitor.java @@ -2,6 +2,7 @@ import com.exasol.sql.*; import com.exasol.sql.dml.insert.InsertFields; +import com.exasol.sql.dql.select.WhereClause; /** * Visitor for value tables. @@ -93,8 +94,15 @@ public interface MergeVisitor extends ValueTableVisitor { /** * Visit a field reference. - * + * * @param field field reference */ public void visit(final Field field); + + /** + * Visit a {@code WHERE} clause. + * + * @param whereClause the {@code WHERE} clause to be visited + */ + public void visit(WhereClause whereClause); } \ No newline at end of file diff --git a/src/main/java/com/exasol/sql/dml/merge/rendering/MergeRenderer.java b/src/main/java/com/exasol/sql/dml/merge/rendering/MergeRenderer.java index 7530f27b..798209aa 100644 --- a/src/main/java/com/exasol/sql/dml/merge/rendering/MergeRenderer.java +++ b/src/main/java/com/exasol/sql/dml/merge/rendering/MergeRenderer.java @@ -4,6 +4,7 @@ import com.exasol.sql.dml.insert.InsertFields; import com.exasol.sql.dml.insert.rendering.InsertRenderer; import com.exasol.sql.dml.merge.*; +import com.exasol.sql.dql.select.WhereClause; import com.exasol.sql.rendering.AbstractFragmentRenderer; import com.exasol.sql.rendering.StringRendererConfig; @@ -150,4 +151,11 @@ public void leave(final ValueTableRow valueTableRow) { append(")"); setLastVisited(valueTableRow); } -} + + @Override + public void visit(final WhereClause whereClause) { + appendKeyWord(" WHERE "); + appendRenderedBooleanExpression(whereClause.getExpression()); + setLastVisited(whereClause); + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/sql/dql/select/WhereClause.java b/src/main/java/com/exasol/sql/dql/select/WhereClause.java index a8c95c2b..3e853035 100644 --- a/src/main/java/com/exasol/sql/dql/select/WhereClause.java +++ b/src/main/java/com/exasol/sql/dql/select/WhereClause.java @@ -2,19 +2,21 @@ import com.exasol.sql.AbstractFragment; import com.exasol.sql.SqlStatement; +import com.exasol.sql.dml.merge.MergeFragment; +import com.exasol.sql.dml.merge.MergeVisitor; import com.exasol.sql.expression.BooleanExpression; /** * This class represents the where clause of an SQL statement. It contains the filter criteria in form of a * {@link BooleanExpression}. */ -public class WhereClause extends AbstractFragment implements SelectFragment { +public class WhereClause extends AbstractFragment implements SelectFragment, MergeFragment { private final BooleanExpression expression; /** * Create a new instance of a {@link WhereClause} * - * @param root SQL statement this WHERE clause belongs to + * @param root SQL statement this WHERE clause belongs to * @param expression boolean expression servicing as criteria for the WHERE clause */ public WhereClause(final SqlStatement root, final BooleanExpression expression) { @@ -31,8 +33,23 @@ public BooleanExpression getExpression() { return this.expression; } + /** + * Accept a visitor for {@code SELECT} statements + * + * @param visitor {@code SELECT} visitor + */ @Override public void accept(final SelectVisitor visitor) { visitor.visit(this); } + + /** + * Accept a visitor for {@code MERGE} statements + * + * @param visitor {@code MERGE} visitor + */ + @Override + public void accept(final MergeVisitor visitor) { + visitor.visit(this); + } } \ No newline at end of file diff --git a/src/main/java/com/exasol/sql/expression/ExpressionTerm.java b/src/main/java/com/exasol/sql/expression/ExpressionTerm.java index e0e8ceee..1398a0a7 100644 --- a/src/main/java/com/exasol/sql/expression/ExpressionTerm.java +++ b/src/main/java/com/exasol/sql/expression/ExpressionTerm.java @@ -34,18 +34,18 @@ public static IntegerLiteral integerLiteral(final int value) { * @param column column name * @return column reference */ - public static ColumnReference columnReference(final String column) { + public static ColumnReference column(final String column) { return ColumnReference.of(column); } /** * Create a reference to a column in a specific table. - * + * * @param column column name * @param table table name * @return column reference */ - public static ColumnReference columnReference(final String column, final String table) { + public static ColumnReference column(final String column, final String table) { return ColumnReference.column(table, column); } } \ No newline at end of file diff --git a/src/test/java/com/exasol/sql/dml/merge/MergeMethodDefinitionTest.java b/src/test/java/com/exasol/sql/dml/merge/MergeMethodDefinitionTest.java new file mode 100644 index 00000000..b99cb7f9 --- /dev/null +++ b/src/test/java/com/exasol/sql/dml/merge/MergeMethodDefinitionTest.java @@ -0,0 +1,49 @@ +package com.exasol.sql.dml.merge; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.exasol.sql.Fragment; +import com.exasol.sql.expression.BooleanLiteral; + +@ExtendWith(MockitoExtension.class) +public class MergeMethodDefinitionTest { + private DummyMergeMethod mergeMethod; + @Mock + private Merge merge; + + @BeforeEach + public void beforeEach() { + this.mergeMethod = new DummyMergeMethod(this.merge); + } + + @Test + public void testGetWhere() throws Exception { + this.mergeMethod.where(BooleanLiteral.of(true)); + assertThat(this.mergeMethod.getWhere().getExpression(), instanceOf(BooleanLiteral.class)); + } + + @Test + public void testHasWhereFalseByDefault() throws Exception { + assertThat(this.mergeMethod.hasWhere(), equalTo(false)); + } + + @Test + public void testHasWhere() throws Exception { + this.mergeMethod.where(BooleanLiteral.of(true)); + assertThat(this.mergeMethod.hasWhere(), equalTo(true)); + } + + private static class DummyMergeMethod extends MergeMethodDefinition { + public DummyMergeMethod(final Fragment root) { + super(root); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/sql/dml/merge/rendering/TestMergeRendering.java b/src/test/java/com/exasol/sql/dml/merge/rendering/TestMergeRendering.java index 66b08428..b8c5e120 100644 --- a/src/test/java/com/exasol/sql/dml/merge/rendering/TestMergeRendering.java +++ b/src/test/java/com/exasol/sql/dml/merge/rendering/TestMergeRendering.java @@ -2,7 +2,9 @@ import static com.exasol.hamcrest.SqlFragmentRenderResultMatcher.rendersTo; import static com.exasol.sql.expression.BooleanTerm.eq; -import static com.exasol.sql.expression.ColumnReference.column; +import static com.exasol.sql.expression.BooleanTerm.gt; +import static com.exasol.sql.expression.ExpressionTerm.column; +import static com.exasol.sql.expression.ExpressionTerm.integerLiteral; import static org.hamcrest.MatcherAssert.assertThat; import org.junit.jupiter.api.BeforeEach; @@ -29,7 +31,7 @@ void testMerge() { void testMergeIntoUsingOn() { assertThat(this.merge // .using("src") // - .on(eq(column("src", "c1"), column("dst", "c1"))), + .on(eq(column("c1", "src"), column("c1", "dst"))), rendersTo("MERGE INTO dst USING src ON src.c1 = dst.c1")); } @@ -37,7 +39,7 @@ void testMergeIntoUsingOn() { void testMergeWhenMatchedUpdate() { this.merge // .using("src") // - .on(eq(column("src", "c1"), column("dst", "c1"))) // + .on(eq(column("c1", "src"), column("c1", "dst"))) // .whenMatched() // .thenUpdate() // .setToDefault("c2") // @@ -48,23 +50,51 @@ void testMergeWhenMatchedUpdate() { } + @Test + void testMergeWhenMatchedUpdateWhere() { + this.merge // + .using("src") // + .on(eq(column("c1", "src"), column("c1", "dst"))) // + .whenMatched() // + .thenUpdate() // + .setToDefault("c2") // + .set("c3", "foo") // + .set("c4", 42).where(gt(column("c5", "src"), integerLiteral(1000))); + assertThat(this.merge, rendersTo("MERGE INTO dst USING src ON src.c1 = dst.c1" // + + " WHEN MATCHED THEN UPDATE SET c2 = DEFAULT, c3 = 'foo', c4 = 42" // + + " WHERE src.c5 > 1000")); + } + @Test void testMergeWhenMatchedDelete() { this.merge // .using("src") // - .on(eq(column("src", "c1"), column("dst", "c1"))) // + .on(eq(column("c1", "src"), column("c1", "dst"))) // .whenMatched() // - .thenDelete(); // + .thenDelete(); assertThat(this.merge, rendersTo("MERGE INTO dst USING src ON src.c1 = dst.c1" // + " WHEN MATCHED THEN DELETE")); } + @Test + void testMergeWhenMatchedDeleteWhere() { + this.merge // + .using("src") // + .on(eq(column("c1", "src"), column("c1", "dst"))) // + .whenMatched() // + .thenDelete() // + .where(gt(column("c5", "src"), integerLiteral(1000))); + assertThat(this.merge, rendersTo("MERGE INTO dst USING src ON src.c1 = dst.c1" // + + " WHEN MATCHED THEN DELETE WHERE src.c5 > 1000")); + + } + @Test void testMergeWhenNotMatchedInsertValues() { this.merge // .using("src") // - .on(eq(column("src", "c1"), column("dst", "c1"))) // + .on(eq(column("c1", "src"), column("c1", "dst"))) // .whenNotMatched() // .thenInsert() // .values("foo", "bar"); @@ -72,11 +102,24 @@ void testMergeWhenNotMatchedInsertValues() { + " WHEN NOT MATCHED THEN INSERT VALUES ('foo', 'bar')")); } + @Test + void testMergeWhenNotMatchedInsertValuesWhere() { + this.merge // + .using("src") // + .on(eq(column("c1", "src"), column("c1", "dst"))) // + .whenNotMatched() // + .thenInsert() // + .values("foo", "bar") // + .where(gt(column("c5", "src"), integerLiteral(1000))); + assertThat(this.merge, rendersTo("MERGE INTO dst USING src ON src.c1 = dst.c1" // + + " WHEN NOT MATCHED THEN INSERT VALUES ('foo', 'bar') WHERE src.c5 > 1000")); + } + @Test void testMergeWhenNotMatchedInsertFieldValues() { this.merge // .using("src") // - .on(eq(column("src", "c1"), column("dst", "c1"))) // + .on(eq(column("c1", "src"), column("c1", "dst"))) // .whenNotMatched() // .thenInsert() // .field("c3", "c4") // @@ -88,7 +131,7 @@ void testMergeWhenNotMatchedInsertFieldValues() { @Test void testComplexMerge() { final Merge complexMerge = StatementFactory.getInstance().mergeInto("dst", "t1").using("src", "t2") // - .on(eq(column("t1", "c1"), column("t2", "c1"))); + .on(eq(column("c1", "t1"), column("c1", "t2"))); complexMerge.whenMatched() // .thenUpdate() // .setToDefault("c2") // diff --git a/src/test/java/com/exasol/sql/dql/select/rendering/TestGroupByRendering.java b/src/test/java/com/exasol/sql/dql/select/rendering/TestGroupByRendering.java index 20a8abf8..4def2cbf 100644 --- a/src/test/java/com/exasol/sql/dql/select/rendering/TestGroupByRendering.java +++ b/src/test/java/com/exasol/sql/dql/select/rendering/TestGroupByRendering.java @@ -22,45 +22,42 @@ void beforeEach() { @Test void testGroupByClause() { - assertThat(this.select.groupBy(columnReference("city")), rendersTo("SELECT * FROM t GROUP BY city")); + assertThat(this.select.groupBy(column("city")), rendersTo("SELECT * FROM t GROUP BY city")); } @Test void testGroupByClause2() { - assertThat(this.select.groupBy(columnReference("city", "t")), rendersTo("SELECT * FROM t GROUP BY t.city")); + assertThat(this.select.groupBy(column("city", "t")), rendersTo("SELECT * FROM t GROUP BY t.city")); } @Test void testGroupByClauseMultipleColumns() { - assertThat(this.select.groupBy(columnReference("city", "t"), columnReference("order", "t"), - columnReference("price", "t")), rendersTo("SELECT * FROM t GROUP BY t.city, t.order, t.price")); + assertThat(this.select.groupBy(column("city", "t"), column("order", "t"), column("price", "t")), + rendersTo("SELECT * FROM t GROUP BY t.city, t.order, t.price")); } @Test void testGroupByClauseMultipleColumnsWithHaving() { assertThat( - this.select - .groupBy(columnReference("city", "t"), columnReference("order", "t"), - columnReference("price", "t")) - .having(lt(columnReference("price", "t"), integerLiteral(10))), + this.select.groupBy(column("city", "t"), column("order", "t"), column("price", "t")) + .having(lt(column("price", "t"), integerLiteral(10))), rendersTo("SELECT * FROM t GROUP BY t.city, t.order, t.price HAVING t.price < 10")); } @Test void testGroupByClauseMultipleColumnsWithMultipleHaving() { assertThat( - this.select.groupBy(columnReference("city"), columnReference("order"), columnReference("price")) - .having(and(le(columnReference("price", "t"), integerLiteral(10)), - ne(columnReference("price", "t"), integerLiteral(5)))), + this.select.groupBy(column("city"), column("order"), column("price")).having( + and(le(column("price", "t"), integerLiteral(10)), ne(column("price", "t"), integerLiteral(5)))), rendersTo("SELECT * FROM t GROUP BY city, order, price HAVING (t.price <= 10) AND (t.price <> 5)")); } @Test void testGroupByClauseMultipleColumnsWithMultipleHaving2() { assertThat( - this.select.groupBy(columnReference("city"), columnReference("order"), columnReference("price")) - .having(or(eq(columnReference("city", "t"), stringLiteral("NEW YORK")), - eq(columnReference("city", "t"), stringLiteral("MOSCOW")))), + this.select.groupBy(column("city"), column("order"), column("price")) + .having(or(eq(column("city", "t"), stringLiteral("NEW YORK")), + eq(column("city", "t"), stringLiteral("MOSCOW")))), rendersTo( "SELECT * FROM t GROUP BY city, order, price HAVING (t.city = 'NEW YORK') OR (t.city = 'MOSCOW')")); } diff --git a/src/test/java/com/exasol/sql/dql/select/rendering/TestOrderByRendering.java b/src/test/java/com/exasol/sql/dql/select/rendering/TestOrderByRendering.java index 24d46dd4..5d4ca492 100644 --- a/src/test/java/com/exasol/sql/dql/select/rendering/TestOrderByRendering.java +++ b/src/test/java/com/exasol/sql/dql/select/rendering/TestOrderByRendering.java @@ -1,7 +1,7 @@ package com.exasol.sql.dql.select.rendering; import static com.exasol.hamcrest.SqlFragmentRenderResultMatcher.rendersTo; -import static com.exasol.sql.expression.ExpressionTerm.columnReference; +import static com.exasol.sql.expression.ExpressionTerm.column; import static org.hamcrest.MatcherAssert.assertThat; import org.junit.jupiter.api.BeforeEach; @@ -21,49 +21,49 @@ void beforeEach() { @Test void testOrderByClause() { - assertThat(this.select.orderBy(columnReference("city", "t"), columnReference("price", "t")), + assertThat(this.select.orderBy(column("city", "t"), column("price", "t")), rendersTo("SELECT * FROM t ORDER BY t.city, t.price")); } @Test void testOrderByClauseDesc() { - assertThat(this.select.orderBy(columnReference("city", "t"), columnReference("price", "t")).desc(), + assertThat(this.select.orderBy(column("city", "t"), column("price", "t")).desc(), rendersTo("SELECT * FROM t ORDER BY t.city, t.price DESC")); } @Test void testOrderByClauseAsc() { - assertThat(this.select.orderBy(columnReference("city", "t"), columnReference("price", "t")).asc(), + assertThat(this.select.orderBy(column("city", "t"), column("price", "t")).asc(), rendersTo("SELECT * FROM t ORDER BY t.city, t.price ASC")); } @Test void testOrderByClauseAscAndDesc() { - assertThat(this.select.orderBy(columnReference("city", "t"), columnReference("price", "t")).asc().desc(), + assertThat(this.select.orderBy(column("city", "t"), column("price", "t")).asc().desc(), rendersTo("SELECT * FROM t ORDER BY t.city, t.price DESC")); } @Test void testOrderByClauseDescAndAsc() { - assertThat(this.select.orderBy(columnReference("city", "t"), columnReference("price", "t")).desc().asc(), + assertThat(this.select.orderBy(column("city", "t"), column("price", "t")).desc().asc(), rendersTo("SELECT * FROM t ORDER BY t.city, t.price ASC")); } @Test void testOrderByClauseNullsFirst() { - assertThat(this.select.orderBy(columnReference("city", "t"), columnReference("price", "t")).nullsFirst(), + assertThat(this.select.orderBy(column("city", "t"), column("price", "t")).nullsFirst(), rendersTo("SELECT * FROM t ORDER BY t.city, t.price NULLS FIRST")); } @Test void testOrderByClauseNullsLast() { - assertThat(this.select.orderBy(columnReference("city", "t"), columnReference("price", "t")).nullsLast(), + assertThat(this.select.orderBy(column("city", "t"), column("price", "t")).nullsLast(), rendersTo("SELECT * FROM t ORDER BY t.city, t.price NULLS LAST")); } @Test void testOrderByClauseNullsFirstAndLast() { - assertThat(this.select.orderBy(columnReference("city", "t"), columnReference("price", "t")).nullsFirst() - .nullsLast(), rendersTo("SELECT * FROM t ORDER BY t.city, t.price NULLS LAST")); + assertThat(this.select.orderBy(column("city", "t"), column("price", "t")).nullsFirst().nullsLast(), + rendersTo("SELECT * FROM t ORDER BY t.city, t.price NULLS LAST")); } } diff --git a/src/test/java/com/exasol/sql/expression/rendering/TestBooleanExpressionRenderer.java b/src/test/java/com/exasol/sql/expression/rendering/TestBooleanExpressionRenderer.java index 142e3f9d..40e91f3c 100644 --- a/src/test/java/com/exasol/sql/expression/rendering/TestBooleanExpressionRenderer.java +++ b/src/test/java/com/exasol/sql/expression/rendering/TestBooleanExpressionRenderer.java @@ -138,15 +138,14 @@ void testComparisonOperatorsWithIntegerLiteral() { @Test void testComparisonOperatorsWithColumnReference() { assertAll( // - () -> assertThat("equal", eq(columnReference("city"), integerLiteral(1)), rendersTo("city = 1")), // - () -> assertThat("not equal", ne(columnReference("city"), integerLiteral(2)), rendersTo("city <> 2")), // - () -> assertThat("not equal", lt(columnReference("city"), stringLiteral("Moscow")), + () -> assertThat("equal", eq(column("city"), integerLiteral(1)), rendersTo("city = 1")), // + () -> assertThat("not equal", ne(column("city"), integerLiteral(2)), rendersTo("city <> 2")), // + () -> assertThat("not equal", lt(column("city"), stringLiteral("Moscow")), rendersTo("city < 'Moscow'")), // - () -> assertThat("not equal", gt(columnReference("city", "t"), stringLiteral("Moscow")), + () -> assertThat("not equal", gt(column("city", "t"), stringLiteral("Moscow")), rendersTo("t.city > 'Moscow'")), // - () -> assertThat("not equal", le(columnReference("city", "t"), columnReference("machi")), - rendersTo("t.city <= machi")), // - () -> assertThat("not equal", ge(columnReference("city", "t"), columnReference("machi", "t")), + () -> assertThat("not equal", le(column("city", "t"), column("machi")), rendersTo("t.city <= machi")), // + () -> assertThat("not equal", ge(column("city", "t"), column("machi", "t")), rendersTo("t.city >= t.machi")) // ); }