Skip to content

Commit

Permalink
0005752: Added support for H2 2.x
Browse files Browse the repository at this point in the history
  • Loading branch information
evan-miller-jumpmind committed Mar 17, 2023
1 parent 2303b39 commit 4f290fd
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 22 deletions.
Expand Up @@ -25,16 +25,23 @@
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.time.FastDateFormat;
import org.h2.jdbc.JdbcBlob;
import org.h2.jdbc.JdbcClob;
import org.jumpmind.symmetric.db.h2.H2Trigger;
import org.jumpmind.symmetric.db.hsqldb.HsqlDbTrigger;

Expand Down Expand Up @@ -139,13 +146,21 @@ private int forEachColumn(int columnCount, Object[] data, StringBuilder out, int
protected Object appendVirtualTableStringValue(Object value, StringBuilder out) {
if (value == null) {
out.append("null");
} else if (value instanceof String || value instanceof Reader) {
} else if (value instanceof String || value instanceof Reader || value instanceof JdbcClob) {
if (value instanceof Reader) {
try {
value = readStringAndClose((Reader) value, -1);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else if (value instanceof JdbcClob) {
try {
value = readStringAndClose(((JdbcClob) value).getCharacterStream(), -1);
} catch (SQLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
out.append("'");
out.append(escapeString(value));
Expand All @@ -163,10 +178,33 @@ protected Object appendVirtualTableStringValue(Object value, StringBuilder out)
}
out.append(escapeString(value));
out.append("'");
} else if (value instanceof JdbcBlob) {
out.append("'");
try {
value = new String(readBytesAndClose(((JdbcBlob) value).getBinaryStream(), -1), StandardCharsets.UTF_8);
} catch (SQLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
out.append(escapeString(value));
out.append("'");
} else if (value instanceof Date) {
out.append("'");
out.append(DATE_FORMATTER.format(value));
out.append("'");
} else if (value instanceof LocalDate) {
out.append("'");
out.append(((LocalDate) value).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
out.append("'");
} else if (value instanceof LocalTime) {
out.append("'");
out.append(((LocalTime) value).format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS")));
out.append("'");
} else if (value instanceof LocalDateTime) {
out.append("'");
out.append(((LocalDateTime) value).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
out.append("'");
} else if (value instanceof byte[]) {
out.append("'");
value = convertBytesToString((byte[]) value, ((byte[]) value).length);
Expand Down
Expand Up @@ -20,15 +20,18 @@
*/
package org.jumpmind.symmetric.db.h2;

import java.sql.Types;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.jumpmind.db.model.Table;
import org.jumpmind.db.platform.DatabaseInfo;
import org.jumpmind.db.platform.h2.H2DdlBuilder;
import org.jumpmind.db.platform.IDatabasePlatform;
import org.jumpmind.db.sql.ISqlTransaction;
import org.jumpmind.db.sql.mapper.StringMapper;
import org.jumpmind.db.util.BinaryEncoding;
import org.jumpmind.symmetric.Version;
import org.jumpmind.symmetric.common.ParameterConstants;
import org.jumpmind.symmetric.db.AbstractEmbeddedSymmetricDialect;
import org.jumpmind.symmetric.db.ISymmetricDialect;
Expand All @@ -40,11 +43,29 @@
*/
public class H2SymmetricDialect extends AbstractEmbeddedSymmetricDialect implements ISymmetricDialect {
static final String SQL_DROP_FUNCTION = "DROP ALIAS $(functionName)";
static final String SQL_FUNCTION_INSTALLED = "select count(*) from INFORMATION_SCHEMA.FUNCTION_ALIASES where ALIAS_NAME='$(functionName)'";
static String SQL_FUNCTION_INSTALLED;

public H2SymmetricDialect(IParameterService parameterService, IDatabasePlatform platform) {
super(parameterService, platform);
this.triggerTemplate = new H2TriggerTemplate(this);
if (!Version.isOlderThanVersion(getProductVersion(), "2.0.202")) {
SQL_FUNCTION_INSTALLED = "select count(*) from INFORMATION_SCHEMA.ROUTINES where ROUTINE_NAME='$(functionName)'";
platform.getDatabaseInfo().addNativeTypeMapping(Types.LONGVARCHAR, "VARCHAR(1000000000)", Types.VARCHAR);
platform.getDatabaseInfo().setDefaultSize(Types.CHAR, 1000000000);
platform.getDatabaseInfo().setMaxSize("CHAR", 1000000000);
platform.getDatabaseInfo().setDefaultSize(Types.VARCHAR, 1000000000);
platform.getDatabaseInfo().setMaxSize("VARCHAR", 1000000000);
platform.getDatabaseInfo().setDefaultSize(Types.BINARY, 1000000000);
platform.getDatabaseInfo().setMaxSize("BINARY", 1000000000);
platform.getDatabaseInfo().setDefaultSize(Types.VARBINARY, 1000000000);
platform.getDatabaseInfo().setMaxSize("VARBINARY", 1000000000);
platform.getDatabaseInfo().setNonBlankCharColumnSpacePadded(true);
platform.getDatabaseInfo().setBlankCharColumnSpacePadded(true);
platform.getDatabaseInfo().setCharColumnSpaceTrimmed(false);
((H2DdlBuilder) platform.getDdlBuilder()).setVersion2(true);
} else {
SQL_FUNCTION_INSTALLED = "select count(*) from INFORMATION_SCHEMA.FUNCTION_ALIASES where ALIAS_NAME='$(functionName)'";
}
}

@Override
Expand Down
Expand Up @@ -22,6 +22,7 @@

import java.util.HashMap;

import org.jumpmind.symmetric.Version;
import org.jumpmind.symmetric.db.AbstractTriggerTemplate;
import org.jumpmind.symmetric.db.ISymmetricDialect;

Expand All @@ -35,6 +36,9 @@ public H2TriggerTemplate(ISymmetricDialect symmetricDialect) {
clobColumnTemplate = "case when $(tableAlias)\"$(columnName)\" is null then '''' else ''\"''||replace(replace($(tableAlias)\"$(columnName)\",''\\'',''\\\\''),''\"'',''\\\"'')||''\"'' end";
blobColumnTemplate = "case when $(tableAlias)\"$(columnName)\" is null then '''' else ''\"''||replace(replace(sym_BASE64_ENCODE($(tableAlias)\"$(columnName)\"),''\\'',''\\\\''),''\"'',''\\\"'')||''\"'' end";
booleanColumnTemplate = "case when $(tableAlias)\"$(columnName)\" is null then '''' when $(tableAlias)\"$(columnName)\" then ''\"1\"'' else ''\"0\"'' end";
if (!Version.isOlderThanVersion(symmetricDialect.getProductVersion(), "2.0.202")) {
timeColumnTemplate = "case when $(tableAlias)\"$(columnName)\" is null then '''' else ''\"''||to_char($(tableAlias)\"$(columnName)\", ''HH:MI:SSXFF3'')||''\"'' end";
}
triggerConcatCharacter = "||";
newTriggerValue = "";
oldTriggerValue = "";
Expand All @@ -52,7 +56,7 @@ public H2TriggerTemplate(ISymmetricDialect symmetricDialect) {
+
" (select ''$(targetTableName)'',''I'',$(triggerHistoryId),$(columns), $(channelExpression), $(txIdExpression), @node_value, $(externalSelect), CURRENT_TIMESTAMP from $(virtualOldNewTable))' "
+
" ); "
" ); COMMIT; "
+
" CREATE TRIGGER $(schemaName)$(triggerName) AFTER INSERT ON $(schemaName)$(tableName) FOR EACH ROW CALL \"org.jumpmind.symmetric.db.h2.H2Trigger\"; ");
sqlTemplates.put("insertReloadTriggerTemplate",
Expand All @@ -66,7 +70,7 @@ public H2TriggerTemplate(ISymmetricDialect symmetricDialect) {
+
" (select ''$(targetTableName)'',''R'',$(triggerHistoryId),$(newKeys), $(channelExpression), $(txIdExpression), @node_value, $(externalSelect), CURRENT_TIMESTAMP from $(virtualOldNewTable))' "
+
" ); "
" ); COMMIT; "
+
" CREATE TRIGGER $(schemaName)$(triggerName) AFTER INSERT ON $(schemaName)$(tableName) FOR EACH ROW CALL \"org.jumpmind.symmetric.db.h2.H2Trigger\"; ");
sqlTemplates.put("updateTriggerTemplate",
Expand All @@ -80,7 +84,7 @@ public H2TriggerTemplate(ISymmetricDialect symmetricDialect) {
+
" (select ''$(targetTableName)'',''U'',$(triggerHistoryId),$(oldKeys),$(columns),$(oldColumns), $(channelExpression), $(txIdExpression), @node_value, $(externalSelect), CURRENT_TIMESTAMP from $(virtualOldNewTable))'"
+
" ); "
" ); COMMIT; "
+
" CREATE TRIGGER $(schemaName)$(triggerName) AFTER UPDATE ON $(schemaName)$(tableName) FOR EACH ROW CALL \"org.jumpmind.symmetric.db.h2.H2Trigger\"; ");
sqlTemplates.put("updateReloadTriggerTemplate",
Expand All @@ -94,7 +98,7 @@ public H2TriggerTemplate(ISymmetricDialect symmetricDialect) {
+
" (select ''$(targetTableName)'',''R'',$(triggerHistoryId),$(oldKeys), $(channelExpression), $(txIdExpression), @node_value, $(externalSelect), CURRENT_TIMESTAMP from $(virtualOldNewTable))'"
+
" ); "
" ); COMMIT; "
+
" CREATE TRIGGER $(schemaName)$(triggerName) AFTER UPDATE ON $(schemaName)$(tableName) FOR EACH ROW CALL \"org.jumpmind.symmetric.db.h2.H2Trigger\"; ");
sqlTemplates.put("deleteTriggerTemplate",
Expand All @@ -108,7 +112,7 @@ public H2TriggerTemplate(ISymmetricDialect symmetricDialect) {
+
" (select ''$(targetTableName)'',''D'',$(triggerHistoryId),$(oldKeys),$(oldColumns),$(channelExpression), $(txIdExpression), @node_value, $(externalSelect), CURRENT_TIMESTAMP from $(virtualOldNewTable))'"
+
" ); "
" ); COMMIT; "
+
" CREATE TRIGGER $(schemaName)$(triggerName) AFTER DELETE ON $(schemaName)$(tableName) FOR EACH ROW CALL \"org.jumpmind.symmetric.db.h2.H2Trigger\"; ");
sqlTemplates.put("initialLoadSqlTemplate",
Expand Down
Expand Up @@ -141,8 +141,15 @@ public void exportTestDatabaseSQL() throws Exception {
return;
}
final int EXPECTED_VARCHAR_MAX_COUNT = engine.getDatabasePlatform().getName().equals(DatabaseNamesConstants.SQLITE) ? 318 : 60;
final String EXPECTED_VARCHAR_MAX_STRING = engine.getDatabasePlatform().getName().equals(DatabaseNamesConstants.DERBY) ? "clob"
: "varchar(" + Integer.MAX_VALUE + ")";
final String EXPECTED_VARCHAR_MAX_STRING;
if (engine.getDatabasePlatform().getName().equals(DatabaseNamesConstants.DERBY)) {
EXPECTED_VARCHAR_MAX_STRING = "clob";
} else if (engine.getDatabasePlatform().getName().equals(DatabaseNamesConstants.H2)
&& !Version.isOlderThanVersion(engine.getSymmetricDialect().getProductVersion(), "2.0.202")) {
EXPECTED_VARCHAR_MAX_STRING = "character varying(1000000000)";
} else {
EXPECTED_VARCHAR_MAX_STRING = "varchar(" + Integer.MAX_VALUE + ")";
}
final int actualVarcharMaxCount = StringUtils.countMatches(output, EXPECTED_VARCHAR_MAX_STRING);
String msg = String.format("Expected %s, but got %s in the following output %s",
EXPECTED_VARCHAR_MAX_COUNT, actualVarcharMaxCount, output);
Expand Down
Expand Up @@ -70,6 +70,8 @@
* The SQL Builder for the H2 database.
*/
public class H2DdlBuilder extends AbstractDdlBuilder {
private boolean isVersion2;

public H2DdlBuilder() {
super(DatabaseNamesConstants.H2);
databaseInfo.setNonPKIdentityColumnsSupported(false);
Expand Down Expand Up @@ -106,6 +108,50 @@ public H2DdlBuilder() {
databaseInfo.setGeneratedColumnsSupported(true);
}

@Override
protected void createTable(Table table, StringBuilder ddl, boolean temporary, boolean recreate) {
if (isVersion2 && !temporary && !recreate) {
for (int idx = 0; idx < table.getColumnCount(); idx++) {
Column column = table.getColumn(idx);
if (column.isAutoIncrement()) {
createAutoIncrementSequence(table, column, ddl);
}
}
}
super.createTable(table, ddl, temporary, recreate);
}

private void createAutoIncrementSequence(Table table, Column column, StringBuilder ddl) {
ddl.append("CREATE SEQUENCE ");
if (StringUtils.isNotBlank(table.getSchema())) {
printIdentifier(table.getSchema(), ddl);
ddl.append(".");
}
printIdentifier(getConstraintName(null, table, column.getName(), "SEQ"), ddl);
printEndOfStatement(ddl);
}

@Override
protected void dropTable(Table table, StringBuilder ddl, boolean temporary, boolean recreate) {
super.dropTable(table, ddl, temporary, recreate);
if (isVersion2 && !temporary && !recreate) {
Column[] columns = table.getAutoIncrementColumns();
for (int idx = 0; idx < columns.length; idx++) {
dropAutoIncrementSequence(table, columns[idx], ddl);
}
}
}

private void dropAutoIncrementSequence(Table table, Column column, StringBuilder ddl) {
ddl.append("DROP SEQUENCE ");
if (StringUtils.isNotBlank(table.getSchema())) {
printIdentifier(table.getSchema(), ddl);
ddl.append(".");
}
printIdentifier(getConstraintName(null, table, column.getName(), "SEQ"), ddl);
printEndOfStatement(ddl);
}

@Override
protected void processTableStructureChanges(Database currentModel, Database desiredModel,
Table sourceTable, Table targetTable, List<TableChange> changes, StringBuilder ddl) {
Expand Down Expand Up @@ -170,6 +216,9 @@ protected void processChange(Database currentModel, Database desiredModel,
ddl.append("DROP COLUMN ");
printIdentifier(getColumnName(change.getColumn()), ddl);
printEndOfStatement(ddl);
if (isVersion2 && change.getColumn().isAutoIncrement()) {
dropAutoIncrementSequence(change.getChangedTable(), change.getColumn(), ddl);
}
change.apply(currentModel, delimitedIdentifierModeOn);
}

Expand Down Expand Up @@ -207,7 +256,15 @@ public void writeExternalIndexDropStmt(Table table, IIndex index, StringBuilder

@Override
protected void writeColumnAutoIncrementStmt(Table table, Column column, StringBuilder ddl) {
ddl.append("AUTO_INCREMENT");
if (isVersion2) {
ddl.append("DEFAULT nextval('");
if (StringUtils.isNotBlank(table.getSchema())) {
ddl.append(table.getSchema()).append(".");
}
ddl.append(getConstraintName(null, table, column.getName(), "SEQ")).append("')");
} else {
ddl.append("AUTO_INCREMENT");
}
}

@Override
Expand All @@ -223,4 +280,8 @@ protected void writeAlterColumn(Table table, Column column, StringBuilder ddl) {
writeColumn(table, column, ddl);
printEndOfStatement(ddl);
}

public void setVersion2(boolean isVersion2) {
this.isVersion2 = isVersion2;
}
}

0 comments on commit 4f290fd

Please sign in to comment.