From 8c076b35ce5c59c65fe586f5afa79ee22dee422b Mon Sep 17 00:00:00 2001 From: Michael Parkin Date: Thu, 2 Mar 2023 10:56:53 +0000 Subject: [PATCH 01/11] cast column definitions for oracle ctas --- .../morf/jdbc/oracle/OracleDialect.java | 107 +++++++++++++++++- .../morf/jdbc/oracle/TestOracleDialect.java | 2 +- 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java b/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java index b6e6938b6..807065603 100755 --- a/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java +++ b/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java @@ -96,6 +96,8 @@ class OracleDialect extends SqlDialect { */ public static final String NULLS_LAST = "NULLS LAST"; + private static final String CANNOT_CONVERT_NULL_STATEMENT_TO_SQL = "Cannot convert a null statement to SQL"; + /** * Database platforms may order nulls first or last. My SQL always orders nulls first, Oracle defaults to ordering nulls last. * Fortunately on Oracle it is possible to specify that nulls should be ordered first. @@ -1109,7 +1111,7 @@ public Collection addTableFromStatements(Table table, SelectStatement se result.add(new StringBuilder() .append(createTableStatement(table, true)) .append(" AS ") - .append(convertStatementToSQL(selectStatement)) + .append(convertStatementToSQL(selectStatement, table)) .toString() ); result.add("ALTER TABLE " + schemaNamePrefix() + table.getName() + " NOPARALLEL LOGGING"); @@ -1123,6 +1125,109 @@ public Collection addTableFromStatements(Table table, SelectStatement se return result.build(); } + /** + * Converts a structured {@link SelectStatement} to the equivalent SQL text. + * + * @param statement the statement to convert + * @return a string containing the SQL to run against the database + */ + public String convertStatementToSQL(SelectStatement statement, Table table) { + if (statement == null) { + throw new IllegalArgumentException(CANNOT_CONVERT_NULL_STATEMENT_TO_SQL); + } + + return getSqlFrom(statement, table); + } + + + /** + * Convert a {@link SelectStatement} into standards compliant SQL. + *

+ * For example, the following code: + *

+ *
+ * + *
+   * SelectStatement stmt = new SelectStatement().from(new Table("agreement"));
+   *                                                                            String result = sqlgen.getSqlFrom(stmt);
+   * 
+ * + *
+ *

+ * Will populate {@code result} with: + *

+ *
+ * + *
+   *    SELECT * FROM agreement
+   * 
+ * + *
+ * + * @param stmt the select statement to generate SQL for + * @return a standards compliant SQL SELECT statement + */ + protected String getSqlFrom(SelectStatement stmt, Table table) { + StringBuilder result = new StringBuilder("SELECT "); + + // Any hint directives which should be inserted before the field list + result.append(selectStatementPreFieldDirectives(stmt)); + + // Start by checking if this is a distinct call, then add the field list + if (stmt.isDistinct()) { + result.append("DISTINCT "); + } + if (stmt.getFields().isEmpty()) { + result.append("*"); + } else { + boolean firstField = true; + for (AliasedField currentField : stmt.getFields()) { + if (!firstField) { + result.append(", "); + } + + //Append this as a Cast, Will need the definition from the table being created. + //result.append(getSqlFrom(currentField)); + Column field = table.columns().stream() + .filter(n -> n.getName().equals(currentField.getImpliedName())) + .findFirst().get(); + + String newSelect = "CAST(" + + field.getName() + + " AS " + + sqlRepresentationOfColumnType(field, false, false, true) + + ") AS " + + field.getName(); + + result.append(newSelect); + // Put an alias in, if requested + appendAlias(result, currentField); + + firstField = false; + } + } + + appendFrom(result, stmt); + appendJoins(result, stmt, innerJoinKeyword(stmt)); + appendWhere(result, stmt); + appendGroupBy(result, stmt); + appendHaving(result, stmt); + appendUnionSet(result, stmt); + appendExceptSet(result, stmt); + appendOrderBy(result, stmt); + + if (stmt.isForUpdate()) { + if (stmt.isDistinct() || !stmt.getGroupBys().isEmpty() || !stmt.getJoins().isEmpty()) { + throw new IllegalArgumentException("GROUP BY, JOIN or DISTINCT cannot be combined with FOR UPDATE (H2 limitations)"); + } + result.append(getForUpdateSql()); + } + + // Any hint directives which should be inserted right at the end of the statement + result.append(selectStatementPostStatementDirectives(stmt)); + + return result.toString(); + } /** * Builds the remaining statements (triggers, sequences and comments). diff --git a/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialect.java b/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialect.java index 3a924b457..69982cf51 100755 --- a/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialect.java +++ b/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialect.java @@ -1452,7 +1452,7 @@ protected String expectedSelectLiteralWithWhereClauseString() { @Override public List expectedAddTableFromStatements() { return ImmutableList.of( - "CREATE TABLE TESTSCHEMA.SomeTable (someField NOT NULL, otherField NOT NULL, CONSTRAINT SomeTable_PK PRIMARY KEY (someField) USING INDEX (CREATE UNIQUE INDEX TESTSCHEMA.SomeTable_PK ON TESTSCHEMA.SomeTable (someField))) PARALLEL NOLOGGING AS SELECT someField, otherField FROM TESTSCHEMA.OtherTable", + "CREATE TABLE TESTSCHEMA.SomeTable (someField NOT NULL, otherField NOT NULL, CONSTRAINT SomeTable_PK PRIMARY KEY (someField) USING INDEX (CREATE UNIQUE INDEX TESTSCHEMA.SomeTable_PK ON TESTSCHEMA.SomeTable (someField))) PARALLEL NOLOGGING AS SELECT CAST(someField AS NVARCHAR2(3)) AS someField, CAST(otherField AS DECIMAL(3,0)) AS otherField FROM TESTSCHEMA.OtherTable", "ALTER TABLE TESTSCHEMA.SomeTable NOPARALLEL LOGGING", "ALTER INDEX TESTSCHEMA.SomeTable_PK NOPARALLEL LOGGING", "COMMENT ON TABLE TESTSCHEMA.SomeTable IS '"+OracleDialect.REAL_NAME_COMMENT_LABEL+":[SomeTable]'", From 8df877b3e3cfbcc6fb872c471405995468c1047e Mon Sep 17 00:00:00 2001 From: Michael Parkin Date: Tue, 7 Mar 2023 08:56:02 +0000 Subject: [PATCH 02/11] cast column definitions for oracle ctas --- .../morf/jdbc/oracle/OracleDialect.java | 54 ++++++++++++++----- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java b/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java index 807065603..3a1403c93 100755 --- a/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java +++ b/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java @@ -63,6 +63,7 @@ import org.alfasoftware.morf.sql.element.ConcatenatedField; import org.alfasoftware.morf.sql.element.FieldReference; import org.alfasoftware.morf.sql.element.Function; +import org.alfasoftware.morf.sql.element.NullFieldLiteral; import org.alfasoftware.morf.sql.element.SqlParameter; import org.alfasoftware.morf.sql.element.TableReference; import org.apache.commons.lang3.StringUtils; @@ -1186,20 +1187,14 @@ protected String getSqlFrom(SelectStatement stmt, Table table) { result.append(", "); } - //Append this as a Cast, Will need the definition from the table being created. - //result.append(getSqlFrom(currentField)); - Column field = table.columns().stream() - .filter(n -> n.getName().equals(currentField.getImpliedName())) - .findFirst().get(); - - String newSelect = "CAST(" - + field.getName() - + " AS " - + sqlRepresentationOfColumnType(field, false, false, true) - + ") AS " - + field.getName(); + if(currentField instanceof NullFieldLiteral) { + result.append(getSqlForNullField(currentField, table, stmt.getFields())); + } else if (currentField instanceof FieldReference) { + result.append(getSqlField(currentField, table, stmt.getFields())); + } else { + result.append(getSqlFrom(currentField)); + } - result.append(newSelect); // Put an alias in, if requested appendAlias(result, currentField); @@ -1229,6 +1224,39 @@ protected String getSqlFrom(SelectStatement stmt, Table table) { return result.toString(); } + /** + * Creates the Cast SQL for a given null field. + * @param currentField - will be a NullFieldLiteral. + * @param table - Table definition for the table being created. + * @param fields - All the fields being added to the select. + * @return String value of the CAST. + */ + private String getSqlForNullField(AliasedField currentField, Table table, List fields) { + Column column = table.columns().get(fields.indexOf(currentField)); + return "CAST(null AS " + + sqlRepresentationOfColumnType(column, false, false, true) + + ") AS " + + column.getName(); + } + + /** + * Creates the Cast SQL for a given field. + * @param currentField - will be a FieldReference. + * @param table - Table definition for the table being created. + * @param fields - All the fields being added to the select. + * @return String value of the CAST. + */ + private String getSqlField(AliasedField currentField, Table table, List fields) { + Column column = table.columns().get(fields.indexOf(currentField)); + + return "CAST(" + + column.getName() + + " AS " + + sqlRepresentationOfColumnType(column, false, false, true) + + ") AS " + + column.getName(); + } + /** * Builds the remaining statements (triggers, sequences and comments). * From 543c731f429f6c0abd6dde914ee7496303ac728f Mon Sep 17 00:00:00 2001 From: Michael Parkin Date: Tue, 7 Mar 2023 09:40:34 +0000 Subject: [PATCH 03/11] cast column definitions for oracle ctas --- .../morf/jdbc/oracle/OracleDialect.java | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java b/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java index 3a1403c93..d290dfd7a 100755 --- a/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java +++ b/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java @@ -1181,25 +1181,7 @@ protected String getSqlFrom(SelectStatement stmt, Table table) { if (stmt.getFields().isEmpty()) { result.append("*"); } else { - boolean firstField = true; - for (AliasedField currentField : stmt.getFields()) { - if (!firstField) { - result.append(", "); - } - - if(currentField instanceof NullFieldLiteral) { - result.append(getSqlForNullField(currentField, table, stmt.getFields())); - } else if (currentField instanceof FieldReference) { - result.append(getSqlField(currentField, table, stmt.getFields())); - } else { - result.append(getSqlFrom(currentField)); - } - - // Put an alias in, if requested - appendAlias(result, currentField); - - firstField = false; - } + result.append(getFieldsForSelect(table, stmt)); } appendFrom(result, stmt); @@ -1224,6 +1206,35 @@ protected String getSqlFrom(SelectStatement stmt, Table table) { return result.toString(); } + /** + * Returns fields to be used in the select statement. + * @param table table definition for the table being created. + * @param stmt the select statement. + * @return returns all the fields to be included in the select statement in SQL format. + */ + private String getFieldsForSelect(Table table, SelectStatement stmt) { + StringBuilder stringBuilder = new StringBuilder(); + boolean firstField = true; + for (AliasedField currentField : stmt.getFields()) { + if (!firstField) { + stringBuilder.append(", "); + } + + if (currentField instanceof NullFieldLiteral) { + stringBuilder.append(getSqlForNullField(currentField, table, stmt.getFields())); + } else if (currentField instanceof FieldReference) { + stringBuilder.append(getSqlField(currentField, table, stmt.getFields())); + } else { + stringBuilder.append(getSqlFrom(currentField)); + } + // Put an alias in, if requested + appendAlias(stringBuilder, currentField); + + firstField = false; + } + return stringBuilder.toString(); + } + /** * Creates the Cast SQL for a given null field. * @param currentField - will be a NullFieldLiteral. From dc380939633ecb3c5279e3aff2ed5dad08306f88 Mon Sep 17 00:00:00 2001 From: Michael Parkin Date: Tue, 7 Mar 2023 14:06:20 +0000 Subject: [PATCH 04/11] add new test for selecting null --- .../morf/jdbc/h2/TestH2Dialect.java | 11 ++++++++++ .../morf/jdbc/mysql/TestMySqlDialect.java | 12 ++++++++++ .../morf/jdbc/nuodb/TestNuoDBDialect.java | 13 +++++++++++ .../morf/jdbc/oracle/TestOracleDialect.java | 22 ++++++++++++++++++- .../postgresql/TestPostgreSQLDialect.java | 17 ++++++++++++++ .../jdbc/sqlserver/TestSqlServerDialect.java | 12 ++++++++++ .../morf/jdbc/AbstractSqlDialectTest.java | 20 +++++++++++++++++ 7 files changed, 106 insertions(+), 1 deletion(-) diff --git a/morf-h2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2Dialect.java b/morf-h2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2Dialect.java index 5b9470735..55374079c 100755 --- a/morf-h2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2Dialect.java +++ b/morf-h2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2Dialect.java @@ -1105,6 +1105,17 @@ protected List expectedAddTableFromStatements() { "INSERT INTO TESTSCHEMA.SomeTable SELECT someField, otherField FROM TESTSCHEMA.OtherTable" ); } + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAddTableFromStatementsNullValue() + */ + @Override + protected List expectedAddTableFromStatementsNullValue() { + return ImmutableList.of( + "CREATE TABLE TESTSCHEMA.SomeTable (someField VARCHAR(3) NOT NULL, otherField DECIMAL(3,0) NOT NULL, nullField VARCHAR(3) NOT NULL, CONSTRAINT SomeTable_PK PRIMARY KEY (someField))", + "CREATE INDEX SomeTable_1 ON TESTSCHEMA.SomeTable (otherField)", + "INSERT INTO TESTSCHEMA.SomeTable SELECT someField, otherField, null FROM TESTSCHEMA.OtherTable" + ); + } /** diff --git a/morf-mysql/src/test/java/org/alfasoftware/morf/jdbc/mysql/TestMySqlDialect.java b/morf-mysql/src/test/java/org/alfasoftware/morf/jdbc/mysql/TestMySqlDialect.java index b93a4f2e6..78e472b7e 100755 --- a/morf-mysql/src/test/java/org/alfasoftware/morf/jdbc/mysql/TestMySqlDialect.java +++ b/morf-mysql/src/test/java/org/alfasoftware/morf/jdbc/mysql/TestMySqlDialect.java @@ -1168,6 +1168,18 @@ protected List expectedAddTableFromStatements() { ); } + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAddTableFromStatementsNullValue() + */ + @Override + protected List expectedAddTableFromStatementsNullValue() { + return ImmutableList.of( + "CREATE TABLE `SomeTable` (`someField` VARCHAR(3) NOT NULL, `otherField` DECIMAL(3,0) NOT NULL, `nullField` VARCHAR(3) NOT NULL, CONSTRAINT `SomeTable_PK` PRIMARY KEY (`someField`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin", + "ALTER TABLE `SomeTable` ADD INDEX `SomeTable_1` (`otherField`)", + "INSERT INTO SomeTable SELECT someField, otherField, null FROM OtherTable" + ); + } + /** * We only support {@link SelectStatement#useImplicitJoinOrder()}, and only to a limited extent. diff --git a/morf-nuodb/src/test/java/org/alfasoftware/morf/jdbc/nuodb/TestNuoDBDialect.java b/morf-nuodb/src/test/java/org/alfasoftware/morf/jdbc/nuodb/TestNuoDBDialect.java index 7717cfe6a..7d215bd55 100755 --- a/morf-nuodb/src/test/java/org/alfasoftware/morf/jdbc/nuodb/TestNuoDBDialect.java +++ b/morf-nuodb/src/test/java/org/alfasoftware/morf/jdbc/nuodb/TestNuoDBDialect.java @@ -1402,6 +1402,19 @@ protected List expectedAddTableFromStatements() { ); } + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAddTableFromStatementsNullValue() + */ + @Override + protected List expectedAddTableFromStatementsNullValue() { + return ImmutableList.of( + "CREATE TABLE SCM.SomeTable (someField VARCHAR(3) NOT NULL, otherField DECIMAL(3,0) NOT NULL, nullField VARCHAR(3) NOT NULL, PRIMARY KEY (someField))", + "DROP INDEX IF EXISTS SCM.SomeTable_1", + "CREATE INDEX SomeTable_1 ON SCM.SomeTable (otherField)", + "INSERT INTO SCM.SomeTable SELECT someField, otherField, null FROM SCM.OtherTable" + ); + } + /** * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedHints1(int) diff --git a/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialect.java b/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialect.java index 69982cf51..ac69b82fa 100755 --- a/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialect.java +++ b/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialect.java @@ -16,7 +16,8 @@ package org.alfasoftware.morf.jdbc.oracle; import static org.alfasoftware.morf.jdbc.oracle.OracleDialect.NULLS_LAST; -import static org.alfasoftware.morf.sql.SqlUtils.parameter; +import static org.alfasoftware.morf.metadata.SchemaUtils.*; +import static org.alfasoftware.morf.sql.SqlUtils.*; import static org.alfasoftware.morf.sql.element.Direction.ASCENDING; import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertEquals; @@ -43,10 +44,13 @@ import org.alfasoftware.morf.jdbc.SqlScriptExecutor; import org.alfasoftware.morf.metadata.DataType; import org.alfasoftware.morf.metadata.SchemaUtils; +import org.alfasoftware.morf.metadata.Table; import org.alfasoftware.morf.sql.CustomHint; import org.alfasoftware.morf.sql.OracleCustomHint; +import org.alfasoftware.morf.sql.SelectStatement; import org.alfasoftware.morf.sql.element.Direction; import org.alfasoftware.morf.sql.element.SqlParameter; +import org.junit.Test; import org.mockito.ArgumentCaptor; import com.google.common.base.Strings; @@ -1461,6 +1465,22 @@ public List expectedAddTableFromStatements() { ); } + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAddTableFromStatementsNullValue() + */ + @Override + public List expectedAddTableFromStatementsNullValue() { + return ImmutableList.of( + "CREATE TABLE TESTSCHEMA.SomeTable (someField NOT NULL, otherField NOT NULL, nullField NOT NULL, CONSTRAINT SomeTable_PK PRIMARY KEY (someField) USING INDEX (CREATE UNIQUE INDEX TESTSCHEMA.SomeTable_PK ON TESTSCHEMA.SomeTable (someField))) PARALLEL NOLOGGING AS SELECT CAST(someField AS NVARCHAR2(3)) AS someField, CAST(otherField AS DECIMAL(3,0)) AS otherField, CAST(null AS NVARCHAR2(3)) AS nullField FROM TESTSCHEMA.OtherTable", + "ALTER TABLE TESTSCHEMA.SomeTable NOPARALLEL LOGGING", + "ALTER INDEX TESTSCHEMA.SomeTable_PK NOPARALLEL LOGGING", + "COMMENT ON TABLE TESTSCHEMA.SomeTable IS 'REALNAME:[SomeTable]'", + "COMMENT ON COLUMN TESTSCHEMA.SomeTable.someField IS 'REALNAME:[someField]/TYPE:[STRING]'", + "COMMENT ON COLUMN TESTSCHEMA.SomeTable.otherField IS 'REALNAME:[otherField]/TYPE:[DECIMAL]'", + "COMMENT ON COLUMN TESTSCHEMA.SomeTable.nullField IS 'REALNAME:[nullField]/TYPE:[STRING]'" + ); + } + /** * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedHints1(int) */ diff --git a/morf-postgresql/src/test/java/org/alfasoftware/morf/jdbc/postgresql/TestPostgreSQLDialect.java b/morf-postgresql/src/test/java/org/alfasoftware/morf/jdbc/postgresql/TestPostgreSQLDialect.java index e66e6d6d1..dd5d59a14 100644 --- a/morf-postgresql/src/test/java/org/alfasoftware/morf/jdbc/postgresql/TestPostgreSQLDialect.java +++ b/morf-postgresql/src/test/java/org/alfasoftware/morf/jdbc/postgresql/TestPostgreSQLDialect.java @@ -1181,6 +1181,23 @@ protected List expectedAddTableFromStatements() { ); } + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAddTableFromStatementsNullValue() + */ + @Override + protected List expectedAddTableFromStatementsNullValue() { + return ImmutableList.of( + "CREATE TABLE testschema.SomeTable (someField VARCHAR(3) COLLATE \"POSIX\" NOT NULL, otherField DECIMAL(3,0) NOT NULL, nullField VARCHAR(3) COLLATE \"POSIX\" NOT NULL, CONSTRAINT SomeTable_PK PRIMARY KEY(someField))", + "COMMENT ON TABLE testschema.SomeTable IS '"+PostgreSQLDialect.REAL_NAME_COMMENT_LABEL+":[SomeTable]'", + "COMMENT ON COLUMN testschema.SomeTable.someField IS '"+PostgreSQLDialect.REAL_NAME_COMMENT_LABEL+":[someField]/TYPE:[STRING]'", + "COMMENT ON COLUMN testschema.SomeTable.otherField IS '"+PostgreSQLDialect.REAL_NAME_COMMENT_LABEL+":[otherField]/TYPE:[DECIMAL]'", + "COMMENT ON COLUMN testschema.SomeTable.nullField IS 'REALNAME:[nullField]/TYPE:[STRING]'", + "CREATE INDEX SomeTable_1 ON testschema.SomeTable (otherField)", + "COMMENT ON INDEX SomeTable_1 IS '"+PostgreSQLDialect.REAL_NAME_COMMENT_LABEL+":[SomeTable_1]'", + "INSERT INTO testschema.SomeTable SELECT someField, otherField, null FROM testschema.OtherTable" + ); + } + /** * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedHints1(int) diff --git a/morf-sqlserver/src/test/java/org/alfasoftware/morf/jdbc/sqlserver/TestSqlServerDialect.java b/morf-sqlserver/src/test/java/org/alfasoftware/morf/jdbc/sqlserver/TestSqlServerDialect.java index ad108868a..d732276ec 100755 --- a/morf-sqlserver/src/test/java/org/alfasoftware/morf/jdbc/sqlserver/TestSqlServerDialect.java +++ b/morf-sqlserver/src/test/java/org/alfasoftware/morf/jdbc/sqlserver/TestSqlServerDialect.java @@ -1235,6 +1235,18 @@ protected List expectedAddTableFromStatements() { ); } + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAddTableFromStatementsNullValue() + */ + @Override + protected List expectedAddTableFromStatementsNullValue() { + return ImmutableList.of( + "CREATE TABLE TESTSCHEMA.SomeTable ([someField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, [otherField] NUMERIC(3,0) NOT NULL, [nullField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, CONSTRAINT [SomeTable_PK] PRIMARY KEY ([someField]))", + "CREATE INDEX SomeTable_1 ON TESTSCHEMA.SomeTable ([otherField])", + "INSERT INTO TESTSCHEMA.SomeTable SELECT someField, otherField, null FROM TESTSCHEMA.OtherTable" + ); + } + /** * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedHints1(int) diff --git a/morf-testsupport/src/main/java/org/alfasoftware/morf/jdbc/AbstractSqlDialectTest.java b/morf-testsupport/src/main/java/org/alfasoftware/morf/jdbc/AbstractSqlDialectTest.java index e155bcba1..6d2c1b18a 100755 --- a/morf-testsupport/src/main/java/org/alfasoftware/morf/jdbc/AbstractSqlDialectTest.java +++ b/morf-testsupport/src/main/java/org/alfasoftware/morf/jdbc/AbstractSqlDialectTest.java @@ -33,6 +33,7 @@ import static org.alfasoftware.morf.sql.SqlUtils.insert; import static org.alfasoftware.morf.sql.SqlUtils.literal; import static org.alfasoftware.morf.sql.SqlUtils.merge; +import static org.alfasoftware.morf.sql.SqlUtils.nullLiteral; import static org.alfasoftware.morf.sql.SqlUtils.parameter; import static org.alfasoftware.morf.sql.SqlUtils.select; import static org.alfasoftware.morf.sql.SqlUtils.selectDistinct; @@ -4218,6 +4219,24 @@ public void testAddTableFromStatements() { compareStatements(expectedAddTableFromStatements(), getTestDialect().addTableFromStatements(table, selectStatement)); } + @Test + @SuppressWarnings("unchecked") + public void testAddTableFromStatementsWithNullValue() { + + Table table = table("SomeTable") + .columns( + column("someField", DataType.STRING, 3).primaryKey(), + column("otherField", DataType.DECIMAL, 3), + column("nullField", DataType.STRING, 3) + ).indexes( + index("SomeTable_1").columns("otherField") + ); + + SelectStatement selectStatement = select(field("someField"), field("otherField"), nullLiteral()).from(tableRef("OtherTable")); + + compareStatements(expectedAddTableFromStatementsNullValue(), getTestDialect().addTableFromStatements(table, selectStatement)); + } + /** * On some databases our string literals need prefixing with N to be @@ -5127,6 +5146,7 @@ protected List expectedPreInsertStatementsNotInsertingUnderAutonumLimit( protected abstract List expectedAddTableFromStatements(); + protected abstract List expectedAddTableFromStatementsNullValue(); /** * @return The expected SQL for an insert to Test that inserts an empty string (i.e. NULL). From a76813523b085c8b1c42e0e3df3d8fcc1f8921e6 Mon Sep 17 00:00:00 2001 From: Michael Parkin Date: Tue, 7 Mar 2023 16:27:02 +0000 Subject: [PATCH 05/11] add new tests to increase coverage --- ...TestOracleDialectBespokeFunctionality.java | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialectBespokeFunctionality.java diff --git a/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialectBespokeFunctionality.java b/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialectBespokeFunctionality.java new file mode 100644 index 000000000..b3a84d1cb --- /dev/null +++ b/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialectBespokeFunctionality.java @@ -0,0 +1,101 @@ +package org.alfasoftware.morf.jdbc.oracle; + +import org.alfasoftware.morf.metadata.DataType; +import org.alfasoftware.morf.metadata.Table; +import org.alfasoftware.morf.sql.SelectStatement; +import org.junit.Test; + +import static org.alfasoftware.morf.metadata.SchemaUtils.*; +import static org.alfasoftware.morf.sql.SqlUtils.*; +import static org.junit.Assert.assertEquals; + +public class TestOracleDialectBespokeFunctionality { + + + + @Test + public void testConvertStatementToSQL() { + Table table = table("SomeTable") + .columns( + column("someField", DataType.STRING, 3).primaryKey(), + column("otherField", DataType.DECIMAL, 3), + column("nullField", DataType.STRING, 3) + ).indexes( + index("SomeTable_1").columns("otherField") + ); + + SelectStatement selectStatement = select(field("someField"), field("otherField"), nullLiteral()).from(tableRef("OtherTable")); + + OracleDialect oracleDialect = new OracleDialect("TEST"); + String result = oracleDialect.convertStatementToSQL(selectStatement, table); + + String expectedResult = "SELECT " + + "CAST(someField AS NVARCHAR2(3)) AS someField, " + + "CAST(otherField AS DECIMAL(3,0)) AS otherField, " + + "CAST(null AS NVARCHAR2(3)) AS nullField " + + "FROM TEST.OtherTable"; + assertEquals(expectedResult, result); + } + + @Test + public void testDistinctStatement() { + OracleDialect oracleDialect = new OracleDialect("TEST"); + + Table table = table("SomeTable") + .columns( + column("someField", DataType.STRING, 3).primaryKey(), + column("otherField", DataType.DECIMAL, 3) + ).indexes( + index("SomeTable_1").columns("otherField") + ); + + SelectStatement selectStatement = select(field("someField"), field("otherField")).from(tableRef("OtherTable")); + SelectStatement distinctSelectStatement = selectStatement.shallowCopy().distinct().build(); + + String result = oracleDialect.convertStatementToSQL(distinctSelectStatement, table); + + String expectedResult = "SELECT DISTINCT " + + "CAST(someField AS NVARCHAR2(3)) AS someField, " + + "CAST(otherField AS DECIMAL(3,0)) AS otherField " + + "FROM TEST.OtherTable"; + assertEquals(expectedResult, result); + } + + + @Test + public void testAllStatement() { + OracleDialect oracleDialect = new OracleDialect("TEST"); + + Table table = table("SomeTable") + .columns( + column("someField", DataType.STRING, 3).primaryKey(), + column("otherField", DataType.DECIMAL, 3) + ).indexes( + index("SomeTable_1").columns("otherField") + ); + + SelectStatement selectStatement = select().from(tableRef("OtherTable")); + SelectStatement distinctSelectStatement = selectStatement.shallowCopy().distinct().build(); + + String result = oracleDialect.convertStatementToSQL(distinctSelectStatement, table); + + String expectedResult = "SELECT DISTINCT * FROM TEST.OtherTable"; + assertEquals(expectedResult, result); + } + + @Test(expected = IllegalArgumentException.class) + public void testIllegalArgumentExeptionIsThrown() { + OracleDialect oracleDialect = new OracleDialect("TEST"); + + Table table = table("SomeTable") + .columns( + column("someField", DataType.STRING, 3).primaryKey(), + column("otherField", DataType.DECIMAL, 3), + column("nullField", DataType.STRING, 3) + ).indexes( + index("SomeTable_1").columns("otherField") + ); + + oracleDialect.convertStatementToSQL(null, table); + } +} From 689719c82db1f370f38cd010b451ed30abc716a4 Mon Sep 17 00:00:00 2001 From: Michael Parkin Date: Tue, 7 Mar 2023 16:52:53 +0000 Subject: [PATCH 06/11] add new tests to increase coverage --- ...TestOracleDialectBespokeFunctionality.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialectBespokeFunctionality.java b/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialectBespokeFunctionality.java index b3a84d1cb..68e6fdfcb 100644 --- a/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialectBespokeFunctionality.java +++ b/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialectBespokeFunctionality.java @@ -83,6 +83,45 @@ public void testAllStatement() { assertEquals(expectedResult, result); } + @Test + public void testUpdateStatement() { + OracleDialect oracleDialect = new OracleDialect("TEST"); + + Table table = table("SomeTable") + .columns( + column("someField", DataType.STRING, 3).primaryKey(), + column("otherField", DataType.DECIMAL, 3) + ).indexes( + index("SomeTable_1").columns("otherField") + ); + + SelectStatement selectStatement = select().from(tableRef("OtherTable")); + SelectStatement distinctSelectStatement = selectStatement.shallowCopy().forUpdate().build(); + + String result = oracleDialect.convertStatementToSQL(distinctSelectStatement, table); + + String expectedResult = "SELECT * FROM TEST.OtherTable FOR UPDATE"; + assertEquals(expectedResult, result); + } + + @Test(expected = IllegalArgumentException.class) + public void testUpdateForDistinctStatement() { + OracleDialect oracleDialect = new OracleDialect("TEST"); + + Table table = table("SomeTable") + .columns( + column("someField", DataType.STRING, 3).primaryKey(), + column("otherField", DataType.DECIMAL, 3) + ).indexes( + index("SomeTable_1").columns("otherField") + ); + + SelectStatement selectStatement = select().from(tableRef("OtherTable")); + SelectStatement distinctSelectStatement = selectStatement.shallowCopy().forUpdate().distinct().build(); + + String result = oracleDialect.convertStatementToSQL(distinctSelectStatement, table); + } + @Test(expected = IllegalArgumentException.class) public void testIllegalArgumentExeptionIsThrown() { OracleDialect oracleDialect = new OracleDialect("TEST"); @@ -98,4 +137,6 @@ public void testIllegalArgumentExeptionIsThrown() { oracleDialect.convertStatementToSQL(null, table); } + + } From 9323bf24e2ef51197a7ae3e021ec7ce978a8e7f2 Mon Sep 17 00:00:00 2001 From: Michael Parkin Date: Tue, 7 Mar 2023 17:25:53 +0000 Subject: [PATCH 07/11] fux duplicate code --- .../org/alfasoftware/morf/jdbc/SqlDialect.java | 15 ++++++++++----- .../morf/jdbc/oracle/OracleDialect.java | 16 +--------------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java b/morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java index 1106be0bf..b48d923a7 100755 --- a/morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java @@ -854,6 +854,16 @@ protected String getSqlFrom(SelectStatement stmt) { } } + result.append(appendStrings(stmt)); + + // Any hint directives which should be inserted right at the end of the statement + result.append(selectStatementPostStatementDirectives(stmt)); + + return result.toString(); + } + + protected String appendStrings(SelectStatement stmt) { + StringBuilder result = new StringBuilder(); appendFrom(result, stmt); appendJoins(result, stmt, innerJoinKeyword(stmt)); appendWhere(result, stmt); @@ -869,14 +879,9 @@ protected String getSqlFrom(SelectStatement stmt) { } result.append(getForUpdateSql()); } - - // Any hint directives which should be inserted right at the end of the statement - result.append(selectStatementPostStatementDirectives(stmt)); - return result.toString(); } - /** * Returns any SQL code which should be added between a SELECT and the field * list for dialect-specific reasons. diff --git a/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java b/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java index d290dfd7a..4023d492a 100755 --- a/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java +++ b/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java @@ -1184,21 +1184,7 @@ protected String getSqlFrom(SelectStatement stmt, Table table) { result.append(getFieldsForSelect(table, stmt)); } - appendFrom(result, stmt); - appendJoins(result, stmt, innerJoinKeyword(stmt)); - appendWhere(result, stmt); - appendGroupBy(result, stmt); - appendHaving(result, stmt); - appendUnionSet(result, stmt); - appendExceptSet(result, stmt); - appendOrderBy(result, stmt); - - if (stmt.isForUpdate()) { - if (stmt.isDistinct() || !stmt.getGroupBys().isEmpty() || !stmt.getJoins().isEmpty()) { - throw new IllegalArgumentException("GROUP BY, JOIN or DISTINCT cannot be combined with FOR UPDATE (H2 limitations)"); - } - result.append(getForUpdateSql()); - } + result.append(appendStrings(stmt)); // Any hint directives which should be inserted right at the end of the statement result.append(selectStatementPostStatementDirectives(stmt)); From 3618284e11ccc51b09ecb740d92ec289cda77908 Mon Sep 17 00:00:00 2001 From: Michael Parkin Date: Tue, 7 Mar 2023 17:40:59 +0000 Subject: [PATCH 08/11] fix method name --- .../src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java | 4 ++-- .../java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java b/morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java index b48d923a7..dad5f10d4 100755 --- a/morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java @@ -854,7 +854,7 @@ protected String getSqlFrom(SelectStatement stmt) { } } - result.append(appendStrings(stmt)); + result.append(appendPostSelectSQL(stmt)); // Any hint directives which should be inserted right at the end of the statement result.append(selectStatementPostStatementDirectives(stmt)); @@ -862,7 +862,7 @@ protected String getSqlFrom(SelectStatement stmt) { return result.toString(); } - protected String appendStrings(SelectStatement stmt) { + protected String appendPostSelectSQL(SelectStatement stmt) { StringBuilder result = new StringBuilder(); appendFrom(result, stmt); appendJoins(result, stmt, innerJoinKeyword(stmt)); diff --git a/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java b/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java index 4023d492a..541d7a2bf 100755 --- a/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java +++ b/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java @@ -1184,7 +1184,7 @@ protected String getSqlFrom(SelectStatement stmt, Table table) { result.append(getFieldsForSelect(table, stmt)); } - result.append(appendStrings(stmt)); + result.append(appendPostSelectSQL(stmt)); // Any hint directives which should be inserted right at the end of the statement result.append(selectStatementPostStatementDirectives(stmt)); From a6bd27d994e67f968fb39a5984d38cc44d43375a Mon Sep 17 00:00:00 2001 From: Michael Parkin Date: Tue, 14 Mar 2023 15:32:59 +0000 Subject: [PATCH 09/11] action review comments --- .../morf/jdbc/oracle/OracleDialect.java | 28 ++++--------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java b/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java index 541d7a2bf..f95b1228a 100755 --- a/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java +++ b/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java @@ -1206,10 +1206,8 @@ private String getFieldsForSelect(Table table, SelectStatement stmt) { stringBuilder.append(", "); } - if (currentField instanceof NullFieldLiteral) { - stringBuilder.append(getSqlForNullField(currentField, table, stmt.getFields())); - } else if (currentField instanceof FieldReference) { - stringBuilder.append(getSqlField(currentField, table, stmt.getFields())); + if (currentField instanceof NullFieldLiteral || currentField instanceof FieldReference) { + stringBuilder.append(getCastSqlFromField(currentField, table, stmt.getFields())); } else { stringBuilder.append(getSqlFrom(currentField)); } @@ -1223,31 +1221,15 @@ private String getFieldsForSelect(Table table, SelectStatement stmt) { /** * Creates the Cast SQL for a given null field. - * @param currentField - will be a NullFieldLiteral. + * @param currentField - Field will be either NullFieldLiteral or FieldReference. * @param table - Table definition for the table being created. * @param fields - All the fields being added to the select. * @return String value of the CAST. */ - private String getSqlForNullField(AliasedField currentField, Table table, List fields) { + private String getCastSqlFromField(AliasedField currentField, Table table, List fields) { Column column = table.columns().get(fields.indexOf(currentField)); - return "CAST(null AS " - + sqlRepresentationOfColumnType(column, false, false, true) - + ") AS " - + column.getName(); - } - - /** - * Creates the Cast SQL for a given field. - * @param currentField - will be a FieldReference. - * @param table - Table definition for the table being created. - * @param fields - All the fields being added to the select. - * @return String value of the CAST. - */ - private String getSqlField(AliasedField currentField, Table table, List fields) { - Column column = table.columns().get(fields.indexOf(currentField)); - return "CAST(" - + column.getName() + + getSqlFrom(currentField) + " AS " + sqlRepresentationOfColumnType(column, false, false, true) + ") AS " From 17abbbab765da6559cac645af61da79702ee16eb Mon Sep 17 00:00:00 2001 From: Michael Parkin Date: Tue, 21 Mar 2023 09:53:11 +0000 Subject: [PATCH 10/11] use Select statement to prevent duplicated code. --- .../sql/AbstractSelectStatementBuilder.java | 5 + .../morf/jdbc/oracle/OracleDialect.java | 127 +++--------------- ...TestOracleDialectBespokeFunctionality.java | 82 ++++++++--- 3 files changed, 85 insertions(+), 129 deletions(-) diff --git a/morf-core/src/main/java/org/alfasoftware/morf/sql/AbstractSelectStatementBuilder.java b/morf-core/src/main/java/org/alfasoftware/morf/sql/AbstractSelectStatementBuilder.java index cf1cff5df..ef13ea93e 100644 --- a/morf-core/src/main/java/org/alfasoftware/morf/sql/AbstractSelectStatementBuilder.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/sql/AbstractSelectStatementBuilder.java @@ -183,6 +183,11 @@ public T fields(AliasedFieldBuilder... fields) { return fields(Arrays.asList(fields)); } + public T withFields(Iterable fields) { + this.fields.clear(); + Iterables.addAll(this.fields, Builder.Helper.buildAll(fields)); + return castToChild(this); + } /** * Selects fields from a specific table: diff --git a/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java b/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java index f95b1228a..e444ba7b3 100755 --- a/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java +++ b/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java @@ -1108,11 +1108,26 @@ public Collection renameTableStatements(Table fromTable, Table toTable) */ @Override public Collection addTableFromStatements(Table table, SelectStatement selectStatement) { + List fieldsToInclude = new ArrayList<>(); + + for(AliasedField field: selectStatement.getFields()) { + if (field instanceof NullFieldLiteral || field instanceof FieldReference) { + Column column = table.columns().get(selectStatement.getFields().indexOf(field)); + fieldsToInclude.add(new Cast( + field, + column.getType(), + column.getWidth(), + column.getScale()).as(column.getName())); + } else { + fieldsToInclude.add(field); + } + } + Builder result = ImmutableList.builder(); result.add(new StringBuilder() .append(createTableStatement(table, true)) .append(" AS ") - .append(convertStatementToSQL(selectStatement, table)) + .append(convertStatementToSQL(selectStatement.shallowCopy().withFields(fieldsToInclude).build())) .toString() ); result.add("ALTER TABLE " + schemaNamePrefix() + table.getName() + " NOPARALLEL LOGGING"); @@ -1126,116 +1141,6 @@ public Collection addTableFromStatements(Table table, SelectStatement se return result.build(); } - /** - * Converts a structured {@link SelectStatement} to the equivalent SQL text. - * - * @param statement the statement to convert - * @return a string containing the SQL to run against the database - */ - public String convertStatementToSQL(SelectStatement statement, Table table) { - if (statement == null) { - throw new IllegalArgumentException(CANNOT_CONVERT_NULL_STATEMENT_TO_SQL); - } - - return getSqlFrom(statement, table); - } - - - /** - * Convert a {@link SelectStatement} into standards compliant SQL. - *

- * For example, the following code: - *

- *
- * - *
-   * SelectStatement stmt = new SelectStatement().from(new Table("agreement"));
-   *                                                                            String result = sqlgen.getSqlFrom(stmt);
-   * 
- * - *
- *

- * Will populate {@code result} with: - *

- *
- * - *
-   *    SELECT * FROM agreement
-   * 
- * - *
- * - * @param stmt the select statement to generate SQL for - * @return a standards compliant SQL SELECT statement - */ - protected String getSqlFrom(SelectStatement stmt, Table table) { - StringBuilder result = new StringBuilder("SELECT "); - - // Any hint directives which should be inserted before the field list - result.append(selectStatementPreFieldDirectives(stmt)); - - // Start by checking if this is a distinct call, then add the field list - if (stmt.isDistinct()) { - result.append("DISTINCT "); - } - if (stmt.getFields().isEmpty()) { - result.append("*"); - } else { - result.append(getFieldsForSelect(table, stmt)); - } - - result.append(appendPostSelectSQL(stmt)); - - // Any hint directives which should be inserted right at the end of the statement - result.append(selectStatementPostStatementDirectives(stmt)); - - return result.toString(); - } - - /** - * Returns fields to be used in the select statement. - * @param table table definition for the table being created. - * @param stmt the select statement. - * @return returns all the fields to be included in the select statement in SQL format. - */ - private String getFieldsForSelect(Table table, SelectStatement stmt) { - StringBuilder stringBuilder = new StringBuilder(); - boolean firstField = true; - for (AliasedField currentField : stmt.getFields()) { - if (!firstField) { - stringBuilder.append(", "); - } - - if (currentField instanceof NullFieldLiteral || currentField instanceof FieldReference) { - stringBuilder.append(getCastSqlFromField(currentField, table, stmt.getFields())); - } else { - stringBuilder.append(getSqlFrom(currentField)); - } - // Put an alias in, if requested - appendAlias(stringBuilder, currentField); - - firstField = false; - } - return stringBuilder.toString(); - } - - /** - * Creates the Cast SQL for a given null field. - * @param currentField - Field will be either NullFieldLiteral or FieldReference. - * @param table - Table definition for the table being created. - * @param fields - All the fields being added to the select. - * @return String value of the CAST. - */ - private String getCastSqlFromField(AliasedField currentField, Table table, List fields) { - Column column = table.columns().get(fields.indexOf(currentField)); - return "CAST(" - + getSqlFrom(currentField) - + " AS " - + sqlRepresentationOfColumnType(column, false, false, true) - + ") AS " - + column.getName(); - } - /** * Builds the remaining statements (triggers, sequences and comments). * diff --git a/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialectBespokeFunctionality.java b/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialectBespokeFunctionality.java index 68e6fdfcb..045a0b3cc 100644 --- a/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialectBespokeFunctionality.java +++ b/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialectBespokeFunctionality.java @@ -27,13 +27,26 @@ public void testConvertStatementToSQL() { SelectStatement selectStatement = select(field("someField"), field("otherField"), nullLiteral()).from(tableRef("OtherTable")); OracleDialect oracleDialect = new OracleDialect("TEST"); - String result = oracleDialect.convertStatementToSQL(selectStatement, table); - - String expectedResult = "SELECT " + + String result = oracleDialect.addTableFromStatements(table, selectStatement).toString(); + + String expectedResult = "[CREATE TABLE TEST.SomeTable (" + + "someField NOT NULL, " + + "otherField NOT NULL, " + + "nullField NOT NULL, " + + "CONSTRAINT SomeTable_PK PRIMARY KEY (someField) " + + "USING INDEX (CREATE UNIQUE INDEX TEST.SomeTable_PK ON TEST.SomeTable (someField))) " + + "PARALLEL NOLOGGING " + + "AS SELECT " + "CAST(someField AS NVARCHAR2(3)) AS someField, " + "CAST(otherField AS DECIMAL(3,0)) AS otherField, " + "CAST(null AS NVARCHAR2(3)) AS nullField " + - "FROM TEST.OtherTable"; + "FROM TEST.OtherTable, " + + "ALTER TABLE TEST.SomeTable NOPARALLEL LOGGING, " + + "ALTER INDEX TEST.SomeTable_PK NOPARALLEL LOGGING, " + + "COMMENT ON TABLE TEST.SomeTable IS 'REALNAME:[SomeTable]', " + + "COMMENT ON COLUMN TEST.SomeTable.someField IS 'REALNAME:[someField]/TYPE:[STRING]', " + + "COMMENT ON COLUMN TEST.SomeTable.otherField IS 'REALNAME:[otherField]/TYPE:[DECIMAL]', " + + "COMMENT ON COLUMN TEST.SomeTable.nullField IS 'REALNAME:[nullField]/TYPE:[STRING]']"; assertEquals(expectedResult, result); } @@ -52,12 +65,22 @@ public void testDistinctStatement() { SelectStatement selectStatement = select(field("someField"), field("otherField")).from(tableRef("OtherTable")); SelectStatement distinctSelectStatement = selectStatement.shallowCopy().distinct().build(); - String result = oracleDialect.convertStatementToSQL(distinctSelectStatement, table); + String result = oracleDialect.addTableFromStatements(table, distinctSelectStatement).toString(); - String expectedResult = "SELECT DISTINCT " + + String expectedResult = "[CREATE TABLE TEST.SomeTable (" + + "someField NOT NULL, " + + "otherField NOT NULL, " + + "CONSTRAINT SomeTable_PK PRIMARY KEY (someField) " + + "USING INDEX (CREATE UNIQUE INDEX TEST.SomeTable_PK ON TEST.SomeTable (someField))) " + + "PARALLEL NOLOGGING " + + "AS SELECT DISTINCT " + "CAST(someField AS NVARCHAR2(3)) AS someField, " + - "CAST(otherField AS DECIMAL(3,0)) AS otherField " + - "FROM TEST.OtherTable"; + "CAST(otherField AS DECIMAL(3,0)) AS otherField FROM TEST.OtherTable, " + + "ALTER TABLE TEST.SomeTable NOPARALLEL LOGGING, " + + "ALTER INDEX TEST.SomeTable_PK NOPARALLEL LOGGING, " + + "COMMENT ON TABLE TEST.SomeTable IS 'REALNAME:[SomeTable]', " + + "COMMENT ON COLUMN TEST.SomeTable.someField IS 'REALNAME:[someField]/TYPE:[STRING]', " + + "COMMENT ON COLUMN TEST.SomeTable.otherField IS 'REALNAME:[otherField]/TYPE:[DECIMAL]']"; assertEquals(expectedResult, result); } @@ -77,9 +100,20 @@ public void testAllStatement() { SelectStatement selectStatement = select().from(tableRef("OtherTable")); SelectStatement distinctSelectStatement = selectStatement.shallowCopy().distinct().build(); - String result = oracleDialect.convertStatementToSQL(distinctSelectStatement, table); - - String expectedResult = "SELECT DISTINCT * FROM TEST.OtherTable"; + String result = oracleDialect.addTableFromStatements(table, distinctSelectStatement).toString(); + + String expectedResult = "[CREATE TABLE TEST.SomeTable (" + + "someField NOT NULL, " + + "otherField NOT NULL, " + + "CONSTRAINT SomeTable_PK PRIMARY KEY (someField) " + + "USING INDEX (CREATE UNIQUE INDEX TEST.SomeTable_PK ON TEST.SomeTable (someField))) " + + "PARALLEL NOLOGGING " + + "AS SELECT DISTINCT * FROM TEST.OtherTable, " + + "ALTER TABLE TEST.SomeTable NOPARALLEL LOGGING, " + + "ALTER INDEX TEST.SomeTable_PK NOPARALLEL LOGGING, " + + "COMMENT ON TABLE TEST.SomeTable IS 'REALNAME:[SomeTable]', " + + "COMMENT ON COLUMN TEST.SomeTable.someField IS 'REALNAME:[someField]/TYPE:[STRING]', " + + "COMMENT ON COLUMN TEST.SomeTable.otherField IS 'REALNAME:[otherField]/TYPE:[DECIMAL]']"; assertEquals(expectedResult, result); } @@ -96,11 +130,23 @@ public void testUpdateStatement() { ); SelectStatement selectStatement = select().from(tableRef("OtherTable")); - SelectStatement distinctSelectStatement = selectStatement.shallowCopy().forUpdate().build(); - - String result = oracleDialect.convertStatementToSQL(distinctSelectStatement, table); - - String expectedResult = "SELECT * FROM TEST.OtherTable FOR UPDATE"; + SelectStatement updateSelectStatement = selectStatement.shallowCopy().forUpdate().build(); + + String result = oracleDialect.addTableFromStatements(table, updateSelectStatement).toString(); + + String expectedResult = "[CREATE TABLE TEST.SomeTable (" + + "someField NOT NULL, " + + "otherField NOT NULL, " + + "CONSTRAINT SomeTable_PK PRIMARY KEY (someField) " + + "USING INDEX (CREATE UNIQUE INDEX TEST.SomeTable_PK ON TEST.SomeTable (someField))) " + + "PARALLEL NOLOGGING " + + "AS SELECT * FROM TEST.OtherTable " + + "FOR UPDATE, " + + "ALTER TABLE TEST.SomeTable NOPARALLEL LOGGING, " + + "ALTER INDEX TEST.SomeTable_PK NOPARALLEL LOGGING, " + + "COMMENT ON TABLE TEST.SomeTable IS 'REALNAME:[SomeTable]', " + + "COMMENT ON COLUMN TEST.SomeTable.someField IS 'REALNAME:[someField]/TYPE:[STRING]', " + + "COMMENT ON COLUMN TEST.SomeTable.otherField IS 'REALNAME:[otherField]/TYPE:[DECIMAL]']"; assertEquals(expectedResult, result); } @@ -119,7 +165,7 @@ public void testUpdateForDistinctStatement() { SelectStatement selectStatement = select().from(tableRef("OtherTable")); SelectStatement distinctSelectStatement = selectStatement.shallowCopy().forUpdate().distinct().build(); - String result = oracleDialect.convertStatementToSQL(distinctSelectStatement, table); + String result = oracleDialect.addTableFromStatements(table, distinctSelectStatement).toString(); } @Test(expected = IllegalArgumentException.class) @@ -135,7 +181,7 @@ public void testIllegalArgumentExeptionIsThrown() { index("SomeTable_1").columns("otherField") ); - oracleDialect.convertStatementToSQL(null, table); + oracleDialect.convertStatementToSQL((SelectStatement) null); } From cd1aca1be3cc7fffac818a92762adf6bb3df47e7 Mon Sep 17 00:00:00 2001 From: Michael Parkin Date: Thu, 23 Mar 2023 11:00:00 +0000 Subject: [PATCH 11/11] remove changes to sql dialect --- .../org/alfasoftware/morf/jdbc/SqlDialect.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java b/morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java index dad5f10d4..1106be0bf 100755 --- a/morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java @@ -854,16 +854,6 @@ protected String getSqlFrom(SelectStatement stmt) { } } - result.append(appendPostSelectSQL(stmt)); - - // Any hint directives which should be inserted right at the end of the statement - result.append(selectStatementPostStatementDirectives(stmt)); - - return result.toString(); - } - - protected String appendPostSelectSQL(SelectStatement stmt) { - StringBuilder result = new StringBuilder(); appendFrom(result, stmt); appendJoins(result, stmt, innerJoinKeyword(stmt)); appendWhere(result, stmt); @@ -879,9 +869,14 @@ protected String appendPostSelectSQL(SelectStatement stmt) { } result.append(getForUpdateSql()); } + + // Any hint directives which should be inserted right at the end of the statement + result.append(selectStatementPostStatementDirectives(stmt)); + return result.toString(); } + /** * Returns any SQL code which should be added between a SELECT and the field * list for dialect-specific reasons.