diff --git a/pom.xml b/pom.xml index f5e65dff5..bfc7d5897 100644 --- a/pom.xml +++ b/pom.xml @@ -130,7 +130,7 @@ io.ebean ebean-migration - 11.3.1 + 11.5.1 diff --git a/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java b/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java index c1a014095..b1ddadc2c 100644 --- a/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java +++ b/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java @@ -517,6 +517,13 @@ public class DatabasePlatform { this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate; } + /** + * Normally not needed - overridden in CockroachPlatform. + */ + public boolean isDdlCommitOnCreateIndex() { + return false; + } + /** * Return the DB identity/sequence features for this platform. * diff --git a/src/main/java/io/ebean/config/dbplatform/cockroach/CockroachPlatform.java b/src/main/java/io/ebean/config/dbplatform/cockroach/CockroachPlatform.java new file mode 100644 index 000000000..502e33cf6 --- /dev/null +++ b/src/main/java/io/ebean/config/dbplatform/cockroach/CockroachPlatform.java @@ -0,0 +1,28 @@ +package io.ebean.config.dbplatform.cockroach; + +import io.ebean.annotation.Platform; +import io.ebean.config.dbplatform.postgres.PostgresPlatform; + +/** + * CockroachDB based platform. + */ +public class CockroachPlatform extends PostgresPlatform { + + public CockroachPlatform() { + super(); + this.platform = Platform.COCKROACH; + // no like escape clause supported + this.likeSpecialCharacters = new char[]{'%', '_'}; + this.likeClauseRaw = "like ?"; + this.likeClauseEscaped = "like ?"; + } + + /** + * Needs a commit after create index such that alter table add foreign key ... succeeds. + */ + @Override + public boolean isDdlCommitOnCreateIndex() { + return true; + } + +} diff --git a/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java b/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java index ae1ef4d08..68455dd89 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java +++ b/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java @@ -37,6 +37,7 @@ public class DdlGenerator { private final boolean runDdl; private final boolean createOnly; private final boolean jaxbPresent; + private final boolean ddlCommitOnCreateIndex; private CurrentModel currentModel; private String dropAllContent; @@ -50,8 +51,10 @@ public class DdlGenerator { if (!serverConfig.getTenantMode().isDdlEnabled() && serverConfig.isDdlRun()) { log.warn("DDL can't be run on startup with TenantMode " + serverConfig.getTenantMode()); this.runDdl = false; + this.ddlCommitOnCreateIndex = false; } else { this.runDdl = serverConfig.isDdlRun(); + this.ddlCommitOnCreateIndex = server.getDatabasePlatform().isDdlCommitOnCreateIndex(); } } @@ -109,6 +112,8 @@ public class DdlGenerator { try { if (expectErrors) { connection.setAutoCommit(true); + } else if (ddlCommitOnCreateIndex) { + runner.setCommitOnCreateIndex(); } int count = runner.runAll(content, connection); if (expectErrors) { diff --git a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/CockroachDdl.java b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/CockroachDdl.java new file mode 100644 index 000000000..303c887bd --- /dev/null +++ b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/CockroachDdl.java @@ -0,0 +1,57 @@ +package io.ebeaninternal.dbmigration.ddlgeneration.platform; + +import io.ebean.config.dbplatform.DatabasePlatform; +import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer; + +/** + * CockroachDB specific DDL handling. + */ +public class CockroachDdl extends PlatformDdl { + + public CockroachDdl(DatabasePlatform platform) { + super(platform); + this.historyDdl = new PostgresHistoryDdl(); + this.dropTableCascade = " cascade"; + this.columnSetType = "type "; + this.alterTableIfExists = "if exists "; + this.columnSetNull = "drop not null"; + } + +// @Override +// protected String convertArrayType(String logicalArrayType) { +// TODO: Need to look at DB Array support ... +// } + + /** + * Map bigint, integer and smallint all into serial. + */ + @Override + public String asIdentityColumn(String columnDefn) { + + if ("bigint".equalsIgnoreCase(columnDefn)) { + return "serial"; + } + if ("integer".equalsIgnoreCase(columnDefn)) { + return "serial"; + } + if ("smallint".equalsIgnoreCase(columnDefn)) { + return "serial"; + } + return columnDefn; + } + + @Override + public void addTableComment(DdlBuffer apply, String tableName, String tableComment) { + // do nothing + } + + @Override + public void addColumnComment(DdlBuffer apply, String table, String column, String comment) { + // do nothing + } + + @Override + public boolean isInlineComments() { + return false; + } +} diff --git a/src/main/java/io/ebeaninternal/server/core/DatabasePlatformFactory.java b/src/main/java/io/ebeaninternal/server/core/DatabasePlatformFactory.java index 3f98a1c6d..75db54f34 100644 --- a/src/main/java/io/ebeaninternal/server/core/DatabasePlatformFactory.java +++ b/src/main/java/io/ebeaninternal/server/core/DatabasePlatformFactory.java @@ -2,6 +2,7 @@ package io.ebeaninternal.server.core; import io.ebean.config.ServerConfig; import io.ebean.config.dbplatform.DatabasePlatform; +import io.ebean.config.dbplatform.cockroach.CockroachPlatform; import io.ebean.config.dbplatform.db2.DB2Platform; import io.ebean.config.dbplatform.h2.H2Platform; import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform; @@ -13,6 +14,7 @@ import io.ebean.config.dbplatform.sqlanywhere.SqlAnywherePlatform; import io.ebean.config.dbplatform.sqlite.SQLitePlatform; import io.ebean.config.dbplatform.sqlserver.SqlServer16Platform; import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform; +import io.ebean.migration.util.JdbcClose; import io.ebeaninternal.dbmigration.DbOffline; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,6 +23,8 @@ import javax.persistence.PersistenceException; import javax.sql.DataSource; import java.sql.Connection; import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; /** @@ -111,20 +115,20 @@ public class DatabasePlatformFactory { */ private DatabasePlatform byDataSource(DataSource dataSource) { - Connection conn = null; + Connection connection = null; try { - conn = dataSource.getConnection(); - DatabaseMetaData metaData = conn.getMetaData(); + connection = dataSource.getConnection(); + DatabaseMetaData metaData = connection.getMetaData(); - return byDatabaseMeta(metaData); + return byDatabaseMeta(metaData, connection); } catch (SQLException ex) { throw new PersistenceException(ex); } finally { try { - if (conn != null) { - conn.close(); + if (connection != null) { + connection.close(); } } catch (SQLException ex) { logger.error(null, ex); @@ -135,7 +139,7 @@ public class DatabasePlatformFactory { /** * Find the platform by the metaData.getDatabaseProductName(). */ - private DatabasePlatform byDatabaseMeta(DatabaseMetaData metaData) throws SQLException { + private DatabasePlatform byDatabaseMeta(DatabaseMetaData metaData, Connection connection) throws SQLException { String dbProductName = metaData.getDatabaseProductName(); dbProductName = dbProductName.toLowerCase(); @@ -151,7 +155,7 @@ public class DatabasePlatformFactory { } else if (dbProductName.contains("hsql database engine")) { return new HsqldbPlatform(); } else if (dbProductName.contains("postgres")) { - return new PostgresPlatform(); + return readPostgres(connection); } else if (dbProductName.contains("sqlite")) { return new SQLitePlatform(); } else if (dbProductName.contains("db2")) { @@ -163,4 +167,32 @@ public class DatabasePlatformFactory { // use the standard one return new DatabasePlatform(); } + + /** + * Use a select version() query as it could be Postgres or CockroachDB. + */ + private static PostgresPlatform readPostgres(Connection connection) { + // Postgres driver uses a hardcoded product name so use version() query + PreparedStatement statement = null; + ResultSet resultSet = null; + try { + statement = connection.prepareStatement("SELECT version() AS \"version\""); + resultSet = statement.executeQuery(); + if (resultSet.next()) { + String productVersion = resultSet.getString("version").toLowerCase(); + if (productVersion.contains("cockroach")) { + return new CockroachPlatform(); + } + } + } catch (SQLException e) { + logger.warn("Error running detection query on Postgres", e); + + } finally { + JdbcClose.close(resultSet); + JdbcClose.close(statement); + } + + // Real Postgres + return new PostgresPlatform(); + } } diff --git a/src/main/java/io/ebeaninternal/server/core/PlatformDdlBuilder.java b/src/main/java/io/ebeaninternal/server/core/PlatformDdlBuilder.java index efd69538e..bf901ed50 100644 --- a/src/main/java/io/ebeaninternal/server/core/PlatformDdlBuilder.java +++ b/src/main/java/io/ebeaninternal/server/core/PlatformDdlBuilder.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.core; import io.ebean.config.dbplatform.DatabasePlatform; +import io.ebeaninternal.dbmigration.ddlgeneration.platform.CockroachDdl; import io.ebeaninternal.dbmigration.ddlgeneration.platform.DB2Ddl; import io.ebeaninternal.dbmigration.ddlgeneration.platform.H2Ddl; import io.ebeaninternal.dbmigration.ddlgeneration.platform.HsqldbDdl; @@ -38,6 +39,8 @@ public class PlatformDdlBuilder { return new PlatformDdl(platform); case POSTGRES: return new PostgresDdl(platform); + case COCKROACH: + return new CockroachDdl(platform); case SQLANYWHERE: return new PlatformDdl(platform); case SQLSERVER16: