From df0b293c3a3fac01a881030f31011d49f888aba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=A4r?= Date: Thu, 5 Dec 2019 14:06:27 +0100 Subject: [PATCH] #56: Support column references in value expressions (#58) * #56: Support column references in value expressions --- .../common_constructs/value_tables.md | 11 ++++ doc/user_guide/statements/merge.md | 4 ++ .../sql-statement-builder mvn package.launch | 2 +- pom.xml | 2 +- src/main/java/com/exasol/sql/ValueTable.java | 58 ++++++++++++++----- .../java/com/exasol/sql/ValueTableRow.java | 25 ++++---- .../dml/insert/AbstractInsertValueTable.java | 14 +++++ .../merge/rendering/MergeRenderingTest.java | 13 +++++ 8 files changed, 101 insertions(+), 28 deletions(-) create mode 100644 doc/user_guide/common_constructs/value_tables.md diff --git a/doc/user_guide/common_constructs/value_tables.md b/doc/user_guide/common_constructs/value_tables.md new file mode 100644 index 00000000..613228b7 --- /dev/null +++ b/doc/user_guide/common_constructs/value_tables.md @@ -0,0 +1,11 @@ +# Value Tables + +Value tables are lists of content definitions for rows. They can appear in [`INSERT`](../statements/insert.md) and [`MERGE`](../statements/merge.md) statements. + +## Defining Value Tables + +The class `ValueTable` contains multiple methods for defining value tables. + +The `appendRow()` methods allow adding all values for a complete row at once. They are especially useful in case all columns have the same type. + +The `add()` methods on the other hand amend the last row with an additional value. \ No newline at end of file diff --git a/doc/user_guide/statements/merge.md b/doc/user_guide/statements/merge.md index 51871660..9e43aab9 100644 --- a/doc/user_guide/statements/merge.md +++ b/doc/user_guide/statements/merge.md @@ -68,6 +68,10 @@ merge.using("src") // .where(gt(column("src", "c5"), integerLiteral(1000))); ``` +### Using Value Tables in `MERGE` Insert Clauses + +In the insert clause of a merge statement, you can use `VALUES` to insert [value tables](../common_constructs/value_tables.md). + ### Rendering `MERGE` Statements Use the `MergeRenderer` to render `Merge` objects into SQL strings. diff --git a/launch/sql-statement-builder mvn package.launch b/launch/sql-statement-builder mvn package.launch index e2007574..46b13400 100644 --- a/launch/sql-statement-builder mvn package.launch +++ b/launch/sql-statement-builder mvn package.launch @@ -15,6 +15,6 @@ - + diff --git a/pom.xml b/pom.xml index d0bd643f..8dba4dee 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.exasol sql-statement-builder - 3.1.0 + 3.2.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 diff --git a/src/main/java/com/exasol/sql/ValueTable.java b/src/main/java/com/exasol/sql/ValueTable.java index 2f831e5c..0b9f9dee 100644 --- a/src/main/java/com/exasol/sql/ValueTable.java +++ b/src/main/java/com/exasol/sql/ValueTable.java @@ -1,10 +1,11 @@ package com.exasol.sql; -import java.util.ArrayList; -import java.util.List; +import java.util.*; + +import com.exasol.sql.expression.ValueExpression; /** - * Value tables are pseudo-tables constructed from rows and columns of expressions (e.g. literals) + * Value tables are pseudo-tables constructed from rows and columns of expressions (e.g. literals). * */ // [impl->dsn~value-table~1] @@ -12,7 +13,7 @@ public class ValueTable extends AbstractFragment { private final List rows = new ArrayList<>(); /** - * Create a new {@link ValueTable} + * Create a new {@link ValueTable}. * * @param root SQL statement this table belongs to */ @@ -21,7 +22,7 @@ public ValueTable(final Fragment root) { } /** - * Append a value table row consisting of value literals to the value table + * Append a value table row consisting of value literals to the value table. * * @param literals literals to be appended * @@ -33,7 +34,7 @@ public ValueTable appendRow(final String... literals) { } /** - * Append a {@link ValueTableRow} to the {@link ValueTable} + * Append a {@link ValueTableRow} to the {@link ValueTable}. * * @param row row to be appended * @@ -45,7 +46,7 @@ public ValueTable appendRow(final ValueTableRow row) { } /** - * Get a list of all rows in the value table + * Get a list of all rows in the value table. * * @return rows */ @@ -54,7 +55,7 @@ public List getRows() { } /** - * Adds values to the last row of the value table + * Add string values to the last row of the value table. * * @param values values to be added */ @@ -62,14 +63,6 @@ public void add(final String... values) { amendLastRow(createLastRowBuilder().add(values).build()); } - private ValueTableRow.Builder createLastRowBuilder() { - final ValueTableRow.Builder builder = ValueTableRow.builder(this.root); - if (!isEmpty()) { - builder.add(getLastRow().getExpressions()); - } - return builder; - } - private synchronized void amendLastRow(final ValueTableRow row) { if (isEmpty()) { this.rows.add(row); @@ -79,14 +72,42 @@ private synchronized void amendLastRow(final ValueTableRow row) { } + private ValueTableRow.Builder createLastRowBuilder() { + final ValueTableRow.Builder builder = ValueTableRow.builder(this.root); + if (!isEmpty()) { + builder.add(getLastRow().getExpressions()); + } + return builder; + } + + /** + * Add integer values to the last row of the value table. + * + * @param values values to be added + */ public void add(final int... values) { amendLastRow(createLastRowBuilder().add(values).build()); } + /** + * Add an unnamed placeholder to the value table. + *

+ * Unnamed placeholders are the "?" in a prepared statement which are replaced by the actual variable values. + *

+ */ public void addPlaceholder() { amendLastRow(createLastRowBuilder().addPlaceholder().build()); } + /** + * Add a list of value expressions to the last row of the value table. + * + * @param expressions value expressions to be added + */ + public void add(final ValueExpression... expressions) { + amendLastRow(createLastRowBuilder().add(Arrays.asList(expressions)).build()); + } + private ValueTableRow getLastRow() { return this.rows.get(this.rows.size() - 1); } @@ -95,6 +116,11 @@ protected boolean isEmpty() { return this.rows.isEmpty(); } + /** + * Accept a visitor. + * + * @param visitor to be accepted + */ public void accept(final ValueTableVisitor visitor) { visitor.visit(this); for (final ValueTableRow row : this.rows) { diff --git a/src/main/java/com/exasol/sql/ValueTableRow.java b/src/main/java/com/exasol/sql/ValueTableRow.java index 071d5764..735b2259 100644 --- a/src/main/java/com/exasol/sql/ValueTableRow.java +++ b/src/main/java/com/exasol/sql/ValueTableRow.java @@ -12,9 +12,9 @@ public class ValueTableRow extends AbstractFragment { private final List expressions; /** - * Create a value table row from a list of expressions + * Create a value table row from a list of expressions. * - * @param root root node of the SQL statement + * @param root root node of the SQL statement * @param expressions value expressions */ public ValueTableRow(final Fragment root, final ValueExpression... expressions) { @@ -23,9 +23,9 @@ public ValueTableRow(final Fragment root, final ValueExpression... expressions) } /** - * Create a value table row from a list of string literals + * Create a value table row from a list of string literals. * - * @param root root node of the SQL statement + * @param root root node of the SQL statement * @param values sting literals */ public ValueTableRow(final Fragment root, final String... values) { @@ -42,7 +42,7 @@ private ValueTableRow(final Builder builder) { } /** - * Get the list of expressions the row consists of + * Get the list of expressions the row consists of. * * @return list of expressions */ @@ -50,13 +50,18 @@ public List getExpressions() { return this.expressions; } + /** + * Accept a visitor. + * + * @param visitor to accept. + */ public void accept(final ValueTableVisitor visitor) { visitor.visit(this); visitor.leave(this); } /** - * Get a {@link Builder} for a {@link ValueTableRow} + * Get a {@link Builder} for a {@link ValueTableRow}. * * @param root root fragment of the SQL statement * @@ -78,7 +83,7 @@ public Builder(final Fragment root) { } /** - * Add one or more string literals to the row + * Add one or more string literals to the row. * * @param values strings to be added * @return this for fluent programming @@ -91,7 +96,7 @@ public Builder add(final String... values) { } /** - * Add one or more integer literals to the row + * Add one or more integer literals to the row. * * @param values integers to be added * @return this for fluent programming @@ -114,7 +119,7 @@ public Builder addPlaceholder() { } /** - * Add a list of expressions to the {@link ValueTableRow} + * Add a list of expressions to the {@link ValueTableRow}. * * @param expressions expressions to be added * @return this for fluent programming @@ -125,7 +130,7 @@ public Builder add(final List expressions) { } /** - * Build a new {@link ValueTableRow} + * Build a new {@link ValueTableRow}. * * @return new {@link ValueTableRow} */ 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 2033522d..16675c10 100644 --- a/src/main/java/com/exasol/sql/dml/insert/AbstractInsertValueTable.java +++ b/src/main/java/com/exasol/sql/dml/insert/AbstractInsertValueTable.java @@ -1,6 +1,7 @@ package com.exasol.sql.dml.insert; import com.exasol.sql.*; +import com.exasol.sql.expression.ValueExpression; /** * Abstract base class for SQL fragments that contain a insert value table (for example {@code INSERT}, {@code MERGE}). @@ -82,6 +83,19 @@ public T values(final int... values) { return self(); } + /** + * Insert a list of value expressions. + * + * @param expressions value expressions to be inserted + * @return this for fluent programming + */ + // [impl->dsn~values-as-insert-source~1] + public T values(final ValueExpression... expressions) { + createInsertValueInstanceIfItDoesNotExist(); + this.insertValueTable.add(expressions); + return self(); + } + /** * Add an unnamed value placeholder to the value list (this is useful for prepared statements). * diff --git a/src/test/java/com/exasol/sql/dml/merge/rendering/MergeRenderingTest.java b/src/test/java/com/exasol/sql/dml/merge/rendering/MergeRenderingTest.java index 2c69c4d1..fe40103b 100644 --- a/src/test/java/com/exasol/sql/dml/merge/rendering/MergeRenderingTest.java +++ b/src/test/java/com/exasol/sql/dml/merge/rendering/MergeRenderingTest.java @@ -157,4 +157,17 @@ void testComplexMerge() { + " WHEN MATCHED THEN UPDATE SET c2 = DEFAULT, c3 = 'foo', c4 = 42" // + " WHEN NOT MATCHED THEN INSERT (c3, c5) VALUES ('foo', 'bar')")); } + + @Test + void testColumnReferenceInInsertValueList() { + this.merge // + .using("src") // + .on(eq(column("src", "c1"), column("dst", "c1"))) // + .whenNotMatched() // + .thenInsert() // + .field("c2") // + .values(column("src", "c2")); + assertThat(this.merge, rendersTo("MERGE INTO dst USING src ON src.c1 = dst.c1" // + + " WHEN NOT MATCHED THEN INSERT (c2) VALUES (src.c2)")); + } } \ No newline at end of file