diff --git a/morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java b/morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java index 4709f33ae..c9303d7ac 100755 --- a/morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java @@ -184,7 +184,7 @@ public SqlDialect(String schemaName) { * @return The statements required to deploy the table and its indexes. */ public Collection tableDeploymentStatements(Table table) { - Builder statements = ImmutableList.builder(); + Builder statements = ImmutableList.builder(); statements.addAll(internalTableDeploymentStatements(table)); @@ -215,16 +215,15 @@ public Collection viewDeploymentStatements(View view) { List statements = new ArrayList<>(); // Create the table deployment statement - StringBuilder createTableStatement = new StringBuilder(); - createTableStatement.append("CREATE "); - createTableStatement.append("VIEW "); - createTableStatement.append(schemaNamePrefix()); - createTableStatement.append(view.getName()); - createTableStatement.append(" AS ("); - createTableStatement.append(convertStatementToSQL(view.getSelectStatement())); - createTableStatement.append(")"); + String createTableStatement = "CREATE " + + "VIEW " + + schemaNamePrefix() + + view.getName() + + " AS (" + + convertStatementToSQL(view.getSelectStatement()) + + ")"; - statements.add(createTableStatement.toString()); + statements.add(createTableStatement); return statements; } @@ -1195,10 +1194,7 @@ protected String getSqlForOrderByField(AliasedField currentOrderByField) { return getSqlForOrderByField((FieldReference) currentOrderByField); } - StringBuilder result = new StringBuilder(getSqlFrom(currentOrderByField)); - result.append(" ").append(defaultNullOrder()); - - return result.toString().trim(); + return (getSqlFrom(currentOrderByField) + " " + defaultNullOrder()).trim(); } @@ -1325,7 +1321,7 @@ protected void appendJoin(StringBuilder result, Join join, String innerJoinKeywo } else { // MySql supports no ON criteria and ON TRUE, but the other platforms // don't, so just keep things simple. - result.append(String.format(" ON 1=1")); + result.append(" ON 1=1"); } } @@ -1568,7 +1564,7 @@ protected String getSqlFrom(Criterion criterion) { result.append(getOperatorLine(criterion, "<=")); break; case LIKE: - result.append(getOperatorLine(criterion, "LIKE") + likeEscapeSuffix()); + result.append(getOperatorLine(criterion, "LIKE")).append(likeEscapeSuffix()); break; case ISNULL: result.append(String.format("%s IS NULL", getSqlFrom(criterion.getField()))); @@ -2078,6 +2074,18 @@ protected String getSqlFrom(Function function) { } return getSqlForRowNumber(); + case CURRENT_UNIX_TIME_MILLISECONDS: + if (!function.getArguments().isEmpty()) { + throw new IllegalArgumentException("The CURRENT_UNIX_TIME_MILLISECONDS function should have zero arguments. This function has " + function.getArguments().size()); + } + return getSqlForCurrentUnixTimeMilliseconds(); + + case CLIENT_HOST: + if (!function.getArguments().isEmpty()) { + throw new IllegalArgumentException("The CLIENT_HOST function should have zero arguments. This function has " + function.getArguments().size()); + } + return getSqlForClientHost(); + default: throw new UnsupportedOperationException("This database does not currently support the [" + function.getType() + "] function"); } @@ -2189,7 +2197,7 @@ protected String getSqlForCoalesce(Function function) { * @return a string representation of the SQL */ protected String getSqlForGreatest(Function function) { - return getGreatestFunctionName() + '(' + Joiner.on(", ").join(function.getArguments().stream().map(f -> getSqlFrom(f)).iterator()) + ')'; + return getGreatestFunctionName() + '(' + Joiner.on(", ").join(function.getArguments().stream().map(this::getSqlFrom).iterator()) + ')'; } @@ -2200,7 +2208,7 @@ protected String getSqlForGreatest(Function function) { * @return a string representation of the SQL */ protected String getSqlForLeast(Function function) { - return getLeastFunctionName() + '(' + Joiner.on(", ").join(function.getArguments().stream().map(f -> getSqlFrom(f)).iterator()) + ')'; + return getLeastFunctionName() + '(' + Joiner.on(", ").join(function.getArguments().stream().map(this::getSqlFrom).iterator()) + ')'; } @@ -2417,13 +2425,29 @@ protected String getLeastFunctionName() { /** * Produce SQL for getting the row number of the row in the partition * - * @return a string representation of the SQL for finding the last day of the month. + * @return a string representation of the SQL for getting the row number of the row in the partition. */ protected String getSqlForRowNumber(){ return "ROW_NUMBER()"; } + /** + * Produce SQL for getting the current unix time in milliseconds + * + * @return A string representation of the SQL for the current unix time in milliseconds + */ + protected abstract String getSqlForCurrentUnixTimeMilliseconds(); + + + /** + * Produce SQL for getting the client host + * + * @return A string representation of the SQL for the client host + */ + protected abstract String getSqlForClientHost(); + + /** * Gets the function name required to perform a substring command. *

@@ -4192,7 +4216,7 @@ protected Iterable getMergeStatementUpdateExpressions(MergeStateme .toSet(); List listOfKeyFieldsWithUpdateExpression = FluentIterable.from(onUpdateExpressions.keySet()) - .filter(a -> keyFields.contains(a)) + .filter(keyFields::contains) .toList(); if (!listOfKeyFieldsWithUpdateExpression.isEmpty()) { diff --git a/morf-core/src/main/java/org/alfasoftware/morf/sql/element/Function.java b/morf-core/src/main/java/org/alfasoftware/morf/sql/element/Function.java index 3f6073584..5d32160d7 100755 --- a/morf-core/src/main/java/org/alfasoftware/morf/sql/element/Function.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/sql/element/Function.java @@ -328,11 +328,11 @@ public static Function addMonths(AliasedField expression, AliasedField number) { * * * - * - * - * - * - * + * + * + * + * + * *
Database rounding references
DatabaseDatabase Manual
Oraclehttp://docs.oracle.com/cd/B19306_01/server.102/b14200/functions135.htm
MySQLhttp://dev.mysql.com/doc/refman/5.0/en/mathematical-functions.html#function_round
SQLServerhttp://technet.microsoft.com/en-us/library/ms175003.aspx
Db2400http://publib.boulder.ibm.com/infocenter/db2luw/v9/index.jsp?topic=%2Fcom.ibm.db2.udb.admin.doc%2Fdoc%2Fr0000845.htm
H2http://www.h2database.com/html/functions.html#round
OracleManual
MySQLManual
SQLServerManual
Db2400Manual
H2Manual
* * @param expression the expression to evaluate @@ -645,6 +645,26 @@ public FunctionType getType() { } + + /** + * Helper method to create an instance of the "currentUnixTimeMilliseconds" SQL function. + * + * @return an instance of a unixtime function + */ + public static Function currentUnixTimeMilliseconds() { + return new Function(FunctionType.CURRENT_UNIX_TIME_MILLISECONDS); + } + + /** + * Helper method to create an instance of the "clientHost" SQL function. + * + * @return an instance of a clientHost function + */ + public static Function clientHost() { + return new Function(FunctionType.CLIENT_HOST); + } + + /** * @see org.alfasoftware.morf.sql.element.AliasedField#deepCopyInternal(DeepCopyTransformation) */ diff --git a/morf-core/src/main/java/org/alfasoftware/morf/sql/element/FunctionType.java b/morf-core/src/main/java/org/alfasoftware/morf/sql/element/FunctionType.java index 95884d002..2d63eeb35 100755 --- a/morf-core/src/main/java/org/alfasoftware/morf/sql/element/FunctionType.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/sql/element/FunctionType.java @@ -226,5 +226,16 @@ public enum FunctionType { /** * Calculates the row number on a partition. Generally used as a Window function */ - ROW_NUMBER + ROW_NUMBER, + + /** + * Unix time, milliseconds since 1st January 1970 UTC + */ + + CURRENT_UNIX_TIME_MILLISECONDS, + + /** + * Identifiable label for the client machine + */ + CLIENT_HOST } \ No newline at end of file diff --git a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/AuditRecordHelper.java b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/AuditRecordHelper.java index d0166a650..1695a7170 100755 --- a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/AuditRecordHelper.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/AuditRecordHelper.java @@ -16,24 +16,46 @@ package org.alfasoftware.morf.upgrade; import static org.alfasoftware.morf.sql.SqlUtils.cast; +import static org.alfasoftware.morf.sql.SqlUtils.field; +import static org.alfasoftware.morf.sql.SqlUtils.literal; +import static org.alfasoftware.morf.sql.SqlUtils.tableRef; +import static org.alfasoftware.morf.sql.element.Function.clientHost; import static org.alfasoftware.morf.sql.element.Function.dateToYyyyMMddHHmmss; import static org.alfasoftware.morf.sql.element.Function.now; +import static org.alfasoftware.morf.sql.element.Function.currentUnixTimeMilliseconds; +import static org.alfasoftware.morf.upgrade.db.DatabaseUpgradeTableContribution.UPGRADE_STEP_DESCRIPTION_LENGTH; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.UUID; import org.alfasoftware.morf.metadata.DataType; import org.alfasoftware.morf.metadata.Schema; import org.alfasoftware.morf.sql.InsertStatement; +import org.alfasoftware.morf.sql.UpdateStatement; import org.alfasoftware.morf.sql.element.FieldLiteral; import org.alfasoftware.morf.sql.element.TableReference; +import org.alfasoftware.morf.upgrade.db.DatabaseUpgradeTableContribution; /** * A helper class to add audit records. */ public class AuditRecordHelper { + private static final String UPGRADE_AUDIT_TABLE_NAME = "UpgradeAudit"; + private static final String UPGRADE_UUID_COLUMN_NAME = "upgradeUUID"; + private static final String APPLIED_TIME_COLUMN_NAME = "appliedTime"; + private static final String STATUS_COLUMN_NAME = "status"; + private static final String SERVER_COLUMN_NAME = "server"; + + private static String serverName; + + // variable to track whether the status, server and processingTimeMs columns have been added + private static boolean extendedUpgradeAuditTableRowsPresent; + /** * Add the audit record, writing out the SQL for the insert. + * @deprecated * * @see org.alfasoftware.morf.upgrade.SchemaChangeVisitor#addAuditRecord(java.util.UUID, java.lang.String) * @@ -42,9 +64,10 @@ public class AuditRecordHelper { * @param uuid The UUID of the step which has been applied * @param description The description of the step. */ - public static void addAuditRecord(SchemaChangeVisitor visitor, Schema schema, UUID uuid, String description) { + @Deprecated + public static void addAuditRecordNoStatus(SchemaChangeVisitor visitor, Schema schema, UUID uuid, String description) { // There's no point adding an UpgradeAudit row if the table isn't there. - if (!schema.tableExists("UpgradeAudit")) + if (!schema.tableExists(UPGRADE_AUDIT_TABLE_NAME)) return; InsertStatement auditRecord = createAuditInsertStatement(uuid, description); @@ -53,6 +76,135 @@ public static void addAuditRecord(SchemaChangeVisitor visitor, Schema schema, UU } + /** + * Add the audit record, writing out the SQL for the insert. + * + * @see org.alfasoftware.morf.upgrade.SchemaChangeVisitor#addAuditRecord(java.util.UUID, java.lang.String) + * + * @param visitor The schema change visitor adding the audit record. + * @param schema The schema to add the audit record to. + * @param uuid The UUID of the step which has been applied + * @param description The description of the step. + */ + public static void addAuditRecord(SchemaChangeVisitor visitor, Schema schema, UUID uuid, String description) { + // There's no point adding an UpgradeAudit row if the table isn't there. + if (!schema.tableExists(UPGRADE_AUDIT_TABLE_NAME)) + return; + + if ( ! extendedUpgradeAuditTableRowsPresent ) { + // adding the extended tables expanded the table from 3 columns to 6. If we have more than 5 columns + // we can assume that we have the extended fields + if(schema.getTable(UPGRADE_AUDIT_TABLE_NAME).columns().size() > 6) { + extendedUpgradeAuditTableRowsPresent = true; + } else { + // If we don't have the expanded fields we can no-op as UpgradeAudit records are only inserted after we + // have run the upgrade step + return; + } + } + + InsertStatement auditRecord = createExtendedAuditInsertStatement(uuid, description); + + visitor.visit(new ExecuteStatement(auditRecord)); + } + + + public static void updateStartedAuditRecord(SchemaChangeVisitor visitor, Schema schema, UUID uuid) { + // There's no point adding an UpgradeAudit row if the table isn't there. + if (!schema.tableExists(UPGRADE_AUDIT_TABLE_NAME)) + return; + + if ( ! extendedUpgradeAuditTableRowsPresent ) { + // adding the extended tables expanded the table from 3 columns to 7. If we have more than 6 columns + // we can assume that we have the extended fields + if(schema.getTable(UPGRADE_AUDIT_TABLE_NAME).columns().size() > 6) { + extendedUpgradeAuditTableRowsPresent = true; + } else { + // If we don't have the expanded fields we can no-op as UpgradeAudit records are only inserted after we + // have run the upgrade step + return; + } + } + + UpdateStatement auditRecord = createStartedAuditUpdateStatement(uuid); + + visitor.visit(new ExecuteStatement(auditRecord)); + } + + + public static void updateFinishedAuditRecord(SchemaChangeVisitor visitor, Schema schema, UUID uuid, String description) { + // There's no point adding an UpgradeAudit row if the table isn't there. + if (!schema.tableExists(UPGRADE_AUDIT_TABLE_NAME)) + return; + + if ( ! extendedUpgradeAuditTableRowsPresent ) { + // adding the extended tables expanded the table from 3 columns to 7. If we have more than 6 columns + // we can assume that we have the extended fields + if(schema.getTable(UPGRADE_AUDIT_TABLE_NAME).columns().size() > 6) { + extendedUpgradeAuditTableRowsPresent = true; + // Just extended so will not have a UpgradeAudit table record. So we need to add one, so it can be upgraded afterwards + addAuditRecord(visitor, schema, uuid, description); + } else { + addAuditRecordNoStatus(visitor, schema, uuid, description); + return; + } + } + + UpdateStatement auditRecord = createFinishedAuditUpdateStatement(uuid); + + visitor.visit(new ExecuteStatement(auditRecord)); + } + + + /** + * Utility method to get a name for the server that this class is executing on. Value is cached after the first execution + * + * @return host name + */ + private static String getServerName() { + if (serverName == null) { + try { + serverName = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + serverName = "unknown server"; + } + } + + return serverName; + } + + + + + // Initial insert sets upgradeUUID, description, server, appliedTime, status = SCHEDULED + public static InsertStatement createExtendedAuditInsertStatement(UUID uuid, String description) { + return new InsertStatement().into( + tableRef(UPGRADE_AUDIT_TABLE_NAME)).values( + literal(uuid.toString()).as(UPGRADE_UUID_COLUMN_NAME), + literal(description.length() > UPGRADE_STEP_DESCRIPTION_LENGTH ? description.substring(0, UPGRADE_STEP_DESCRIPTION_LENGTH) : description).as("description"), + cast(dateToYyyyMMddHHmmss(now())).asType(DataType.DECIMAL, 14).as(APPLIED_TIME_COLUMN_NAME), + literal(UpgradeStepStatus.SCHEDULED.name()).as(STATUS_COLUMN_NAME), + new FieldLiteral(getServerName()).as(SERVER_COLUMN_NAME) + ); + } + + public static UpdateStatement createStartedAuditUpdateStatement(UUID uuid) { + return new UpdateStatement(new TableReference(DatabaseUpgradeTableContribution.UPGRADE_AUDIT_NAME)) + .set(cast(dateToYyyyMMddHHmmss(now())).asType(DataType.DECIMAL, 14).as(APPLIED_TIME_COLUMN_NAME)) + .set(literal(UpgradeStepStatus.STARTED.name()).as(STATUS_COLUMN_NAME)) + .set(clientHost().as(SERVER_COLUMN_NAME)) + .set(currentUnixTimeMilliseconds().as("startTimeMs")) + .where(field(UPGRADE_UUID_COLUMN_NAME).eq(uuid.toString())); + } + + public static UpdateStatement createFinishedAuditUpdateStatement(UUID uuid) { + return new UpdateStatement(new TableReference(DatabaseUpgradeTableContribution.UPGRADE_AUDIT_NAME)) + .set(currentUnixTimeMilliseconds().minus(field("startTimeMs")).as("processingTimeMs")) + .set(literal( UpgradeStepStatus.COMPLETED.name()).as(STATUS_COLUMN_NAME)) + .where(field(UPGRADE_UUID_COLUMN_NAME).eq(uuid.toString())); + } + + /** * Returns an {@link InsertStatement} used to be added to the upgrade audit table. * @@ -61,12 +213,12 @@ public static void addAuditRecord(SchemaChangeVisitor visitor, Schema schema, UU * @return The insert statement */ public static InsertStatement createAuditInsertStatement(UUID uuid, String description) { - InsertStatement auditRecord = new InsertStatement().into( - new TableReference("UpgradeAudit")).values( - new FieldLiteral(uuid.toString()).as("upgradeUUID"), - new FieldLiteral(description).as("description"), - cast(dateToYyyyMMddHHmmss(now())).asType(DataType.DECIMAL, 14).as("appliedTime") + + return new InsertStatement().into( + new TableReference(UPGRADE_AUDIT_TABLE_NAME)).values( + new FieldLiteral(uuid.toString()).as(UPGRADE_UUID_COLUMN_NAME), + new FieldLiteral(description.length() > UPGRADE_STEP_DESCRIPTION_LENGTH ? description.substring(0, UPGRADE_STEP_DESCRIPTION_LENGTH) : description).as("description"), + cast(dateToYyyyMMddHHmmss(now())).asType(DataType.DECIMAL, 14).as(APPLIED_TIME_COLUMN_NAME) ); - return auditRecord; } } diff --git a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/GraphBasedUpgradeSchemaChangeVisitor.java b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/GraphBasedUpgradeSchemaChangeVisitor.java index d825fa3c2..4ccc596d9 100644 --- a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/GraphBasedUpgradeSchemaChangeVisitor.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/GraphBasedUpgradeSchemaChangeVisitor.java @@ -48,6 +48,7 @@ class GraphBasedUpgradeSchemaChangeVisitor implements SchemaChangeVisitor { * Write statements to the current node */ private void writeStatements(Collection statements) { + //TODO jonk - log SQL currentNode.addAllUpgradeStatements(statements); } @@ -179,6 +180,15 @@ public void addAuditRecord(UUID uuid, String description) { AuditRecordHelper.addAuditRecord(this, sourceSchema, uuid, description); } + @Override + public void updateStartedAuditRecord(UUID uuid) { + AuditRecordHelper.updateStartedAuditRecord(this, sourceSchema, uuid); + } + + @Override + public void updateFinishedAuditRecord(UUID uuid, String description) { + AuditRecordHelper.updateFinishedAuditRecord(this, sourceSchema, uuid, description); + } /** * Set the current {@link GraphBasedUpgradeNode} which is being processed. diff --git a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/GraphBasedUpgradeTraversalService.java b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/GraphBasedUpgradeTraversalService.java index ae055fb77..b51d5f038 100644 --- a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/GraphBasedUpgradeTraversalService.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/GraphBasedUpgradeTraversalService.java @@ -143,7 +143,7 @@ public void waitForReadyToExecuteNode() throws InterruptedException { while(readyToExecuteNodes.isEmpty() && !allNodesCompletedNoLock()) { // The result of this await is (indirectly) checked by the wait loop // so there is no need to check the result of the await (so NOSONAR). - newReadyToExecuteNode.await(500, TimeUnit.MILLISECONDS); // NOSONAR + newReadyToExecuteNode.await(500, TimeUnit.MILLISECONDS); //NOSONAR } } catch (InterruptedException e) { LOG.error("InterruptedException in GraphBasedUpgradeService.waitForAllNodesToBeCompleted", e); diff --git a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/HumanReadableStatementHelper.java b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/HumanReadableStatementHelper.java index 0d6e8dd6d..9466559b7 100755 --- a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/HumanReadableStatementHelper.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/HumanReadableStatementHelper.java @@ -140,6 +140,8 @@ public FunctionTypeMetaData(final String prefix, final String suffix, final Stri .put(FunctionType.TRIM, new FunctionTypeMetaData("trimmed ", "", "", false, false)) .put(FunctionType.UPPER, new FunctionTypeMetaData("upper case ", "", "", false, false)) .put(FunctionType.LAST_DAY_OF_MONTH, new FunctionTypeMetaData("last day of month ", "", "", false, false)) + .put(FunctionType.CURRENT_UNIX_TIME_MILLISECONDS, new FunctionTypeMetaData("current unix time in milliseconds ", "", "", false, false)) + .put(FunctionType.CLIENT_HOST, new FunctionTypeMetaData("client hostname ", "", "", false, false)) .build(); /** @@ -177,12 +179,10 @@ private static String generateColumnDefinitionString(final Column definition) { * @return a string containing the human-readable version of the action */ public static String generateChangePrimaryKeyColumnsString(String tableName, List oldPrimaryKeyColumns, List newPrimaryKeyColumns) { - StringBuilder changePrimaryKeyColumnsBuilder = new StringBuilder(); - changePrimaryKeyColumnsBuilder.append(String.format("Change primary key columns on %s from %s to %s", - tableName, - "(" + Joiner.on(", ").join(oldPrimaryKeyColumns) + ")", - "(" + Joiner.on(", ").join(newPrimaryKeyColumns) + ")")); - return changePrimaryKeyColumnsBuilder.toString(); + return String.format("Change primary key columns on %s from %s to %s", + tableName, + "(" + Joiner.on(", ").join(oldPrimaryKeyColumns) + ")", + "(" + Joiner.on(", ").join(newPrimaryKeyColumns) + ")"); } @@ -192,11 +192,9 @@ public static String generateChangePrimaryKeyColumnsString(String tableName, Lis * @return a string containing the human-readable version of the action */ public static String generateChangePrimaryKeyColumnsString(String tableName, List newPrimaryKeyColumns) { - StringBuilder changePrimaryKeyColumnsBuilder = new StringBuilder(); - changePrimaryKeyColumnsBuilder.append(String.format("Change primary key columns on %s to become %s", - tableName, - "(" + Joiner.on(", ").join(newPrimaryKeyColumns) + ")")); - return changePrimaryKeyColumnsBuilder.toString(); + return String.format("Change primary key columns on %s to become %s", + tableName, + "(" + Joiner.on(", ").join(newPrimaryKeyColumns) + ")"); } @@ -390,11 +388,9 @@ public static String generateAnalyseTableFromString(String tableName) { * @return a string containing the human-readable version of the action */ public static String generateRenameTableString(String from, String to) { - StringBuilder renameTableBuilder = new StringBuilder(); - renameTableBuilder.append(String.format("Rename table %s to %s", - from, - to)); - return renameTableBuilder.toString(); + return String.format("Rename table %s to %s", + from, + to); } @@ -968,7 +964,7 @@ private static String generateSelectStatementString(final AbstractSelectStatemen if (prefix) { sb.append("select"); } - if ((AbstractSelectStatement)statement instanceof SelectFirstStatement) { + if (statement instanceof SelectFirstStatement) { if (sb.length() > 0) { sb.append(' '); } diff --git a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/InlineTableUpgrader.java b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/InlineTableUpgrader.java index d89fd1ed6..bab57e7f2 100755 --- a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/InlineTableUpgrader.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/InlineTableUpgrader.java @@ -248,6 +248,17 @@ public void addAuditRecord(UUID uuid, String description) { } + @Override + public void updateStartedAuditRecord(UUID uuid) { + AuditRecordHelper.updateStartedAuditRecord(this,currentSchema, uuid); + } + + @Override + public void updateFinishedAuditRecord(UUID uuid, String description) { + AuditRecordHelper.updateFinishedAuditRecord(this, currentSchema, uuid, description); + } + + /** * @see org.alfasoftware.morf.upgrade.SchemaChangeVisitor#startStep(java.lang.Class) */ diff --git a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/SchemaChangeSequence.java b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/SchemaChangeSequence.java index a84bd61de..fb9c0fca2 100755 --- a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/SchemaChangeSequence.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/SchemaChangeSequence.java @@ -137,14 +137,31 @@ public UpgradeTableResolution getUpgradeTableResolution() { * @param visitor The schema change visitor against which to write the changes. */ public void applyTo(SchemaChangeVisitor visitor) { + + // Add all audit records + for (UpgradeStepWithChanges changesForStep : allChanges) { + visitor.addAuditRecord(changesForStep.getUUID(), changesForStep.getDescription()); + } + for (UpgradeStepWithChanges changesForStep : allChanges) { try { + + //TODO roll up line below into visitor.startStep + // Update Audit record to show upgrade step is running + visitor.updateStartedAuditRecord(changesForStep.getUUID()); + // Run prerequisites visitor.startStep(changesForStep.getUpgradeClass()); + + + // Apply each change for (SchemaChange change : changesForStep.getChanges()) { change.accept(visitor); } - visitor.addAuditRecord(changesForStep.getUUID(), changesForStep.getDescription()); + + // Update Audit Record will successful run + visitor.updateFinishedAuditRecord(changesForStep.getUUID(), changesForStep.getDescription()); } catch (Exception e) { + // Set Audit Record to failed then throw runtime exception throw new RuntimeException("Failed to apply step: [" + changesForStep.getUpgradeClass() + "]", e); } } @@ -392,8 +409,8 @@ private static class UpgradeStepWithChanges { /** - * @param delegate - * @param changes + * @param delegate Upgrade Step + * @param changes List of Schema Changes */ UpgradeStepWithChanges(UpgradeStep delegate, List changes) { super(); @@ -500,6 +517,15 @@ public void addAuditRecord(java.util.UUID uuid, String description) { // no-op here. We don't need to record the UUIDs until we actually apply the changes. } + @Override + public void updateStartedAuditRecord(java.util.UUID uuid) { + // no-op here. We don't need to record the UUIDs until we actually apply the changes. + } + + @Override + public void updateFinishedAuditRecord(java.util.UUID uuid, String description) { + // no-op here. We don't need to record the UUIDs until we actually apply the changes. + } @Override public void startStep(Class upgradeClass) { diff --git a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/SchemaChangeVisitor.java b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/SchemaChangeVisitor.java index 33d5829ee..a8ef7b30a 100755 --- a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/SchemaChangeVisitor.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/SchemaChangeVisitor.java @@ -16,6 +16,8 @@ package org.alfasoftware.morf.upgrade; +import java.util.UUID; + /** * Interface for any upgrade / downgrade strategy which handles all the * defined {@link SchemaChange} implementations. @@ -33,7 +35,7 @@ public interface SchemaChangeVisitor { * * @param addColumn instance of {@link AddColumn} to visit. */ - public void visit(AddColumn addColumn); + void visit(AddColumn addColumn); /** @@ -41,7 +43,7 @@ public interface SchemaChangeVisitor { * * @param addTable instance of {@link AddTable} to visit. */ - public void visit(AddTable addTable); + void visit(AddTable addTable); /** @@ -49,7 +51,7 @@ public interface SchemaChangeVisitor { * * @param removeTable instance of {@link RemoveTable} to visit. */ - public void visit(RemoveTable removeTable); + void visit(RemoveTable removeTable); /** @@ -57,7 +59,7 @@ public interface SchemaChangeVisitor { * * @param addIndex instance of {@link AddIndex} to visit. */ - public void visit(AddIndex addIndex); + void visit(AddIndex addIndex); /** @@ -65,7 +67,7 @@ public interface SchemaChangeVisitor { * * @param changeColumn instance of {@link ChangeColumn} to visit. */ - public void visit(ChangeColumn changeColumn); + void visit(ChangeColumn changeColumn); /** @@ -73,7 +75,7 @@ public interface SchemaChangeVisitor { * * @param removeColumn instance of {@link RemoveColumn} to visit. */ - public void visit(RemoveColumn removeColumn); + void visit(RemoveColumn removeColumn); /** @@ -81,7 +83,7 @@ public interface SchemaChangeVisitor { * * @param removeIndex instance of {@link RemoveIndex} to visit. */ - public void visit(RemoveIndex removeIndex); + void visit(RemoveIndex removeIndex); /** @@ -89,7 +91,7 @@ public interface SchemaChangeVisitor { * * @param changeIndex instance of {@link ChangeIndex} to visit. */ - public void visit(ChangeIndex changeIndex); + void visit(ChangeIndex changeIndex); /** @@ -97,7 +99,7 @@ public interface SchemaChangeVisitor { * * @param renameIndex instance of {@link RenameIndex} to visit. */ - public void visit(RenameIndex renameIndex); + void visit(RenameIndex renameIndex); @@ -106,7 +108,7 @@ public interface SchemaChangeVisitor { * * @param executeStatement instance of {@link ExecuteStatement} to visit. */ - public void visit(ExecuteStatement executeStatement); + void visit(ExecuteStatement executeStatement); /** @@ -114,14 +116,14 @@ public interface SchemaChangeVisitor { * * @param renameTable instance of {@link RenameTable} to visit. */ - public void visit(RenameTable renameTable); + void visit(RenameTable renameTable); /** * Perform visit operation on a {@link ChangePrimaryKeyColumns} instance. * * @param renameTable instance of {@link ChangePrimaryKeyColumns} to visit. */ - public void visit(ChangePrimaryKeyColumns renameTable); + void visit(ChangePrimaryKeyColumns renameTable); /** @@ -129,7 +131,7 @@ public interface SchemaChangeVisitor { * * @param addTableFrom instance of {@link AddTableFrom} to visit. */ - public void visit(AddTableFrom addTableFrom); + void visit(AddTableFrom addTableFrom); /** @@ -137,16 +139,32 @@ public interface SchemaChangeVisitor { * * @param analyseTable instance of {@link AnalyseTable} to visit. */ - public void visit(AnalyseTable analyseTable); + void visit(AnalyseTable analyseTable); + + /** + * Add the UUID audit record in {@link UpgradeStepStatus#SCHEDULED} status + * + * @param uuid The UUID of the step which has been applied + * @param description The description of the step. + */ + void addAuditRecord(java.util.UUID uuid, String description); + + + /** + * Update an existing audit record to {@link UpgradeStepStatus#STARTED} status + * + * @param uuid The UUID of the step which has been applied + */ + void updateStartedAuditRecord(UUID uuid); /** - * Add the UUID audit record. + * Update the UUID audit record to {@link UpgradeStepStatus#SCHEDULED} status * * @param uuid The UUID of the step which has been applied * @param description The description of the step. */ - public void addAuditRecord(java.util.UUID uuid, String description); + void updateFinishedAuditRecord(UUID uuid, String description); /** @@ -154,5 +172,5 @@ public interface SchemaChangeVisitor { * * @param upgradeClass The upgrade step being started. */ - public void startStep(Class upgradeClass); + void startStep(Class upgradeClass); } diff --git a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/UpgradeStatus.java b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/UpgradeStatus.java index a48322d67..392435221 100644 --- a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/UpgradeStatus.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/UpgradeStatus.java @@ -43,6 +43,11 @@ public enum UpgradeStatus { */ IN_PROGRESS, + /** + * Upgrade failed. + */ + FAILED, + /** * Upgrade has been completed. */ diff --git a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/UpgradeStepStatus.java b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/UpgradeStepStatus.java new file mode 100644 index 000000000..eb1d19270 --- /dev/null +++ b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/UpgradeStepStatus.java @@ -0,0 +1,7 @@ +package org.alfasoftware.morf.upgrade; + +public enum UpgradeStepStatus { + SCHEDULED, + STARTED, + COMPLETED +} diff --git a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/db/DatabaseUpgradeTableContribution.java b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/db/DatabaseUpgradeTableContribution.java index ef6eef381..ec2d96013 100755 --- a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/db/DatabaseUpgradeTableContribution.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/db/DatabaseUpgradeTableContribution.java @@ -41,6 +41,7 @@ public class DatabaseUpgradeTableContribution implements TableContribution { /** Name of the table containing information on the views deployed within the app's database. */ public static final String DEPLOYED_VIEWS_NAME = "DeployedViews"; + public static final int UPGRADE_STEP_DESCRIPTION_LENGTH = 200; /** * @return The Table descriptor of UpgradeAudit @@ -48,9 +49,13 @@ public class DatabaseUpgradeTableContribution implements TableContribution { public static Table upgradeAuditTable() { return table(UPGRADE_AUDIT_NAME) .columns( - column("upgradeUUID", DataType.STRING, 100).primaryKey(), - column("description", DataType.STRING, 200).nullable(), - column("appliedTime", DataType.DECIMAL, 14).nullable() + column("upgradeUUID", DataType.STRING, 100).primaryKey(), + column("description", DataType.STRING, UPGRADE_STEP_DESCRIPTION_LENGTH).nullable(), + column("appliedTime", DataType.DECIMAL, 14).nullable(), + column("status", DataType.STRING, 10).nullable(), + column("server", DataType.STRING, 100).nullable(), + column("processingTimeMs", DataType.DECIMAL, 14).nullable(), + column("startTimeMs", DataType.DECIMAL, 18).nullable() ); } diff --git a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/upgrade/AddExtraLoggingToUpgradeAuditTable.java b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/upgrade/AddExtraLoggingToUpgradeAuditTable.java new file mode 100644 index 000000000..4404e8096 --- /dev/null +++ b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/upgrade/AddExtraLoggingToUpgradeAuditTable.java @@ -0,0 +1,58 @@ +package org.alfasoftware.morf.upgrade.upgrade; + +import org.alfasoftware.morf.metadata.DataType; +import org.alfasoftware.morf.sql.UpdateStatement; +import org.alfasoftware.morf.sql.element.FieldLiteral; +import org.alfasoftware.morf.sql.element.TableReference; +import org.alfasoftware.morf.upgrade.DataEditor; +import org.alfasoftware.morf.upgrade.SchemaEditor; +import org.alfasoftware.morf.upgrade.Sequence; +import org.alfasoftware.morf.upgrade.UUID; +import org.alfasoftware.morf.upgrade.UpgradeStep; +import org.alfasoftware.morf.upgrade.UpgradeStepStatus; +import org.alfasoftware.morf.upgrade.Version; +import org.alfasoftware.morf.upgrade.db.DatabaseUpgradeTableContribution; + +import static org.alfasoftware.morf.metadata.SchemaUtils.column; +import static org.alfasoftware.morf.sql.SqlUtils.field; +import static org.alfasoftware.morf.sql.element.Criterion.isNull; + +@Version("2.5.2") +@Sequence(1686844860) +@UUID("47832d23-f1e1-422f-b6de-b76e57517334") +public class AddExtraLoggingToUpgradeAuditTable implements UpgradeStep { + + @Override + public String getJiraId() { + return "MORF-72"; + } + + @Override + public String getDescription() { + return "Add extra logging columns to the UpgradeAudit table"; + } + + @Override + public void execute(SchemaEditor schema, DataEditor data) { + + String statusColumn = "status"; + schema.addColumn(DatabaseUpgradeTableContribution.UPGRADE_AUDIT_NAME, + column(statusColumn, DataType.STRING, 10).nullable()); + String serverColumn = "server"; + schema.addColumn(DatabaseUpgradeTableContribution.UPGRADE_AUDIT_NAME, + column(serverColumn, DataType.STRING, 100).nullable()); + String processingTimeMsColumn = "processingTimeMs"; + schema.addColumn(DatabaseUpgradeTableContribution.UPGRADE_AUDIT_NAME, + column(processingTimeMsColumn, DataType.DECIMAL, 14).nullable()); + String startTimeMsColumn = "startTimeMs"; + schema.addColumn(DatabaseUpgradeTableContribution.UPGRADE_AUDIT_NAME, + column(startTimeMsColumn, DataType.DECIMAL, 18).nullable()); + + data.executeStatement( + new UpdateStatement(new TableReference(DatabaseUpgradeTableContribution.UPGRADE_AUDIT_NAME)) + .set(new FieldLiteral(UpgradeStepStatus.COMPLETED.name()).as(statusColumn)) + .where(isNull(field(statusColumn))) + ); + + } +} diff --git a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/upgrade/UpgradeSteps.java b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/upgrade/UpgradeSteps.java index abd4f1f37..e44f7e61f 100644 --- a/morf-core/src/main/java/org/alfasoftware/morf/upgrade/upgrade/UpgradeSteps.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/upgrade/upgrade/UpgradeSteps.java @@ -11,6 +11,7 @@ public class UpgradeSteps { public static final List> LIST = ImmutableList.of( CreateDeployedViews.class, RecreateOracleSequences.class, - AddDeployedViewsSqlDefinition.class + AddDeployedViewsSqlDefinition.class, + AddExtraLoggingToUpgradeAuditTable.class ); } diff --git a/morf-core/src/test/java/org/alfasoftware/morf/jdbc/MockDialect.java b/morf-core/src/test/java/org/alfasoftware/morf/jdbc/MockDialect.java index a2cdbbd36..1025cc67a 100755 --- a/morf-core/src/test/java/org/alfasoftware/morf/jdbc/MockDialect.java +++ b/morf-core/src/test/java/org/alfasoftware/morf/jdbc/MockDialect.java @@ -313,6 +313,24 @@ protected String getSqlForLastDayOfMonth(AliasedField date) { } + /** + * @see SqlDialect#getSqlForClientHost() + */ + @Override + protected String getSqlForClientHost() { + return "'CLIENT'"; + } + + + /** + * @see SqlDialect#getSqlForCurrentUnixTimeMilliseconds() + */ + @Override + protected String getSqlForCurrentUnixTimeMilliseconds() { + return "123456789"; + } + + /** * @see org.alfasoftware.morf.jdbc.SqlDialect#tableNameWithSchemaName(org.alfasoftware.morf.sql.element.TableReference) */ diff --git a/morf-core/src/test/java/org/alfasoftware/morf/sql/element/TestFunctionDetail.java b/morf-core/src/test/java/org/alfasoftware/morf/sql/element/TestFunctionDetail.java index b3571b697..a02af5178 100644 --- a/morf-core/src/test/java/org/alfasoftware/morf/sql/element/TestFunctionDetail.java +++ b/morf-core/src/test/java/org/alfasoftware/morf/sql/element/TestFunctionDetail.java @@ -285,4 +285,31 @@ public void testUpperCase() { assertTrue("First argument should be a field reference", firstArgument instanceof FieldReference); assertEquals("First argument should have correct name", "agreementNumber", ((FieldReference) firstArgument).getName()); } + + + /** + * Tests indirect usage of the CurrentUnixTimeMilliseconds function + */ + @Test + public void testCurrentUnixTimeMilliseconds() { + Function function = Function.currentUnixTimeMilliseconds(); + + assertEquals("Function should be of type CURRENT_UNIX_TIME_MILLISECONDS", FunctionType.CURRENT_UNIX_TIME_MILLISECONDS, function.getType()); + assertNotNull("Function should have empty arguments", function.getArguments()); + assertEquals("Function should have no arguments", 0, function.getArguments().size()); + } + + /** + * Tests indirect usage of the CurrentUnixTimeMilliseconds function + */ + @Test + public void testClientHost() { + Function function = Function.clientHost(); + + assertEquals("Function should be of type CLIENT_HOST", FunctionType.CLIENT_HOST, function.getType()); + assertNotNull("Function should have empty arguments", function.getArguments()); + assertEquals("Function should have no arguments", 0, function.getArguments().size()); + } + + } \ No newline at end of file diff --git a/morf-core/src/test/java/org/alfasoftware/morf/upgrade/TestAuditRecordHelper.java b/morf-core/src/test/java/org/alfasoftware/morf/upgrade/TestAuditRecordHelper.java index 274851485..9ee12829e 100755 --- a/morf-core/src/test/java/org/alfasoftware/morf/upgrade/TestAuditRecordHelper.java +++ b/morf-core/src/test/java/org/alfasoftware/morf/upgrade/TestAuditRecordHelper.java @@ -17,15 +17,23 @@ import static com.google.common.collect.FluentIterable.from; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.NoSuchElementException; import java.util.UUID; +import org.alfasoftware.morf.metadata.Column; import org.alfasoftware.morf.metadata.Schema; +import org.alfasoftware.morf.metadata.Table; import org.alfasoftware.morf.sql.InsertStatement; import org.alfasoftware.morf.sql.element.AliasedField; import org.alfasoftware.morf.sql.element.Cast; @@ -49,9 +57,14 @@ public void testAddAuditRecord() throws ParseException { // given SchemaChangeVisitor visitor = mock(SchemaChangeVisitor.class); Schema schema = mock(Schema.class); + Table table = mock(Table.class); + List columns = Arrays.asList(mock(Column.class), mock(Column.class), mock(Column.class), mock(Column.class), mock(Column.class), mock(Column.class), mock(Column.class)); + UUID uuid = UUID.randomUUID(); String description = "Description"; given(schema.tableExists("UpgradeAudit")).willReturn(true); + given(schema.getTable("UpgradeAudit")).willReturn(table); + given(table.columns()).willReturn(columns); // when AuditRecordHelper.addAuditRecord(visitor, schema, uuid, description); @@ -64,6 +77,32 @@ public void testAddAuditRecord() throws ParseException { } + /** + * Tests the upgrade audit record is not created if the UpgradeAudit table is still in the old 3 column format + */ + @Test + public void testAddAuditRecordPreExpansion() { + // given + SchemaChangeVisitor visitor = mock(SchemaChangeVisitor.class); + Schema schema = mock(Schema.class); + Table table = mock(Table.class); + List columns = Arrays.asList(mock(Column.class), mock(Column.class), mock(Column.class)); + + UUID uuid = UUID.randomUUID(); + String description = "Description"; + given(schema.tableExists("UpgradeAudit")).willReturn(true); + given(schema.getTable("UpgradeAudit")).willReturn(table); + given(table.columns()).willReturn(columns); + + // when + AuditRecordHelper.addAuditRecord(visitor, schema, uuid, description); + + // then + ArgumentCaptor argument = ArgumentCaptor.forClass(ExecuteStatement.class); + assert(argument.getAllValues().size()==0); + } + + /** * Verifies that the {@link AuditRecordHelper#createAuditInsertStatement(UUID, String)} returns a correct * {@link InsertStatement}. @@ -82,13 +121,38 @@ public void createAuditInsertStatement() throws Exception { } + + /** + * Verifies that the {@link AuditRecordHelper#createAuditInsertStatement(UUID, String)} returns a correct + * {@link InsertStatement}. + */ + @Test + public void createAuditInsertStatementLongDescription() throws Exception { + // given + UUID uuid = UUID.randomUUID(); + String str10 = "0123456789"; + String str40 = str10 + str10 + str10 + str10; + String str200 = str40 + str40 + str40 + str40 + str40; + String str210 = str200 + str10; + + // when an overlength description is passed + InsertStatement statement = AuditRecordHelper.createAuditInsertStatement(uuid, str210); + + // then it is trimmed to 200 characters + assertAuditInsertStatement(uuid, str200, statement); + } + + + + + private void assertAuditInsertStatement(UUID uuid, String description, InsertStatement statement) { assertEquals("Table name", "UpgradeAudit", statement.getTable().getName()); assertEquals("UUID ", uuid.toString(), getValueWithAlias(statement, "upgradeUUID").getValue()); assertEquals("UUID ", description, getValueWithAlias(statement, "description").getValue()); Cast nowCastRepresentation = getCastWithAlias(statement, "appliedTime"); - assertEquals("Wraped in integer date function with now function as argument", FunctionType.DATE_TO_YYYYMMDDHHMMSS.toString() + "(" + FunctionType.NOW + "())", nowCastRepresentation.getExpression().toString()); + assertEquals("Wrapped in integer date function with now function as argument", FunctionType.DATE_TO_YYYYMMDDHHMMSS + "(" + FunctionType.NOW + "())", nowCastRepresentation.getExpression().toString()); } diff --git a/morf-core/src/test/java/org/alfasoftware/morf/upgrade/TestUpgrade.java b/morf-core/src/test/java/org/alfasoftware/morf/upgrade/TestUpgrade.java index aeff3a20f..eebfac4c9 100755 --- a/morf-core/src/test/java/org/alfasoftware/morf/upgrade/TestUpgrade.java +++ b/morf-core/src/test/java/org/alfasoftware/morf/upgrade/TestUpgrade.java @@ -179,7 +179,7 @@ public void testUpgrade() throws SQLException { .findPath(targetSchema, upgradeSteps, Lists.newArrayList("^Drivers$", "^EXCLUDE_.*$"), mockConnectionResources.getDataSource()); assertEquals("Should be two steps.", 2, results.getSteps().size()); - assertEquals("Number of SQL statements", 18, results.getSql().size()); // Includes statements to create, truncate and then drop temp table, also 2 comments + assertEquals("Number of SQL statements", 22, results.getSql().size()); // Includes statements to create, truncate and then drop temp table, also 2 comments } @@ -885,7 +885,11 @@ private static Table upgradeAudit() { versionColumn(), column("upgradeUUID", DataType.STRING, 100).nullable(), column("description", DataType.STRING, 200).nullable(), - column("appliedTime", DataType.BIG_INTEGER).nullable() + column("appliedTime", DataType.BIG_INTEGER).nullable(), + column("status", DataType.STRING, 10).nullable(), + column("server", DataType.STRING, 100).nullable(), + column("processingTimeMs", DataType.DECIMAL, 14).nullable(), + column("startTimeMs", DataType.DECIMAL, 18).nullable() ); } diff --git a/morf-core/src/test/java/org/alfasoftware/morf/upgrade/upgrade/TestUpgradeSteps.java b/morf-core/src/test/java/org/alfasoftware/morf/upgrade/upgrade/TestUpgradeSteps.java index 90ad3d10f..3a159528e 100644 --- a/morf-core/src/test/java/org/alfasoftware/morf/upgrade/upgrade/TestUpgradeSteps.java +++ b/morf-core/src/test/java/org/alfasoftware/morf/upgrade/upgrade/TestUpgradeSteps.java @@ -33,6 +33,17 @@ public void testCreateDeployedViews() { verify(schema, times(1)).addTable(any()); } + @Test + public void testAddExtraLoggingToUpgradeAuditTable() { + AddExtraLoggingToUpgradeAuditTable upgradeStep = new AddExtraLoggingToUpgradeAuditTable(); + testUpgradeStep(upgradeStep); + SchemaEditor schema = mock(SchemaEditor.class); + DataEditor dataEditor = mock(DataEditor.class); + upgradeStep.execute(schema, dataEditor); + verify(schema, times(4)).addColumn(any(), any()); + } + + @Test public void testRecreateOracleSequences() { RecreateOracleSequences upgradeStep = new RecreateOracleSequences(); 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 0a5465c54..dcbf59015 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 @@ -489,6 +489,24 @@ public Collection renameTableStatements(Table from, Table to) { } + /** + * @see SqlDialect#getSqlForClientHost() + */ + @Override + protected String getSqlForClientHost() { + return "CAST(SESSION_ID() AS VARCHAR2)"; + } + + + /** + * @see SqlDialect#getSqlForCurrentUnixTimeMilliseconds() + */ + @Override + protected String getSqlForCurrentUnixTimeMilliseconds(){ + return "trunc(extract(epoch from now() at time zone 'UTC')*1000)"; + } + + /** * 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 diff --git a/morf-h2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2Dialect.java b/morf-h2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2Dialect.java index c2399d745..916405e22 100755 --- a/morf-h2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2Dialect.java +++ b/morf-h2/src/test/java/org/alfasoftware/morf/jdbc/h2/TestH2Dialect.java @@ -1221,6 +1221,22 @@ protected String expectedRowNumber() { } + /** + * @return The expected SQL for the unix time stamp in milliseconds + */ + @Override + protected String expectedCurrentUnixTimeMilliseconds() { + return "trunc(extract(epoch from now() at time zone 'UTC')*1000)"; + } + + /** + * @return The expected SQL for the client host + */ + @Override + protected String expectedClientHost() { + return "CAST(SESSION_ID() AS VARCHAR2)"; + } + /** * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#tableName(java.lang.String) */ diff --git a/morf-integration-test/src/test/java/org/alfasoftware/morf/examples/TestStartHere.java b/morf-integration-test/src/test/java/org/alfasoftware/morf/examples/TestStartHere.java index 407113414..4eb9374d3 100644 --- a/morf-integration-test/src/test/java/org/alfasoftware/morf/examples/TestStartHere.java +++ b/morf-integration-test/src/test/java/org/alfasoftware/morf/examples/TestStartHere.java @@ -33,6 +33,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; +import java.sql.SQLException; import java.util.Collection; import java.util.HashSet; import java.util.function.Consumer; diff --git a/morf-integration-test/src/test/java/org/alfasoftware/morf/integration/TestSqlStatements.java b/morf-integration-test/src/test/java/org/alfasoftware/morf/integration/TestSqlStatements.java index 764db8292..61589ba3c 100755 --- a/morf-integration-test/src/test/java/org/alfasoftware/morf/integration/TestSqlStatements.java +++ b/morf-integration-test/src/test/java/org/alfasoftware/morf/integration/TestSqlStatements.java @@ -52,6 +52,7 @@ import static org.alfasoftware.morf.sql.element.Function.average; import static org.alfasoftware.morf.sql.element.Function.averageDistinct; import static org.alfasoftware.morf.sql.element.Function.blobLength; +import static org.alfasoftware.morf.sql.element.Function.clientHost; import static org.alfasoftware.morf.sql.element.Function.coalesce; import static org.alfasoftware.morf.sql.element.Function.count; import static org.alfasoftware.morf.sql.element.Function.countDistinct; @@ -81,6 +82,7 @@ import static org.alfasoftware.morf.sql.element.Function.sum; import static org.alfasoftware.morf.sql.element.Function.sumDistinct; import static org.alfasoftware.morf.sql.element.Function.trim; +import static org.alfasoftware.morf.sql.element.Function.currentUnixTimeMilliseconds; import static org.alfasoftware.morf.sql.element.Function.upperCase; import static org.alfasoftware.morf.sql.element.Function.yyyymmddToDate; import static org.hamcrest.Matchers.allOf; @@ -2785,6 +2787,63 @@ public Void process(ResultSet resultSet) throws SQLException { } + + /** + * Tests execute current unix time in milliseconds function. + * + * @throws SQLException if something goes wrong. + */ + @Test + public void testCurrentUnixTimeMilliseconds() throws SQLException { + SelectStatement select = select(currentUnixTimeMilliseconds()); + + SqlScriptExecutor executor = sqlScriptExecutorProvider.get(new LoggingSqlScriptVisitor()); + + executor.executeQuery(convertStatementToSQL(select), connection, new ResultSetProcessor() { + @Override + public Void process(ResultSet resultSet) throws SQLException { + resultSet.next(); + + final long databaseTime = resultSet.getLong(1); + + log.info("Current database time: " + databaseTime); + + assertTrue("Database unix time is set and has a value after July 6th 2023 ", databaseTime > 1688648895428L); + + return null; + } + }); + } + + + /** + * Tests execute unix time function. + * + * @throws SQLException if something goes wrong. + */ + @Test + public void testClientHost() throws SQLException { + SelectStatement select = select(clientHost()); + + SqlScriptExecutor executor = sqlScriptExecutorProvider.get(new LoggingSqlScriptVisitor()); + + executor.executeQuery(convertStatementToSQL(select), connection, new ResultSetProcessor() { + @Override + public Void process(ResultSet resultSet) throws SQLException { + resultSet.next(); + + final String clientHost = resultSet.getString(1); + + log.info("Current host: " + clientHost); + + assertFalse("Client Host returns a non-empty string", clientHost.isEmpty()); + + return null; + } + }); + } + + @Test public void testExecuteSqlStatementWithParams() { InsertStatement insert = insert().into(tableRef("ParamStatementsTest")).values( diff --git a/morf-integration-test/src/test/java/org/alfasoftware/morf/upgrade/TestFullDeployment.java b/morf-integration-test/src/test/java/org/alfasoftware/morf/upgrade/TestFullDeployment.java index 8706a1232..65354c8d8 100755 --- a/morf-integration-test/src/test/java/org/alfasoftware/morf/upgrade/TestFullDeployment.java +++ b/morf-integration-test/src/test/java/org/alfasoftware/morf/upgrade/TestFullDeployment.java @@ -24,11 +24,14 @@ import java.sql.Connection; import java.sql.ResultSet; +import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; +import java.util.List; import javax.sql.DataSource; +import com.google.common.collect.ImmutableList; import org.alfasoftware.morf.guicesupport.InjectMembersRule; import org.alfasoftware.morf.guicesupport.MorfModule; import org.alfasoftware.morf.jdbc.ConnectionResources; @@ -38,6 +41,8 @@ import org.alfasoftware.morf.testing.DatabaseSchemaManager; import org.alfasoftware.morf.testing.TestingDataSourceModule; import org.alfasoftware.morf.upgrade.Deployment.DeploymentFactory; +import org.alfasoftware.morf.upgrade.db.DatabaseUpgradeTableContribution; +import org.alfasoftware.morf.upgrade.upgrade.AddExtraLoggingToUpgradeAuditTable; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -126,4 +131,82 @@ protected void configure() { connection.close(); } } + + + + /** + * Tests full deployment with two simple domain classes + * @throws SQLException If database access fails. + */ + @Test + public void testTwoClassDeploymentWithUpgradeSteps() throws SQLException { + Schema targetSchema = schema( + table("UpgradeAudit").columns( + column("upgradeUUID", DataType.STRING, 100).primaryKey(), + column("description", DataType.STRING, DatabaseUpgradeTableContribution.UPGRADE_STEP_DESCRIPTION_LENGTH).nullable(), + column("appliedTime", DataType.DECIMAL, 14).nullable(), + column("status", DataType.STRING, 10).nullable(), + column("server", DataType.STRING, 100).nullable(), + column("processingTimeMs", DataType.DECIMAL, 14).nullable(), + column("startTimeMs", DataType.DECIMAL, 18).nullable() + ), + table("FirstTestBean").columns( + column("identifier", DataType.DECIMAL, 10).nullable(), + column("stringColumn", DataType.STRING, 10).nullable(), + column("doubleColumn", DataType.DECIMAL, 13, 2) + ), + table("SecondTestBean").columns( + column("identifier", DataType.DECIMAL, 10).nullable(), + column("intColumn", DataType.DECIMAL, 10).nullable() + ) + ); + + final List> upgradeStepList = ImmutableList.of( + AddExtraLoggingToUpgradeAuditTable.class + ); + + // Try accessing the new database + Connection connection = dataSource.getConnection(); + try { + // -- Set up the database... + // + DeploymentFactory deploymentFactory = Guice.createInjector(new MorfModule(), new AbstractModule() { + @Override + protected void configure() { + bind(SqlDialect.class).toInstance(connectionResources.sqlDialect()); + bind(DataSource.class).toInstance(connectionResources.getDataSource()); // TODO Need to discuss more widely about what we want to do here + bind(ConnectionResources.class).toInstance(connectionResources); + } + }).getInstance(DeploymentFactory.class); + + deploymentFactory.create(connectionResources).deploy(targetSchema); + + + Upgrade.performUpgrade(targetSchema, upgradeStepList, connectionResources, new ViewDeploymentValidator.AlwaysValidate()); + + String schemaNamePrefix = connectionResources.sqlDialect().schemaNamePrefix(); + + // A simple query + Statement statement = connection.createStatement(); + assertFalse("Empty select results", statement.executeQuery("select * from "+schemaNamePrefix+"FirstTestBean").next()); + + // An insert followed by a read + statement.execute("insert into "+schemaNamePrefix+"SecondTestBean values(0, 33)"); + ResultSet resultSet = statement.executeQuery("select * from "+schemaNamePrefix+"SecondTestBean"); + assertTrue("Second result set has a record", resultSet.next()); + assertEquals("Column value", 33, resultSet.getInt("intColumn")); + assertFalse("Second result set has exactly one record", resultSet.next()); + + ResultSet resultSetUpgradeAudit = statement.executeQuery("select * from "+schemaNamePrefix+"UpgradeAudit"); + ResultSetMetaData resultSetMetaData = resultSetUpgradeAudit.getMetaData(); + + assertEquals("Column Count", 7, resultSetMetaData.getColumnCount()); + } finally { + upgradeStatusTableService.tidyUp(connectionResources.getDataSource()); + connection.close(); + } + } + + + } diff --git a/morf-mysql/src/main/java/org/alfasoftware/morf/jdbc/mysql/MySqlDialect.java b/morf-mysql/src/main/java/org/alfasoftware/morf/jdbc/mysql/MySqlDialect.java index 1bc379de5..f7af1d3c8 100755 --- a/morf-mysql/src/main/java/org/alfasoftware/morf/jdbc/mysql/MySqlDialect.java +++ b/morf-mysql/src/main/java/org/alfasoftware/morf/jdbc/mysql/MySqlDialect.java @@ -376,7 +376,7 @@ private String checkMaxIdAutonumberStatement(Table table,Column autoIncrementCol * MySQL defaults to fetching * all records into memory when a JDBC query is executed, which causes OOM * errors when used with large data sets (Cryo and ETLs being prime offenders). Ideally - * we would use a nice big paging size here (like 200 as used in {@link OracleDialect}) + * we would use a nice big paging size here (like 200 as used in OracleDialect) * but as noted in the link above, MySQL only supports one record at a time or all at * once, with nothing in between. As a result, we default to one record for bulk loads * as the only safe choice. @@ -660,6 +660,24 @@ protected String getSqlForNow(Function function) { /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForCurrentUnixTimeMilliseconds() + */ + @Override + protected String getSqlForCurrentUnixTimeMilliseconds() { + return "CAST( 1000*UNIX_TIMESTAMP(current_timestamp(3)) AS UNSIGNED INTEGER)"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForClientHost() + */ + @Override + protected String getSqlForClientHost() { + return "SUBSTRING_INDEX(USER(), '@', -1)"; + } + + + /** * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForDaysBetween(org.alfasoftware.morf.sql.element.AliasedField, org.alfasoftware.morf.sql.element.AliasedField) */ @Override diff --git a/morf-mysql/src/test/java/org/alfasoftware/morf/jdbc/mysql/TestMySqlDialect.java b/morf-mysql/src/test/java/org/alfasoftware/morf/jdbc/mysql/TestMySqlDialect.java index f993bf6cb..a9c43ab7e 100755 --- a/morf-mysql/src/test/java/org/alfasoftware/morf/jdbc/mysql/TestMySqlDialect.java +++ b/morf-mysql/src/test/java/org/alfasoftware/morf/jdbc/mysql/TestMySqlDialect.java @@ -924,6 +924,24 @@ protected String expectedNow() { } + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedCurrentUnixTimeMilliseconds() + */ + @Override + protected String expectedCurrentUnixTimeMilliseconds() { + return "CAST( 1000*UNIX_TIMESTAMP(current_timestamp(3)) AS UNSIGNED INTEGER)"; + } + + + /** + * @see AbstractSqlDialectTest#expectedClientHost() + */ + @Override + protected String expectedClientHost() { + return "SUBSTRING_INDEX(USER(), '@', -1)"; + } + + /** * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedDropViewStatements() */ diff --git a/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java b/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java index c599e2162..b0cae4788 100755 --- a/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java +++ b/morf-oracle/src/main/java/org/alfasoftware/morf/jdbc/oracle/OracleDialect.java @@ -1419,6 +1419,24 @@ protected String getSqlForLastDayOfMonth(AliasedField date) { } + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForCurrentUnixTimeMilliseconds() + */ + @Override + protected String getSqlForCurrentUnixTimeMilliseconds() { + return "EXTRACT(DAY FROM(sys_extract_utc(systimestamp) - to_timestamp('1970-01-01', 'YYYY-MM-DD'))) * 86400000+ to_number(TO_CHAR(sys_extract_utc(systimestamp), 'SSSSSFF3'))"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForClientHost() + */ + @Override + protected String getSqlForClientHost() { + return "SYS_CONTEXT('USERENV','HOST')"; + } + + /** * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForAnalyseTable(Table) */ diff --git a/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialect.java b/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialect.java index 8916de9cb..e32197909 100755 --- a/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialect.java +++ b/morf-oracle/src/test/java/org/alfasoftware/morf/jdbc/oracle/TestOracleDialect.java @@ -1175,6 +1175,24 @@ protected String expectedNow() { } + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedCurrentUnixTimeMilliseconds() + */ + @Override + protected String expectedCurrentUnixTimeMilliseconds() { + return "EXTRACT(DAY FROM(sys_extract_utc(systimestamp) - to_timestamp('1970-01-01', 'YYYY-MM-DD'))) * 86400000+ to_number(TO_CHAR(sys_extract_utc(systimestamp), 'SSSSSFF3'))"; + } + + + /** + * @see AbstractSqlDialectTest#expectedClientHost() () + */ + @Override + protected String expectedClientHost() { + return "SYS_CONTEXT('USERENV','HOST')"; + } + + /** * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedDaysBetween() */ diff --git a/morf-postgresql/src/main/java/org/alfasoftware/morf/jdbc/postgresql/PostgreSQLDialect.java b/morf-postgresql/src/main/java/org/alfasoftware/morf/jdbc/postgresql/PostgreSQLDialect.java index d78805e75..b85341a38 100644 --- a/morf-postgresql/src/main/java/org/alfasoftware/morf/jdbc/postgresql/PostgreSQLDialect.java +++ b/morf-postgresql/src/main/java/org/alfasoftware/morf/jdbc/postgresql/PostgreSQLDialect.java @@ -552,6 +552,22 @@ protected String getSqlForRound(Function function) { return "ROUND((" + getSqlFrom(function.getArguments().get(0)) + ") :: NUMERIC, " + getSqlFrom(function.getArguments().get(1)) + ")"; } + /** + * @see SqlDialect#getSqlForCurrentUnixTimeMilliseconds() + */ + @Override + protected String getSqlForCurrentUnixTimeMilliseconds(){ + return "trunc(extract(epoch from now() at time zone 'UTC')*1000)"; + } + + /** + * @see SqlDialect#getSqlForClientHost() + */ + @Override + protected String getSqlForClientHost() { + return "inet_client_addr()"; + } + @Override protected String getSqlFrom(MergeStatement statement) { diff --git a/morf-postgresql/src/test/java/org/alfasoftware/morf/jdbc/postgresql/TestPostgreSQLDialect.java b/morf-postgresql/src/test/java/org/alfasoftware/morf/jdbc/postgresql/TestPostgreSQLDialect.java index df67ce941..6a347466b 100644 --- a/morf-postgresql/src/test/java/org/alfasoftware/morf/jdbc/postgresql/TestPostgreSQLDialect.java +++ b/morf-postgresql/src/test/java/org/alfasoftware/morf/jdbc/postgresql/TestPostgreSQLDialect.java @@ -986,6 +986,24 @@ protected String expectedNow() { } + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedCurrentUnixTimeMilliseconds() + */ + @Override + protected String expectedCurrentUnixTimeMilliseconds() { + return "trunc(extract(epoch from now() at time zone 'UTC')*1000)"; + } + + + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedClientHost() + */ + @Override + protected String expectedClientHost() { + return "inet_client_addr()"; + } + + /** * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedDropViewStatements() */ diff --git a/morf-sqlserver/src/main/java/org/alfasoftware/morf/jdbc/sqlserver/SqlServerDialect.java b/morf-sqlserver/src/main/java/org/alfasoftware/morf/jdbc/sqlserver/SqlServerDialect.java index e3c448e3d..8f6542a70 100755 --- a/morf-sqlserver/src/main/java/org/alfasoftware/morf/jdbc/sqlserver/SqlServerDialect.java +++ b/morf-sqlserver/src/main/java/org/alfasoftware/morf/jdbc/sqlserver/SqlServerDialect.java @@ -812,7 +812,25 @@ protected String getSqlForNow(Function function) { } - /** + /** + * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForCurrentUnixTimeMilliseconds() + */ + @Override + protected String getSqlForCurrentUnixTimeMilliseconds() { + return "DATEDIFF_BIG(MILLISECOND,'1970-01-01 00:00:00.000', SYSUTCDATETIME())"; + } + + + /** + * @see SqlDialect#getSqlForClientHost() () + */ + @Override + protected String getSqlForClientHost() { + return "HOST_NAME()"; + } + + + /** * @see org.alfasoftware.morf.jdbc.SqlDialect#getSqlForDaysBetween(org.alfasoftware.morf.sql.element.AliasedField, org.alfasoftware.morf.sql.element.AliasedField) */ @Override diff --git a/morf-sqlserver/src/test/java/org/alfasoftware/morf/jdbc/sqlserver/TestSqlServerDialect.java b/morf-sqlserver/src/test/java/org/alfasoftware/morf/jdbc/sqlserver/TestSqlServerDialect.java index ad4bc1268..6ff629c7a 100755 --- a/morf-sqlserver/src/test/java/org/alfasoftware/morf/jdbc/sqlserver/TestSqlServerDialect.java +++ b/morf-sqlserver/src/test/java/org/alfasoftware/morf/jdbc/sqlserver/TestSqlServerDialect.java @@ -41,8 +41,8 @@ */ public class TestSqlServerDialect extends AbstractSqlDialectTest { - @SuppressWarnings({"unchecked","rawtypes"}) - private final ArgumentCaptor> listCaptor = ArgumentCaptor.forClass((Class>)(Class)List.class); + @SuppressWarnings({"unchecked", "rawtypes"}) + private final ArgumentCaptor> listCaptor = ArgumentCaptor.forClass((Class>) (Class) List.class); /** * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#createTestDialect() @@ -62,15 +62,15 @@ protected SqlDialect createTestDialect() { @Override protected List expectedCreateTableStatements() { return Arrays - .asList( - "CREATE TABLE TESTSCHEMA.Test ([id] BIGINT NOT NULL, [version] INTEGER CONSTRAINT Test_version_DF DEFAULT 0, [stringField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS, [intField] INTEGER, [floatField] NUMERIC(13,2) NOT NULL, [dateField] DATE, [booleanField] BIT, [charField] NVARCHAR(1) COLLATE SQL_Latin1_General_CP1_CS_AS, [blobField] IMAGE, [bigIntegerField] BIGINT CONSTRAINT Test_bigIntegerField_DF DEFAULT 12345, [clobField] NVARCHAR(MAX) COLLATE SQL_Latin1_General_CP1_CS_AS, CONSTRAINT [Test_PK] PRIMARY KEY ([id]))", - "CREATE UNIQUE NONCLUSTERED INDEX Test_NK ON TESTSCHEMA.Test ([stringField])", - "CREATE UNIQUE NONCLUSTERED INDEX Test_1 ON TESTSCHEMA.Test ([intField], [floatField])", - "CREATE TABLE TESTSCHEMA.Alternate ([id] BIGINT NOT NULL, [version] INTEGER CONSTRAINT Alternate_version_DF DEFAULT 0, [stringField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS, CONSTRAINT [Alternate_PK] PRIMARY KEY ([id]))", - "CREATE INDEX Alternate_1 ON TESTSCHEMA.Alternate ([stringField])", - "CREATE TABLE TESTSCHEMA.NonNull ([id] BIGINT NOT NULL, [version] INTEGER CONSTRAINT NonNull_version_DF DEFAULT 0, [stringField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, [intField] NUMERIC(8,0) NOT NULL, [booleanField] BIT NOT NULL, [dateField] DATE NOT NULL, [blobField] IMAGE NOT NULL, CONSTRAINT [NonNull_PK] PRIMARY KEY ([id]))", - "CREATE TABLE TESTSCHEMA.CompositePrimaryKey ([id] BIGINT NOT NULL, [version] INTEGER CONSTRAINT CompositePrimaryKey_version_DF DEFAULT 0, [stringField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, [secondPrimaryKey] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, CONSTRAINT [CompositePrimaryKey_PK] PRIMARY KEY ([id], [secondPrimaryKey]))", - "CREATE TABLE TESTSCHEMA.AutoNumber ([intField] BIGINT NOT NULL IDENTITY(5, 1), CONSTRAINT [AutoNumber_PK] PRIMARY KEY ([intField]))" + .asList( + "CREATE TABLE TESTSCHEMA.Test ([id] BIGINT NOT NULL, [version] INTEGER CONSTRAINT Test_version_DF DEFAULT 0, [stringField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS, [intField] INTEGER, [floatField] NUMERIC(13,2) NOT NULL, [dateField] DATE, [booleanField] BIT, [charField] NVARCHAR(1) COLLATE SQL_Latin1_General_CP1_CS_AS, [blobField] IMAGE, [bigIntegerField] BIGINT CONSTRAINT Test_bigIntegerField_DF DEFAULT 12345, [clobField] NVARCHAR(MAX) COLLATE SQL_Latin1_General_CP1_CS_AS, CONSTRAINT [Test_PK] PRIMARY KEY ([id]))", + "CREATE UNIQUE NONCLUSTERED INDEX Test_NK ON TESTSCHEMA.Test ([stringField])", + "CREATE UNIQUE NONCLUSTERED INDEX Test_1 ON TESTSCHEMA.Test ([intField], [floatField])", + "CREATE TABLE TESTSCHEMA.Alternate ([id] BIGINT NOT NULL, [version] INTEGER CONSTRAINT Alternate_version_DF DEFAULT 0, [stringField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS, CONSTRAINT [Alternate_PK] PRIMARY KEY ([id]))", + "CREATE INDEX Alternate_1 ON TESTSCHEMA.Alternate ([stringField])", + "CREATE TABLE TESTSCHEMA.NonNull ([id] BIGINT NOT NULL, [version] INTEGER CONSTRAINT NonNull_version_DF DEFAULT 0, [stringField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, [intField] NUMERIC(8,0) NOT NULL, [booleanField] BIT NOT NULL, [dateField] DATE NOT NULL, [blobField] IMAGE NOT NULL, CONSTRAINT [NonNull_PK] PRIMARY KEY ([id]))", + "CREATE TABLE TESTSCHEMA.CompositePrimaryKey ([id] BIGINT NOT NULL, [version] INTEGER CONSTRAINT CompositePrimaryKey_version_DF DEFAULT 0, [stringField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, [secondPrimaryKey] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, CONSTRAINT [CompositePrimaryKey_PK] PRIMARY KEY ([id], [secondPrimaryKey]))", + "CREATE TABLE TESTSCHEMA.AutoNumber ([intField] BIGINT NOT NULL IDENTITY(5, 1), CONSTRAINT [AutoNumber_PK] PRIMARY KEY ([intField]))" ); } @@ -84,13 +84,13 @@ protected List expectedCreateTableStatements() { @Override protected List expectedCreateTemporaryTableStatements() { return Arrays - .asList( - "CREATE TABLE TESTSCHEMA.#TempTest ([id] BIGINT NOT NULL, [version] INTEGER CONSTRAINT #TempTest_version_DF DEFAULT 0, [stringField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS, [intField] INTEGER, [floatField] NUMERIC(13,2) NOT NULL, [dateField] DATE, [booleanField] BIT, [charField] NVARCHAR(1) COLLATE SQL_Latin1_General_CP1_CS_AS, [blobField] IMAGE, [bigIntegerField] BIGINT CONSTRAINT #TempTest_bigIntegerField_DF DEFAULT 12345, [clobField] NVARCHAR(MAX) COLLATE SQL_Latin1_General_CP1_CS_AS, CONSTRAINT [TempTest_PK] PRIMARY KEY ([id]))", - "CREATE UNIQUE NONCLUSTERED INDEX TempTest_NK ON TESTSCHEMA.#TempTest ([stringField])", - "CREATE INDEX TempTest_1 ON TESTSCHEMA.#TempTest ([intField], [floatField])", - "CREATE TABLE TESTSCHEMA.#TempAlternate ([id] BIGINT NOT NULL, [version] INTEGER CONSTRAINT #TempAlternate_version_DF DEFAULT 0, [stringField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS, CONSTRAINT [TempAlternate_PK] PRIMARY KEY ([id]))", - "CREATE INDEX TempAlternate_1 ON TESTSCHEMA.#TempAlternate ([stringField])", - "CREATE TABLE TESTSCHEMA.#TempNonNull ([id] BIGINT NOT NULL, [version] INTEGER CONSTRAINT #TempNonNull_version_DF DEFAULT 0, [stringField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, [intField] NUMERIC(8,0) NOT NULL, [booleanField] BIT NOT NULL, [dateField] DATE NOT NULL, [blobField] IMAGE NOT NULL, CONSTRAINT [TempNonNull_PK] PRIMARY KEY ([id]))"); + .asList( + "CREATE TABLE TESTSCHEMA.#TempTest ([id] BIGINT NOT NULL, [version] INTEGER CONSTRAINT #TempTest_version_DF DEFAULT 0, [stringField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS, [intField] INTEGER, [floatField] NUMERIC(13,2) NOT NULL, [dateField] DATE, [booleanField] BIT, [charField] NVARCHAR(1) COLLATE SQL_Latin1_General_CP1_CS_AS, [blobField] IMAGE, [bigIntegerField] BIGINT CONSTRAINT #TempTest_bigIntegerField_DF DEFAULT 12345, [clobField] NVARCHAR(MAX) COLLATE SQL_Latin1_General_CP1_CS_AS, CONSTRAINT [TempTest_PK] PRIMARY KEY ([id]))", + "CREATE UNIQUE NONCLUSTERED INDEX TempTest_NK ON TESTSCHEMA.#TempTest ([stringField])", + "CREATE INDEX TempTest_1 ON TESTSCHEMA.#TempTest ([intField], [floatField])", + "CREATE TABLE TESTSCHEMA.#TempAlternate ([id] BIGINT NOT NULL, [version] INTEGER CONSTRAINT #TempAlternate_version_DF DEFAULT 0, [stringField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS, CONSTRAINT [TempAlternate_PK] PRIMARY KEY ([id]))", + "CREATE INDEX TempAlternate_1 ON TESTSCHEMA.#TempAlternate ([stringField])", + "CREATE TABLE TESTSCHEMA.#TempNonNull ([id] BIGINT NOT NULL, [version] INTEGER CONSTRAINT #TempNonNull_version_DF DEFAULT 0, [stringField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, [intField] NUMERIC(8,0) NOT NULL, [booleanField] BIT NOT NULL, [dateField] DATE NOT NULL, [blobField] IMAGE NOT NULL, CONSTRAINT [TempNonNull_PK] PRIMARY KEY ([id]))"); } @@ -100,9 +100,9 @@ protected List expectedCreateTemporaryTableStatements() { @Override protected List expectedCreateTableStatementsWithLongTableName() { return Arrays.asList( - "CREATE TABLE TESTSCHEMA.tableWithANameThatExceedsTwentySevenCharactersToMakeSureSchemaNameDoesNotGetFactoredIntoOracleNameTruncation ([id] BIGINT NOT NULL, [version] INTEGER CONSTRAINT tableWithANameThatExceedsTwentySevenCharactersToMakeSureSchemaNameDoesNotGetFactoredIntoOracleNameTruncation_version_DF DEFAULT 0, [stringField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS, [intField] NUMERIC(8,0), [floatField] NUMERIC(13,2) NOT NULL, [dateField] DATE, [booleanField] BIT, [charField] NVARCHAR(1) COLLATE SQL_Latin1_General_CP1_CS_AS, CONSTRAINT [tableWithANameThatExceedsTwentySevenCharactersToMakeSureSchemaNameDoesNotGetFactoredIntoOracleNameTruncation_PK] PRIMARY KEY ([id]))", - "CREATE UNIQUE NONCLUSTERED INDEX Test_NK ON TESTSCHEMA.tableWithANameThatExceedsTwentySevenCharactersToMakeSureSchemaNameDoesNotGetFactoredIntoOracleNameTruncation ([stringField])", - "CREATE INDEX Test_1 ON TESTSCHEMA.tableWithANameThatExceedsTwentySevenCharactersToMakeSureSchemaNameDoesNotGetFactoredIntoOracleNameTruncation ([intField], [floatField])" + "CREATE TABLE TESTSCHEMA.tableWithANameThatExceedsTwentySevenCharactersToMakeSureSchemaNameDoesNotGetFactoredIntoOracleNameTruncation ([id] BIGINT NOT NULL, [version] INTEGER CONSTRAINT tableWithANameThatExceedsTwentySevenCharactersToMakeSureSchemaNameDoesNotGetFactoredIntoOracleNameTruncation_version_DF DEFAULT 0, [stringField] NVARCHAR(3) COLLATE SQL_Latin1_General_CP1_CS_AS, [intField] NUMERIC(8,0), [floatField] NUMERIC(13,2) NOT NULL, [dateField] DATE, [booleanField] BIT, [charField] NVARCHAR(1) COLLATE SQL_Latin1_General_CP1_CS_AS, CONSTRAINT [tableWithANameThatExceedsTwentySevenCharactersToMakeSureSchemaNameDoesNotGetFactoredIntoOracleNameTruncation_PK] PRIMARY KEY ([id]))", + "CREATE UNIQUE NONCLUSTERED INDEX Test_NK ON TESTSCHEMA.tableWithANameThatExceedsTwentySevenCharactersToMakeSureSchemaNameDoesNotGetFactoredIntoOracleNameTruncation ([stringField])", + "CREATE INDEX Test_1 ON TESTSCHEMA.tableWithANameThatExceedsTwentySevenCharactersToMakeSureSchemaNameDoesNotGetFactoredIntoOracleNameTruncation ([intField], [floatField])" ); } @@ -203,10 +203,10 @@ protected String expectedParameterisedInsertStatementWithTableInDifferentSchema( @Override protected List expectedAutoGenerateIdStatement() { return Arrays.asList( - "DELETE FROM TESTSCHEMA.idvalues where name = 'Test'", - "INSERT INTO TESTSCHEMA.idvalues (name, "+ID_INCREMENTOR_TABLE_COLUMN_VALUE+") VALUES('Test', (SELECT COALESCE(MAX(id) + 1, 1) AS CurrentValue FROM TESTSCHEMA.Test))", - "INSERT INTO TESTSCHEMA.Test (version, stringField, id) SELECT version, stringField, (SELECT COALESCE("+ID_INCREMENTOR_TABLE_COLUMN_VALUE+", 0) FROM TESTSCHEMA.idvalues WHERE (name = 'Test')) + Other.id FROM TESTSCHEMA.Other" - ); + "DELETE FROM TESTSCHEMA.idvalues where name = 'Test'", + "INSERT INTO TESTSCHEMA.idvalues (name, " + ID_INCREMENTOR_TABLE_COLUMN_VALUE + ") VALUES('Test', (SELECT COALESCE(MAX(id) + 1, 1) AS CurrentValue FROM TESTSCHEMA.Test))", + "INSERT INTO TESTSCHEMA.Test (version, stringField, id) SELECT version, stringField, (SELECT COALESCE(" + ID_INCREMENTOR_TABLE_COLUMN_VALUE + ", 0) FROM TESTSCHEMA.idvalues WHERE (name = 'Test')) + Other.id FROM TESTSCHEMA.Other" + ); } @@ -216,46 +216,43 @@ protected List expectedAutoGenerateIdStatement() { @Override protected List expectedInsertWithIdAndVersion() { return Arrays.asList( - "DELETE FROM TESTSCHEMA.idvalues where name = 'Test'", - "INSERT INTO TESTSCHEMA.idvalues (name, "+ID_INCREMENTOR_TABLE_COLUMN_VALUE+") VALUES('Test', (SELECT COALESCE(MAX(id) + 1, 1) AS CurrentValue FROM TESTSCHEMA.Test))", - "INSERT INTO TESTSCHEMA.Test (stringField, id, version) SELECT stringField, (SELECT COALESCE("+ID_INCREMENTOR_TABLE_COLUMN_VALUE+", 0) FROM TESTSCHEMA.idvalues WHERE (name = 'Test')) + Other.id, 0 AS version FROM TESTSCHEMA.Other" - ); + "DELETE FROM TESTSCHEMA.idvalues where name = 'Test'", + "INSERT INTO TESTSCHEMA.idvalues (name, " + ID_INCREMENTOR_TABLE_COLUMN_VALUE + ") VALUES('Test', (SELECT COALESCE(MAX(id) + 1, 1) AS CurrentValue FROM TESTSCHEMA.Test))", + "INSERT INTO TESTSCHEMA.Test (stringField, id, version) SELECT stringField, (SELECT COALESCE(" + ID_INCREMENTOR_TABLE_COLUMN_VALUE + ", 0) FROM TESTSCHEMA.idvalues WHERE (name = 'Test')) + Other.id, 0 AS version FROM TESTSCHEMA.Other" + ); } private List expectedPreInsertStatements() { return ImmutableList.of( - "SET IDENTITY_INSERT TESTSCHEMA.AutoNumber ON" - ); + "SET IDENTITY_INSERT TESTSCHEMA.AutoNumber ON" + ); } - - private void verifyPostInsertStatements(List executedStatements) { - assertThat(executedStatements,contains( - "SET IDENTITY_INSERT TESTSCHEMA.AutoNumber OFF", - - "IF EXISTS (SELECT 1 FROM TESTSCHEMA.AutoNumber)\n" + - "BEGIN\n" + - " DBCC CHECKIDENT (\"TESTSCHEMA.AutoNumber\", RESEED, 4)\n" + - " DBCC CHECKIDENT (\"TESTSCHEMA.AutoNumber\", RESEED)\n" + - "END\n" + - "ELSE\n" + - "BEGIN\n" + - " DBCC CHECKIDENT (\"TESTSCHEMA.AutoNumber\", RESEED, 5)\n" + - "END" + assertThat(executedStatements, contains( + "SET IDENTITY_INSERT TESTSCHEMA.AutoNumber OFF", + + "IF EXISTS (SELECT 1 FROM TESTSCHEMA.AutoNumber)\n" + + "BEGIN\n" + + " DBCC CHECKIDENT (\"TESTSCHEMA.AutoNumber\", RESEED, 4)\n" + + " DBCC CHECKIDENT (\"TESTSCHEMA.AutoNumber\", RESEED)\n" + + "END\n" + + "ELSE\n" + + "BEGIN\n" + + " DBCC CHECKIDENT (\"TESTSCHEMA.AutoNumber\", RESEED, 5)\n" + + "END" )); } - /** * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#verifyPostInsertStatementsInsertingUnderAutonumLimit(org.alfasoftware.morf.jdbc.SqlScriptExecutor, java.sql.Connection) */ @Override protected void verifyPostInsertStatementsInsertingUnderAutonumLimit(SqlScriptExecutor sqlScriptExecutor, Connection connection) { - verify(sqlScriptExecutor).execute(listCaptor.capture(),eq(connection)); + verify(sqlScriptExecutor).execute(listCaptor.capture(), eq(connection)); verifyPostInsertStatements(listCaptor.getValue()); verifyNoMoreInteractions(sqlScriptExecutor); } @@ -266,7 +263,7 @@ protected void verifyPostInsertStatementsInsertingUnderAutonumLimit(SqlScriptExe */ @Override protected void verifyPostInsertStatementsNotInsertingUnderAutonumLimit(SqlScriptExecutor sqlScriptExecutor, Connection connection) { - verify(sqlScriptExecutor).execute(listCaptor.capture(),eq(connection)); + verify(sqlScriptExecutor).execute(listCaptor.capture(), eq(connection)); verifyPostInsertStatements(listCaptor.getValue()); verifyNoMoreInteractions(sqlScriptExecutor); } @@ -305,10 +302,10 @@ protected String tableName(String baseName) { @Override protected List expectedSpecifiedValueInsert() { return Arrays.asList( - "DELETE FROM TESTSCHEMA.idvalues where name = 'Test'", - "INSERT INTO TESTSCHEMA.idvalues (name, "+ID_INCREMENTOR_TABLE_COLUMN_VALUE+") VALUES('Test', (SELECT COALESCE(MAX(id) + 1, 1) AS CurrentValue FROM TESTSCHEMA.Test))", - "INSERT INTO TESTSCHEMA.Test (stringField, intField, floatField, dateField, booleanField, charField, id, version, blobField, bigIntegerField, clobField) VALUES ('Escap''d', 7, 11.25, 20100405, 1, 'X', (SELECT COALESCE("+ID_INCREMENTOR_TABLE_COLUMN_VALUE+", 1) FROM TESTSCHEMA.idvalues WHERE (name = 'Test')), 0, null, 12345, null)" - ); + "DELETE FROM TESTSCHEMA.idvalues where name = 'Test'", + "INSERT INTO TESTSCHEMA.idvalues (name, " + ID_INCREMENTOR_TABLE_COLUMN_VALUE + ") VALUES('Test', (SELECT COALESCE(MAX(id) + 1, 1) AS CurrentValue FROM TESTSCHEMA.Test))", + "INSERT INTO TESTSCHEMA.Test (stringField, intField, floatField, dateField, booleanField, charField, id, version, blobField, bigIntegerField, clobField) VALUES ('Escap''d', 7, 11.25, 20100405, 1, 'X', (SELECT COALESCE(" + ID_INCREMENTOR_TABLE_COLUMN_VALUE + ", 1) FROM TESTSCHEMA.idvalues WHERE (name = 'Test')), 0, null, 12345, null)" + ); } @@ -318,9 +315,9 @@ protected List expectedSpecifiedValueInsert() { @Override protected List expectedSpecifiedValueInsertWithTableInDifferentSchema() { return Arrays.asList( - "DELETE FROM TESTSCHEMA.idvalues where name = 'Test'", - "INSERT INTO TESTSCHEMA.idvalues (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 ('Escap''d', 7, 11.25, 20100405, 1, 'X', (SELECT COALESCE("+ID_INCREMENTOR_TABLE_COLUMN_VALUE+", 1) FROM TESTSCHEMA.idvalues WHERE (name = 'Test')), 0, null, 12345, null)" + "DELETE FROM TESTSCHEMA.idvalues where name = 'Test'", + "INSERT INTO TESTSCHEMA.idvalues (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 ('Escap''d', 7, 11.25, 20100405, 1, 'X', (SELECT COALESCE(" + ID_INCREMENTOR_TABLE_COLUMN_VALUE + ", 1) FROM TESTSCHEMA.idvalues WHERE (name = 'Test')), 0, null, 12345, null)" ); } @@ -339,7 +336,7 @@ protected String expectedParameterisedInsertStatementWithNoColumnValues() { */ @Override protected String expectedEmptyStringInsertStatement() { - return "INSERT INTO TESTSCHEMA.Test (stringField, id, version, intField, floatField, dateField, booleanField, charField, blobField, bigIntegerField, clobField) VALUES (NULL, (SELECT COALESCE("+ID_INCREMENTOR_TABLE_COLUMN_VALUE+", 1) FROM TESTSCHEMA.idvalues WHERE (name = 'Test')), 0, 0, 0, null, 0, NULL, null, 12345, null)"; + return "INSERT INTO TESTSCHEMA.Test (stringField, id, version, intField, floatField, dateField, booleanField, charField, blobField, bigIntegerField, clobField) VALUES (NULL, (SELECT COALESCE(" + ID_INCREMENTOR_TABLE_COLUMN_VALUE + ", 1) FROM TESTSCHEMA.idvalues WHERE (name = 'Test')), 0, 0, 0, null, 0, NULL, null, 12345, null)"; } @@ -540,8 +537,8 @@ protected List expectedAlterTableAddStringColumnStatement() { @Override protected List expectedAlterTableAlterStringColumnStatement() { return Arrays.asList("DROP INDEX Test_NK ON TESTSCHEMA.Test", - "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN stringField NVARCHAR(6) COLLATE SQL_Latin1_General_CP1_CS_AS", - "CREATE UNIQUE NONCLUSTERED INDEX Test_NK ON TESTSCHEMA.Test ([stringField])"); + "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN stringField NVARCHAR(6) COLLATE SQL_Latin1_General_CP1_CS_AS", + "CREATE UNIQUE NONCLUSTERED INDEX Test_NK ON TESTSCHEMA.Test ([stringField])"); } @@ -560,8 +557,8 @@ protected List expectedAlterTableAddDecimalColumnStatement() { @Override protected List expectedAlterTableAlterDecimalColumnStatement() { return Arrays.asList("DROP INDEX Test_1 ON TESTSCHEMA.Test", - "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN floatField NUMERIC(14,3)", - "CREATE UNIQUE NONCLUSTERED INDEX Test_1 ON TESTSCHEMA.Test ([intField], [floatField])"); + "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN floatField NUMERIC(14,3)", + "CREATE UNIQUE NONCLUSTERED INDEX Test_1 ON TESTSCHEMA.Test ([intField], [floatField])"); } @@ -580,8 +577,8 @@ protected List expectedAlterTableAddBigIntegerColumnStatement() { @Override protected List expectedAlterTableAlterBigIntegerColumnStatement() { return Arrays.asList( - SqlServerDialect.dropDefaultForColumnSql.replace("{table}", "Test").replace("{column}", "bigIntegerField"), - "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN bigIntegerField BIGINT"); + SqlServerDialect.dropDefaultForColumnSql.replace("{table}", "Test").replace("{column}", "bigIntegerField"), + "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN bigIntegerField BIGINT"); } @@ -600,7 +597,7 @@ protected List expectedAlterTableAddBlobColumnStatement() { @Override protected List expectedAlterTableAlterBlobColumnStatement() { return Arrays.asList( - "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN blobField IMAGE NOT NULL"); + "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN blobField IMAGE NOT NULL"); } @@ -619,9 +616,9 @@ protected List expectedAlterTableAddColumnWithDefaultStatement() { @Override protected List expectedAlterTableAlterColumnWithDefaultStatement() { return Arrays.asList( - SqlServerDialect.dropDefaultForColumnSql.replace("{table}", "Test").replace("{column}", "bigIntegerField"), - "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN bigIntegerField BIGINT CONSTRAINT Test_bigIntegerField_DF DEFAULT 54321 WITH VALUES" - ); + SqlServerDialect.dropDefaultForColumnSql.replace("{table}", "Test").replace("{column}", "bigIntegerField"), + "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN bigIntegerField BIGINT CONSTRAINT Test_bigIntegerField_DF DEFAULT 54321 WITH VALUES" + ); } @@ -633,7 +630,7 @@ protected List expectedAlterTableAlterColumnWithDefaultStatement() { @Override protected List expectedAlterTableAlterBooleanColumnStatement() { return Arrays.asList( - "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN booleanField BIT NOT NULL"); + "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN booleanField BIT NOT NULL"); } @@ -661,9 +658,9 @@ protected List expectedAlterTableAddIntegerColumnStatement() { @Override protected List expectedAlterTableAlterIntegerColumnStatement() { return Arrays.asList( - "DROP INDEX Test_1 ON TESTSCHEMA.Test", - "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN intField INTEGER NOT NULL", - "CREATE UNIQUE NONCLUSTERED INDEX Test_1 ON TESTSCHEMA.Test ([intField], [floatField])" + "DROP INDEX Test_1 ON TESTSCHEMA.Test", + "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN intField INTEGER NOT NULL", + "CREATE UNIQUE NONCLUSTERED INDEX Test_1 ON TESTSCHEMA.Test ([intField], [floatField])" ); } @@ -683,7 +680,7 @@ protected List expectedAlterTableAddDateColumnStatement() { @Override protected List expectedAlterTableAlterDateColumnStatement() { return Arrays.asList( - "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN dateField DATE NOT NULL"); + "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN dateField DATE NOT NULL"); } @@ -702,7 +699,7 @@ protected List expectedAlterTableAddColumnNotNullableStatement() { @Override protected List expectedAlterTableAlterColumnFromNullableToNotNullableStatement() { return Arrays.asList( - "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN dateField DATE NOT NULL"); + "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN dateField DATE NOT NULL"); } @@ -712,8 +709,8 @@ protected List expectedAlterTableAlterColumnFromNullableToNotNullableSta @Override protected List expectedAlterTableAlterColumnFromNotNullableToNotNullableStatement() { return Arrays.asList("DROP INDEX Test_1 ON TESTSCHEMA.Test", - "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN floatField NUMERIC(20,3) NOT NULL", - "CREATE UNIQUE NONCLUSTERED INDEX Test_1 ON TESTSCHEMA.Test ([intField], [floatField])"); + "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN floatField NUMERIC(20,3) NOT NULL", + "CREATE UNIQUE NONCLUSTERED INDEX Test_1 ON TESTSCHEMA.Test ([intField], [floatField])"); } @@ -723,8 +720,8 @@ protected List expectedAlterTableAlterColumnFromNotNullableToNotNullable @Override protected List expectedAlterTableAlterColumnFromNotNullableToNullableStatement() { return Arrays.asList("DROP INDEX Test_1 ON TESTSCHEMA.Test", - "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN floatField NUMERIC(20,3)", - "CREATE UNIQUE NONCLUSTERED INDEX Test_1 ON TESTSCHEMA.Test ([intField], [floatField])"); + "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN floatField NUMERIC(20,3)", + "CREATE UNIQUE NONCLUSTERED INDEX Test_1 ON TESTSCHEMA.Test ([intField], [floatField])"); } @@ -779,9 +776,9 @@ protected List expectedIndexDropStatements() { @Override protected List expectedAlterColumnMakePrimaryStatements() { return Arrays.asList( - "ALTER TABLE TESTSCHEMA.Test DROP CONSTRAINT [Test_PK]", - "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN dateField DATE", - "ALTER TABLE TESTSCHEMA.Test ADD CONSTRAINT [Test_PK] PRIMARY KEY ([id], [dateField])" + "ALTER TABLE TESTSCHEMA.Test DROP CONSTRAINT [Test_PK]", + "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN dateField DATE", + "ALTER TABLE TESTSCHEMA.Test ADD CONSTRAINT [Test_PK] PRIMARY KEY ([id], [dateField])" ); } @@ -792,9 +789,9 @@ protected List expectedAlterColumnMakePrimaryStatements() { @Override protected List expectedAlterPrimaryKeyColumnCompositeKeyStatements() { return Arrays.asList( - "ALTER TABLE TESTSCHEMA.CompositePrimaryKey DROP CONSTRAINT [CompositePrimaryKey_PK]", - "ALTER TABLE TESTSCHEMA.CompositePrimaryKey ALTER COLUMN secondPrimaryKey NVARCHAR(5) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL", - "ALTER TABLE TESTSCHEMA.CompositePrimaryKey ADD CONSTRAINT [CompositePrimaryKey_PK] PRIMARY KEY ([id], [secondPrimaryKey])" + "ALTER TABLE TESTSCHEMA.CompositePrimaryKey DROP CONSTRAINT [CompositePrimaryKey_PK]", + "ALTER TABLE TESTSCHEMA.CompositePrimaryKey ALTER COLUMN secondPrimaryKey NVARCHAR(5) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL", + "ALTER TABLE TESTSCHEMA.CompositePrimaryKey ADD CONSTRAINT [CompositePrimaryKey_PK] PRIMARY KEY ([id], [secondPrimaryKey])" ); } @@ -805,9 +802,9 @@ protected List expectedAlterPrimaryKeyColumnCompositeKeyStatements() { @Override protected List expectedAlterRemoveColumnFromCompositeKeyStatements() { return Arrays.asList( - "ALTER TABLE TESTSCHEMA.CompositePrimaryKey DROP CONSTRAINT [CompositePrimaryKey_PK]", - "ALTER TABLE TESTSCHEMA.CompositePrimaryKey ALTER COLUMN secondPrimaryKey NVARCHAR(5) COLLATE SQL_Latin1_General_CP1_CS_AS", - "ALTER TABLE TESTSCHEMA.CompositePrimaryKey ADD CONSTRAINT [CompositePrimaryKey_PK] PRIMARY KEY ([id])"); + "ALTER TABLE TESTSCHEMA.CompositePrimaryKey DROP CONSTRAINT [CompositePrimaryKey_PK]", + "ALTER TABLE TESTSCHEMA.CompositePrimaryKey ALTER COLUMN secondPrimaryKey NVARCHAR(5) COLLATE SQL_Latin1_General_CP1_CS_AS", + "ALTER TABLE TESTSCHEMA.CompositePrimaryKey ADD CONSTRAINT [CompositePrimaryKey_PK] PRIMARY KEY ([id])"); } @@ -817,10 +814,10 @@ protected List expectedAlterRemoveColumnFromCompositeKeyStatements() { @Override protected List expectedAlterPrimaryKeyColumnStatements() { return Arrays.asList( - "EXEC sp_rename 'TESTSCHEMA.Test.id', 'renamedId', 'COLUMN'", - "ALTER TABLE TESTSCHEMA.Test DROP CONSTRAINT [Test_PK]", - "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN renamedId BIGINT NOT NULL", - "ALTER TABLE TESTSCHEMA.Test ADD CONSTRAINT [Test_PK] PRIMARY KEY ([renamedId])"); + "EXEC sp_rename 'TESTSCHEMA.Test.id', 'renamedId', 'COLUMN'", + "ALTER TABLE TESTSCHEMA.Test DROP CONSTRAINT [Test_PK]", + "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN renamedId BIGINT NOT NULL", + "ALTER TABLE TESTSCHEMA.Test ADD CONSTRAINT [Test_PK] PRIMARY KEY ([renamedId])"); } @@ -830,8 +827,8 @@ protected List expectedAlterPrimaryKeyColumnStatements() { @Override protected List expectedAlterColumnRenamingAndChangingNullability() { return Arrays.asList( - "EXEC sp_rename 'TESTSCHEMA.Other.floatField', 'blahField', 'COLUMN'", - "ALTER TABLE TESTSCHEMA.Other ALTER COLUMN blahField NUMERIC(20,3)"); + "EXEC sp_rename 'TESTSCHEMA.Other.floatField', 'blahField', 'COLUMN'", + "ALTER TABLE TESTSCHEMA.Other ALTER COLUMN blahField NUMERIC(20,3)"); } @@ -841,7 +838,7 @@ protected List expectedAlterColumnRenamingAndChangingNullability() { @Override protected List expectedAlterColumnChangingLengthAndCase() { return Arrays.asList("EXEC sp_rename 'TESTSCHEMA.Other.floatField', 'FloatField', 'COLUMN'", - "ALTER TABLE TESTSCHEMA.Other ALTER COLUMN FloatField NUMERIC(20,3) NOT NULL"); + "ALTER TABLE TESTSCHEMA.Other ALTER COLUMN FloatField NUMERIC(20,3) NOT NULL"); } @@ -869,8 +866,8 @@ protected List expectedAlterTableAddStringColumnWithDefaultStatement() { @Override protected List expectedAlterTableDropColumnWithDefaultStatement() { return ImmutableList.of( - SqlServerDialect.dropDefaultForColumnSql.replace("{table}", "Test").replace("{column}", "bigIntegerField"), - "ALTER TABLE TESTSCHEMA.Test DROP COLUMN bigIntegerField" + SqlServerDialect.dropDefaultForColumnSql.replace("{table}", "Test").replace("{column}", "bigIntegerField"), + "ALTER TABLE TESTSCHEMA.Test DROP COLUMN bigIntegerField" ); } @@ -881,18 +878,19 @@ protected List expectedAlterTableDropColumnWithDefaultStatement() { @Override protected List expectedChangeIndexFollowedByChangeOfAssociatedColumnStatement() { return Arrays.asList( - // dropIndexStatements & addIndexStatements - "DROP INDEX Test_1 ON TESTSCHEMA.Test", - "CREATE INDEX Test_1 ON TESTSCHEMA.Test ([intField])", - // changeColumnStatements - "DROP INDEX Test_1 ON TESTSCHEMA.Test", - "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN intField INTEGER NOT NULL", - "CREATE INDEX Test_1 ON TESTSCHEMA.Test ([INTFIELD])"); + // dropIndexStatements & addIndexStatements + "DROP INDEX Test_1 ON TESTSCHEMA.Test", + "CREATE INDEX Test_1 ON TESTSCHEMA.Test ([intField])", + // changeColumnStatements + "DROP INDEX Test_1 ON TESTSCHEMA.Test", + "ALTER TABLE TESTSCHEMA.Test ALTER COLUMN intField INTEGER NOT NULL", + "CREATE INDEX Test_1 ON TESTSCHEMA.Test ([INTFIELD])"); } /** * {@inheritDoc} + * * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedAutonumberUpdate() */ @Override @@ -903,6 +901,7 @@ protected List expectedAutonumberUpdate() { /** * {@inheritDoc} + * * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedUpdateWithSelectMinimum() */ @Override @@ -915,6 +914,7 @@ protected String expectedUpdateWithSelectMinimum() { /** * {@inheritDoc} + * * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedUpdateUsingAliasedDestinationTable() */ @Override @@ -938,7 +938,7 @@ protected String expectedUpdateUsingTargetTableInDifferentSchema() { @Override protected String expectedUpdateUsingSourceTableInDifferentSchema() { return "UPDATE " + tableName("FloatingRateRate") + " SET settlementFrequency = (SELECT settlementFrequency FROM " + - "MYSCHEMA.FloatingRateDetail B WHERE (A.floatingRateDetailId = B.id)) FROM " + tableName("FloatingRateRate") + " A"; + "MYSCHEMA.FloatingRateDetail B WHERE (A.floatingRateDetailId = B.id)) FROM " + tableName("FloatingRateRate") + " A"; } @@ -978,6 +978,24 @@ protected String expectedNow() { } + /** + * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedCurrentUnixTimeMilliseconds() + */ + @Override + protected String expectedCurrentUnixTimeMilliseconds(){ + return "DATEDIFF_BIG(MILLISECOND,'1970-01-01 00:00:00.000', SYSUTCDATETIME())"; + } + + + /** + * @see AbstractSqlDialectTest#expectedClientHost() () + */ + @Override + protected String expectedClientHost(){ + return "HOST_NAME()"; + } + + /** * @see org.alfasoftware.morf.jdbc.AbstractSqlDialectTest#expectedDaysBetween() */ diff --git a/morf-testsupport/src/main/java/org/alfasoftware/morf/jdbc/AbstractSqlDialectTest.java b/morf-testsupport/src/main/java/org/alfasoftware/morf/jdbc/AbstractSqlDialectTest.java index d8475d7c9..5264cd185 100755 --- a/morf-testsupport/src/main/java/org/alfasoftware/morf/jdbc/AbstractSqlDialectTest.java +++ b/morf-testsupport/src/main/java/org/alfasoftware/morf/jdbc/AbstractSqlDialectTest.java @@ -58,6 +58,7 @@ import static org.alfasoftware.morf.sql.element.Function.addMonths; import static org.alfasoftware.morf.sql.element.Function.average; import static org.alfasoftware.morf.sql.element.Function.averageDistinct; +import static org.alfasoftware.morf.sql.element.Function.clientHost; import static org.alfasoftware.morf.sql.element.Function.coalesce; import static org.alfasoftware.morf.sql.element.Function.count; import static org.alfasoftware.morf.sql.element.Function.countDistinct; @@ -87,6 +88,7 @@ import static org.alfasoftware.morf.sql.element.Function.sum; import static org.alfasoftware.morf.sql.element.Function.sumDistinct; import static org.alfasoftware.morf.sql.element.Function.trim; +import static org.alfasoftware.morf.sql.element.Function.currentUnixTimeMilliseconds; import static org.alfasoftware.morf.sql.element.Function.upperCase; import static org.alfasoftware.morf.sql.element.Function.yyyymmddToDate; import static org.junit.Assert.assertArrayEquals; @@ -2576,6 +2578,25 @@ public void testNow() { } + /** + * Test that current unix time in milliseconds functionality behaves as expected. + */ + @Test + public void testCurrentUnixTimeMilliseconds() { + String result = testDialect.getSqlFrom(currentUnixTimeMilliseconds()); + assertEquals(expectedCurrentUnixTimeMilliseconds(), result); + } + + + /** + * Test that client host functionality behaves as expected. + */ + @Test + public void testClientHost() { + String result = testDialect.getSqlFrom(clientHost()); + assertEquals(expectedClientHost(), result); + } + /** * Test that AddDays functionality behaves as expected. */ @@ -5328,19 +5349,19 @@ protected List expectedPreInsertStatementsNotInsertingUnderAutonumLimit( /** - * @return The expected SQL for conversion of YYYYMMDD into a date + * @return The expected SQL for conversion of YYYYMMDD into a date. */ protected abstract String expectedYYYYMMDDToDate(); /** - * @return The expected SQL for conversion of a date to a YYYYMMDD integer + * @return The expected SQL for conversion of a date to a YYYYMMDD integer. */ protected abstract String expectedDateToYyyymmdd(); /** - * @return The expected SQL for conversion of a date to a YYYYMMDDHHmmss integer + * @return The expected SQL for conversion of a date to a YYYYMMDDHHmmss integer. */ protected abstract String expectedDateToYyyymmddHHmmss(); @@ -5352,13 +5373,25 @@ protected List expectedPreInsertStatementsNotInsertingUnderAutonumLimit( /** - * @return The expected SQL for adding days + * @return The expected SQL for current unix time in milliseconds function returning milliseconds since 01/01/1970 UTC. + */ + protected abstract String expectedCurrentUnixTimeMilliseconds(); + + + /** + * @return The expected SQL for client host function. + */ + protected abstract String expectedClientHost(); + + + /** + * @return The expected SQL for adding days. */ protected abstract String expectedAddDays(); /** - * @return The expected SQL for adding days + * @return The expected SQL for adding days. */ protected abstract String expectedAddMonths();