diff --git a/README.md b/README.md index 027bf34b..cf6685b2 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ final String sql = renderer.render(); * [User Guide](doc/user_guide/user_guide.md) * [API Documentation](https://javadoc.io/doc/com.exasol/sql-statement-builder) * [MIT License](LICENSE) +* [Changelog](doc/changes/changelog.md) ### Information for Developers diff --git a/doc/changes/changes-4.0.0.md b/doc/changes/changes-4.0.0.md index d1375e8a..3e44b552 100644 --- a/doc/changes/changes-4.0.0.md +++ b/doc/changes/changes-4.0.0.md @@ -8,6 +8,7 @@ * #80: Added ossindex-maven-plugin and versions-maven-plugin, updated dependencies. * #81: Ported from Java 8 to Java 11. +* #76: Added `SELECT FROM VALUES ... AS` support. ## Dependency updates diff --git a/src/main/java/com/exasol/sql/ValueTable.java b/src/main/java/com/exasol/sql/ValueTable.java index 1f53b77f..06347ae4 100644 --- a/src/main/java/com/exasol/sql/ValueTable.java +++ b/src/main/java/com/exasol/sql/ValueTable.java @@ -11,6 +11,8 @@ // [impl->dsn~value-table~1] public class ValueTable extends AbstractFragment { private final List rows = new ArrayList<>(); + private String tableNameAlias; + private final List columnNameAliases = new ArrayList<>(); /** * Create a new {@link ValueTable}. @@ -173,4 +175,45 @@ public void accept(final ValueTableVisitor visitor) { } visitor.leave(this); } -} + + /** + * Set alias for the value table. + * + * @param tableNameAlias table name alias + * @param columnNameAliases zero or more column names aliases + */ + public void alias(final String tableNameAlias, final String... columnNameAliases) { + this.tableNameAlias = tableNameAlias; + if (columnNameAliases.length > 0) { + this.columnNameAliases.addAll(Arrays.asList(columnNameAliases)); + } + } + + /** + * CHeck if the value table has an alias. + * + * @return true if the value table has an alias + */ + public boolean hasAlias() { + return (this.tableNameAlias != null) && (!this.tableNameAlias.isEmpty()) // + && (!this.columnNameAliases.isEmpty()); + } + + /** + * Get a table name alias. + * + * @return table name alias + */ + public String getTableNameAlias() { + return this.tableNameAlias; + } + + /** + * Get column name aliases. + * + * @return column name aliases + */ + public List getColumnNameAliases() { + return this.columnNameAliases; + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/sql/dql/select/FromClause.java b/src/main/java/com/exasol/sql/dql/select/FromClause.java index ffaf856d..8294118e 100644 --- a/src/main/java/com/exasol/sql/dql/select/FromClause.java +++ b/src/main/java/com/exasol/sql/dql/select/FromClause.java @@ -34,7 +34,7 @@ public FromClause table(final String name) { } /** - * Add a table name with an an alias to the {@link FromClause}. + * Add a table name with an alias to the {@link FromClause}. * * @param name table name * @param as table alias @@ -56,6 +56,21 @@ public FromClause valueTable(final ValueTable valueTable) { return this; } + /** + * Create a {@link FromClause} from a value table and an alias. + * + * @param valueTable table of value expressions + * @param tableNameAlias table alias + * @param columnNameAliases columns aliases + * @return parent {@code FROM} clause + */ + public FromClause valueTableAs(final ValueTable valueTable, final String tableNameAlias, + final String... columnNameAliases) { + valueTable.alias(tableNameAlias, columnNameAliases); + this.valueTables.add(valueTable); + return this; + } + /** * Create a new {@link Join} that belongs to a {@code FROM} clause. * diff --git a/src/main/java/com/exasol/sql/dql/select/rendering/SelectRenderer.java b/src/main/java/com/exasol/sql/dql/select/rendering/SelectRenderer.java index f671ff8a..2ca2c472 100644 --- a/src/main/java/com/exasol/sql/dql/select/rendering/SelectRenderer.java +++ b/src/main/java/com/exasol/sql/dql/select/rendering/SelectRenderer.java @@ -1,5 +1,7 @@ package com.exasol.sql.dql.select.rendering; +import java.util.List; + import com.exasol.sql.*; import com.exasol.sql.dql.select.*; import com.exasol.sql.expression.BooleanExpression; @@ -129,6 +131,19 @@ public void visit(final ValueTable valueTable) { @Override public void leave(final ValueTable valueTable) { append(")"); + if (valueTable.hasAlias()) { + appendKeyWord(" AS "); + appendAutoQuoted(valueTable.getTableNameAlias()); + append("("); + final List columnNameAliases = valueTable.getColumnNameAliases(); + for (int i = 0; i < columnNameAliases.size(); i++) { + appendAutoQuoted(columnNameAliases.get(i)); + if (i < columnNameAliases.size() - 1) { + append(", "); + } + } + append(")"); + } setLastVisited(valueTable); } diff --git a/src/test/java/com/exasol/sql/dql/select/rendering/TestSelectRendering.java b/src/test/java/com/exasol/sql/dql/select/rendering/TestSelectRendering.java index b5b08070..862b79c0 100644 --- a/src/test/java/com/exasol/sql/dql/select/rendering/TestSelectRendering.java +++ b/src/test/java/com/exasol/sql/dql/select/rendering/TestSelectRendering.java @@ -97,7 +97,7 @@ void testSelectWithQuotedIdentifiersDoesNotAddExtraQuotes() { @Test void testQuotedIdentifiers() { final StringRendererConfig config = StringRendererConfig.builder().quoteIdentifiers(true).build(); - Select select = this.select.all(); + final Select select = this.select.all(); select.from().table("person"); select.where(eq(ExpressionTerm.stringLiteral("foo"), ColumnReference.of("test"))); assertThat(select, rendersWithConfigTo(config, "SELECT * FROM \"person\" WHERE 'foo' = \"test\"")); diff --git a/src/test/java/com/exasol/sql/dql/select/rendering/TestValueTableRendering.java b/src/test/java/com/exasol/sql/dql/select/rendering/TestValueTableRendering.java index aba8267b..6c2e95fe 100644 --- a/src/test/java/com/exasol/sql/dql/select/rendering/TestValueTableRendering.java +++ b/src/test/java/com/exasol/sql/dql/select/rendering/TestValueTableRendering.java @@ -1,6 +1,7 @@ package com.exasol.sql.dql.select.rendering; import static com.exasol.hamcrest.SqlFragmentRenderResultMatcher.rendersTo; +import static com.exasol.hamcrest.SqlFragmentRenderResultMatcher.rendersWithConfigTo; import static org.hamcrest.MatcherAssert.assertThat; import org.junit.jupiter.api.BeforeEach; @@ -9,6 +10,7 @@ import com.exasol.sql.StatementFactory; import com.exasol.sql.ValueTable; import com.exasol.sql.dql.select.Select; +import com.exasol.sql.rendering.StringRendererConfig; class TestValueTableRendering { private Select select; @@ -26,4 +28,34 @@ void testSelectFromMultipleTableAs() { assertThat(this.select.all().from().valueTable(values), rendersTo("SELECT * FROM (VALUES ('r1c1', 'r1c2'), ('r2c1', 'r2c2'))")); } + + @Test + void testSelectFromValuesAs() { + final ValueTable values = new ValueTable(this.select); + values.add(1, 2); + final Select select = this.select.field("COL1"); + select.from().valueTableAs(values, "T", "COL1", "COL2"); + assertThat(select, rendersTo("SELECT COL1 FROM (VALUES (1, 2)) AS T(COL1, COL2)")); + } + + @Test + void testSelectFromValuesAsWithQuotation() { + final StringRendererConfig config = StringRendererConfig.builder().quoteIdentifiers(true).build(); + final ValueTable values = new ValueTable(this.select); + values.add(1, 2); + final Select select = this.select.field("COL1"); + select.from().valueTableAs(values, "T", "COL1", "COL2"); + assertThat(select, + rendersWithConfigTo(config, "SELECT \"COL1\" FROM (VALUES (1, 2)) AS \"T\"(\"COL1\", \"COL2\")")); + } + + @Test + void testSelectFromValuesAsWithQuotationOneColumn() { + final StringRendererConfig config = StringRendererConfig.builder().quoteIdentifiers(true).build(); + final ValueTable values = new ValueTable(this.select); + values.add(1); + final Select select = this.select.field("COL1"); + select.from().valueTableAs(values, "T", "COL1"); + assertThat(select, rendersWithConfigTo(config, "SELECT \"COL1\" FROM (VALUES (1)) AS \"T\"(\"COL1\")")); + } } \ No newline at end of file