/** * Licensed to JumpMind Inc under one or more contributor * license agreements. See the NOTICE file distributed * with this work for additional information regarding * copyright ownership. JumpMind Inc licenses this file * to you under the GNU General Public License, version 3.0 (GPLv3) * (the "License"); you may not use this file except in compliance * with the License. * * You should have received a copy of the GNU General Public License, * version 3.0 (GPLv3) along with this library; if not, see * <http://www.gnu.org/licenses/>. * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.jumpmind.symmetric.db; import static org.apache.commons.lang.StringUtils.isBlank; import static org.apache.commons.lang.StringUtils.isNotBlank; import java.sql.Types; import java.util.Map; import org.apache.commons.lang.NotImplementedException; import org.apache.commons.lang.StringUtils; import org.jumpmind.db.model.Column; import org.jumpmind.db.model.ColumnTypes; import org.jumpmind.db.model.Table; import org.jumpmind.db.model.TypeMap; import org.jumpmind.db.sql.DmlStatement.DmlType; import org.jumpmind.symmetric.common.Constants; import org.jumpmind.symmetric.common.ParameterConstants; import org.jumpmind.symmetric.io.data.DataEventType; import org.jumpmind.symmetric.model.Channel; import org.jumpmind.symmetric.model.Node; import org.jumpmind.symmetric.model.Trigger; import org.jumpmind.symmetric.model.TriggerHistory; import org.jumpmind.symmetric.model.TriggerRouter; import org.jumpmind.symmetric.service.IParameterService; import org.jumpmind.symmetric.util.SymmetricUtils; import org.jumpmind.util.FormatUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Responsible for generating dialect specific SQL such as trigger bodies and * functions */ abstract public class AbstractTriggerTemplate { protected final Logger log = LoggerFactory.getLogger(getClass()); protected static final String ORIG_TABLE_ALIAS = "orig"; static final String INSERT_TRIGGER_TEMPLATE = "insertTriggerTemplate"; static final String UPDATE_TRIGGER_TEMPLATE = "updateTriggerTemplate"; static final String DELETE_TRIGGER_TEMPLATE = "deleteTriggerTemplate"; static final String INITIAL_LOAD_SQL_TEMPLATE = "initialLoadSqlTemplate"; protected Map<String, String> sqlTemplates; protected String emptyColumnTemplate = "''"; protected String stringColumnTemplate; protected String xmlColumnTemplate; protected String arrayColumnTemplate; protected String numberColumnTemplate; protected String datetimeColumnTemplate; protected String timeColumnTemplate; protected String dateColumnTemplate; protected String dateTimeWithTimeZoneColumnTemplate; protected String dateTimeWithLocalTimeZoneColumnTemplate; protected String geometryColumnTemplate; protected String geographyColumnTemplate; protected String clobColumnTemplate; protected String blobColumnTemplate; protected String binaryColumnTemplate; protected String imageColumnTemplate; protected String wrappedBlobColumnTemplate; protected String booleanColumnTemplate; protected String triggerConcatCharacter; protected String newTriggerValue; protected String oldTriggerValue; protected String oldColumnPrefix = ""; protected String newColumnPrefix = ""; protected String otherColumnTemplate; protected ISymmetricDialect symmetricDialect; protected AbstractTriggerTemplate(ISymmetricDialect symmetricDialect) { this.symmetricDialect = symmetricDialect; } /** * When {@link ParameterConstants#INITIAL_LOAD_CONCAT_CSV_IN_SQL_ENABLED} is false * most dialects are going to want to still use the trigger templates because they * have type translation details (like geometry templates). However, some dialects * cannot handle the complex SQL generated (Firebird). We needed a way to tell * the dialect that we want to select the columns straight up. */ public boolean useTriggerTemplateForColumnTemplatesDuringInitialLoad() { return true; } public String createInitalLoadSql(Node node, TriggerRouter triggerRouter, Table originalTable, TriggerHistory triggerHistory, Channel channel, String overrideSelectSql) { IParameterService parameterService = symmetricDialect.getParameterService(); boolean dateTimeAsString = parameterService.is( ParameterConstants.DATA_LOADER_TREAT_DATETIME_AS_VARCHAR); boolean concatInCsv = parameterService.is( ParameterConstants.INITIAL_LOAD_CONCAT_CSV_IN_SQL_ENABLED); Table table = originalTable.copyAndFilterColumns(triggerHistory.getParsedColumnNames(), triggerHistory.getParsedPkColumnNames(), true); Column[] columns = table.getColumns(); String textColumnExpression = parameterService.getString(ParameterConstants.DATA_EXTRACTOR_TEXT_COLUMN_EXPRESSION); String sql = null; String tableAlias = symmetricDialect.getInitialLoadTableAlias(); if (concatInCsv) { sql = sqlTemplates.get(INITIAL_LOAD_SQL_TEMPLATE); String columnsText = buildColumnsString(tableAlias, tableAlias, "", columns, DataEventType.INSERT, false, channel, triggerRouter.getTrigger()).columnString; if (isNotBlank(textColumnExpression)) { columnsText = textColumnExpression.replace("$(columnName)", columnsText); } sql = FormatUtils.replace("columns", columnsText, sql); } else { sql = "select $(columns) from $(schemaName)$(tableName) t where $(whereClause)"; StringBuilder columnList = new StringBuilder(); for (int i = 0; i < columns.length; i++) { Column column = columns[i]; if (column != null) { if (i > 0) { columnList.append(","); } boolean isLob = symmetricDialect.getPlatform() .isLob(column.getMappedTypeCode()); if (!(isLob && triggerRouter.getTrigger().isUseStreamLobs())) { String columnExpression = null; if (useTriggerTemplateForColumnTemplatesDuringInitialLoad()) { ColumnString columnString = fillOutColumnTemplate(tableAlias, tableAlias, "", column, DataEventType.INSERT, false, channel, triggerRouter.getTrigger()); columnExpression = columnString.columnString; if (isNotBlank(textColumnExpression) && TypeMap.isTextType(column.getMappedTypeCode())) { columnExpression = textColumnExpression.replace("$(columnName)", columnExpression); } } else { columnExpression = SymmetricUtils.quote(symmetricDialect, column.getName()); if (dateTimeAsString && TypeMap.isDateTimeType(column.getMappedTypeCode())) { columnExpression = castDatetimeColumnToString(column.getName()); } else if (isNotBlank(textColumnExpression) && TypeMap.isTextType(column.getMappedTypeCode())) { columnExpression = textColumnExpression.replace("$(columnName)", columnExpression); } } columnList.append(columnExpression).append(" as ").append("x__").append(i); } else { columnList.append(" ").append(emptyColumnTemplate).append(" as ").append("x__").append(i); } } } sql = FormatUtils.replace("columns", columnList.toString(), sql); } String initialLoadSelect = StringUtils.isBlank(triggerRouter.getInitialLoadSelect()) ? Constants.ALWAYS_TRUE_CONDITION : triggerRouter.getInitialLoadSelect(); if (StringUtils.isNotBlank(overrideSelectSql)) { initialLoadSelect = overrideSelectSql; } sql = FormatUtils .replace( "whereClause", initialLoadSelect , sql); sql = FormatUtils.replace("tableName", SymmetricUtils.quote(symmetricDialect, table.getName()), sql); sql = FormatUtils.replace("schemaName", triggerHistory == null ? getSourceTablePrefix(originalTable) : getSourceTablePrefix(triggerHistory), sql); sql = FormatUtils.replace( "primaryKeyWhereString", getPrimaryKeyWhereString(symmetricDialect.getInitialLoadTableAlias(), table.hasPrimaryKey() ? table.getPrimaryKeyColumns() : table.getColumns()), sql); // Replace these parameters to give the initiaLoadContition a chance to // reference the node that is being loaded sql = FormatUtils.replace("groupId", node.getNodeGroupId(), sql); sql = FormatUtils.replace("externalId", node.getExternalId(), sql); sql = FormatUtils.replace("nodeId", node.getNodeId(), sql); sql = replaceDefaultSchemaAndCatalog(sql); sql = FormatUtils.replace("prefixName", symmetricDialect.getTablePrefix(), sql); sql = FormatUtils.replace("oracleToClob", triggerRouter.getTrigger().isUseCaptureLobs() ? "to_clob('')||" : "", sql); return sql; } protected String castDatetimeColumnToString(String columnName) { return SymmetricUtils.quote(symmetricDialect, columnName); } protected String getSourceTablePrefix(Table table) { String prefix = (isNotBlank(table.getSchema()) ? table.getSchema() + symmetricDialect.getPlatform().getDatabaseInfo().getSchemaSeparator() : ""); prefix = (isNotBlank(table.getCatalog()) ? table.getCatalog() + symmetricDialect.getPlatform().getDatabaseInfo().getCatalogSeparator() : "") + prefix; if (isBlank(prefix)) { prefix = (isNotBlank(symmetricDialect.getPlatform().getDefaultSchema()) ? SymmetricUtils .quote(symmetricDialect, symmetricDialect.getPlatform().getDefaultSchema()) + "." : ""); prefix = (isNotBlank(symmetricDialect.getPlatform().getDefaultCatalog()) ? SymmetricUtils .quote(symmetricDialect, symmetricDialect.getPlatform().getDefaultCatalog()) + "." : "") + prefix; } return prefix; } protected String getSourceTablePrefix(TriggerHistory triggerHistory) { String prefix = (isNotBlank(triggerHistory.getSourceSchemaName()) ? SymmetricUtils.quote( symmetricDialect, triggerHistory.getSourceSchemaName()) + symmetricDialect.getPlatform().getDatabaseInfo().getSchemaSeparator() : ""); prefix = (isNotBlank(triggerHistory.getSourceCatalogName()) ? SymmetricUtils.quote( symmetricDialect, triggerHistory.getSourceCatalogName()) + symmetricDialect.getPlatform().getDatabaseInfo().getCatalogSeparator() : "") + prefix; if (isBlank(prefix)) { prefix = (isNotBlank(symmetricDialect.getPlatform().getDefaultSchema()) ? SymmetricUtils .quote(symmetricDialect, symmetricDialect.getPlatform().getDefaultSchema()) + "." : ""); prefix = (isNotBlank(symmetricDialect.getPlatform().getDefaultCatalog()) ? SymmetricUtils .quote(symmetricDialect, symmetricDialect.getPlatform().getDefaultCatalog()) + "." : "") + prefix; } return prefix; } protected String replaceDefaultSchemaAndCatalog(String sql) { String defaultCatalog = symmetricDialect.getPlatform().getDefaultCatalog(); String defaultSchema = symmetricDialect.getPlatform().getDefaultSchema(); sql = replaceDefaultSchema(sql, defaultSchema); sql = replaceDefaultCatalog(sql, defaultCatalog); return sql; } public String createCsvDataSql(Trigger trigger, TriggerHistory triggerHistory, Table originalTable, Channel channel, String whereClause) { Table table = originalTable.copyAndFilterColumns(triggerHistory.getParsedColumnNames(), triggerHistory.getParsedPkColumnNames(), true); String sql = sqlTemplates.get(INITIAL_LOAD_SQL_TEMPLATE); Column[] columns = table.getColumns(); String columnsText = buildColumnsString(symmetricDialect.getInitialLoadTableAlias(), symmetricDialect.getInitialLoadTableAlias(), "", columns, DataEventType.INSERT, false, channel, trigger).columnString; sql = FormatUtils.replace("columns", columnsText, sql); sql = FormatUtils.replace("oracleToClob", trigger.isUseCaptureLobs() ? "to_clob('')||" : "", sql); sql = FormatUtils.replace("tableName", SymmetricUtils.quote(symmetricDialect, table.getName()), sql); sql = FormatUtils.replace("schemaName", triggerHistory == null ? getSourceTablePrefix(originalTable) : getSourceTablePrefix(triggerHistory), sql); sql = FormatUtils.replace("whereClause", whereClause, sql); sql = FormatUtils.replace( "primaryKeyWhereString", getPrimaryKeyWhereString(symmetricDialect.getInitialLoadTableAlias(), table.hasPrimaryKey() ? table.getPrimaryKeyColumns() : table.getColumns()), sql); sql = replaceDefaultSchemaAndCatalog(sql); return sql; } public String createCsvPrimaryKeySql(Trigger trigger, TriggerHistory triggerHistory, Table table, Channel channel, String whereClause) { String sql = sqlTemplates.get(INITIAL_LOAD_SQL_TEMPLATE); Column[] columns = table.getPrimaryKeyColumns(); String columnsText = buildColumnsString(symmetricDialect.getInitialLoadTableAlias(), symmetricDialect.getInitialLoadTableAlias(), "", columns, DataEventType.INSERT, false, channel, trigger).toString(); sql = FormatUtils.replace("columns", columnsText, sql); sql = FormatUtils.replace("oracleToClob", trigger.isUseCaptureLobs() ? "to_clob('')||" : "", sql); sql = FormatUtils.replace("tableName", SymmetricUtils.quote(symmetricDialect, table.getName()), sql); sql = FormatUtils.replace("schemaName", triggerHistory == null ? getSourceTablePrefix(table) : getSourceTablePrefix(triggerHistory), sql); sql = FormatUtils.replace("whereClause", whereClause, sql); sql = FormatUtils.replace( "primaryKeyWhereString", getPrimaryKeyWhereString(symmetricDialect.getInitialLoadTableAlias(), table.hasPrimaryKey() ? table.getPrimaryKeyColumns() : table.getColumns()), sql); return sql; } public String createTriggerDDL(DataEventType dml, Trigger trigger, TriggerHistory history, Channel channel, String tablePrefix, Table originalTable, String defaultCatalog, String defaultSchema) { Table table = originalTable.copyAndFilterColumns(history.getParsedColumnNames(), history.getParsedPkColumnNames(), true); String ddl = sqlTemplates.get(dml.name().toLowerCase() + "TriggerTemplate"); if (dml.getDmlType().equals(DmlType.UPDATE) && trigger.isUseHandleKeyUpdates()) { String temp = sqlTemplates.get(dml.name().toLowerCase() + "HandleKeyUpdates" + "TriggerTemplate"); if (StringUtils.trimToNull(temp)!=null) { ddl=temp; } } if (ddl == null) { throw new NotImplementedException(dml.name() + " trigger is not implemented for " + symmetricDialect.getPlatform().getName()); } return replaceTemplateVariables(dml, trigger, history, channel, tablePrefix, originalTable, table, defaultCatalog, defaultSchema, ddl); } public String createPostTriggerDDL(DataEventType dml, Trigger trigger, TriggerHistory history, Channel channel, String tablePrefix, Table originalTable, String defaultCatalog, String defaultSchema) { Table table = originalTable.copyAndFilterColumns(history.getParsedColumnNames(), history.getParsedPkColumnNames(), true); String ddl = sqlTemplates.get(dml.name().toLowerCase() + "PostTriggerTemplate"); return replaceTemplateVariables(dml, trigger, history, channel, tablePrefix, originalTable, table, defaultCatalog, defaultSchema, ddl); } protected String getDefaultTargetTableName(Trigger trigger, TriggerHistory history) { String targetTableName = null; if (history != null) { targetTableName = history.getSourceTableName(); } else { targetTableName = trigger.getSourceTableName(); } return targetTableName; } protected String replaceTemplateVariables(DataEventType dml, Trigger trigger, TriggerHistory history, Channel channel, String tablePrefix, Table originalTable, Table table, String defaultCatalog, String defaultSchema, String ddl) { ddl = FormatUtils.replace("targetTableName", getDefaultTargetTableName(trigger, history), ddl); ddl = FormatUtils.replace("triggerName", history.getTriggerNameForDmlType(dml), ddl); ddl = FormatUtils.replace("channelName", trigger.getChannelId(), ddl); ddl = FormatUtils.replace("triggerHistoryId", Integer.toString(history == null ? -1 : history.getTriggerHistoryId()), ddl); String triggerExpression = symmetricDialect.getTransactionTriggerExpression(defaultCatalog, defaultSchema, trigger); if (symmetricDialect.isTransactionIdOverrideSupported() && !StringUtils.isBlank(trigger.getTxIdExpression())) { triggerExpression = trigger.getTxIdExpression(); } ddl = FormatUtils.replace("txIdExpression", symmetricDialect.preProcessTriggerSqlClause(triggerExpression), ddl); ddl = FormatUtils.replace("channelExpression", symmetricDialect.preProcessTriggerSqlClause(getChannelExpression(trigger)), ddl); ddl = FormatUtils.replace("externalSelect", (trigger.getExternalSelect() == null ? "null" : "(" + symmetricDialect.preProcessTriggerSqlClause(trigger.getExternalSelect()) + ")"), ddl); ddl = FormatUtils.replace("syncOnInsertCondition", symmetricDialect.preProcessTriggerSqlClause(trigger.getSyncOnInsertCondition()), ddl); ddl = FormatUtils.replace("syncOnUpdateCondition", symmetricDialect.preProcessTriggerSqlClause(trigger.getSyncOnUpdateCondition()), ddl); ddl = FormatUtils.replace("syncOnDeleteCondition", symmetricDialect.preProcessTriggerSqlClause(trigger.getSyncOnDeleteCondition()), ddl); ddl = FormatUtils.replace("custom_on_insert_text", trigger.getCustomOnInsertText()==null ? "" : trigger.getCustomOnInsertText(), ddl); ddl = FormatUtils.replace("custom_on_update_text", trigger.getCustomOnUpdateText()==null ? "" : trigger.getCustomOnUpdateText(), ddl); ddl = FormatUtils.replace("custom_on_delete_text", trigger.getCustomOnDeleteText()==null ? "" : trigger.getCustomOnDeleteText(), ddl); ddl = FormatUtils.replace("dataHasChangedCondition", symmetricDialect .preProcessTriggerSqlClause(symmetricDialect.getDataHasChangedCondition(trigger)), ddl); ddl = FormatUtils.replace("sourceNodeExpression", symmetricDialect.getSourceNodeExpression(), ddl); ddl = FormatUtils.replace("oracleLobType", trigger.isUseCaptureLobs() ? "clob" : "long", ddl); String syncTriggersExpression = symmetricDialect.getSyncTriggersExpression(); ddl = FormatUtils.replace("syncOnIncomingBatchCondition", trigger.isSyncOnIncomingBatch() ? Constants.ALWAYS_TRUE_CONDITION : syncTriggersExpression, ddl); ddl = FormatUtils.replace("origTableAlias", ORIG_TABLE_ALIAS, ddl); Column[] orderedColumns = table.getColumns(); ColumnString columnString = buildColumnsString(ORIG_TABLE_ALIAS, newTriggerValue, newColumnPrefix, orderedColumns, dml, false, channel, trigger); ddl = FormatUtils.replace("columns", columnString.toString(), ddl); ddl = replaceDefaultSchemaAndCatalog(ddl); ddl = FormatUtils.replace("virtualOldNewTable", buildVirtualTableSql(oldColumnPrefix, newColumnPrefix, originalTable.getColumns()), ddl); ddl = FormatUtils.replace( "oldColumns", trigger.isUseCaptureOldData() ? buildColumnsString(ORIG_TABLE_ALIAS, oldTriggerValue, oldColumnPrefix, orderedColumns, dml, true, channel, trigger).toString() : "null", ddl); ddl = eval(columnString.isBlobClob, "containsBlobClobColumns", ddl); // some column templates need tableName and schemaName ddl = FormatUtils.replace("tableName", SymmetricUtils.quote(symmetricDialect, table.getName()), ddl); ddl = FormatUtils.replace("schemaName", history == null ? getSourceTablePrefix(originalTable) : getSourceTablePrefix(history), ddl); Column[] primaryKeyColumns = table.getPrimaryKeyColumns(); ddl = FormatUtils.replace( "oldKeys", buildColumnsString(ORIG_TABLE_ALIAS, oldTriggerValue, oldColumnPrefix, primaryKeyColumns, dml, true, channel, trigger).toString(), ddl); ddl = FormatUtils.replace( "oldNewPrimaryKeyJoin", aliasedPrimaryKeyJoin(oldTriggerValue, newTriggerValue, primaryKeyColumns.length == 0 ? orderedColumns : primaryKeyColumns), ddl); ddl = FormatUtils.replace( "tableNewPrimaryKeyJoin", aliasedPrimaryKeyJoin(ORIG_TABLE_ALIAS, newTriggerValue, primaryKeyColumns.length == 0 ? orderedColumns : primaryKeyColumns), ddl); ddl = FormatUtils.replace( "primaryKeyWhereString", getPrimaryKeyWhereString(dml == DataEventType.DELETE ? oldTriggerValue : newTriggerValue, table.hasPrimaryKey() ? table.getPrimaryKeyColumns() : table.getColumns()), ddl); String builtString = buildColumnNameString(oldTriggerValue, true, trigger, primaryKeyColumns); ddl = FormatUtils.replace("oldKeyNames", StringUtils.isNotBlank(builtString) ? "," + builtString : "", ddl); builtString = buildColumnNameString(newTriggerValue, true, trigger, primaryKeyColumns); ddl = FormatUtils.replace("newKeyNames", StringUtils.isNotBlank(builtString) ? "," + builtString : "", ddl); ddl = FormatUtils.replace("columnNames", buildColumnNameString(null, false, trigger, orderedColumns), ddl); ddl = FormatUtils.replace("pkColumnNames", buildColumnNameString(null, false, trigger, primaryKeyColumns), ddl); builtString = buildKeyVariablesString(primaryKeyColumns, "old"); ddl = FormatUtils.replace("oldKeyVariables", StringUtils.isNotBlank(builtString) ? "," + builtString : "", ddl); builtString = buildKeyVariablesString(primaryKeyColumns, "new"); ddl = FormatUtils.replace("newKeyVariables", StringUtils.isNotBlank(builtString) ? "," + builtString : "", ddl); ddl = FormatUtils.replace("varNewPrimaryKeyJoin", aliasedPrimaryKeyJoinVar(newTriggerValue, "new", primaryKeyColumns), ddl); ddl = FormatUtils.replace("varOldPrimaryKeyJoin", aliasedPrimaryKeyJoinVar(oldTriggerValue, "old", primaryKeyColumns), ddl); // replace $(newTriggerValue) and $(oldTriggerValue) ddl = FormatUtils.replace("newTriggerValue", newTriggerValue, ddl); ddl = FormatUtils.replace("oldTriggerValue", oldTriggerValue, ddl); ddl = FormatUtils.replace("newColumnPrefix", newColumnPrefix, ddl); ddl = FormatUtils.replace("oldColumnPrefix", oldColumnPrefix, ddl); ddl = FormatUtils.replace("prefixName", tablePrefix, ddl); ddl = replaceDefaultSchemaAndCatalog(ddl); ddl = FormatUtils.replace("oracleToClob", trigger.isUseCaptureLobs() ? "to_clob('')||" : "", ddl); switch (dml) { case DELETE: ddl = FormatUtils.replace("curTriggerValue", oldTriggerValue, ddl); ddl = FormatUtils.replace("curColumnPrefix", oldColumnPrefix, ddl); break; case INSERT: case UPDATE: default: ddl = FormatUtils.replace("curTriggerValue", newTriggerValue, ddl); ddl = FormatUtils.replace("curColumnPrefix", newColumnPrefix, ddl); break; } return ddl; } protected String getChannelExpression(Trigger trigger) { if (trigger.getChannelId().equals(Constants.CHANNEL_DYNAMIC)) { if (StringUtils.isNotBlank(trigger.getChannelExpression())) { return trigger.getChannelExpression(); } else { throw new IllegalStateException("When the channel is set to '" + Constants.CHANNEL_DYNAMIC + "', a channel expression must be provided."); } } else { return "'" + trigger.getChannelId() + "'"; } } protected String buildVirtualTableSql(String oldTriggerValue, String newTriggerValue, Column[] columns) { if (oldTriggerValue.indexOf(".") >= 0) { oldTriggerValue = oldTriggerValue.substring(oldTriggerValue.indexOf(".") + 1); } if (newTriggerValue.indexOf(".") >= 0) { newTriggerValue = newTriggerValue.substring(newTriggerValue.indexOf(".") + 1); } StringBuilder b = new StringBuilder("(SELECT "); for (Column columnType : columns) { String column = columnType.getName(); b.append("? as "); b.append("\"").append(newTriggerValue).append(column).append("\","); } for (Column columnType : columns) { String column = columnType.getName(); b.append("? AS "); b.append("\"").append(oldTriggerValue).append(column).append("\","); } b.deleteCharAt(b.length() - 1); b.append(" FROM DUAL) T "); return b.toString(); } protected String eval(boolean condition, String prop, String ddl) { if (ddl != null) { String ifStmt = "$(if:" + prop + ")"; String elseStmt = "$(else:" + prop + ")"; String endStmt = "$(end:" + prop + ")"; int ifIndex = ddl.indexOf(ifStmt); if (ifIndex >= 0) { int endIndex = ddl.indexOf(endStmt); if (endIndex >= 0) { String onTrue = ddl.substring(ifIndex + ifStmt.length(), endIndex); String onFalse = ""; int elseIndex = onTrue.indexOf(elseStmt); if (elseIndex >= 0) { onFalse = onTrue.substring(elseIndex + elseStmt.length()); onTrue = onTrue.substring(0, elseIndex); } if (condition) { ddl = ddl.substring(0, ifIndex) + onTrue + ddl.substring(endIndex + endStmt.length()); } else { ddl = ddl.substring(0, ifIndex) + onFalse + ddl.substring(endIndex + endStmt.length()); } } else { throw new IllegalStateException(ifStmt + " has to have a " + endStmt); } } } return ddl; } protected String aliasedPrimaryKeyJoin(String aliasOne, String aliasTwo, Column[] columns) { StringBuilder b = new StringBuilder(); for (Column column : columns) { b.append(aliasOne).append(".\"").append(column.getName()).append("\""); b.append("=").append(aliasTwo).append(".\"").append(column.getName()).append("\""); if (!column.equals(columns[columns.length - 1])) { b.append(" and "); } } return b.toString(); } protected String aliasedPrimaryKeyJoinVar(String alias, String prefix, Column[] columns) { String text = ""; for (int i = 0; i < columns.length; i++) { text += alias + ".\"" + columns[i].getName() + "\""; text += "=@" + prefix + "pk" + i; if (i + 1 < columns.length) { text += " and "; } } return text; } /** * Specific to Derby. Needs to be removed when the initial load is * refactored to concat in Java vs. in SQL */ @Deprecated protected String getPrimaryKeyWhereString(String alias, Column[] columns) { return null; } protected boolean requiresWrappedBlobTemplateForBlobType() { return false; } protected boolean requiresEmptyLobTemplateForDeletes() { return false; } protected String buildColumnNameString(String tableAlias, boolean quote, Trigger trigger, Column[] columns) { StringBuilder columnsText = new StringBuilder(); for (Column column : columns) { boolean isLob = symmetricDialect.getPlatform().isLob(column.getMappedTypeCode()); String columnName = column.getName(); if (quote) { columnName = SymmetricUtils.quote(symmetricDialect, columnName); } if (!(isLob && trigger.isUseStreamLobs())) { if (StringUtils.isNotBlank(tableAlias)) { columnsText.append(tableAlias); columnsText.append("."); } columnsText.append(columnName); } else { columnsText.append("null"); } columnsText.append(","); } return columnsText.length() > 0 ? columnsText.substring(0, columnsText.length() - 1) : columnsText.toString(); } protected ColumnString buildColumnsString(String origTableAlias, String tableAlias, String columnPrefix, Column[] columns, DataEventType dml, boolean isOld, Channel channel, Trigger trigger) { String columnsText = ""; boolean containsLob = false; String lastCommandToken = symmetricDialect.escapesTemplatesForDatabaseInserts() ? (triggerConcatCharacter + "'',''" + triggerConcatCharacter) : (triggerConcatCharacter + "','" + triggerConcatCharacter); for (int i = 0; i < columns.length; i++) { Column column = columns[i]; if (column != null) { ColumnString columnString = fillOutColumnTemplate(origTableAlias, tableAlias, columnPrefix, column, dml, isOld, channel, trigger); columnsText = columnsText + "\n " + columnString.columnString + lastCommandToken; containsLob |= columnString.isBlobClob; } } if (columnsText.endsWith(lastCommandToken)) { columnsText = columnsText .substring(0, columnsText.length() - lastCommandToken.length()); } return new ColumnString(columnsText, containsLob); } protected ColumnString fillOutColumnTemplate(String origTableAlias, String tableAlias, String columnPrefix, Column column, DataEventType dml, boolean isOld, Channel channel, Trigger trigger) { boolean isLob = symmetricDialect.getPlatform().isLob(column.getMappedTypeCode()); String templateToUse = null; if (column.getJdbcTypeName() != null && (column.getJdbcTypeName().toUpperCase().contains(TypeMap.GEOMETRY)) && StringUtils.isNotBlank(geometryColumnTemplate)) { templateToUse = geometryColumnTemplate; } else if (column.getJdbcTypeName() != null && (column.getJdbcTypeName().toUpperCase().contains(TypeMap.GEOGRAPHY)) && StringUtils.isNotBlank(geographyColumnTemplate)) { templateToUse = geographyColumnTemplate; } else { switch (column.getMappedTypeCode()) { case Types.TINYINT: case Types.SMALLINT: case Types.INTEGER: case Types.BIGINT: case Types.FLOAT: case Types.REAL: case Types.DOUBLE: case Types.NUMERIC: case Types.DECIMAL: templateToUse = numberColumnTemplate; break; case Types.CHAR: case Types.NCHAR: case Types.VARCHAR: case ColumnTypes.NVARCHAR: templateToUse = stringColumnTemplate; break; case ColumnTypes.SQLXML: templateToUse = xmlColumnTemplate; break; case Types.ARRAY: templateToUse = arrayColumnTemplate; break; case Types.LONGVARCHAR: case ColumnTypes.LONGNVARCHAR: if (!isLob) { templateToUse = stringColumnTemplate; break; } case Types.CLOB: if (isOld && symmetricDialect.needsToSelectLobData()) { templateToUse = emptyColumnTemplate; } else { templateToUse = clobColumnTemplate; } break; case Types.BINARY: case Types.VARBINARY: if (isNotBlank(binaryColumnTemplate)) { templateToUse = binaryColumnTemplate; break; } case Types.BLOB: if (requiresWrappedBlobTemplateForBlobType()) { templateToUse = wrappedBlobColumnTemplate; break; } case Types.LONGVARBINARY: case -10: // SQL-Server ntext binary type if (column.getJdbcTypeName() != null && (column.getJdbcTypeName().toUpperCase().contains(TypeMap.IMAGE)) && StringUtils.isNotBlank(imageColumnTemplate)) { if (isOld) { templateToUse = emptyColumnTemplate; } else { templateToUse = imageColumnTemplate; } } else if (isOld && symmetricDialect.needsToSelectLobData()) { templateToUse = emptyColumnTemplate; } else { templateToUse = blobColumnTemplate; } break; case Types.DATE: if (noDateColumnTemplate()) { templateToUse = datetimeColumnTemplate; break; } templateToUse = dateColumnTemplate; break; case Types.TIME: if (noTimeColumnTemplate()) { templateToUse = datetimeColumnTemplate; break; } templateToUse = timeColumnTemplate; break; case Types.TIMESTAMP: templateToUse = datetimeColumnTemplate; break; case Types.BOOLEAN: case Types.BIT: templateToUse = booleanColumnTemplate; break; default: if (column.getJdbcTypeName() != null) { if (column.getJdbcTypeName().toUpperCase().equals(TypeMap.INTERVAL)) { templateToUse = numberColumnTemplate; break; } else if (column.getMappedType().equals(TypeMap.TIMESTAMPTZ) && StringUtils.isNotBlank(this.dateTimeWithTimeZoneColumnTemplate)) { templateToUse = this.dateTimeWithTimeZoneColumnTemplate; break; } else if (column.getMappedType().equals(TypeMap.TIMESTAMPLTZ) && StringUtils .isNotBlank(this.dateTimeWithLocalTimeZoneColumnTemplate)) { templateToUse = this.dateTimeWithLocalTimeZoneColumnTemplate; break; } } if (StringUtils.isBlank(templateToUse) && StringUtils.isNotBlank(this.otherColumnTemplate)) { templateToUse = this.otherColumnTemplate; break; } throw new NotImplementedException(column.getName() + " is of type " + column.getMappedType() + " with JDBC type of " + column.getJdbcTypeName()); } } if (dml == DataEventType.DELETE && isLob && requiresEmptyLobTemplateForDeletes()) { templateToUse = emptyColumnTemplate; } else if (isLob && trigger.isUseStreamLobs()) { templateToUse = emptyColumnTemplate; } if (templateToUse != null) { templateToUse = templateToUse.trim(); } else { throw new NotImplementedException(); } String formattedColumnText = FormatUtils.replace("columnName", String.format("%s%s", columnPrefix, column.getName()), templateToUse); formattedColumnText = FormatUtils.replace("columnSize", column.getSize(), formattedColumnText); formattedColumnText = FormatUtils.replace("masterCollation", symmetricDialect.getMasterCollation(), formattedColumnText); if (isLob) { formattedColumnText = symmetricDialect.massageForLob(formattedColumnText, channel); } formattedColumnText = FormatUtils.replace("origTableAlias", origTableAlias, formattedColumnText); formattedColumnText = FormatUtils.replace("tableAlias", tableAlias, formattedColumnText); formattedColumnText = FormatUtils.replace("prefixName", symmetricDialect.getTablePrefix(), formattedColumnText); return new ColumnString(formattedColumnText, isLob); } public String getOtherColumnTemplate() { return otherColumnTemplate; } protected boolean noTimeColumnTemplate() { return timeColumnTemplate == null || timeColumnTemplate.equals("null") || timeColumnTemplate.trim().equals(""); } protected boolean noDateColumnTemplate() { return dateColumnTemplate == null || dateColumnTemplate.equals("null") || dateColumnTemplate.trim().equals(""); } protected String buildKeyVariablesDeclare(Column[] columns, String prefix) { String text = ""; for (int i = 0; i < columns.length; i++) { text += "declare @" + prefix + "pk" + i + " "; switch (columns[i].getMappedTypeCode()) { case Types.TINYINT: case Types.SMALLINT: case Types.INTEGER: case Types.BIGINT: text += "bigint\n"; break; case Types.NUMERIC: case Types.DECIMAL: text += "decimal\n"; break; case Types.FLOAT: case Types.REAL: case Types.DOUBLE: text += "float\n"; break; case Types.CHAR: case Types.VARCHAR: case ColumnTypes.NVARCHAR: case ColumnTypes.LONGNVARCHAR: case Types.LONGVARCHAR: text += "varchar(1000)\n"; break; case Types.DATE: text += "date\n"; break; case Types.TIME: text += "time\n"; break; case Types.TIMESTAMP: text += "datetime\n"; break; case Types.BOOLEAN: case Types.BIT: text += "bit\n"; break; case Types.CLOB: text += "varchar(max)\n"; break; case Types.BLOB: case Types.BINARY: case Types.VARBINARY: case Types.LONGVARBINARY: case -10: // SQL-Server ntext binary type text += "varbinary(max)\n"; break; case Types.OTHER: text += "varbinary(max)\n"; break; default: if (columns[i].getJdbcTypeName() != null && columns[i].getJdbcTypeName().equalsIgnoreCase("interval")) { text += "interval"; break; } throw new NotImplementedException(columns[i] + " is of type " + columns[i].getMappedType()); } } return text; } protected String buildKeyVariablesString(Column[] columns, String prefix) { String text = ""; for (int i = 0; i < columns.length; i++) { text += "@" + prefix + "pk" + i; if (i + 1 < columns.length) { text += ", "; } } return text; } public String getClobColumnTemplate() { return clobColumnTemplate; } public void setBooleanColumnTemplate(String booleanColumnTemplate) { this.booleanColumnTemplate = booleanColumnTemplate; } public String getNewTriggerValue() { return newTriggerValue; } public String getOldTriggerValue() { return oldTriggerValue; } public String getBlobColumnTemplate() { return blobColumnTemplate; } public String getWrappedBlobColumnTemplate() { return wrappedBlobColumnTemplate; } protected String replaceDefaultSchema(String ddl, String defaultSchema) { if (StringUtils.isNotBlank(defaultSchema)) { ddl = FormatUtils.replace("defaultSchema", SymmetricUtils.quote(symmetricDialect, defaultSchema) + ".", ddl); } else { ddl = FormatUtils.replace("defaultSchema", "", ddl); } return ddl; } protected String replaceDefaultCatalog(String ddl, String defaultCatalog) { if (StringUtils.isNotBlank(defaultCatalog)) { ddl = FormatUtils.replace("defaultCatalog", SymmetricUtils.quote(symmetricDialect, defaultCatalog) + ".", ddl); } else { ddl = FormatUtils.replace("defaultCatalog", "", ddl); } return ddl; } public String getTimeColumnTemplate() { return timeColumnTemplate; } public void setTimeColumnTemplate(String timeColumnTemplate) { this.timeColumnTemplate = timeColumnTemplate; } public String getDateColumnTemplate() { return dateColumnTemplate; } public void setDateColumnTemplate(String dateColumnTemplate) { this.dateColumnTemplate = dateColumnTemplate; } public String getImageColumnTemplate() { return imageColumnTemplate; } public void setImageColumnTemplate(String imageColumnTemplate) { this.imageColumnTemplate = imageColumnTemplate; } protected class ColumnString { String columnString; boolean isBlobClob = false; ColumnString(String columnExpression, boolean isBlobClob) { this.columnString = columnExpression; this.isBlobClob = isBlobClob; } @Override public String toString() { return StringUtils.isBlank(columnString) ? "null" : columnString; } } public int toHashedValue() { int hashedValue = 0; if (sqlTemplates != null) { for (String key : sqlTemplates.keySet()) { hashedValue += sqlTemplates.get(key).hashCode(); } } return hashedValue; } }