diff --git a/morf-h2/pom.xml b/morf-h2/pom.xml index d81537ece..6397392b3 100755 --- a/morf-h2/pom.xml +++ b/morf-h2/pom.xml @@ -52,6 +52,7 @@ com.h2database h2 + 1.4.200 \ No newline at end of file diff --git a/morf-h2/src/main/java/org/alfasoftware/morf/jdbc/h2/H2Dialect.java b/morf-h2/src/main/java/org/alfasoftware/morf/jdbc/h2/H2Dialect.java index ad35eb9c8..6a81fdb0b 100755 --- a/morf-h2/src/main/java/org/alfasoftware/morf/jdbc/h2/H2Dialect.java +++ b/morf-h2/src/main/java/org/alfasoftware/morf/jdbc/h2/H2Dialect.java @@ -36,8 +36,6 @@ import org.alfasoftware.morf.sql.element.AliasedField; import org.alfasoftware.morf.sql.element.Function; import org.alfasoftware.morf.sql.element.FunctionType; -import org.alfasoftware.morf.sql.element.PortableSqlExpression; -import org.alfasoftware.morf.sql.element.PortableSqlFunction; import org.alfasoftware.morf.sql.element.SequenceReference; import org.alfasoftware.morf.sql.element.SqlParameter; import org.alfasoftware.morf.sql.element.TableReference; diff --git a/morf-h2v2/.gitignore b/morf-h2v2/.gitignore new file mode 100644 index 000000000..082346810 --- /dev/null +++ b/morf-h2v2/.gitignore @@ -0,0 +1 @@ +/ivy.xml diff --git a/morf-h2v2/pom.xml b/morf-h2v2/pom.xml new file mode 100755 index 000000000..96a233587 --- /dev/null +++ b/morf-h2v2/pom.xml @@ -0,0 +1,58 @@ + + 4.0.0 + + + org.alfasoftware + morf-parent + 2.30.1-SNAPSHOT + + + Morf - H2 version 2 + Morf is a library for cross-platform evolutionary relational database mechanics, database access and database imaging/cloning. + https://github.com/alfasoftware/morf + + morf-h2v2 + + + ${basedir}/../${aggregate.report.dir} + + + + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + + + + + org.alfasoftware + morf-core + + + org.alfasoftware + morf-testsupport + test + + + log4j + log4j + test + + + com.h2database + h2 + 2.3.232 + + + \ No newline at end of file diff --git a/morf-h2v2/src/main/java/org/alfasoftware/morf/jdbc/h2/H2.java b/morf-h2v2/src/main/java/org/alfasoftware/morf/jdbc/h2/H2.java new file mode 100755 index 000000000..cdf0d7e88 --- /dev/null +++ b/morf-h2v2/src/main/java/org/alfasoftware/morf/jdbc/h2/H2.java @@ -0,0 +1,149 @@ +/* Copyright 2017 Alfa Financial Software + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.alfasoftware.morf.jdbc.h2; + +import java.io.File; +import java.sql.Connection; +import java.util.Optional; + +import javax.sql.XADataSource; + +import org.alfasoftware.morf.jdbc.AbstractDatabaseType; +import org.alfasoftware.morf.jdbc.JdbcUrlElements; +import org.alfasoftware.morf.jdbc.SqlDialect; +import org.alfasoftware.morf.metadata.Schema; +import org.apache.commons.lang3.StringUtils; + +/** + * Support for H2 database hosts. + * + * @author Copyright (c) Alfa Financial Software 2017 + */ +public final class H2 extends AbstractDatabaseType { + + public static final String IDENTIFIER = "H2"; + + + /** + * Constructor. + */ + public H2() { + super("org.h2.Driver", IDENTIFIER); + } + + + /** + * @see org.alfasoftware.morf.jdbc.DatabaseType#formatJdbcUrl(JdbcUrlElements) + */ + @Override + public String formatJdbcUrl(JdbcUrlElements jdbcUrlElements) { + // http://www.h2database.com/html/features.html#database_url + + StringBuilder builder = new StringBuilder() + .append("jdbc:h2:"); + + if (StringUtils.isNotBlank(jdbcUrlElements.getHostName()) && !"localhost".equals(jdbcUrlElements.getHostName()) || jdbcUrlElements.getPort() > 0) { + builder + .append("tcp://") + .append(jdbcUrlElements.getHostName()) + .append(jdbcUrlElements.getPort() == 0 ? "" : ":" + jdbcUrlElements.getPort()) + .append("/mem:") // this means we're going to use a remote in-memory DB which isn't ideal + .append(jdbcUrlElements.getDatabaseName()); + + } else { + // no host, try the instanceName + if (StringUtils.isBlank(jdbcUrlElements.getInstanceName())) { + builder + .append("mem:") + .append(jdbcUrlElements.getDatabaseName()); + } else { + // Allow the instanceName to have a trailing slash, or not. + builder + .append("file:") + .append(jdbcUrlElements.getInstanceName()) + .append(jdbcUrlElements.getInstanceName().endsWith(File.separator) ? "" : File.separator) + .append(jdbcUrlElements.getDatabaseName()); + } + } + + // The DB_CLOSE_DELAY=-1 prevents the database being lost when the last connection is closed. + // The DEFAULT_LOCK_TIMEOUT=150000 sets the default lock timeout to 150 + // seconds. When the value is not set, it takes default + // org.h2.engine.Constants.INITIAL_LOCK_TIMEOUT=2000 value + // The LOB_TIMEOUT defines how long a lob returned from a ResultSet is available post-commit, defaulting to 5 minutes (300000 ms) + // Set this to 2 seconds to allow certain tests using lob fields to work + // The MV_STORE is a flag that governs whether to use the new storage engine (defaulting to true as of H2 version 1.4, false in prior versions) + // Note that implementations of H2 prior to version 1.4.199 had an MVCC parameter used to allow higher concurrency. + // This configuration has been removed and the old "PageStore" implementation (MV_STORE=FALSE) is no longer supported. + builder.append(";DB_CLOSE_DELAY=-1;DEFAULT_LOCK_TIMEOUT=150000;LOB_TIMEOUT=2000;MV_STORE=TRUE;NON_KEYWORDS=YEAR,MONTH"); + + return builder.toString(); + } + + /** + * @see org.alfasoftware.morf.jdbc.DatabaseType#openSchema(Connection, String, String) + */ + @Override + public Schema openSchema(Connection connection, String databaseName, String schemaName) { + return new H2MetaDataProvider(connection); + } + + + /** + * @see org.alfasoftware.morf.jdbc.DatabaseType#getXADataSource(java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public XADataSource getXADataSource(String jdbcUrl, String username, String password) { + throw new UnsupportedOperationException("H2 does not fully support XA connections. " + + "It may cause many different problems while running integration tests with H2. " + + "Please switch off Atomikos or change database engine. See WEB-31172 for details"); + // JdbcDataSource xaDataSource = new JdbcDataSource(); + // xaDataSource.setURL(jdbcUrl); + // xaDataSource.setUser(username); + // xaDataSource.setPassword(password); + // return xaDataSource; + } + + + /** + * @see org.alfasoftware.morf.jdbc.DatabaseType#sqlDialect(java.lang.String) + */ + @Override + public SqlDialect sqlDialect(String schemaName) { + return new H2Dialect(schemaName); + } + + + /** + * @see org.alfasoftware.morf.jdbc.DatabaseType#matchesProduct(java.lang.String) + */ + @Override + public boolean matchesProduct(String product) { + return product.equalsIgnoreCase("H2"); + } + + + /** + * We don't need to support extracting connection details from H2. It's only + * used for in-memory databases currently. + * + * @see org.alfasoftware.morf.jdbc.DatabaseType#extractJdbcUrl(java.lang.String) + */ + @Override + public Optional extractJdbcUrl(String url) { + return Optional.empty(); + } +} diff --git a/morf-h2v2/src/main/java/org/alfasoftware/morf/jdbc/h2/H2Dialect.java b/morf-h2v2/src/main/java/org/alfasoftware/morf/jdbc/h2/H2Dialect.java new file mode 100755 index 000000000..6720fe414 --- /dev/null +++ b/morf-h2v2/src/main/java/org/alfasoftware/morf/jdbc/h2/H2Dialect.java @@ -0,0 +1,712 @@ +/* Copyright 2017 Alfa Financial Software + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.alfasoftware.morf.jdbc.h2; + +import static org.alfasoftware.morf.metadata.SchemaUtils.namesOfColumns; +import static org.alfasoftware.morf.metadata.SchemaUtils.primaryKeysForTable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.alfasoftware.morf.jdbc.DatabaseType; +import org.alfasoftware.morf.jdbc.SqlDialect; +import org.alfasoftware.morf.metadata.Column; +import org.alfasoftware.morf.metadata.DataType; +import org.alfasoftware.morf.metadata.Index; +import org.alfasoftware.morf.metadata.Sequence; +import org.alfasoftware.morf.metadata.Table; +import org.alfasoftware.morf.sql.MergeStatement; +import org.alfasoftware.morf.sql.element.AliasedField; +import org.alfasoftware.morf.sql.element.BlobFieldLiteral; +import org.alfasoftware.morf.sql.element.Function; +import org.alfasoftware.morf.sql.element.FunctionType; +import org.alfasoftware.morf.sql.element.SequenceReference; +import org.alfasoftware.morf.sql.element.SqlParameter; +import org.alfasoftware.morf.sql.element.TableReference; +import org.apache.commons.lang3.StringUtils; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; +import com.google.common.collect.Lists; + +/** + * Implements database specific statement generation for H2. + * + * @author Copyright (c) Alfa Financial Software 2010 + */ +class H2Dialect extends SqlDialect { + + /** + * The prefix to add to all temporary tables. + */ + public static final String TEMPORARY_TABLE_PREFIX = "TEMP_"; + public static final String SYSTEM_SEQUENCE_PREFIX = "SYSTEM_SEQUENCE_"; + + + + /** + * @param schemaName Name of the schema to connect to + * + */ + public H2Dialect(String schemaName) { + super(schemaName); + } + + + @Override + protected String databaseTypeIdentifier() { + return H2.IDENTIFIER; + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#tableDeploymentStatements(org.alfasoftware.morf.metadata.Table) + */ + @Override + public Collection internalTableDeploymentStatements(Table table) { + List statements = new ArrayList<>(); + + // Create the table deployment statement + StringBuilder createTableStatement = new StringBuilder(); + createTableStatement.append("CREATE "); + + if (table.isTemporary()) { + createTableStatement.append("TEMPORARY "); + } + + createTableStatement.append("TABLE "); + createTableStatement.append(schemaNamePrefix()); + createTableStatement.append(table.getName()); + createTableStatement.append(" ("); + + List primaryKeys = new ArrayList<>(); + boolean first = true; + for (Column column : table.columns()) { + if (!first) { + createTableStatement.append(", "); + } + createTableStatement.append(column.getName()).append(" "); + createTableStatement.append(sqlRepresentationOfColumnType(column)); + if (column.isAutoNumbered()) { + int autoNumberStart = column.getAutoNumberStart() == -1 ? 1 : column.getAutoNumberStart(); + createTableStatement.append(" AUTO_INCREMENT(").append(autoNumberStart) + .append(") COMMENT 'AUTONUMSTART:[").append(autoNumberStart).append("]'"); + } + + if (column.isPrimaryKey()) { + primaryKeys.add(column.getName()); + } + + first = false; + } + + if (!primaryKeys.isEmpty()) { + createTableStatement.append(", CONSTRAINT "); + createTableStatement.append(table.getName()); + createTableStatement.append("_PK PRIMARY KEY ("); + createTableStatement.append(Joiner.on(", ").join(primaryKeys)); + createTableStatement.append(")"); + } + + createTableStatement.append(")"); + + statements.add(createTableStatement.toString()); + + return statements; + } + + + /** + * @see SqlDialect#internalSequenceDeploymentStatements(Sequence) + */ + @Override + public Collection internalSequenceDeploymentStatements(Sequence sequence) { + List statements = new ArrayList<>(); + + // Create the sequence deployment statement + StringBuilder createSequenceStatement = new StringBuilder(); + createSequenceStatement.append("CREATE "); + + createSequenceStatement.append("SEQUENCE "); + createSequenceStatement.append(schemaNamePrefix()); + createSequenceStatement.append(sequence.getName()); + + if (sequence.getStartsWith() != null) { + createSequenceStatement.append(" START WITH "); + createSequenceStatement.append(sequence.getStartsWith()); + } + + statements.add(createSequenceStatement.toString()); + + return statements; + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#dropStatements(org.alfasoftware.morf.metadata.Table) + */ + @Override + public Collection dropStatements(Table table) { + return dropTables(Lists.newArrayList(table), false, true); + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getColumnRepresentation(org.alfasoftware.morf.metadata.DataType, + * int, int) + */ + @Override + protected String getColumnRepresentation(DataType dataType, int width, int scale) { + switch (dataType) { + case STRING: + return width == 0 ? "VARCHAR" : String.format("VARCHAR(%d)", width); + + case DECIMAL: + return width == 0 ? "DECIMAL" : String.format("DECIMAL(%d,%d)", width, scale); + + case DATE: + return "DATE"; + + case BOOLEAN: + return "BIT"; + + case BIG_INTEGER: + return "BIGINT"; + + case INTEGER: + return "INTEGER"; + + case BLOB: + return "LONGVARBINARY"; + + case CLOB: + return "NCLOB"; + + default: + throw new UnsupportedOperationException("Cannot map column with type [" + dataType + "]"); + } + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getFromDummyTable() + */ + @Override + protected String getFromDummyTable() { + return " FROM dual"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#connectionTestStatement() + */ + @Override + public String connectionTestStatement() { + return "select 1"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getDatabaseType() + */ + @Override + public DatabaseType getDatabaseType() { + return DatabaseType.Registry.findByIdentifier(H2.IDENTIFIER); + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSelectLimitSuffix(int) + */ + @Override + protected Optional getSelectLimitSuffix(int limit) { + return Optional.of("LIMIT " + limit); + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#alterTableAddColumnStatements(org.alfasoftware.morf.metadata.Table, org.alfasoftware.morf.metadata.Column) + */ + @Override + public Collection alterTableAddColumnStatements(Table table, Column column) { + String statement = "ALTER TABLE " + schemaNamePrefix() + table.getName() + " ADD COLUMN " + + column.getName() + ' ' + sqlRepresentationOfColumnType(column, true); + + return Collections.singletonList(statement); + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#alterTableChangeColumnStatements(org.alfasoftware.morf.metadata.Table, org.alfasoftware.morf.metadata.Column, org.alfasoftware.morf.metadata.Column) + */ + @Override + public Collection alterTableChangeColumnStatements(Table table, Column oldColumn, Column newColumn) { + List result = new ArrayList<>(); + + if (oldColumn.isPrimaryKey() && !newColumn.isPrimaryKey()) { + result.add(dropPrimaryKeyConstraintStatement(table)); + } + + // Rename has to happen BEFORE any operations on the newly renamed column + if (!newColumn.getName().equals(oldColumn.getName())) { + result.add("ALTER TABLE " + schemaNamePrefix() + table.getName() + " ALTER COLUMN " + oldColumn.getName() + " RENAME TO " + + newColumn.getName()); + } + + // Now do column operations on the new + if (StringUtils.isNotEmpty(newColumn.getDefaultValue())) { + + result.add("ALTER TABLE " + schemaNamePrefix() + table.getName() + " ALTER COLUMN " + newColumn.getName() + " SET DEFAULT " + + sqlForDefaultClauseLiteral(newColumn)); + } + + if (oldColumn.isNullable() != newColumn.isNullable()) { + result.add("ALTER TABLE " + schemaNamePrefix() + table.getName() + " ALTER COLUMN " + newColumn.getName() + " SET " + + (newColumn.isNullable() ? "NULL" : "NOT NULL")); + } + + if (oldColumn.getType() != newColumn.getType() || + oldColumn.getScale() != newColumn.getScale() || + oldColumn.getWidth() != newColumn.getWidth() || + !StringUtils.equals(oldColumn.getDefaultValue(), newColumn.getDefaultValue()) || + oldColumn.isAutoNumbered() != newColumn.isAutoNumbered()) { + result.add("ALTER TABLE " + schemaNamePrefix() + table.getName() + " ALTER COLUMN " + newColumn.getName() + " " + + sqlRepresentationOfColumnType(newColumn, false, false, true)); + } + + // rebuild the PK if required + List primaryKeys = primaryKeysForTable(table); + if (oldColumn.isPrimaryKey() != newColumn.isPrimaryKey() && !primaryKeys.isEmpty()) { + result.add(addPrimaryKeyConstraintStatement(table, namesOfColumns(primaryKeys))); + } + + return result; + } + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#alterTableDropColumnStatements(org.alfasoftware.morf.metadata.Table, org.alfasoftware.morf.metadata.Column) + */ + @Override + public Collection alterTableDropColumnStatements(Table table, Column column) { + String statement = "ALTER TABLE " + schemaNamePrefix() + table.getName() + + " DROP COLUMN " + column.getName(); + + return Collections.singletonList(statement); + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#changePrimaryKeyColumns(org.alfasoftware.morf.metadata.Table, java.util.List, java.util.List) + */ + @Override + public Collection changePrimaryKeyColumns(Table table, List oldPrimaryKeyColumns, List newPrimaryKeyColumns) { + List result = new ArrayList<>(); + + if (!oldPrimaryKeyColumns.isEmpty()) { + result.add(dropPrimaryKeyConstraintStatement(table)); + } + + if (!newPrimaryKeyColumns.isEmpty()) { + result.add(addPrimaryKeyConstraintStatement(table, newPrimaryKeyColumns)); + } + + return result; + } + + + /** + * @param table The table to add the constraint for + * @param primaryKeyColumnNames List of the column names of the primary key + * @return The statement + */ + private String addPrimaryKeyConstraintStatement(Table table, List primaryKeyColumnNames) { + return "ALTER TABLE " + schemaNamePrefix() + table.getName() + " ADD CONSTRAINT " + table.getName() + "_PK PRIMARY KEY (" + Joiner.on(", ").join(primaryKeyColumnNames) + ")"; + } + + + /** + * @param table The table whose primary key should be dropped + * @return The statement + */ + private String dropPrimaryKeyConstraintStatement(Table table) { + return "ALTER TABLE " + schemaNamePrefix() + table.getName() + " DROP PRIMARY KEY"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#indexDeploymentStatements(org.alfasoftware.morf.metadata.Table, + * org.alfasoftware.morf.metadata.Index) + */ + @Override + protected Collection indexDeploymentStatements(Table table, Index index) { + StringBuilder statement = new StringBuilder(); + + statement.append("CREATE "); + if (index.isUnique()) { + statement.append("UNIQUE "); + } + statement.append("INDEX ").append(index.getName()).append(" ON ").append(schemaNamePrefix()).append(table.getName()).append(" (") + .append(Joiner.on(',').join(index.columnNames())).append(")"); + + return Collections.singletonList(statement.toString()); + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#indexDropStatements(org.alfasoftware.morf.metadata.Table, + * org.alfasoftware.morf.metadata.Index) + */ + @Override + public Collection indexDropStatements(Table table, Index indexToBeRemoved) { + return Arrays.asList("DROP INDEX " + indexToBeRemoved.getName()); + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlFrom(Boolean) + */ + @Override + protected String getSqlFrom(Boolean literalValue) { + return literalValue ? "true" : "false"; + } + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlFrom(BlobFieldLiteral) + */ + @Override + protected String getSqlFrom(BlobFieldLiteral field) { + return String.format("X'%s'", field.getValue()); + } + + /** + * It does explicit VARCHAR casting to avoid a HSQLDB 'feature' in which + * string literal values are effectively returned as CHAR (fixed width) data + * types rather than VARCHARs, where the length of the CHAR to hold the value + * is given by the maximum string length of any of the values that can be + * returned by the CASE statement. + * + * @see org.alfasoftware.morf.jdbc.SqlDialect#makeStringLiteral(java.lang.String) + */ + @Override + protected String makeStringLiteral(String literalValue) { + if (StringUtils.isEmpty(literalValue)) { + return "NULL"; + } + + return String.format("CAST(%s AS VARCHAR(%d))", super.makeStringLiteral(literalValue), literalValue.length()); + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#decorateTemporaryTableName(java.lang.String) + */ + @Override + public String decorateTemporaryTableName(String undecoratedName) { + return TEMPORARY_TABLE_PREFIX + undecoratedName; + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForYYYYMMDDToDate(org.alfasoftware.morf.sql.element.Function) + */ + @Override + protected String getSqlForYYYYMMDDToDate(Function function) { + AliasedField field = function.getArguments().get(0); + return "CAST(SUBSTRING(" + getSqlFrom(field) + ", 1, 4)||'-'||SUBSTRING(" + getSqlFrom(field) + ", 5, 2)||'-'||SUBSTRING(" + getSqlFrom(field) + ", 7, 2) AS DATE)"; + } + + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForDateToYyyymmdd(org.alfasoftware.morf.sql.element.Function) + */ + @Override + protected String getSqlForDateToYyyymmdd(Function function) { + String sqlExpression = getSqlFrom(function.getArguments().get(0)); + return String.format("CAST(SUBSTRING(%1$s, 1, 4)||SUBSTRING(%1$s, 6, 2)||SUBSTRING(%1$s, 9, 2) AS DECIMAL(8))",sqlExpression); + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForDateToYyyymmddHHmmss(org.alfasoftware.morf.sql.element.Function) + */ + @Override + protected String getSqlForDateToYyyymmddHHmmss(Function function) { + String sqlExpression = getSqlFrom(function.getArguments().get(0)); + // Example for CURRENT_TIMESTAMP() -> 2015-06-23 11:25:08.11 + return String.format("CAST(SUBSTRING(%1$s, 1, 4)||SUBSTRING(%1$s, 6, 2)||SUBSTRING(%1$s, 9, 2)||SUBSTRING(%1$s, 12, 2)||SUBSTRING(%1$s, 15, 2)||SUBSTRING(%1$s, 18, 2) AS DECIMAL(14))", sqlExpression); + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForNow(org.alfasoftware.morf.sql.element.Function) + */ + @Override + protected String getSqlForNow(Function function) { + return "CURRENT_TIMESTAMP()"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForDaysBetween(org.alfasoftware.morf.sql.element.AliasedField, + * org.alfasoftware.morf.sql.element.AliasedField) + */ + @Override + protected String getSqlForDaysBetween(AliasedField toDate, AliasedField fromDate) { + return "DATEDIFF('DAY'," + getSqlFrom(fromDate) + ", " + getSqlFrom(toDate) + ")"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForMonthsBetween(org.alfasoftware.morf.sql.element.AliasedField, org.alfasoftware.morf.sql.element.AliasedField) + */ + @Override + protected String getSqlForMonthsBetween(AliasedField toDate, AliasedField fromDate) { + return String.format( + "CASE " + + "WHEN %1$s = %2$s THEN 0 " + + "ELSE " + + "DATEDIFF(MONTH, %1$s, %2$s) + " + + "CASE " + + "WHEN %2$s > %1$s THEN " + + "CASE " + + "WHEN DAY(%1$s) <= DAY(%2$s) OR MONTH(%2$s) <> MONTH(DATEADD(DAY, 1, %2$s)) THEN 0 " + + "ELSE -1 " + + "END " + + "ELSE " + + "CASE " + + "WHEN DAY(%2$s) <= DAY(%1$s) OR MONTH(%1$s) <> MONTH(DATEADD(DAY, 1, %1$s)) THEN 0 " + + "ELSE 1 " + + "END " + + "END " + + "END ", + getSqlFrom(fromDate), getSqlFrom(toDate) + ); + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForAddDays(org.alfasoftware.morf.sql.element.Function) + */ + @Override + protected String getSqlForAddDays(Function function) { + return String.format( + "DATEADD('DAY', %s, %s)", + getSqlFrom(function.getArguments().get(1)), + getSqlFrom(function.getArguments().get(0)) + ); + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForAddMonths(org.alfasoftware.morf.sql.element.Function) + */ + @Override + protected String getSqlForAddMonths(Function function) { + return String.format( + "DATEADD('MONTH', %s, %s)", + getSqlFrom(function.getArguments().get(1)), + getSqlFrom(function.getArguments().get(0)) + ); + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#renameTableStatements(org.alfasoftware.morf.metadata.Table, org.alfasoftware.morf.metadata.Table) + */ + @Override + public Collection renameTableStatements(Table from, Table to) { + + Builder builder = ImmutableList.builder(); + + // H2 special: PK gets dropped upon rename! + if (!primaryKeysForTable(from).isEmpty()) { + builder.add(dropPrimaryKeyConstraintStatement(from)); + } + + builder.add("ALTER TABLE " + schemaNamePrefix() + from.getName() + " RENAME TO " + to.getName()); + + if (!primaryKeysForTable(to).isEmpty()) { + builder.add(addPrimaryKeyConstraintStatement(to, namesOfColumns(primaryKeysForTable(to)))); + } + + return builder.build(); + } + + + /** + * TODO + * The following is a workaround to a bug in H2 version 1.4.200 whereby the MERGE...USING statement does not release the source select statement + * Please remove this method once issue 2196 has been fixed and H2 upgraded to the fixed version + * This workaround uses the following alternative syntax, which fortunately does not lead to the same bug: + * + *
+   *   WITH xmergesource AS (SELECT ...)
+   *   MERGE INTO Table
+   *     USING xmergesource
+   *     ON (Table.id = xmergesource.id)
+   *     WHEN MATCHED THEN UPDATE ...
+   *     WHEN NOT MATCHED THEN INSERT ...
+   * 
+ * + * @see SqlDialect#getSqlFrom(MergeStatement) + */ +// @Override +// protected String getSqlFrom(MergeStatement statement) { +// +// // --- TODO +// // call the original implementation which performs various consistency checks +// super.getSqlFrom(statement); +// +// // --- TODO +// // but ignore whatever it produces, and create a slightly different variant +// final StringBuilder sqlBuilder = new StringBuilder(); +// +// // WITH xmergesource AS (SELECT ...) +// sqlBuilder.append("WITH ") +// .append(MERGE_SOURCE_ALIAS) +// .append(" AS (") +// .append(getSqlFrom(statement.getSelectStatement())) +// .append(") "); +// +// // MERGE INTO Table USING xmergesource +// sqlBuilder.append("MERGE INTO ") +// .append(schemaNamePrefix()) +// .append(statement.getTable().getName()) +// .append(" USING ") +// .append(MERGE_SOURCE_ALIAS); +// +// // ON (Table.id = xmergesource.id) +// sqlBuilder.append(" ON (") +// .append(matchConditionSqlForMergeFields(statement, MERGE_SOURCE_ALIAS, statement.getTable().getName())) +// .append(")"); +// +// // WHEN MATCHED THEN UPDATE ... +// sqlBuilder.append(mergeStatementWhenMatchedUpdateClause(statement)); +// +// // WHEN NOT MATCHED THEN INSERT ... +// Iterable insertField = Iterables.transform(statement.getSelectStatement().getFields(), AliasedField::getImpliedName); +// Iterable valueFields = Iterables.transform(statement.getSelectStatement().getFields(), field -> MERGE_SOURCE_ALIAS + "." + field.getImpliedName()); +// +// sqlBuilder.append(" WHEN NOT MATCHED THEN INSERT (") +// .append(Joiner.on(", ").join(insertField)) +// .append(") VALUES (") +// .append(Joiner.on(", ").join(valueFields)) +// .append(")"); +// +// return sqlBuilder.toString(); +// } + + + @Override + protected String getSqlFrom(SqlParameter sqlParameter) { + return String.format("CAST(:%s AS %s)", sqlParameter.getMetadata().getName(), sqlRepresentationOfColumnType(sqlParameter.getMetadata(), false)); + } + + + /** + * @see SqlDialect#getSqlFrom(SequenceReference) + */ + @Override + protected String getSqlFrom(SequenceReference sequenceReference) { + StringBuilder result = new StringBuilder(); + + switch (sequenceReference.getTypeOfOperation()) { + case NEXT_VALUE: + result.append("NEXT"); + break; + case CURRENT_VALUE: + result.append("CURRENT"); + break; + } + result.append(" VALUE FOR "); + result.append(sequenceReference.getName()); + + return result.toString(); + } + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForRandomString(org.alfasoftware.morf.sql.element.Function) + */ + @Override + protected String getSqlForRandomString(Function function) { + return String.format("SUBSTRING(REPLACE(RANDOM_UUID(),'-'), 1, %s)", getSqlFrom(function.getArguments().get(0))); + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForLastDayOfMonth + */ + @Override + protected String getSqlForLastDayOfMonth(AliasedField date) { + return "DATEADD(dd, -DAY(DATEADD(m,1," + getSqlFrom(date) + ")), DATEADD(m,1," + getSqlFrom(date) + "))"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForRowNumber() + */ + @Override + protected String getSqlForRowNumber() { + return "ROW_NUMBER() OVER()"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForWindowFunction(Function) + */ + @Override + protected String getSqlForWindowFunction(Function function) { + FunctionType functionType = function.getType(); + switch (functionType) { + case ROW_NUMBER: + return "ROW_NUMBER()"; + + default: + return super.getSqlForWindowFunction(function); + } + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getDeleteLimitSuffix(int) + */ + @Override + protected Optional getDeleteLimitSuffix(int limit) { + return Optional.of("LIMIT " + limit); + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#tableNameWithSchemaName(org.alfasoftware.morf.sql.element.TableReference) + */ + @Override + protected String tableNameWithSchemaName(TableReference tableRef) { + if (!StringUtils.isEmpty(tableRef.getDblink())) throw new IllegalStateException("DB Links are not supported in the H2 dialect. Found dbLink=" + tableRef.getDblink() + " for tableNameWithSchemaName=" + super.tableNameWithSchemaName(tableRef)); + return super.tableNameWithSchemaName(tableRef); + } + + + @Override + public boolean useForcedSerialImport() { + return true; + } +} \ No newline at end of file diff --git a/morf-h2v2/src/main/java/org/alfasoftware/morf/jdbc/h2/H2MetaDataProvider.java b/morf-h2v2/src/main/java/org/alfasoftware/morf/jdbc/h2/H2MetaDataProvider.java new file mode 100755 index 000000000..978007b41 --- /dev/null +++ b/morf-h2v2/src/main/java/org/alfasoftware/morf/jdbc/h2/H2MetaDataProvider.java @@ -0,0 +1,161 @@ +/* Copyright 2017 Alfa Financial Software + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.alfasoftware.morf.jdbc.h2; + +import static org.alfasoftware.morf.jdbc.DatabaseMetaDataProviderUtils.getAutoIncrementStartValue; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Set; + +import org.alfasoftware.morf.jdbc.DatabaseMetaDataProvider; +import org.alfasoftware.morf.metadata.SchemaUtils.ColumnBuilder; + +/** + * Database meta-data layer for H2. + * + * @author Copyright (c) Alfa Financial Software 2010 + */ +class H2MetaDataProvider extends DatabaseMetaDataProvider { + + /** + * @param connection DataSource to provide meta data for. + */ + public H2MetaDataProvider(Connection connection) { + super(connection, "PUBLIC"); + } + + /** + * @param connection DataSource to provide meta data for. + * @param schemaName The schema to connect to. + */ + public H2MetaDataProvider(Connection connection, String schemaName) { + super(connection, schemaName); + } + + /** + * H2 reports its primary key indexes as PRIMARY_KEY_49 or similar. + * + * @see org.alfasoftware.morf.jdbc.DatabaseMetaDataProvider#isPrimaryKeyIndex(RealName) + */ + @Override + protected boolean isPrimaryKeyIndex(RealName indexName) { + return indexName.getDbName().startsWith("PRIMARY_KEY"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.DatabaseMetaDataProvider#isIgnoredTable(RealName) + */ + @Override + protected boolean isIgnoredTable(RealName tableName) { + // Ignore temporary tables + return tableName.getDbName().startsWith(H2Dialect.TEMPORARY_TABLE_PREFIX); + } + + + private static final Set SYSTEM_TABLES = Set.of( + "CHECK_CONSTRAINTS", + "COLLATIONS", + "COLUMNS", + "COLUMN_PRIVILEGES", + "CONSTANTS", + "CONSTRAINT_COLUMN_USAGE", + "DOMAINS", + "DOMAIN_CONSTRAINTS", + "ELEMENT_TYPES", + "ENUM_VALUES", + "FIELDS", + "INDEXES", + "INDEX_COLUMNS", + "INFORMATION_SCHEMA_CATALOG_NAME", + "IN_DOUBT", + "KEY_COLUMN_USAGE", + "LOCKS", + "PARAMETERS", + "QUERY_STATISTICS", + "REFERENTIAL_CONSTRAINTS", + "RIGHTS", + "ROLES", + "ROUTINES", + "SCHEMATA", + "SEQUENCES", + "SESSIONS", + "SESSION_STATE", + "SETTINGS", + "SYNONYMS", + "TABLES", + "TABLE_CONSTRAINTS", + "TABLE_PRIVILEGES", + "TRIGGERS", + "USERS", + "VIEWS" + ); + + + /** + * @see org.alfasoftware.morf.jdbc.DatabaseMetaDataProvider#isSystemTable(RealName) + */ + @Override + protected boolean isSystemTable(RealName tableName) { + // Ignore system tables + return SYSTEM_TABLES.contains(tableName.getDbName()); + } + + + /** + * @see org.alfasoftware.morf.jdbc.DatabaseMetaDataProvider#isIgnoredSequence(RealName) + */ + @Override + protected boolean isSystemSequence(RealName sequenceName) { + // Ignore system sequences + return sequenceName.getDbName().startsWith(H2Dialect.SYSTEM_SEQUENCE_PREFIX); + } + + + /** + * H2 can (and must) provide the auto-increment start value from the column remarks. + * + * @see org.alfasoftware.morf.jdbc.DatabaseMetaDataProvider#setAdditionalColumnMetadata(RealName, ColumnBuilder, ResultSet) + */ + @Override + protected ColumnBuilder setAdditionalColumnMetadata(RealName tableName, ColumnBuilder columnBuilder, ResultSet columnMetaData) throws SQLException { + columnBuilder = super.setAdditionalColumnMetadata(tableName, columnBuilder, columnMetaData); + if (columnBuilder.isAutoNumbered()) { + int startValue = getAutoIncrementStartValue(columnMetaData.getString(COLUMN_REMARKS)); + return columnBuilder.autoNumbered(startValue == -1 ? 1 : startValue); + } else { + return columnBuilder; + } + } + + + /** + * @see DatabaseMetaDataProvider#buildSequenceSql(String) + */ + @Override + protected String buildSequenceSql(String schemaName) { + StringBuilder sequenceSqlBuilder = new StringBuilder(); + sequenceSqlBuilder.append("SELECT SEQUENCE_NAME FROM INFORMATION_SCHEMA.SEQUENCES"); + + if (schemaName != null && !schemaName.isBlank()) { + sequenceSqlBuilder.append(" WHERE SEQUENCE_SCHEMA =?"); + } + + return sequenceSqlBuilder.toString(); + } +} diff --git a/morf-h2v2/src/main/java/org/alfasoftware/morf/jdbc/h2/H2Sql.java b/morf-h2v2/src/main/java/org/alfasoftware/morf/jdbc/h2/H2Sql.java new file mode 100644 index 000000000..3ce78ec07 --- /dev/null +++ b/morf-h2v2/src/main/java/org/alfasoftware/morf/jdbc/h2/H2Sql.java @@ -0,0 +1,170 @@ +package org.alfasoftware.morf.jdbc.h2; + +import static org.alfasoftware.morf.sql.SqlUtils.select; + +import java.util.List; + +import org.alfasoftware.morf.sql.DeleteStatement; +import org.alfasoftware.morf.sql.InsertStatement; +import org.alfasoftware.morf.sql.MergeStatement; +import org.alfasoftware.morf.sql.SelectFirstStatement; +import org.alfasoftware.morf.sql.SelectStatement; +import org.alfasoftware.morf.sql.Statement; +import org.alfasoftware.morf.sql.TruncateStatement; +import org.alfasoftware.morf.sql.UpdateStatement; +import org.alfasoftware.morf.sql.element.Criterion; +import org.alfasoftware.morf.sql.element.TableReference; + +/** + * Converts parts of Morf's DSL into H2-based SQL. + * + * This can be used to produce static SQL from Morf's DSL, + * but it should be noted that no considerations are made about + * schema names, or anything related to an actual connection. + * + * @author Copyright (c) Alfa Financial Software 2024 + */ +public interface H2Sql { + + /** + * Creates an instance of {@link H2Sql}. + * @return {@link H2Sql} instance. + */ + public static H2Sql createH2Sql() { + return new H2DialectExt().createH2Sql(); + } + + /** + * Converts given statement to SQL. + * @param statement Statement to convert. + * @return Resulting SQL. + */ + public String sqlFrom(SelectStatement statement); + + /** + * Converts given statement to SQL. + * @param statement Statement to convert. + * @return Resulting SQL. + */ + public String sqlFrom(SelectFirstStatement statement); + + /** + * Converts given statement to SQL. + * @param statement Statement to convert. + * @return Resulting SQL. + */ + public List sqlFrom(InsertStatement statement); + + /** + * Converts given statement to SQL. + * @param statement Statement to convert. + * @return Resulting SQL. + */ + public String sqlFrom(UpdateStatement statement); + + /** + * Converts given statement to SQL. + * @param statement Statement to convert. + * @return Resulting SQL. + */ + public String sqlFrom(MergeStatement statement); + + /** + * Converts given statement to SQL. + * @param statement Statement to convert. + * @return Resulting SQL. + */ + public String sqlFrom(DeleteStatement statement); + + /** + * Converts given statement to SQL. + * @param statement Statement to convert. + * @return Resulting SQL. + */ + public String sqlFrom(TruncateStatement statement); + + /** + * Converts given statement to SQL. + * @param statement Statement to convert. + * @return Resulting SQL. + */ + public List sqlFrom(Statement statement); + + /** + * Converts given criterion to SQL. + * @param criterion Criterion to convert. + * @return Resulting SQL clause. + */ + public String sqlFrom(Criterion criterion); +} + +/** + * Implementation of {@link H2Sql}. + * + * @author Copyright (c) Alfa Financial Software 2024 + */ +class H2DialectExt extends H2Dialect { + + public H2DialectExt() { + super(null); + } + + public H2Sql createH2Sql() { + return new H2Sql() { + + @Override + public String sqlFrom(SelectStatement statement) { + return H2DialectExt.super.convertStatementToSQL(statement); + } + + @Override + public String sqlFrom(SelectFirstStatement statement) { + return H2DialectExt.super.convertStatementToSQL(statement); + } + + @Override + public List sqlFrom(InsertStatement statement) { + return H2DialectExt.super.convertStatementToSQL(statement); + } + + @Override + public String sqlFrom(UpdateStatement statement) { + return H2DialectExt.super.convertStatementToSQL(statement); + } + + @Override + public String sqlFrom(MergeStatement statement) { + return H2DialectExt.super.convertStatementToSQL(statement); + } + + @Override + public String sqlFrom(DeleteStatement statement) { + return H2DialectExt.super.convertStatementToSQL(statement); + } + + @Override + public String sqlFrom(TruncateStatement statement) { + return H2DialectExt.super.convertStatementToSQL(statement); + } + + @Override + public List sqlFrom(Statement statement) { + if (statement instanceof InsertStatement) { + InsertStatement insertStatement = (InsertStatement)statement; + if (null == insertStatement.getSelectStatement() && insertStatement.getFromTable() != null) { + statement = insertStatement.shallowCopy().from((TableReference)null).from(select().from(insertStatement.getFromTable())).build(); + } + if (null == insertStatement.getSelectStatement() && insertStatement.getFromTable() == null && insertStatement.getValues().isEmpty()) { + return H2DialectExt.super.buildSpecificValueInsert(insertStatement, null, null); + } + } + return H2DialectExt.super.convertStatementToSQL(statement, null, null); + } + + @Override + public String sqlFrom(Criterion criterion) { + return H2DialectExt.super.getSqlFrom(criterion); + } + }; + } +} diff --git a/morf-h2v2/src/main/resources/META-INF/services/org.alfasoftware.morf.jdbc.DatabaseType b/morf-h2v2/src/main/resources/META-INF/services/org.alfasoftware.morf.jdbc.DatabaseType new file mode 100755 index 000000000..f9dc29476 --- /dev/null +++ b/morf-h2v2/src/main/resources/META-INF/services/org.alfasoftware.morf.jdbc.DatabaseType @@ -0,0 +1 @@ +org.alfasoftware.morf.jdbc.h2.H2 \ No newline at end of file diff --git a/morf-h2v2/src/main/resources/org/alfasoftware/morf/jdbc/h2/package.html b/morf-h2v2/src/main/resources/org/alfasoftware/morf/jdbc/h2/package.html new file mode 100755 index 000000000..29bd598b3 --- /dev/null +++ b/morf-h2v2/src/main/resources/org/alfasoftware/morf/jdbc/h2/package.html @@ -0,0 +1,9 @@ + + + + Package details + + +

Implementations specific to H2 databases.

+ + diff --git a/morf-h2v2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2DatabaseType.java b/morf-h2v2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2DatabaseType.java new file mode 100755 index 000000000..18a1bb32d --- /dev/null +++ b/morf-h2v2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2DatabaseType.java @@ -0,0 +1,99 @@ +/* Copyright 2017 Alfa Financial Software + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.alfasoftware.morf.jdbc.h2; + +import static org.alfasoftware.morf.jdbc.DatabaseTypeIdentifierTestUtils.mockDataSourceFor; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.verify; + +import java.io.File; +import java.sql.SQLException; +import java.util.Optional; + +import javax.sql.DataSource; + +import org.alfasoftware.morf.jdbc.DatabaseType; +import org.alfasoftware.morf.jdbc.DatabaseTypeIdentifier; +import org.alfasoftware.morf.jdbc.JdbcUrlElements; +import org.alfasoftware.morf.jdbc.JdbcUrlElements.Builder; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for DatabaseType.H2 + * + * @author Copyright (c) Alfa Financial Software 2014 + */ +public class TestH2DatabaseType { + + private DatabaseType databaseType; + + + @Before + public void setup() { + databaseType = DatabaseType.Registry.findByIdentifier(H2.IDENTIFIER); + } + + + /** + * Test the JDBC URL construction. + */ + @Test + public void testFormatJdbcUrl() { + String suffix = ";DB_CLOSE_DELAY=-1;DEFAULT_LOCK_TIMEOUT=150000;LOB_TIMEOUT=2000;MV_STORE=TRUE;NON_KEYWORDS=YEAR,MONTH"; + + assertEquals("jdbc:h2:tcp://foo.com:123/mem:alfa" + suffix, databaseType.formatJdbcUrl(jdbcUrlElementBuilder().withHost("foo.com").withPort(123).withDatabaseName("alfa").build())); + assertEquals("jdbc:h2:tcp://foo.com/mem:alfa" + suffix, databaseType.formatJdbcUrl(jdbcUrlElementBuilder().withHost("foo.com").withDatabaseName("alfa").build())); + + assertEquals("jdbc:h2:mem:alfa" + suffix, databaseType.formatJdbcUrl(jdbcUrlElementBuilder().withDatabaseName("alfa").build())); + + assertEquals("jdbc:h2:file:." + File.separator + "alfa" + suffix, databaseType.formatJdbcUrl(jdbcUrlElementBuilder().withInstanceName(".").withDatabaseName("alfa").build())); + assertEquals("jdbc:h2:file:bar" + File.separator + "alfa" + suffix, databaseType.formatJdbcUrl(jdbcUrlElementBuilder().withInstanceName("bar" + File.separator).withDatabaseName("alfa").build())); + + assertEquals("jdbc:h2:mem:data;DB_CLOSE_DELAY=-1;DEFAULT_LOCK_TIMEOUT=150000;LOB_TIMEOUT=2000;MV_STORE=TRUE;NON_KEYWORDS=YEAR,MONTH", databaseType.formatJdbcUrl(jdbcUrlElementBuilder().withHost("localhost").withDatabaseName("data").build())); + } + + + + @Test(expected = UnsupportedOperationException.class) + public void getXADataSourceForH2ShouldThrowException(){ + databaseType.getXADataSource(null, null, null); + } + + + /** + * Test identification of a database platform. + * + * @throws SQLException as part of tested contract. + */ + @Test + public void testIdentifyFromMetaData() throws SQLException { + // -- Unknown and resource management... + // + DataSource dataSource = mockDataSourceFor("FictiousDB", "9.9.9", 9, 9); + assertEquals(Optional.empty(), new DatabaseTypeIdentifier(dataSource).identifyFromMetaData()); + verify(dataSource.getConnection()).close(); + + // -- Support platforms... + // + assertEquals(databaseType, new DatabaseTypeIdentifier(mockDataSourceFor("H2", "1.3.167 (2012-05-23)", 1, 3)).identifyFromMetaData().get()); + } + + + private Builder jdbcUrlElementBuilder() { + return JdbcUrlElements.forDatabaseType(H2.IDENTIFIER); + } +} diff --git a/morf-h2v2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2Dialect.java b/morf-h2v2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2Dialect.java new file mode 100755 index 000000000..49c2aa94e --- /dev/null +++ b/morf-h2v2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2Dialect.java @@ -0,0 +1,1401 @@ +/* Copyright 2017 Alfa Financial Software + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.alfasoftware.morf.jdbc.h2; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.alfasoftware.morf.jdbc.AbstractSqlDialectTest; +import org.alfasoftware.morf.jdbc.SqlDialect; +import org.apache.commons.lang3.StringUtils; + +import com.google.common.collect.ImmutableList; + +/** + * Tests SQL statements generated for H2. + * + * @author Copyright (c) Alfa Financial Software 2010 + */ +public class TestH2Dialect extends AbstractSqlDialectTest { + + + private final static String TEST_SCHEMA = "TESTSCHEMA"; + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#createTestDialect() + */ + @Override + protected SqlDialect createTestDialect() { + return new H2Dialect(TEST_SCHEMA); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedCreateTableStatements() + */ + @Override + protected List expectedCreateTableStatements() { + return Arrays + .asList( + "CREATE TABLE "+TEST_SCHEMA+".Test (id BIGINT NOT NULL, version INTEGER DEFAULT 0, stringField VARCHAR(3), intField INTEGER, floatField DECIMAL(13,2) NOT NULL, dateField DATE, booleanField BIT, charField VARCHAR(1), blobField LONGVARBINARY, bigIntegerField BIGINT DEFAULT 12345, clobField NCLOB, CONSTRAINT Test_PK PRIMARY KEY (id))", + "CREATE UNIQUE INDEX Test_NK ON "+TEST_SCHEMA+".Test (stringField)", + "CREATE UNIQUE INDEX Test_1 ON "+TEST_SCHEMA+".Test (intField,floatField)", + "CREATE TABLE "+TEST_SCHEMA+".Alternate (id BIGINT NOT NULL, version INTEGER DEFAULT 0, stringField VARCHAR(3), CONSTRAINT Alternate_PK PRIMARY KEY (id))", + "CREATE INDEX Alternate_1 ON "+TEST_SCHEMA+".Alternate (stringField)", + "CREATE TABLE "+TEST_SCHEMA+".NonNull (id BIGINT NOT NULL, version INTEGER DEFAULT 0, stringField VARCHAR(3) NOT NULL, intField DECIMAL(8,0) NOT NULL, booleanField BIT NOT NULL, dateField DATE NOT NULL, blobField LONGVARBINARY NOT NULL, CONSTRAINT NonNull_PK PRIMARY KEY (id))", + "CREATE TABLE "+TEST_SCHEMA+".CompositePrimaryKey (id BIGINT NOT NULL, version INTEGER DEFAULT 0, stringField VARCHAR(3) NOT NULL, secondPrimaryKey VARCHAR(3) NOT NULL, CONSTRAINT CompositePrimaryKey_PK PRIMARY KEY (id, secondPrimaryKey))", + "CREATE TABLE "+TEST_SCHEMA+".AutoNumber (intField BIGINT AUTO_INCREMENT(5) COMMENT 'AUTONUMSTART:[5]', CONSTRAINT AutoNumber_PK PRIMARY KEY (intField))" + ); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedCreateTemporaryTableStatements() + */ + @Override + protected List expectedCreateTemporaryTableStatements() { + return Arrays + .asList( + "CREATE TEMPORARY TABLE "+TEST_SCHEMA+".TEMP_TempTest (id BIGINT NOT NULL, version INTEGER DEFAULT 0, stringField VARCHAR(3), intField INTEGER, floatField DECIMAL(13,2) NOT NULL, dateField DATE, booleanField BIT, charField VARCHAR(1), blobField LONGVARBINARY, bigIntegerField BIGINT DEFAULT 12345, clobField NCLOB, CONSTRAINT TEMP_TempTest_PK PRIMARY KEY (id))", + "CREATE UNIQUE INDEX TempTest_NK ON "+TEST_SCHEMA+".TEMP_TempTest (stringField)", + "CREATE INDEX TempTest_1 ON "+TEST_SCHEMA+".TEMP_TempTest (intField,floatField)", + "CREATE TEMPORARY TABLE "+TEST_SCHEMA+".TEMP_TempAlternate (id BIGINT NOT NULL, version INTEGER DEFAULT 0, stringField VARCHAR(3), CONSTRAINT TEMP_TempAlternate_PK PRIMARY KEY (id))", + "CREATE INDEX TempAlternate_1 ON "+TEST_SCHEMA+".TEMP_TempAlternate (stringField)", + "CREATE TEMPORARY TABLE "+TEST_SCHEMA+".TEMP_TempNonNull (id BIGINT NOT NULL, version INTEGER DEFAULT 0, stringField VARCHAR(3) NOT NULL, intField DECIMAL(8,0) NOT NULL, booleanField BIT NOT NULL, dateField DATE NOT NULL, blobField LONGVARBINARY NOT NULL, CONSTRAINT TEMP_TempNonNull_PK PRIMARY KEY (id))"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedCreateTableStatementsWithLongTableName() + */ + @Override + protected List expectedCreateTableStatementsWithLongTableName() { + return Arrays + .asList("CREATE TABLE "+TEST_SCHEMA+"." + + TABLE_WITH_VERY_LONG_NAME + + " (id BIGINT NOT NULL, version INTEGER DEFAULT 0, stringField VARCHAR(3), intField DECIMAL(8,0), floatField DECIMAL(13,2) NOT NULL, dateField DATE, booleanField BIT, charField VARCHAR(1), CONSTRAINT " + + TABLE_WITH_VERY_LONG_NAME + "_PK PRIMARY KEY (id))", + "CREATE UNIQUE INDEX Test_NK ON "+TEST_SCHEMA+"."+ TABLE_WITH_VERY_LONG_NAME + " (stringField)", + "CREATE INDEX Test_1 ON "+TEST_SCHEMA+"."+ TABLE_WITH_VERY_LONG_NAME + " (intField,floatField)" + ); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedDropTableStatements() + */ + @Override + protected List expectedDropTableStatements() { + return Arrays.asList("DROP TABLE "+TEST_SCHEMA+".Test CASCADE"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedDropSingleTable() + */ + @Override + protected List expectedDropSingleTable() { + return Arrays.asList("DROP TABLE "+TEST_SCHEMA+".Test"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedDropTables() + */ + @Override + protected List expectedDropTables() { + return Arrays.asList("DROP TABLE "+TEST_SCHEMA+".Test, "+TEST_SCHEMA+".Other"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedDropTablesWithParameters() + */ + @Override + protected List expectedDropTablesWithParameters() { + return Arrays.asList("DROP TABLE IF EXISTS "+TEST_SCHEMA+".Test, "+TEST_SCHEMA+".Other CASCADE"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedDropTempTableStatements() + */ + @Override + protected List expectedDropTempTableStatements() { + return Arrays.asList("DROP TABLE "+TEST_SCHEMA+".TEMP_TempTest CASCADE"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedTruncateTableStatements() + */ + @Override + protected List expectedTruncateTableStatements() { + return Arrays.asList("TRUNCATE TABLE "+TEST_SCHEMA+".Test"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedTruncateTempTableStatements() + */ + @Override + protected List expectedTruncateTempTableStatements() { + return Arrays.asList("TRUNCATE TABLE "+TEST_SCHEMA+".TEMP_TempTest"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedDeleteAllFromTableStatements() + */ + @Override + protected List expectedDeleteAllFromTableStatements() { + return Arrays.asList("DELETE FROM "+TEST_SCHEMA+".Test"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedParameterisedInsertStatement() + */ + @Override + protected String expectedParameterisedInsertStatement() { + return "INSERT INTO "+TEST_SCHEMA+".Test (id, version, stringField, intField, floatField, dateField, booleanField, charField, blobField, bigIntegerField, clobField) VALUES (5, CAST(:version AS INTEGER), CAST('Escap''d' AS VARCHAR(7)), 7, CAST(:floatField AS DECIMAL(13,2)), 20100405, true, CAST(:charField AS VARCHAR(1)), CAST(:blobField AS LONGVARBINARY), CAST(:bigIntegerField AS BIGINT), CAST(:clobField AS NCLOB))"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedParameterisedInsertStatementWithTableInDifferentSchema() + */ + @Override + protected String expectedParameterisedInsertStatementWithTableInDifferentSchema() { + return "INSERT INTO MYSCHEMA.Test (id, version, stringField, intField, floatField, dateField, booleanField, charField, blobField, bigIntegerField, clobField) VALUES (5, CAST(:version AS INTEGER), CAST('Escap''d' AS VARCHAR(7)), 7, CAST(:floatField AS DECIMAL(13,2)), 20100405, true, CAST(:charField AS VARCHAR(1)), CAST(:blobField AS LONGVARBINARY), CAST(:bigIntegerField AS BIGINT), CAST(:clobField AS NCLOB))"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAutoGenerateIdStatement() + */ + @Override + protected List expectedAutoGenerateIdStatement() { + return Arrays.asList( + "DELETE FROM "+TEST_SCHEMA+".idvalues where name = 'Test'", + "INSERT INTO "+TEST_SCHEMA+"."+ID_VALUES_TABLE+" (name, "+ID_INCREMENTOR_TABLE_COLUMN_VALUE+") VALUES('Test', (SELECT COALESCE(MAX(id) + 1, 1) AS CurrentValue FROM "+TEST_SCHEMA+".Test))", + "INSERT INTO "+TEST_SCHEMA+".Test (version, stringField, id) SELECT version, stringField, (SELECT COALESCE("+ID_INCREMENTOR_TABLE_COLUMN_VALUE+", 0) FROM "+TEST_SCHEMA+"."+ID_VALUES_TABLE+" WHERE (name = CAST('Test' AS VARCHAR(4)))) + Other.id FROM "+TEST_SCHEMA+".Other" + ); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedInsertWithIdAndVersion() + */ + @Override + protected List expectedInsertWithIdAndVersion() { + return Arrays.asList( + "DELETE FROM "+TEST_SCHEMA+"."+ID_VALUES_TABLE+" where name = 'Test'", + "INSERT INTO "+TEST_SCHEMA+"."+ID_VALUES_TABLE+" (name, "+ID_INCREMENTOR_TABLE_COLUMN_VALUE+") VALUES('Test', (SELECT COALESCE(MAX(id) + 1, 1) AS CurrentValue FROM "+TEST_SCHEMA+".Test))", + "INSERT INTO "+TEST_SCHEMA+".Test (stringField, id, version) SELECT stringField, (SELECT COALESCE("+ID_INCREMENTOR_TABLE_COLUMN_VALUE+", 0) FROM "+TEST_SCHEMA+"."+ID_VALUES_TABLE+" WHERE (name = CAST('Test' AS VARCHAR(4)))) + Other.id, 0 AS version FROM "+TEST_SCHEMA+".Other" + ); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedSpecifiedValueInsert() + */ + @Override + protected List expectedSpecifiedValueInsert() { + return Arrays.asList( + "DELETE FROM "+TEST_SCHEMA+"."+ID_VALUES_TABLE+" where name = 'Test'", + "INSERT INTO "+TEST_SCHEMA+"."+ID_VALUES_TABLE+" (name, "+ID_INCREMENTOR_TABLE_COLUMN_VALUE+") VALUES('Test', (SELECT COALESCE(MAX(id) + 1, 1) AS CurrentValue FROM "+TEST_SCHEMA+".Test))", + "INSERT INTO "+TEST_SCHEMA+".Test (stringField, intField, floatField, dateField, booleanField, charField, id, version, blobField, bigIntegerField, clobField) VALUES (CAST('Escap''d' AS VARCHAR(7)), 7, 11.25, 20100405, true, CAST('X' AS VARCHAR(1)), (SELECT COALESCE("+ID_INCREMENTOR_TABLE_COLUMN_VALUE+", 1) FROM "+TEST_SCHEMA+"."+ID_VALUES_TABLE+" WHERE (name = CAST('Test' AS VARCHAR(4)))), 0, null, 12345, null)" + ); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedSpecifiedValueInsertWithTableInDifferentSchema() + */ + @Override + protected List expectedSpecifiedValueInsertWithTableInDifferentSchema() { + return Arrays.asList( + "DELETE FROM "+TEST_SCHEMA+"."+ID_VALUES_TABLE+" where name = 'Test'", + "INSERT INTO "+TEST_SCHEMA+"."+ID_VALUES_TABLE+" (name, "+ID_INCREMENTOR_TABLE_COLUMN_VALUE+") VALUES('Test', (SELECT COALESCE(MAX(id) + 1, 1) AS CurrentValue FROM MYSCHEMA.Test))", + "INSERT INTO MYSCHEMA.Test (stringField, intField, floatField, dateField, booleanField, charField, id, version, blobField, bigIntegerField, clobField) VALUES (CAST('Escap''d' AS VARCHAR(7)), 7, 11.25, 20100405, true, CAST('X' AS VARCHAR(1)), (SELECT COALESCE("+ID_INCREMENTOR_TABLE_COLUMN_VALUE+", 1) FROM "+TEST_SCHEMA+"."+ID_VALUES_TABLE+" WHERE (name = CAST('Test' AS VARCHAR(4)))), 0, null, 12345, null)" + ); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedParameterisedInsertStatementWithNoColumnValues() + */ + @Override + protected String expectedParameterisedInsertStatementWithNoColumnValues() { + return "INSERT INTO "+TEST_SCHEMA+".Test (id, version, stringField, intField, floatField, dateField, booleanField, charField, blobField, bigIntegerField, clobField) VALUES (CAST(:id AS BIGINT), CAST(:version AS INTEGER), CAST(:stringField AS VARCHAR(3)), CAST(:intField AS INTEGER), CAST(:floatField AS DECIMAL(13,2)), CAST(:dateField AS DATE), CAST(:booleanField AS BIT), CAST(:charField AS VARCHAR(1)), CAST(:blobField AS LONGVARBINARY), CAST(:bigIntegerField AS BIGINT), CAST(:clobField AS NCLOB))"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedEmptyStringInsertStatement() + */ + @Override + protected String expectedEmptyStringInsertStatement() { + return "INSERT INTO "+TEST_SCHEMA+".Test (stringField, id, version, intField, floatField, dateField, booleanField, charField, blobField, bigIntegerField, clobField) VALUES (NULL, (SELECT COALESCE("+ID_INCREMENTOR_TABLE_COLUMN_VALUE+", 1) FROM "+TEST_SCHEMA+"."+ID_VALUES_TABLE+" WHERE (name = CAST('Test' AS VARCHAR(4)))), 0, 0, 0, null, false, NULL, null, 12345, null)"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedConcatenationWithCase() + */ + @Override + protected String expectedConcatenationWithCase() { + return "SELECT COALESCE(assetDescriptionLine1,'') || COALESCE(CASE WHEN (taxVariationIndicator = CAST('Y' AS VARCHAR(1))) THEN exposureCustomerNumber ELSE invoicingCustomerNumber END,'') AS test FROM "+TEST_SCHEMA+".schedule"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedConcatenationWithFunction() + */ + @Override + protected String expectedConcatenationWithFunction() { + return "SELECT COALESCE(assetDescriptionLine1,'') || COALESCE(MAX(scheduleStartDate),'') AS test FROM "+TEST_SCHEMA+".schedule"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedConcatenationWithMultipleFieldLiterals() + */ + @Override + protected String expectedConcatenationWithMultipleFieldLiterals() { + return "SELECT COALESCE(CAST('ABC' AS VARCHAR(3)),'') || COALESCE(CAST(' ' AS VARCHAR(1)),'') || COALESCE(CAST('DEF' AS VARCHAR(3)),'') AS assetDescription FROM "+TEST_SCHEMA+".schedule"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedNestedConcatenations() + */ + @Override + protected String expectedNestedConcatenations() { + return "SELECT COALESCE(field1,'') || COALESCE(COALESCE(field2,'') || COALESCE(CAST('XYZ' AS VARCHAR(3)),''),'') AS test FROM "+TEST_SCHEMA+".schedule"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedSelectWithConcatenation1() + */ + @Override + protected String expectedSelectWithConcatenation1() { + return "SELECT COALESCE(assetDescriptionLine1,'') || COALESCE(CAST(' ' AS VARCHAR(1)),'') || COALESCE(assetDescriptionLine2,'') AS assetDescription FROM "+TEST_SCHEMA+".schedule"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedSelectWithConcatenation2() + */ + @Override + protected String expectedSelectWithConcatenation2() { + return "SELECT COALESCE(assetDescriptionLine1,'') || COALESCE(CAST('XYZ' AS VARCHAR(3)),'') || COALESCE(assetDescriptionLine2,'') AS assetDescription FROM "+TEST_SCHEMA+".schedule"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedIsNull() + */ + @Override + protected String expectedIsNull() { + return "COALESCE(CAST('A' AS VARCHAR(1)), CAST('B' AS VARCHAR(1)))"; + } + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedBlobLiteral(String) + */ + @Override + protected String expectedBlobLiteral(String value) { + return String.format("X'%s'", value); + } + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedMathsPlus() + */ + @Override + protected String expectedMathsPlus() { + return "1 + 1"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedMathsMinus() + */ + @Override + protected String expectedMathsMinus() { + return "1 - 1"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedMathsDivide() + */ + @Override + protected String expectedMathsDivide() { + return "1 / 1"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedMathsMultiply() + */ + @Override + protected String expectedMathsMultiply() { + return "1 * 1"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedStringCast() + */ + @Override + protected String expectedStringCast() { + return "CAST(value AS VARCHAR(10))"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedBigIntCast() + */ + @Override + protected String expectedBigIntCast() { + return "CAST(value AS BIGINT)"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedBigIntFunctionCast() + */ + @Override + protected String expectedBigIntFunctionCast() { + return "CAST(MIN(value) AS BIGINT)"; + } + + + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedBooleanCast() + */ + @Override + protected String expectedBooleanCast() { + return "CAST(value AS BIT)"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedBooleanLiteral(boolean) + */ + @Override + protected String expectedBooleanLiteral(boolean value) { + return value ? "true" : "false"; + } + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedDateCast() + */ + @Override + protected String expectedDateCast() { + return "CAST(value AS DATE)"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedDecimalCast() + */ + @Override + protected String expectedDecimalCast() { + return "CAST(value AS DECIMAL(10,2))"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedIntegerCast() + */ + @Override + protected String expectedIntegerCast() { + return "CAST(value AS INTEGER)"; + } + + + /** + * {@inheritDoc} + * + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedSelectWithUnion() + */ + @Override + protected String expectedSelectWithUnion() { + return "SELECT stringField FROM "+TEST_SCHEMA+".Other UNION SELECT stringField FROM "+TEST_SCHEMA+".Test UNION ALL SELECT stringField FROM "+TEST_SCHEMA+".Alternate ORDER BY stringField"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedLeftPad() + */ + @Override + protected String expectedLeftPad() { + return "SELECT LPAD(stringField, 10, CAST('j' AS VARCHAR(1))) FROM "+TEST_SCHEMA+".Test"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedRightPad() + */ + @Override + protected String expectedRightPad() { + return "SELECT RPAD(stringField, 10, CAST('j' AS VARCHAR(1))) FROM "+TEST_SCHEMA+".Test"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAddBlobColumnStatement() + */ + @Override + protected List expectedAlterTableAddBlobColumnStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ADD COLUMN blobField_new LONGVARBINARY NULL"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAlterBlobColumnStatement() + */ + @Override + protected List expectedAlterTableAlterBlobColumnStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ALTER COLUMN blobField SET NOT NULL"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAlterBooleanColumnStatement() + */ + @Override + protected List expectedAlterTableAlterBooleanColumnStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ALTER COLUMN booleanField SET NOT NULL"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAddBooleanColumnStatement() + */ + @Override + protected List expectedAlterTableAddBooleanColumnStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ADD COLUMN booleanField_new BIT NULL"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAddStringColumnStatement() + */ + @Override + protected List expectedAlterTableAddStringColumnStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ADD COLUMN stringField_new VARCHAR(6) NULL"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAlterStringColumnStatement() + */ + @Override + protected List expectedAlterTableAlterStringColumnStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ALTER COLUMN stringField VARCHAR(6)"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAddIntegerColumnStatement() + */ + @Override + protected List expectedAlterTableAddIntegerColumnStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ADD COLUMN intField_new INTEGER NULL"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAlterIntegerColumnStatement() + */ + @Override + protected List expectedAlterTableAlterIntegerColumnStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ALTER COLUMN intField SET NOT NULL"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAddDateColumnStatement() + */ + @Override + protected List expectedAlterTableAddDateColumnStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ADD COLUMN dateField_new DATE NULL"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAlterDateColumnStatement() + */ + @Override + protected List expectedAlterTableAlterDateColumnStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ALTER COLUMN dateField SET NOT NULL"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAddDecimalColumnStatement() + */ + @Override + protected List expectedAlterTableAddDecimalColumnStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ADD COLUMN floatField_new DECIMAL(6,3) NULL"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAlterDecimalColumnStatement() + */ + @Override + protected List expectedAlterTableAlterDecimalColumnStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ALTER COLUMN floatField SET NULL", + "ALTER TABLE "+TEST_SCHEMA+".Test ALTER COLUMN floatField DECIMAL(14,3)"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAddBigIntegerColumnStatement() + */ + @Override + protected List expectedAlterTableAddBigIntegerColumnStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ADD COLUMN bigIntegerField_new BIGINT NULL"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAlterBigIntegerColumnStatement() + */ + @Override + protected List expectedAlterTableAlterBigIntegerColumnStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ALTER COLUMN bigIntegerField BIGINT"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAddColumnNotNullableStatement() + */ + @Override + protected List expectedAlterTableAddColumnNotNullableStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ADD COLUMN dateField_new DATE DEFAULT DATE '2010-01-01' NOT NULL"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAlterColumnFromNullableToNotNullableStatement() + */ + @Override + protected List expectedAlterTableAlterColumnFromNullableToNotNullableStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ALTER COLUMN dateField SET NOT NULL"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAlterColumnFromNotNullableToNotNullableStatement() + */ + @Override + protected List expectedAlterTableAlterColumnFromNotNullableToNotNullableStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ALTER COLUMN floatField DECIMAL(20,3)"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableDropColumnWithDefaultStatement() + */ + @Override + protected List expectedAlterTableDropColumnWithDefaultStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test DROP COLUMN bigIntegerField"); + } + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAlterColumnFromNotNullableToNullableStatement() + */ + @Override + protected List expectedAlterTableAlterColumnFromNotNullableToNullableStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ALTER COLUMN floatField SET NULL", + "ALTER TABLE "+TEST_SCHEMA+".Test ALTER COLUMN floatField DECIMAL(20,3)"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAddColumnWithDefaultStatement() + */ + @Override + protected List expectedAlterTableAddColumnWithDefaultStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ADD COLUMN floatField_new DECIMAL(6,3) DEFAULT 20.33 NULL"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAlterColumnWithDefaultStatement() + */ + @Override + protected List expectedAlterTableAlterColumnWithDefaultStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ALTER COLUMN bigIntegerField SET DEFAULT 54321", + "ALTER TABLE "+TEST_SCHEMA+".Test ALTER COLUMN bigIntegerField BIGINT"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedChangeIndexFollowedByChangeOfAssociatedColumnStatement() + */ + @Override + protected List expectedChangeIndexFollowedByChangeOfAssociatedColumnStatement() { + return Arrays.asList( + // dropIndexStatements & addIndexStatements + "DROP INDEX Test_1", + "CREATE INDEX Test_1 ON "+TEST_SCHEMA+".Test (intField)", + // changeColumnStatements + "ALTER TABLE "+TEST_SCHEMA+".Test ALTER COLUMN intField SET NOT NULL"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAddIndexStatementsOnSingleColumn() + */ + @Override + protected List expectedAddIndexStatementsOnSingleColumn() { + return Arrays.asList("CREATE INDEX indexName ON "+TEST_SCHEMA+".Test (id)"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAddIndexStatementsOnMultipleColumns() + */ + @Override + protected List expectedAddIndexStatementsOnMultipleColumns() { + return Arrays.asList("CREATE INDEX indexName ON "+TEST_SCHEMA+".Test (id,version)"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAddIndexStatementsUnique() + */ + @Override + protected List expectedAddIndexStatementsUnique() { + return Arrays.asList("CREATE UNIQUE INDEX indexName ON "+TEST_SCHEMA+".Test (id)"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAddIndexStatementsUniqueNullable() + */ + @Override + protected List expectedAddIndexStatementsUniqueNullable() { + return Arrays.asList("CREATE UNIQUE INDEX indexName ON "+TEST_SCHEMA+".Test (stringField,intField,floatField,dateField)"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedIndexDropStatements() + */ + @Override + protected List expectedIndexDropStatements() { + return Arrays.asList("DROP INDEX indexName"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterColumnMakePrimaryStatements() + */ + @Override + protected List expectedAlterColumnMakePrimaryStatements() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ADD CONSTRAINT Test_PK PRIMARY KEY (id, dateField)"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterPrimaryKeyColumnCompositeKeyStatements() + */ + @Override + protected List expectedAlterPrimaryKeyColumnCompositeKeyStatements() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".CompositePrimaryKey ALTER COLUMN secondPrimaryKey VARCHAR(5)"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterRemoveColumnFromCompositeKeyStatements() + */ + @Override + protected List expectedAlterRemoveColumnFromCompositeKeyStatements() { + return ImmutableList.of( + "ALTER TABLE "+TEST_SCHEMA+".CompositePrimaryKey DROP PRIMARY KEY", + "ALTER TABLE "+TEST_SCHEMA+".CompositePrimaryKey ALTER COLUMN secondPrimaryKey SET NULL", + "ALTER TABLE "+TEST_SCHEMA+".CompositePrimaryKey ALTER COLUMN secondPrimaryKey VARCHAR(5)", + "ALTER TABLE "+TEST_SCHEMA+".CompositePrimaryKey ADD CONSTRAINT CompositePrimaryKey_PK PRIMARY KEY (id)" + ); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterPrimaryKeyColumnStatements() + */ + @Override + protected List expectedAlterPrimaryKeyColumnStatements() { + return Arrays.asList( + "ALTER TABLE "+TEST_SCHEMA+".Test ALTER COLUMN id RENAME TO renamedId" + ); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterColumnRenameNonPrimaryIndexedColumn() + */ + @Override + protected List expectedAlterColumnRenameNonPrimaryIndexedColumn() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Alternate ALTER COLUMN stringField RENAME TO blahField"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterColumnRenamingAndChangingNullability() + */ + @Override + protected List expectedAlterColumnRenamingAndChangingNullability() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Other ALTER COLUMN floatField RENAME TO blahField", + "ALTER TABLE "+TEST_SCHEMA+".Other ALTER COLUMN blahField SET NULL", "ALTER TABLE "+TEST_SCHEMA+".Other ALTER COLUMN blahField DECIMAL(20,3)"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterColumnChangingLengthAndCase() + */ + @Override + protected List expectedAlterColumnChangingLengthAndCase() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Other ALTER COLUMN floatField RENAME TO FloatField", + "ALTER TABLE "+TEST_SCHEMA+".Other ALTER COLUMN FloatField DECIMAL(20,3)"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#varCharCast(java.lang.String) + */ + @Override + protected String varCharCast(String value) { + return String.format("CAST(%s AS VARCHAR(%d))", value, StringUtils.replace(value, "'", "").length()); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterTableAddStringColumnWithDefaultStatement() + */ + @Override + protected List expectedAlterTableAddStringColumnWithDefaultStatement() { + return Arrays.asList("ALTER TABLE "+TEST_SCHEMA+".Test ADD COLUMN stringField_with_default VARCHAR(6) DEFAULT CAST('N' AS VARCHAR(1)) NOT NULL"); + } + + + /** + * {@inheritDoc} + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAutonumberUpdate() + */ + @Override + protected List expectedAutonumberUpdate() { + return Arrays.asList("MERGE INTO "+TEST_SCHEMA+".Autonumber (id, value) SELECT 'TestTable', (SELECT GREATEST((SELECT COALESCE(MAX(id) + 1, 1) AS CurrentValue FROM "+TEST_SCHEMA+".TestTable), (SELECT value from Autonumber WHERE name='TestTable'), 1))"); + } + + + /** + * {@inheritDoc} + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedUpdateWithSelectMinimum() + */ + @Override + protected String expectedUpdateWithSelectMinimum() { + String value1 = varCharCast("'S'"); + String value2 = varCharCast("'Y'"); + return "UPDATE " + tableName("Other") + " O SET intField = (SELECT MIN(intField) FROM " + tableName("Test") + " T WHERE ((T.charField = " + stringLiteralPrefix() + value1 + ") AND (T.stringField = O.stringField) AND (T.intField = O.intField))) WHERE (stringField = " + stringLiteralPrefix() + value2 + ")"; + } + + + /** + * {@inheritDoc} + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedUpdateUsingAliasedDestinationTable() + */ + @Override + protected String expectedUpdateUsingAliasedDestinationTable() { + return "UPDATE " + tableName("FloatingRateRate") + " A SET settlementFrequency = (SELECT settlementFrequency FROM " + tableName("FloatingRateDetail") + " B WHERE (A.floatingRateDetailId = B.id))"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedCreateViewStatements() + */ + @Override + protected List expectedCreateViewStatements() { + return Arrays.asList("CREATE VIEW " + tableName("TestView") + " AS (SELECT stringField FROM " + tableName("Test") + " WHERE (stringField = " + varCharCast("'blah'") + "))"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedCreateViewStatements() + */ + @Override + protected List expectedCreateSequenceStatements() { + return Arrays.asList("CREATE SEQUENCE " + tableName("TestSequence") + " START WITH 1"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedCreateViewStatements() + */ + @Override + protected List expectedCreateTemporarySequenceStatements() { + return Arrays.asList("CREATE SEQUENCE " + tableName("TestSequence") + " START WITH 1"); + } + + + /** + * @see AbstractSqlDialectTest#expectedCreateSequenceStatementsWithNoStartWith() + * @return + */ + @Override + protected List expectedCreateSequenceStatementsWithNoStartWith() { + return Arrays.asList("CREATE SEQUENCE " + tableName("TestSequence")); + } + + + /** + * @see AbstractSqlDialectTest#expectedCreateTemporarySequenceStatementsWithNoStartWith() + * @return + */ + @Override + protected List expectedCreateTemporarySequenceStatementsWithNoStartWith() { + return Arrays.asList("CREATE SEQUENCE " + tableName("TestSequence")); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedYYYYMMDDToDate() + */ + @Override + protected String expectedYYYYMMDDToDate() { + return "CAST(SUBSTRING(CAST('20100101' AS VARCHAR(8)), 1, 4)||'-'||SUBSTRING(CAST('20100101' AS VARCHAR(8)), 5, 2)||'-'||SUBSTRING(CAST('20100101' AS VARCHAR(8)), 7, 2) AS DATE)"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedDateToYyyymmdd() + */ + @Override + protected String expectedDateToYyyymmdd() { + return "CAST(SUBSTRING(testField, 1, 4)||SUBSTRING(testField, 6, 2)||SUBSTRING(testField, 9, 2) AS DECIMAL(8))"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedDateToYyyymmddHHmmss() + */ + @Override + protected String expectedDateToYyyymmddHHmmss() { + return "CAST(SUBSTRING(testField, 1, 4)||SUBSTRING(testField, 6, 2)||SUBSTRING(testField, 9, 2)||SUBSTRING(testField, 12, 2)||SUBSTRING(testField, 15, 2)||SUBSTRING(testField, 18, 2) AS DECIMAL(14))"; + } + + @Override + protected String expectedClobLiteralCast() { + return "CAST('CREATE VIEW viewName AS (SELECT tableField1, tableField2, tableField3, tableField4, tableField5, tableField6, tableField7, tableField8, tableField9, tableField10, tableField11, tableField12, tableField13, tableField14, tableField15, tableField16, tableField17, tableField18, tableField19, tableField20, tableField21, tableField22, tableField23, tableField24, tableField25, tableField26, tableField27, tableField28, tableField29, tableField30 FROM table INNER JOIN table2 ON (table1.tableField1 = table2 = tableField1));' AS VARCHAR(519))"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedNow() + */ + @Override + protected String expectedNow() { + return "CURRENT_TIMESTAMP()"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedDropViewStatements() + */ + @Override + protected List expectedDropViewStatements() { + return Arrays.asList("DROP VIEW " + tableName("TestView") + " IF EXISTS CASCADE"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedStringLiteralToIntegerCast() + */ + @Override + protected String expectedStringLiteralToIntegerCast() { + return "CAST(" + varCharCast("'1234567890'") + " AS INTEGER)"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedSubstring() + */ + @Override + protected String expectedSubstring() { + return "SELECT SUBSTRING(field1, 1, 3) FROM " + tableName("schedule"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAutonumberUpdateForNonIdColumn() + */ + @Override + protected List expectedAutonumberUpdateForNonIdColumn() { + return Arrays.asList("MERGE INTO "+TEST_SCHEMA+".Autonumber (id, value) SELECT 'TestTable', (SELECT GREATEST((SELECT COALESCE(MAX(generatedColumn) + 1, 1) AS CurrentValue FROM TestTable), (SELECT value from Autonumber WHERE name='TestTable'), 1))"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedStringFunctionCast() + */ + @Override + protected String expectedStringFunctionCast() { + return "CAST(MIN(field) AS VARCHAR(8))"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedDaysBetween() + */ + @Override + protected String expectedDaysBetween() { + return "SELECT DATEDIFF('DAY',dateOne, dateTwo) FROM "+TEST_SCHEMA+".MyTable"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedMergeSimple() + */ + @Override + protected String expectedMergeSimple() { + return "MERGE INTO "+TEST_SCHEMA+".foo" + + " USING (SELECT somewhere.newId AS id, somewhere.newBar AS bar FROM "+TEST_SCHEMA+".somewhere) xmergesource" + + " ON (foo.id = xmergesource.id)" + + " WHEN MATCHED THEN UPDATE SET bar = xmergesource.bar" + + " WHEN NOT MATCHED THEN INSERT (id, bar) VALUES (xmergesource.id, xmergesource.bar)"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedMergeComplex() + */ + @Override + protected String expectedMergeComplex() { + return "MERGE INTO "+TEST_SCHEMA+".foo" + + " USING (SELECT somewhere.newId AS id, join.joinBar AS bar FROM "+TEST_SCHEMA+".somewhere INNER JOIN "+TEST_SCHEMA+".join ON (somewhere.newId = join.joinId)) xmergesource" + + " ON (foo.id = xmergesource.id)" + + " WHEN MATCHED THEN UPDATE SET bar = xmergesource.bar" + + " WHEN NOT MATCHED THEN INSERT (id, bar) VALUES (xmergesource.id, xmergesource.bar)"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedMergeSourceInDifferentSchema() + */ + @Override + protected String expectedMergeSourceInDifferentSchema() { + return "MERGE INTO "+TEST_SCHEMA+".foo" + + " USING (SELECT somewhere.newId AS id, somewhere.newBar AS bar FROM MYSCHEMA.somewhere) xmergesource" + + " ON (foo.id = xmergesource.id)" + + " WHEN MATCHED THEN UPDATE SET bar = xmergesource.bar" + + " WHEN NOT MATCHED THEN INSERT (id, bar) VALUES (xmergesource.id, xmergesource.bar)"; + } + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedMergeTargetInDifferentSchema() + */ + @Override + protected String expectedMergeTargetInDifferentSchema() { + + return "MERGE INTO MYSCHEMA.foo" + + " USING (SELECT somewhere.newId AS id, somewhere.newBar AS bar FROM "+TEST_SCHEMA+".somewhere) xmergesource" + + " ON (foo.id = xmergesource.id)" + + " WHEN MATCHED THEN UPDATE SET bar = xmergesource.bar" + + " WHEN NOT MATCHED THEN INSERT (id, bar) VALUES (xmergesource.id, xmergesource.bar)"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedMergeForAllPrimaryKeys() + */ + @Override + protected String expectedMergeForAllPrimaryKeys() { + return "MERGE INTO "+TEST_SCHEMA+".foo" + + " USING (SELECT somewhere.newId AS id FROM "+TEST_SCHEMA+".somewhere) xmergesource" + + " ON (foo.id = xmergesource.id)" + + " WHEN NOT MATCHED THEN INSERT (id) VALUES (xmergesource.id)"; + } + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedMergeWithUpdateExpressions() + */ + @Override + protected String expectedMergeWithUpdateExpressions() { + return "MERGE INTO "+TEST_SCHEMA+".foo" + + " USING (SELECT somewhere.newId AS id, somewhere.newBar AS bar FROM "+TEST_SCHEMA+".somewhere) xmergesource" + + " ON (foo.id = xmergesource.id)" + + " WHEN MATCHED THEN UPDATE SET bar = xmergesource.bar + foo.bar" + + " WHEN NOT MATCHED THEN INSERT (id, bar) VALUES (xmergesource.id, xmergesource.bar)"; + } + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedMergeWithUpdateWhereClause() + */ + @Override + protected String expectedMergeWithUpdateWhereClause() { + return "MERGE INTO "+TEST_SCHEMA+".foo" + + " USING (SELECT 12345 AS id, 1004 AS typeId, CAST('2025-04-20' AS VARCHAR(10)) AS eventDate, 5.00001 AS rate, CAST('important rate' AS VARCHAR(14)) AS description, 43037 AS sequenceId FROM dual) xmergesource" + + " ON (foo.typeId = xmergesource.typeId AND foo.eventDate = xmergesource.eventDate)" + + " WHEN MATCHED" + + " AND ((foo.rate <> xmergesource.rate) OR (foo.description <> xmergesource.description))" // smart update check + + " THEN UPDATE SET id = xmergesource.id, rate = xmergesource.rate, description = xmergesource.description, sequenceId = xmergesource.sequenceId" + + " WHEN NOT MATCHED THEN INSERT (id, typeId, eventDate, rate, description, sequenceId) VALUES (xmergesource.id, xmergesource.typeId, xmergesource.eventDate, xmergesource.rate, xmergesource.description, xmergesource.sequenceId)"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAddDays() + */ + @Override + protected String expectedAddDays() { + return "DATEADD('DAY', -20, testField)"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAddMonths() + */ + @Override + protected String expectedAddMonths() { + return "DATEADD('MONTH', -3, testField)"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAlterRemoveColumnFromSimpleKeyStatements() + */ + @Override + protected List expectedAlterRemoveColumnFromSimpleKeyStatements() { + return Collections.singletonList("ALTER TABLE "+TEST_SCHEMA+".Test DROP COLUMN id"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedRenameTableStatements() + */ + @Override + protected List expectedRenameTableStatements() { + return ImmutableList.of( + "ALTER TABLE "+TEST_SCHEMA+".Test DROP PRIMARY KEY", + "ALTER TABLE "+TEST_SCHEMA+".Test RENAME TO Renamed", + "ALTER TABLE "+TEST_SCHEMA+".Renamed ADD CONSTRAINT Renamed_PK PRIMARY KEY (id)" + ); + } + + + /** + * @return the expected statements for renaming a table with a long name. + */ + @Override + protected List getRenamingTableWithLongNameStatements() { + return ImmutableList.of( + "ALTER TABLE "+TEST_SCHEMA+".123456789012345678901234567890X DROP PRIMARY KEY", + "ALTER TABLE "+TEST_SCHEMA+".123456789012345678901234567890X RENAME TO Blah", + "ALTER TABLE "+TEST_SCHEMA+".Blah ADD CONSTRAINT Blah_PK PRIMARY KEY (id)"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedRenameIndexStatements() + */ + @Override + protected List expectedRenameIndexStatements() { + return ImmutableList.of("ALTER INDEX "+TEST_SCHEMA+".Test_1 RENAME TO Test_2"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedRenameIndexStatements() + */ + @Override + protected List expectedRenameTempIndexStatements() { + return ImmutableList.of("ALTER INDEX "+TEST_SCHEMA+".TempTest_1 RENAME TO TempTest_2"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedRandomString() + */ + @Override + protected String expectedRandomString() { + return "SUBSTRING(REPLACE(RANDOM_UUID(),'-'), 1, 10)"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedSelectLiteralWithWhereClauseString() + */ + @Override + protected String expectedSelectLiteralWithWhereClauseString() { + return "SELECT CAST('LITERAL' AS VARCHAR(7)) FROM dual WHERE (CAST('ONE' AS VARCHAR(3)) = CAST('ONE' AS VARCHAR(3)))"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAddTableFromStatements() + */ + @Override + protected List expectedAddTableFromStatements() { + return ImmutableList.of( + "CREATE TABLE "+TEST_SCHEMA+".SomeTable (someField VARCHAR(3) NOT NULL, otherField DECIMAL(3,0) NOT NULL, CONSTRAINT SomeTable_PK PRIMARY KEY (someField))", + "CREATE INDEX SomeTable_1 ON "+TEST_SCHEMA+".SomeTable (otherField)", + "INSERT INTO "+TEST_SCHEMA+".SomeTable SELECT someField, otherField FROM "+TEST_SCHEMA+".OtherTable" + ); + } + + + @Override + protected List expectedReplaceTableFromStatements() { + return ImmutableList.of( + "CREATE TABLE "+TEST_SCHEMA+".tmp_SomeTable (someField VARCHAR(3) NOT NULL, otherField DECIMAL(3,0) NOT NULL, thirdField DECIMAL(5,0) NOT NULL, CONSTRAINT tmp_SomeTable_PK PRIMARY KEY (someField))", + "INSERT INTO "+TEST_SCHEMA+".tmp_SomeTable SELECT someField, otherField, CAST(thirdField AS DECIMAL(5,0)) AS thirdField FROM "+TEST_SCHEMA+".OtherTable", + "DROP TABLE "+TEST_SCHEMA+".SomeTable CASCADE", + "ALTER TABLE "+TEST_SCHEMA+".tmp_SomeTable DROP PRIMARY KEY", + "ALTER TABLE "+TEST_SCHEMA+".tmp_SomeTable RENAME TO SomeTable", + "ALTER TABLE "+TEST_SCHEMA+".SomeTable ADD CONSTRAINT SomeTable_PK PRIMARY KEY (someField)", + "CREATE INDEX SomeTable_1 ON "+TEST_SCHEMA+".SomeTable (otherField)" + ); + } + + + @Override + protected List expectedReplaceTableWithAutonumber() { + return ImmutableList.of( + "CREATE TABLE "+TEST_SCHEMA+".tmp_SomeTable (someField VARCHAR(3) NOT NULL, otherField DECIMAL(3,0) AUTO_INCREMENT(1) COMMENT 'AUTONUMSTART:[1]', thirdField DECIMAL(5,0) NOT NULL, CONSTRAINT tmp_SomeTable_PK PRIMARY KEY (someField))", + "INSERT INTO "+TEST_SCHEMA+".tmp_SomeTable SELECT someField, otherField, CAST(thirdField AS DECIMAL(5,0)) AS thirdField FROM "+TEST_SCHEMA+".OtherTable", + "DROP TABLE "+TEST_SCHEMA+".SomeTable CASCADE", + "ALTER TABLE "+TEST_SCHEMA+".tmp_SomeTable DROP PRIMARY KEY", + "ALTER TABLE "+TEST_SCHEMA+".tmp_SomeTable RENAME TO SomeTable", + "ALTER TABLE "+TEST_SCHEMA+".SomeTable ADD CONSTRAINT SomeTable_PK PRIMARY KEY (someField)", + "CREATE INDEX SomeTable_1 ON "+TEST_SCHEMA+".SomeTable (otherField)" + ); + } + + + /** + * No hints are supported. + * + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedHints1(int) + */ + @Override + protected String expectedHints1(int rowCount) { + return "SELECT * FROM SCHEMA2.Foo INNER JOIN "+TEST_SCHEMA+".Bar ON (a = b) LEFT OUTER JOIN "+TEST_SCHEMA+".Fo ON (a = b) INNER JOIN "+TEST_SCHEMA+".Fum Fumble ON (a = b) ORDER BY a"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAnalyseTableSql() + */ + @Override + protected Collection expectedAnalyseTableSql() { + return SqlDialect.NO_STATEMENTS; + } + + + /** + * @return The expected SQL for a delete statement with a limit and where criterion. + */ + @Override + protected String expectedDeleteWithLimitAndWhere(String value) { + return "DELETE FROM " + tableName(TEST_TABLE) + " WHERE (Test.stringField = " + stringLiteralPrefix() + value + ") LIMIT 1000"; + } + + + /** + * @return The expected SQL for a delete statement with a limit and where criterion. + */ + @Override + protected String expectedDeleteWithLimitAndComplexWhere(String value1, String value2) { + return "DELETE FROM " + tableName(TEST_TABLE) + " WHERE ((Test.stringField = " + stringLiteralPrefix() + value1 + ") OR (Test.stringField = " + stringLiteralPrefix() + value2 + ")) LIMIT 1000"; + } + + + /** + * @return The expected SQL for a delete statement with a limit and where criterion. + */ + @Override + protected String expectedDeleteWithLimitWithoutWhere() { + return "DELETE FROM " + tableName(TEST_TABLE) + " LIMIT 1000"; + } + + + /** + * @return The expected SQL for retrieving the row number + */ + @Override + protected String expectedRowNumber() { + return "ROW_NUMBER() OVER()"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#tableName(java.lang.String) + */ + @Override + protected String tableName(String baseName) { + return TEST_SCHEMA+"." + baseName; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedCreateViewOverUnionSelectStatements() + */ + @Override + protected List expectedCreateViewOverUnionSelectStatements() { + return Arrays.asList("CREATE VIEW " + tableName("TestView") + " AS (SELECT stringField FROM " + tableName(TEST_TABLE) + " WHERE (stringField = " + stringLiteralPrefix() + "CAST('blah' AS VARCHAR(4))) UNION ALL SELECT stringField FROM " + tableName(OTHER_TABLE) + " WHERE (stringField = " + stringLiteralPrefix() + "CAST('blah' AS VARCHAR(4))))"); + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedSelectWithExcept() + */ + @Override + protected String expectedSelectWithExcept() { + return null; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedSelectWithDbLink() + */ + @Override + protected String expectedSelectWithDbLink() { + return null; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedSelectWithExceptAndDbLinkFormer() + */ + @Override + protected String expectedSelectWithExceptAndDbLinkFormer() { + return null; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedSelectWithExceptAndDbLinkLatter() + */ + @Override + protected String expectedSelectWithExceptAndDbLinkLatter() { + return null; + } + + + /** + * @see AbstractSqlDialectTest#expectedNextValForSequence() + * @return + */ + @Override + protected String expectedNextValForSequence() { + return "SELECT NEXT VALUE FOR TestSequence FROM dual"; + } + + + /** + * @see AbstractSqlDialectTest#expectedCurrValForSequence() + */ + @Override + protected String expectedCurrValForSequence() { + return "SELECT CURRENT VALUE FOR TestSequence FROM dual"; + } + + /** + * @see AbstractSqlDialectTest#expectedPortableStatement() + */ + @Override + protected String expectedPortableStatement() { + return "UPDATE TESTSCHEMA.Table SET field = BTRIM(field, CAST('2' AS VARCHAR(1)), CAST('B' AS VARCHAR(1)))"; + } + + /** + * @see AbstractSqlDialectTest#expectedPortableSqlExpression() + */ + @Override + protected String expectedPortableSqlExpression() { + return "SELECT JSON_VALUE(payload, '$.type') AS event_type FROM TESTSCHEMA.Test"; + } + + /** + * @return The expected value for the force serial import setting. + */ + @Override + protected boolean expectedForceSerialImport() { + return true; + } + + + @Override + protected String expectedSelectWithLimit() { + return "SELECT * FROM " + tableName(TEST_TABLE) + " LIMIT 10"; + } + + + @Override + protected String expectedSelectWithOrderByAndLimit() { + return "SELECT id FROM " + tableName(TEST_TABLE) + " ORDER BY id LIMIT 10"; + } + + + @Override + protected String expectedSelectWithLimitInSubquery() { + return "SELECT COUNT(*) AS cnt FROM (SELECT * FROM " + tableName(TEST_TABLE) + " LIMIT 1000) t"; + } + + + @Override + protected String expectedSelectWithWhereAndLimit() { + return "SELECT id, stringField FROM " + tableName(TEST_TABLE) + " WHERE (intField = 100) LIMIT 5"; + } + + + @Override + protected String expectedSelectWithDistinctAndLimit() { + return "SELECT DISTINCT stringField FROM " + tableName(TEST_TABLE) + " LIMIT 20"; + } + + + @Override + protected String expectedSelectWithGroupByAndLimit() { + return "SELECT stringField, COUNT(*) AS cnt FROM " + tableName(TEST_TABLE) + " GROUP BY stringField LIMIT 15"; + } + + + @Override + protected String expectedSelectWithJoinAndLimit() { + return "SELECT Test.id, Alternate.stringField FROM " + tableName(TEST_TABLE) + " INNER JOIN " + tableName("Alternate") + " ON (Test.id = Alternate.id) LIMIT 25"; + } + + + @Override + protected String expectedSelectWithOrderByWhereAndLimit() { + return "SELECT id, stringField FROM " + tableName(TEST_TABLE) + " WHERE (stringField IS NOT NULL) ORDER BY id DESC LIMIT 10"; + } + +} diff --git a/morf-h2v2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2MetaDataProvider.java b/morf-h2v2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2MetaDataProvider.java new file mode 100644 index 000000000..67fe33233 --- /dev/null +++ b/morf-h2v2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2MetaDataProvider.java @@ -0,0 +1,119 @@ +/* Copyright 2017 Alfa Financial Software + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.alfasoftware.morf.jdbc.h2; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.RETURNS_SMART_NULLS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import javax.sql.DataSource; + +import org.alfasoftware.morf.jdbc.DatabaseType; +import org.alfasoftware.morf.metadata.Schema; +import org.alfasoftware.morf.metadata.Sequence; +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + + +/** + * Test class for {@link H2MetaDataProvider} + * + * @author Copyright (c) Alfa Financial Software Ltd. 2024 + */ +public class TestH2MetaDataProvider { + + private final DataSource dataSource = mock(DataSource.class, RETURNS_SMART_NULLS); + private final Connection connection = mock(Connection.class, RETURNS_SMART_NULLS); + private DatabaseType h2; + + @Before + public void setup() { + h2 = DatabaseType.Registry.findByIdentifier(H2.IDENTIFIER); + } + + + @Before + public void before() throws SQLException { + when(dataSource.getConnection()).thenReturn(connection); + } + + + /** + * Checks the SQL run for retrieving sequences information + * + * @throws SQLException exception + */ + @Test + public void testLoadSequences() throws SQLException { + // Given + final PreparedStatement statement = mock(PreparedStatement.class, RETURNS_SMART_NULLS); + when(connection.prepareStatement("SELECT SEQUENCE_NAME FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_SCHEMA =?")).thenReturn(statement); + when(statement.executeQuery()).thenAnswer(new ReturnMockResultSetWithSequence(1)); + + // When + final Schema h2MetaDataProvider = h2.openSchema(connection, "TestDatabase", "TestSchema"); + assertEquals("Sequence names", "[Sequence1]", h2MetaDataProvider.sequenceNames().toString()); + Sequence sequence = h2MetaDataProvider.sequences().iterator().next(); + assertEquals("Sequence name", "Sequence1", sequence.getName()); + + verify(statement, never()).setString(1, "TestSchema"); + } + + + /** + * Mockito {@link Answer} that returns a mock result set with a given number of resultRows. + */ + private static final class ReturnMockResultSetWithSequence implements Answer { + + private final int numberOfResultRows; + + /** + * @param numberOfResultRows + */ + private ReturnMockResultSetWithSequence(int numberOfResultRows) { + super(); + this.numberOfResultRows = numberOfResultRows; + } + + @Override + public ResultSet answer(final InvocationOnMock invocation) throws Throwable { + final ResultSet resultSet = mock(ResultSet.class, RETURNS_SMART_NULLS); + when(resultSet.next()).thenAnswer(new Answer() { + private int counter; + + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + return counter++ < numberOfResultRows; + } + }); + + when(resultSet.getString(1)).thenReturn("Sequence1"); + + return resultSet; + } + } + +} diff --git a/morf-h2v2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2Sql.java b/morf-h2v2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2Sql.java new file mode 100644 index 000000000..989c66c12 --- /dev/null +++ b/morf-h2v2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2Sql.java @@ -0,0 +1,211 @@ +package org.alfasoftware.morf.jdbc.h2; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.util.List; + +import org.alfasoftware.morf.sql.DeleteStatement; +import org.alfasoftware.morf.sql.InsertStatement; +import org.alfasoftware.morf.sql.MergeStatement; +import org.alfasoftware.morf.sql.SelectFirstStatement; +import org.alfasoftware.morf.sql.SelectStatement; +import org.alfasoftware.morf.sql.Statement; +import org.alfasoftware.morf.sql.TruncateStatement; +import org.alfasoftware.morf.sql.UpdateStatement; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +/** + * Tests class for H2Sql and H2DialectExt. + * + * @author Copyright (c) Alfa Financial Software 2024 + */ +public class TestH2Sql { + + @Spy + private H2DialectExt h2DialectExt = new H2DialectExt(); + + private H2Sql h2Sql; + + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + h2Sql = h2DialectExt.createH2Sql(); + } + + /** + * Test for the creation of H2Sql instance. + * Verifies that the H2Sql instance is not null. + */ + @Test + public void testCreateH2Sql() { + // Given + H2DialectExt h2DialectExt = new H2DialectExt(); + + // When + H2Sql result = h2DialectExt.createH2Sql(); + + // Then + assertNotNull(result); + } + + /** + * Test for converting SelectStatement to SQL. + * Verifies that the correct SQL string is returned for a given SelectStatement. + */ + @Test + public void testSqlFromSelectStatement() { + // Given + SelectStatement statement = mock(SelectStatement.class); + String expectedSql = "SELECT * FROM table"; + doReturn(expectedSql).when(h2DialectExt).convertStatementToSQL(statement); + + // When + String result = h2Sql.sqlFrom(statement); + + // Then + assertEquals(expectedSql, result); + verify(h2DialectExt, times(1)).convertStatementToSQL(statement); + } + + /** + * Test for converting SelectFirstStatement to SQL. + * Verifies that the correct SQL string is returned for a given SelectFirstStatement. + */ + @Test + public void testSqlFromSelectFirstStatement() { + // Given + SelectFirstStatement statement = mock(SelectFirstStatement.class); + String expectedSql = "SELECT FIRST * FROM table"; + doReturn(expectedSql).when(h2DialectExt).convertStatementToSQL(statement); + + // When + String result = h2Sql.sqlFrom(statement); + + // Then + assertEquals(expectedSql, result); + verify(h2DialectExt, times(1)).convertStatementToSQL(statement); + } + + /** + * Test for converting InsertStatement to SQL. + * Verifies that the correct list of SQL strings is returned for a given InsertStatement. + */ + @Test + public void testSqlFromInsertStatement() { + // Given + InsertStatement statement = mock(InsertStatement.class); + List expectedSql = List.of("INSERT INTO table (column1) VALUES ('value1')"); + doReturn(expectedSql).when(h2DialectExt).convertStatementToSQL(statement); + + // When + List result = h2Sql.sqlFrom(statement); + + // Then + assertEquals(expectedSql, result); + verify(h2DialectExt, times(1)).convertStatementToSQL(statement); + } + + /** + * Test for converting UpdateStatement to SQL. + * Verifies that the correct SQL string is returned for a given UpdateStatement. + */ + @Test + public void testSqlFromUpdateStatement() { + // Given + UpdateStatement statement = mock(UpdateStatement.class); + String expectedSql = "UPDATE table SET column1 = 'value1' WHERE condition"; + doReturn(expectedSql).when(h2DialectExt).convertStatementToSQL(statement); + + // When + String result = h2Sql.sqlFrom(statement); + + // Then + assertEquals(expectedSql, result); + verify(h2DialectExt, times(1)).convertStatementToSQL(statement); + } + + /** + * Test for converting MergeStatement to SQL. + * Verifies that the correct SQL string is returned for a given MergeStatement. + */ + @Test + public void testSqlFromMergeStatement() { + // Given + MergeStatement statement = mock(MergeStatement.class); + String expectedSql = "MERGE INTO table USING source ON condition WHEN MATCHED THEN UPDATE SET column1 = 'value1' WHEN NOT MATCHED THEN INSERT (column1) VALUES ('value1')"; + doReturn(expectedSql).when(h2DialectExt).convertStatementToSQL(statement); + + // When + String result = h2Sql.sqlFrom(statement); + + // Then + assertEquals(expectedSql, result); + verify(h2DialectExt, times(1)).convertStatementToSQL(statement); + } + + /** + * Test for converting DeleteStatement to SQL. + * Verifies that the correct SQL string is returned for a given DeleteStatement. + */ + @Test + public void testSqlFromDeleteStatement() { + // Given + DeleteStatement statement = mock(DeleteStatement.class); + String expectedSql = "DELETE FROM table WHERE condition"; + doReturn(expectedSql).when(h2DialectExt).convertStatementToSQL(statement); + + // When + String result = h2Sql.sqlFrom(statement); + + // Then + assertEquals(expectedSql, result); + verify(h2DialectExt, times(1)).convertStatementToSQL(statement); + } + + /** + * Test for converting TruncateStatement to SQL. + * Verifies that the correct SQL string is returned for a given TruncateStatement. + */ + @Test + public void testSqlFromTruncateStatement() { + // Given + TruncateStatement statement = mock(TruncateStatement.class); + String expectedSql = "TRUNCATE TABLE table"; + doReturn(expectedSql).when(h2DialectExt).convertStatementToSQL(statement); + + // When + String result = h2Sql.sqlFrom(statement); + + // Then + assertEquals(expectedSql, result); + verify(h2DialectExt, times(1)).convertStatementToSQL(statement); + } + + /** + * Test for sqlFrom method when the Statement is not an InsertStatement. + * Verifies that convertStatementToSQL is called directly and the correct list of SQL strings is returned. + */ + @Test + public void testSqlFromNonInsertStatement() { + // Given + Statement updateStatement = mock(Statement.class); + List expectedSql = List.of("UPDATE table SET column1 = 'value1' WHERE condition"); + + doReturn(expectedSql).when(h2DialectExt).convertStatementToSQL(updateStatement, null, null); + + // When + List result = h2Sql.sqlFrom(updateStatement); + + // Then + assertEquals(expectedSql, result); + verify(h2DialectExt, times(1)).convertStatementToSQL(updateStatement, null, null); + } +} diff --git a/morf-h2v2/src/test/resources/commons-logging.properties b/morf-h2v2/src/test/resources/commons-logging.properties new file mode 100644 index 000000000..fbb009a55 --- /dev/null +++ b/morf-h2v2/src/test/resources/commons-logging.properties @@ -0,0 +1,3 @@ +# This is here to override other files that might get picked up on the classpath from third party JARs. +org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImpl +org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger \ No newline at end of file diff --git a/morf-h2v2/src/test/resources/log4j.properties b/morf-h2v2/src/test/resources/log4j.properties new file mode 100755 index 000000000..87c889588 --- /dev/null +++ b/morf-h2v2/src/test/resources/log4j.properties @@ -0,0 +1,9 @@ +# Logging configuration + +log4j.rootLogger=INFO, CONSOLE + +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n + +log4j.logger.org.alfasoftware.morf.diagnostics.DatabaseAndSystemTimeConsistencyChecker=DEBUG diff --git a/morf-integration-test/pom.xml b/morf-integration-test/pom.xml index 582fb79c9..32cb164ae 100755 --- a/morf-integration-test/pom.xml +++ b/morf-integration-test/pom.xml @@ -56,7 +56,31 @@ - + + + morf-h2-v1 + + + org.alfasoftware + morf-h2 + runtime + + + + + morf-h2-v2 + + true + + + + org.alfasoftware + morf-h2v2 + runtime + + + + net.jcip @@ -75,8 +99,8 @@ org.alfasoftware - morf-h2 - runtime + morf-h2v2 + provided org.alfasoftware diff --git a/morf-integration-test/src/test/java/org/alfasoftware/morf/integration/TestSqlNulls.java b/morf-integration-test/src/test/java/org/alfasoftware/morf/integration/TestSqlNulls.java index a39109990..cde8f9bb0 100644 --- a/morf-integration-test/src/test/java/org/alfasoftware/morf/integration/TestSqlNulls.java +++ b/morf-integration-test/src/test/java/org/alfasoftware/morf/integration/TestSqlNulls.java @@ -136,7 +136,7 @@ public class TestSqlNulls { ); private String databaseType; - + private int databaseVersion; @Before public void before() throws SQLException { @@ -146,6 +146,8 @@ public void before() throws SQLException { new DataSetConnector(dataSet, databaseDataSetConsumer.get()).connect(); databaseType = connectionResources.getDatabaseType(); + databaseVersion = connectionResources.getDataSource().getConnection().getMetaData().getDatabaseMajorVersion(); + } @@ -350,8 +352,10 @@ private BigDecimal expectedLeastOfSomeNulls() { switch (databaseType) { case "ORACLE": case "MY_SQL": - return null; - + return null; // null is the SQL standard implementation + case "H2": + return databaseVersion == 1 ? new BigDecimal(5) : null; + case "PGSQL": default: return new BigDecimal(5); } @@ -363,7 +367,9 @@ private BigDecimal expectedGreatestOfSomeNulls() { case "ORACLE": case "MY_SQL": return null; - + case "H2": + return databaseVersion == 1 ? new BigDecimal(1) : null; + case "PGSQL": default: return new BigDecimal(1); } diff --git a/morf-testsupport/pom.xml b/morf-testsupport/pom.xml index bf935b94c..64aaaeafc 100644 --- a/morf-testsupport/pom.xml +++ b/morf-testsupport/pom.xml @@ -16,7 +16,7 @@ ${basedir}/../${aggregate.report.dir} - + @@ -33,6 +33,32 @@ + + + + h2version2 + + true + + + + com.h2database + h2 + 2.3.232 + + + + + h2version1 + + + com.h2database + h2 + 1.4.200 + + + + @@ -61,10 +87,6 @@ junit junit - - com.h2database - h2 - net.jcip jcip-annotations diff --git a/pom.xml b/pom.xml index a8642ae83..05ce9b0a2 100644 --- a/pom.xml +++ b/pom.xml @@ -20,12 +20,13 @@ morf-core morf-testsupport morf-excel - morf-h2 morf-mysql morf-oracle morf-postgresql morf-sqlserver morf-integration-test + morf-h2 + morf-h2v2 @@ -52,7 +53,6 @@ 1.2 32.0.0-jre 7.0.0 - 1.4.200 1.3 1.0 2.1 @@ -274,6 +274,11 @@ morf-h2 ${project.version} + + org.alfasoftware + morf-h2v2 + ${project.version} + org.alfasoftware morf-mysql @@ -325,11 +330,6 @@ guice-assistedinject ${guice.version} - - com.h2database - h2 - ${h2.version} - org.hamcrest hamcrest-all