From a894924e96513c773fc984ff3bb099e9d7dccd20 Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Wed, 29 Jan 2020 20:22:32 +1300 Subject: [PATCH] #1920 Support @Index concurrently and definition --- pom.xml | 4 +- src/main/java/io/ebean/ScriptRunner.java | 9 +++ .../dbmigration/DdlGenerator.java | 2 +- .../ddlgeneration/platform/BaseTableDdl.java | 8 +- .../ddlgeneration/platform/ClickHouseDdl.java | 4 +- .../platform/HanaColumnStoreDdl.java | 11 ++- .../ddlgeneration/platform/MySqlDdl.java | 2 +- .../ddlgeneration/platform/PlatformDdl.java | 26 +++++-- .../ddlgeneration/platform/PostgresDdl.java | 9 ++- .../ddlgeneration/platform/SqlServerDdl.java | 2 +- .../platform/WriteCreateIndex.java | 61 +++++++++++++++ .../migration/AddUniqueConstraint.java | 15 +++- .../dbmigration/migration/CreateIndex.java | 75 +++++++++++++++++-- .../dbmigration/migration/CreateTable.java | 1 + .../dbmigration/migration/DropIndex.java | 38 +++++++++- .../migration/UniqueConstraint.java | 15 +++- .../dbmigration/model/MIndex.java | 43 ++++++++--- .../build/ModelBuildPropertyVisitor.java | 2 +- .../server/core/DScriptRunner.java | 14 +++- .../server/deploy/IndexDefinition.java | 31 ++++++-- .../server/deploy/parse/AnnotationClass.java | 3 +- .../server/deploy/parse/AnnotationFields.java | 2 +- src/main/resources/ebean-dbmigration-1.0.xsd | 3 + src/test/ddl-review/pg-create-all.sql | 4 +- src/test/ddl-review/pg-drop-all.sql | 4 +- .../dbmigration/DbMigrationTest.java | 51 +++++-------- .../server/deploy/IndexDefinitionTest.java | 41 ++++++++++ .../java/misc/migration/v1_0/OtoChild.java | 4 +- .../java/org/tests/model/basic/TSDetail.java | 4 +- .../tests/saveassociation/TestSaveSamePK.java | 19 ++--- .../model/1.0__initial.model.xml | 2 + .../migrationtest/model/1.1.model.xml | 2 + .../migrationtest/postgres/1.0__initial.sql | 2 + .../migrationtest/postgres/1.1.sql | 2 + 34 files changed, 401 insertions(+), 114 deletions(-) create mode 100644 src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/WriteCreateIndex.java create mode 100644 src/test/java/io/ebeaninternal/server/deploy/IndexDefinitionTest.java diff --git a/pom.xml b/pom.xml index bec721071..591f8b9e4 100644 --- a/pom.xml +++ b/pom.xml @@ -118,7 +118,7 @@ io.ebean ebean-annotation - 6.3 + 6.4 @@ -136,7 +136,7 @@ io.ebean ebean-migration - 12.1.3 + 12.1.4 diff --git a/src/main/java/io/ebean/ScriptRunner.java b/src/main/java/io/ebean/ScriptRunner.java index 2336d1628..15057741e 100644 --- a/src/main/java/io/ebean/ScriptRunner.java +++ b/src/main/java/io/ebean/ScriptRunner.java @@ -50,4 +50,13 @@ public interface ScriptRunner { */ void run(URL resource, Map placeholderMap); + /** + * Run the raw provided DDL or SQL script. + * + * @param name The name of the script for logging purposes + * @param content The SQL content + * @param useAutoCommit Set to true to use auto commit true and continue when any errors occur + */ + void runScript(String name, String content, boolean useAutoCommit); + } diff --git a/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java b/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java index ebc8b9ded..95ed4c942 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java +++ b/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java @@ -182,7 +182,7 @@ public class DdlGenerator { } private DdlRunner createDdlRunner(boolean expectErrors, String scriptName) { - return new DdlRunner(expectErrors, scriptName, DdlAutoCommit.forPlatform(platformName)); + return new DdlRunner(expectErrors, scriptName, platformName); } protected void runDropSql(Connection connection) throws IOException { diff --git a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java index 3d7e74065..b7622de18 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java +++ b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java @@ -451,7 +451,7 @@ public class BaseTableDdl implements TableDdl { String tableName = lowerTableName(request.table()); if (request.indexName() != null) { // no matching unique constraint so add the index - fkeyBuffer.appendStatement(platformDdl.createIndex(request.indexName(), tableName, request.cols(), false)); + fkeyBuffer.appendStatement(platformDdl.createIndex(new WriteCreateIndex(request.indexName(), tableName, request.cols(), false))); } alterTableAddForeignKey(write.getOptions(), fkeyBuffer, request); @@ -611,15 +611,15 @@ public class BaseTableDdl implements TableDdl { @Override public void generate(DdlWrite writer, CreateIndex index) throws IOException { if (platformInclude(index.getPlatforms())) { - writer.apply().appendStatement(platformDdl.createIndex(index.getIndexName(), index.getTableName(), split(index.getColumns()), Boolean.TRUE.equals(index.isUnique()))); - writer.dropAll().appendStatement(platformDdl.dropIndex(index.getIndexName(), index.getTableName())); + writer.apply().appendStatement(platformDdl.createIndex(new WriteCreateIndex(index))); + writer.dropAll().appendStatement(platformDdl.dropIndex(index.getIndexName(), index.getTableName(), Boolean.TRUE.equals(index.isConcurrent()))); } } @Override public void generate(DdlWrite writer, DropIndex dropIndex) throws IOException { if (platformInclude(dropIndex.getPlatforms())) { - writer.apply().appendStatement(platformDdl.dropIndex(dropIndex.getIndexName(), dropIndex.getTableName())); + writer.apply().appendStatement(platformDdl.dropIndex(dropIndex.getIndexName(), dropIndex.getTableName(), Boolean.TRUE.equals(dropIndex.isConcurrent()))); } } diff --git a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/ClickHouseDdl.java b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/ClickHouseDdl.java index ee3fe32eb..f9f943385 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/ClickHouseDdl.java +++ b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/ClickHouseDdl.java @@ -56,12 +56,12 @@ public class ClickHouseDdl extends PlatformDdl { } @Override - public String dropIndex(String indexName, String tableName) { + public String dropIndex(String indexName, String tableName, boolean concurrent) { return null; } @Override - public String createIndex(String indexName, String tableName, String[] columns, boolean unique) { + public String createIndex(WriteCreateIndex create) { return null; } diff --git a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/HanaColumnStoreDdl.java b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/HanaColumnStoreDdl.java index 39fee3dd3..872fc3930 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/HanaColumnStoreDdl.java +++ b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/HanaColumnStoreDdl.java @@ -13,25 +13,24 @@ public class HanaColumnStoreDdl extends AbstractHanaDdl { } @Override - public String createIndex(String indexName, String tableName, String[] columns, boolean unique) { + public String createIndex(WriteCreateIndex create) { + final String[] columns = create.getColumns(); if (columns == null || columns.length == 0) { return "-- cannot create index: no columns given"; } - if (columns.length == 1) { - return "-- explicit index \"" + indexName + "\" for single column \"" + columns[0] + "\" of table \"" + tableName + return "-- explicit index \"" + create.getIndexName() + "\" for single column \"" + columns[0] + "\" of table \"" + create.getTableName() + "\" is not necessary"; } StringBuilder buffer = new StringBuilder(); - buffer.append("create inverted hash index ").append(maxConstraintName(indexName)).append(" on ").append(tableName); + buffer.append("create inverted hash index ").append(maxConstraintName(create.getIndexName())).append(" on ").append(create.getTableName()); appendColumns(columns, buffer); - return buffer.toString(); } @Override - public String dropIndex(String indexName, String tableName) { + public String dropIndex(String indexName, String tableName, boolean concurrent) { DdlBuffer buffer = new BaseDdlBuffer(null); try { buffer.append("delimiter $$").newLine(); diff --git a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/MySqlDdl.java b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/MySqlDdl.java index 4db6d6bff..d0009c0e4 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/MySqlDdl.java +++ b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/MySqlDdl.java @@ -30,7 +30,7 @@ public class MySqlDdl extends PlatformDdl { * Return the drop index statement. */ @Override - public String dropIndex(String indexName, String tableName) { + public String dropIndex(String indexName, String tableName, boolean concurrent) { return "drop index " + maxConstraintName(indexName) + " on " + tableName; } diff --git a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl.java b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl.java index 9d59faa5e..dc99cbed5 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl.java +++ b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl.java @@ -110,6 +110,7 @@ public class PlatformDdl { protected String addForeignKeySkipCheck = ""; protected String uniqueIndex = "unique"; + protected String indexConcurrent = ""; /** * Set false for MsSqlServer to allow multiple nulls for OneToOne mapping. @@ -382,23 +383,34 @@ public class PlatformDdl { } /** - * Return the drop index statement. + * Return the drop index statement for known non concurrent index. */ public String dropIndex(String indexName, String tableName) { - return dropIndexIfExists + maxConstraintName(indexName); + return dropIndex(indexName, tableName, false); } /** - * Return the create index statement. + * Return the drop index statement. */ - public String createIndex(String indexName, String tableName, String[] columns, boolean unique) { + public String dropIndex(String indexName, String tableName, boolean concurrent) { + return dropIndexIfExists + maxConstraintName(indexName); + } + + public String createIndex(WriteCreateIndex create) { + if (create.useDefinition()) { + return create.getDefinition(); + } StringBuilder buffer = new StringBuilder(); buffer.append("create "); - if (unique) { + if (create.isUnique()) { buffer.append(uniqueIndex).append(" "); } - buffer.append("index ").append(maxConstraintName(indexName)).append(" on ").append(tableName); - appendColumns(columns, buffer); + buffer.append("index "); + if (create.isConcurrent()) { + buffer.append(indexConcurrent); + } + buffer.append(maxConstraintName(create.getIndexName())).append(" on ").append(create.getTableName()); + appendColumns(create.getColumns(), buffer); return buffer.toString(); } diff --git a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PostgresDdl.java b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PostgresDdl.java index 017f8f4fb..8a36b57b7 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PostgresDdl.java +++ b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PostgresDdl.java @@ -10,6 +10,8 @@ import java.io.IOException; */ public class PostgresDdl extends PlatformDdl { + private static final String dropIndexConcurrentlyIfExists = "drop index concurrently if exists "; + public PostgresDdl(DatabasePlatform platform) { super(platform); this.historyDdl = new PostgresHistoryDdl(); @@ -18,6 +20,7 @@ public class PostgresDdl extends PlatformDdl { this.alterTableIfExists = "if exists "; this.columnSetNull = "drop not null"; this.addForeignKeySkipCheck = " not valid"; + this.indexConcurrent = "concurrently "; } public String setLockTimeout(int lockTimeoutSeconds) { @@ -39,7 +42,6 @@ public class PostgresDdl extends PlatformDdl { */ @Override public String asIdentityColumn(String columnDefn) { - if ("bigint".equalsIgnoreCase(columnDefn)) { return "bigserial"; } @@ -56,4 +58,9 @@ public class PostgresDdl extends PlatformDdl { public void addTablePartition(DdlBuffer apply, String partitionMode, String partitionColumn) throws IOException { apply.append(" partition by range (").append(partitionColumn).append(")"); } + + @Override + public String dropIndex(String indexName, String tableName, boolean concurrent) { + return (concurrent ? dropIndexConcurrentlyIfExists : dropIndexIfExists) + maxConstraintName(indexName); + } } diff --git a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/SqlServerDdl.java b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/SqlServerDdl.java index 0db74278f..7dc2a2fba 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/SqlServerDdl.java +++ b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/SqlServerDdl.java @@ -58,7 +58,7 @@ public class SqlServerDdl extends PlatformDdl { } @Override - public String dropIndex(String indexName, String tableName) { + public String dropIndex(String indexName, String tableName, boolean concurrent) { return "IF EXISTS (SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID('" + tableName + "','U') AND name = '" + maxConstraintName(indexName) + "') drop index " + maxConstraintName(indexName) + " ON " + tableName; } diff --git a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/WriteCreateIndex.java b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/WriteCreateIndex.java new file mode 100644 index 000000000..c743c979c --- /dev/null +++ b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/WriteCreateIndex.java @@ -0,0 +1,61 @@ +package io.ebeaninternal.dbmigration.ddlgeneration.platform; + +import io.ebeaninternal.dbmigration.migration.CreateIndex; + +import static io.ebeaninternal.dbmigration.ddlgeneration.platform.SplitColumns.split; + +class WriteCreateIndex { + + private final String indexName; + private final String tableName; + private final String[] columns; + private final boolean unique; + private final boolean concurrent; + private final String definition; + + WriteCreateIndex(String indexName, String tableName, String[] columns, boolean unique) { + this.indexName = indexName; + this.tableName = tableName; + this.columns = columns; + this.unique = unique; + this.concurrent = false; + this.definition = null; + } + + public WriteCreateIndex(CreateIndex index) { + this.indexName = index.getIndexName(); + this.tableName = index.getTableName(); + this.columns = split(index.getColumns()); + this.unique = Boolean.TRUE.equals(index.isUnique()); + this.concurrent = Boolean.TRUE.equals(index.isConcurrent()); + this.definition = index.getDefinition(); + } + + public String getIndexName() { + return indexName; + } + + public String getTableName() { + return tableName; + } + + public String[] getColumns() { + return columns; + } + + public boolean isUnique() { + return unique; + } + + public boolean isConcurrent() { + return concurrent; + } + + public String getDefinition() { + return definition; + } + + public boolean useDefinition() { + return definition != null && !definition.isEmpty(); + } +} diff --git a/src/main/java/io/ebeaninternal/dbmigration/migration/AddUniqueConstraint.java b/src/main/java/io/ebeaninternal/dbmigration/migration/AddUniqueConstraint.java index c98c28b59..5041a194e 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/migration/AddUniqueConstraint.java +++ b/src/main/java/io/ebeaninternal/dbmigration/migration/AddUniqueConstraint.java @@ -146,16 +146,23 @@ public class AddUniqueConstraint { } /** - * Return the platforms. + * Gets the value of the platforms property. + * + * @return possible object is + * {@link String } */ public String getPlatforms() { return platforms; } /** - * Set the platforms. + * Sets the value of the platforms property. + * + * @param value allowed object is + * {@link String } */ - public void setPlatforms(String platforms) { - this.platforms = platforms; + public void setPlatforms(String value) { + this.platforms = value; } + } diff --git a/src/main/java/io/ebeaninternal/dbmigration/migration/CreateIndex.java b/src/main/java/io/ebeaninternal/dbmigration/migration/CreateIndex.java index de0cdb0e3..d3c796c8c 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/migration/CreateIndex.java +++ b/src/main/java/io/ebeaninternal/dbmigration/migration/CreateIndex.java @@ -20,6 +20,8 @@ import javax.xml.bind.annotation.XmlType; * <attribute name="tableName" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="columns" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="unique" type="{http://www.w3.org/2001/XMLSchema}boolean" /> + * <attribute name="concurrent" type="{http://www.w3.org/2001/XMLSchema}boolean" /> + * <attribute name="definition" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="platforms" type="{http://www.w3.org/2001/XMLSchema}string" /> * </restriction> * </complexContent> @@ -39,6 +41,10 @@ public class CreateIndex { protected String columns; @XmlAttribute(name = "unique") protected Boolean unique; + @XmlAttribute(name = "concurrent") + protected Boolean concurrent; + @XmlAttribute(name = "definition") + protected String definition; @XmlAttribute(name = "platforms") protected String platforms; @@ -103,30 +109,83 @@ public class CreateIndex { } /** - * Return the unique property. + * Gets the value of the unique property. + * + * @return possible object is + * {@link Boolean } */ public Boolean isUnique() { return unique; } /** - * Set the unique property. + * Sets the value of the unique property. + * + * @param value allowed object is + * {@link Boolean } */ - public void setUnique(Boolean unique) { - this.unique = unique; + public void setUnique(Boolean value) { + this.unique = value; } /** - * Return the platforms. + * Gets the value of the concurrent property. + * + * @return possible object is + * {@link Boolean } + */ + public Boolean isConcurrent() { + return concurrent; + } + + /** + * Sets the value of the concurrent property. + * + * @param value allowed object is + * {@link Boolean } + */ + public void setConcurrent(Boolean value) { + this.concurrent = value; + } + + /** + * Gets the value of the definition property. + * + * @return possible object is + * {@link String } + */ + public String getDefinition() { + return definition; + } + + /** + * Sets the value of the definition property. + * + * @param value allowed object is + * {@link String } + */ + public void setDefinition(String value) { + this.definition = value; + } + + /** + * Gets the value of the platforms property. + * + * @return possible object is + * {@link String } */ public String getPlatforms() { return platforms; } /** - * Set the platforms. + * Sets the value of the platforms property. + * + * @param value allowed object is + * {@link String } */ - public void setPlatforms(String platforms) { - this.platforms = platforms; + public void setPlatforms(String value) { + this.platforms = value; } + } diff --git a/src/main/java/io/ebeaninternal/dbmigration/migration/CreateTable.java b/src/main/java/io/ebeaninternal/dbmigration/migration/CreateTable.java index c088ffc09..3e52db4a1 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/migration/CreateTable.java +++ b/src/main/java/io/ebeaninternal/dbmigration/migration/CreateTable.java @@ -37,6 +37,7 @@ import java.util.List; * <attribute name="sequenceInitial" type="{http://www.w3.org/2001/XMLSchema}positiveInteger" /> * <attribute name="sequenceAllocate" type="{http://www.w3.org/2001/XMLSchema}positiveInteger" /> * <attribute name="pkName" type="{http://www.w3.org/2001/XMLSchema}string" /> + * <attribute name="storageEngine" type="{http://www.w3.org/2001/XMLSchema}string" /> * </restriction> * </complexContent> * </complexType> diff --git a/src/main/java/io/ebeaninternal/dbmigration/migration/DropIndex.java b/src/main/java/io/ebeaninternal/dbmigration/migration/DropIndex.java index c05b92a78..201634741 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/migration/DropIndex.java +++ b/src/main/java/io/ebeaninternal/dbmigration/migration/DropIndex.java @@ -18,6 +18,7 @@ import javax.xml.bind.annotation.XmlType; * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType"> * <attribute name="indexName" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="tableName" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /> + * <attribute name="concurrent" type="{http://www.w3.org/2001/XMLSchema}boolean" /> * <attribute name="platforms" type="{http://www.w3.org/2001/XMLSchema}string" /> * </restriction> * </complexContent> @@ -33,6 +34,8 @@ public class DropIndex { protected String indexName; @XmlAttribute(name = "tableName", required = true) protected String tableName; + @XmlAttribute(name = "concurrent") + protected Boolean concurrent; @XmlAttribute(name = "platforms") protected String platforms; @@ -77,16 +80,43 @@ public class DropIndex { } /** - * Return the platforms. + * Gets the value of the concurrent property. + * + * @return possible object is + * {@link Boolean } + */ + public Boolean isConcurrent() { + return concurrent; + } + + /** + * Sets the value of the concurrent property. + * + * @param value allowed object is + * {@link Boolean } + */ + public void setConcurrent(Boolean value) { + this.concurrent = value; + } + + /** + * Gets the value of the platforms property. + * + * @return possible object is + * {@link String } */ public String getPlatforms() { return platforms; } /** - * Set the platforms. + * Sets the value of the platforms property. + * + * @param value allowed object is + * {@link String } */ - public void setPlatforms(String platforms) { - this.platforms = platforms; + public void setPlatforms(String value) { + this.platforms = value; } + } diff --git a/src/main/java/io/ebeaninternal/dbmigration/migration/UniqueConstraint.java b/src/main/java/io/ebeaninternal/dbmigration/migration/UniqueConstraint.java index 7f7de32f3..f4abbe7b7 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/migration/UniqueConstraint.java +++ b/src/main/java/io/ebeaninternal/dbmigration/migration/UniqueConstraint.java @@ -123,16 +123,23 @@ public class UniqueConstraint { } /** - * Return the platforms. + * Gets the value of the platforms property. + * + * @return possible object is + * {@link String } */ public String getPlatforms() { return platforms; } /** - * Set the platforms. + * Sets the value of the platforms property. + * + * @param value allowed object is + * {@link String } */ - public void setPlatforms(String platforms) { - this.platforms = platforms; + public void setPlatforms(String value) { + this.platforms = value; } + } diff --git a/src/main/java/io/ebeaninternal/dbmigration/model/MIndex.java b/src/main/java/io/ebeaninternal/dbmigration/model/MIndex.java index 08556c414..976bde5f3 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/model/MIndex.java +++ b/src/main/java/io/ebeaninternal/dbmigration/model/MIndex.java @@ -6,6 +6,7 @@ import io.ebeaninternal.dbmigration.migration.DropIndex; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; /** * Index as part of the logical model. @@ -13,14 +14,12 @@ import java.util.List; public class MIndex { private String tableName; - private String indexName; - private String platforms; - private List columns = new ArrayList<>(); - private boolean unique; + private boolean concurrent; + private String definition; /** * Create a single column non unique index. @@ -31,12 +30,6 @@ public class MIndex { this.columns.add(columnName); } - public MIndex(String indexName, String tableName, String[] columnNames, String platforms, boolean unique) { - this(indexName, tableName, columnNames); - this.platforms = platforms; - this.unique = unique; - } - /** * Create a multi column non unique index. */ @@ -46,12 +39,22 @@ public class MIndex { Collections.addAll(this.columns, columnNames); } + public MIndex(String indexName, String tableName, String[] columnNames, String platforms, boolean unique, boolean concurrent, String definition) { + this(indexName, tableName, columnNames); + this.platforms = platforms; + this.unique = unique; + this.concurrent = concurrent; + this.definition = emptyToNull(definition); + } + public MIndex(CreateIndex createIndex) { this.indexName = createIndex.getIndexName(); this.tableName = createIndex.getTableName(); this.columns = split(createIndex.getColumns()); this.platforms = createIndex.getPlatforms(); this.unique = Boolean.TRUE.equals(createIndex.isUnique()); + this.concurrent = Boolean.TRUE.equals(createIndex.isConcurrent()); + this.definition = emptyToNull(createIndex.getDefinition()); } public String getKey() { @@ -92,9 +95,20 @@ public class MIndex { if (Boolean.TRUE.equals(unique)) { create.setUnique(Boolean.TRUE); } + if (Boolean.TRUE.equals(concurrent)) { + create.setConcurrent(Boolean.TRUE); + } + create.setDefinition(emptyToNull(definition)); return create; } + private String emptyToNull(String val) { + if (val == null || val.isEmpty()) { + return null; + } + return val; + } + /** * Create a DropIndex migration for this index. */ @@ -103,6 +117,9 @@ public class MIndex { dropIndex.setIndexName(indexName); dropIndex.setTableName(tableName); dropIndex.setPlatforms(platforms); + if (Boolean.TRUE.equals(concurrent)) { + dropIndex.setConcurrent(Boolean.TRUE); + } return dropIndex; } @@ -127,6 +144,9 @@ public class MIndex { if (unique != newIndex.unique) { return true; } + if (!Objects.equals(definition, newIndex.definition)) { + return true; + } List newColumns = newIndex.getColumns(); if (columns.size() != newColumns.size()) { return true; @@ -141,6 +161,9 @@ public class MIndex { private List split(String columns) { + if (columns.isEmpty()) { + return Collections.emptyList(); + } String[] cols = columns.split(","); List colList = new ArrayList<>(cols.length); Collections.addAll(colList, cols); diff --git a/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java b/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java index 969a7cf23..2d474883b 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java +++ b/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java @@ -92,7 +92,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor { } private MIndex createMIndex(String indexName, String tableName, IndexDefinition index) { - return new MIndex(indexName, tableName, index.getColumns(), platforms(index.getPlatforms()), index.isUnique()); + return new MIndex(indexName, tableName, index.getColumns(), platforms(index.getPlatforms()), index.isUnique(), index.isConcurrent(), index.getDefinition()); } private String platforms(Platform[] platforms) { diff --git a/src/main/java/io/ebeaninternal/server/core/DScriptRunner.java b/src/main/java/io/ebeaninternal/server/core/DScriptRunner.java index 5fdea4a9a..171f66724 100644 --- a/src/main/java/io/ebeaninternal/server/core/DScriptRunner.java +++ b/src/main/java/io/ebeaninternal/server/core/DScriptRunner.java @@ -22,9 +22,11 @@ class DScriptRunner implements ScriptRunner { private static final String NEWLINE = "\n"; private final SpiEbeanServer server; + private final String platformName; DScriptRunner(SpiEbeanServer server) { this.server = server; + this.platformName = this.server.getDatabasePlatform().getPlatform().base().name(); } @Override @@ -56,7 +58,7 @@ class DScriptRunner implements ScriptRunner { } String content = content(resource); - runScript(content, scriptName, placeholderMap); + runScript(content, scriptName, placeholderMap, false); } private String content(URL resource) { @@ -72,21 +74,25 @@ class DScriptRunner implements ScriptRunner { } } + @Override + public void runScript(String name, String content, boolean useAutoCommit) { + runScript(content, name, null, useAutoCommit); + } /** * Execute all the DDL statements in the script. */ - private void runScript(String content, String scriptName, Map placeholderMap) { - + private void runScript(String content, String scriptName, Map placeholderMap, boolean useAutoCommit) { try { if (placeholderMap != null) { content = ScriptTransform.build(null, placeholderMap).transform(content); } try (Connection connection = obtainConnection()) { - DdlRunner runner = new DdlRunner(false, scriptName); + DdlRunner runner = new DdlRunner(useAutoCommit, scriptName, platformName); runner.runAll(content, connection); connection.commit(); + runner.runNonTransactional(connection); } } catch (SQLException e) { diff --git a/src/main/java/io/ebeaninternal/server/deploy/IndexDefinition.java b/src/main/java/io/ebeaninternal/server/deploy/IndexDefinition.java index 65bf62665..3fec7ecf4 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/IndexDefinition.java +++ b/src/main/java/io/ebeaninternal/server/deploy/IndexDefinition.java @@ -8,21 +8,22 @@ import io.ebean.annotation.Platform; public class IndexDefinition { private final String[] columns; - private final String name; - private final Platform[] platforms; - private final boolean unique; + private final boolean concurrent; + private final String definition; /** * Create from Index annotation. */ - public IndexDefinition(String[] columns, String name, boolean unique, Platform[] platforms) { + public IndexDefinition(String[] columns, String name, boolean unique, Platform[] platforms, boolean concurrent, String definition) { this.columns = columns; this.unique = unique; this.name = name; this.platforms = platforms; + this.concurrent = concurrent; + this.definition = definition; } /** @@ -33,13 +34,19 @@ public class IndexDefinition { this.unique = true; this.name = null; this.platforms = null; + this.concurrent = false; + this.definition = null; } /** * Return true if this can be used as a unique constraint. */ public boolean isUniqueConstraint() { - return unique && noColumnFormulas(); + return unique && !concurrent && noDefinition() && noColumnFormulas(); + } + + private boolean noDefinition() { + return definition == null || definition.isEmpty(); } private boolean noColumnFormulas() { @@ -78,4 +85,18 @@ public class IndexDefinition { public Platform[] getPlatforms() { return platforms; } + + /** + * Return true if this index has the concurrent flag. + */ + public boolean isConcurrent() { + return concurrent; + } + + /** + * Return the raw definition of the index if supplied. + */ + public String getDefinition() { + return definition; + } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java index 3281b258a..a20943388 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java @@ -134,7 +134,8 @@ public class AnnotationClass extends AnnotationParser { } for (Index index : findAnnotationsRecursive(cls, Index.class)) { - descriptor.addIndex(new IndexDefinition(convertColumnNames(index.columnNames()), index.name(), index.unique(), index.platforms())); + descriptor.addIndex(new IndexDefinition(convertColumnNames(index.columnNames()), index.name(), + index.unique(), index.platforms(), index.concurrent(), index.definition())); } UniqueConstraint uc = findAnnotationRecursive(cls, UniqueConstraint.class); diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java index 73e34f9df..3def9df45 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -416,7 +416,7 @@ public class AnnotationFields extends AnnotationParser { if (columnNames.length == 1 && hasRelationshipItem(prop)) { throw new RuntimeException("Can't use Index on foreign key relationships."); } - descriptor.addIndex(new IndexDefinition(columnNames, index.name(), index.unique(), index.platforms())); + descriptor.addIndex(new IndexDefinition(columnNames, index.name(), index.unique(), index.platforms(), index.concurrent(), index.definition())); } private void readJsonAnnotations(DeployBeanProperty prop) { diff --git a/src/main/resources/ebean-dbmigration-1.0.xsd b/src/main/resources/ebean-dbmigration-1.0.xsd index 73d7e91ef..16489724a 100644 --- a/src/main/resources/ebean-dbmigration-1.0.xsd +++ b/src/main/resources/ebean-dbmigration-1.0.xsd @@ -291,6 +291,8 @@ + + @@ -299,6 +301,7 @@ + diff --git a/src/test/ddl-review/pg-create-all.sql b/src/test/ddl-review/pg-create-all.sql index d0fd6457c..996afc929 100644 --- a/src/test/ddl-review/pg-create-all.sql +++ b/src/test/ddl-review/pg-create-all.sql @@ -1,4 +1,4 @@ --- Generated by ebean unknown at 2020-01-29T03:48:48.954541Z +-- Generated by ebean unknown at 2020-01-29T03:58:42.859353Z create table asimple_bean ( id bigserial not null, name varchar(255), @@ -3980,6 +3980,8 @@ create index ix_e_basic_name on e_basic (name); create index ix_efile2_no_fk_owner_id on efile2_no_fk (owner_id); create index ix_ecsm_values_host_id on ecsm_values (host_id); create index ix_organization_node_kind on organization_node (kind); +create unique index concurrently ix_t_detail_with_other_namexxxyy_lowername on t_detail_with_other_namexxxyy (lower(name)); +create index ix_t_detail_with_other_namexxxyy_defn on t_detail_with_other_namexxxyy using hash (lower(name)) where lower(name) like 'r%'; create index ix_bar_foo_id on bar (foo_id); alter table bar add constraint fk_bar_foo_id foreign key (foo_id) references foo (foo_id) on delete restrict on update restrict; diff --git a/src/test/ddl-review/pg-drop-all.sql b/src/test/ddl-review/pg-drop-all.sql index 6fb3709bb..df6f5815b 100644 --- a/src/test/ddl-review/pg-drop-all.sql +++ b/src/test/ddl-review/pg-drop-all.sql @@ -1,4 +1,4 @@ --- Generated by ebean unknown at 2020-01-29T03:48:48.954541Z +-- Generated by ebean unknown at 2020-01-29T03:58:42.859353Z alter table if exists bar drop constraint if exists fk_bar_foo_id; drop index if exists ix_bar_foo_id; @@ -1876,3 +1876,5 @@ drop index if exists ix_e_basic_name; drop index if exists ix_efile2_no_fk_owner_id; drop index if exists ix_ecsm_values_host_id; drop index if exists ix_organization_node_kind; +drop index concurrently if exists ix_t_detail_with_other_namexxxyy_lowername; +drop index if exists ix_t_detail_with_other_namexxxyy_defn; diff --git a/src/test/java/io/ebeaninternal/dbmigration/DbMigrationTest.java b/src/test/java/io/ebeaninternal/dbmigration/DbMigrationTest.java index 4be34902a..53c477266 100644 --- a/src/test/java/io/ebeaninternal/dbmigration/DbMigrationTest.java +++ b/src/test/java/io/ebeaninternal/dbmigration/DbMigrationTest.java @@ -3,17 +3,12 @@ package io.ebeaninternal.dbmigration; import io.ebean.BaseTestCase; import io.ebean.SqlRow; import io.ebean.SqlUpdate; -import io.ebean.Transaction; import io.ebean.annotation.IgnorePlatform; import io.ebean.annotation.Platform; -import io.ebean.migration.ddl.DdlRunner; import io.ebeaninternal.dbmigration.ddlgeneration.Helper; import org.junit.Test; -import javax.persistence.PersistenceException; import java.io.IOException; -import java.sql.Connection; -import java.sql.SQLException; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -25,28 +20,8 @@ public class DbMigrationTest extends BaseTestCase { runScript(expectErrors, ddl, scriptName); } - private void runScript(boolean expectErrors, String content, String scriptName) { - - DdlRunner runner = new DdlRunner(expectErrors, scriptName); - - Transaction transaction = server().createTransaction(); - Connection connection = transaction.getConnection(); - try { - if (expectErrors) { - connection.setAutoCommit(true); - } - runner.runAll(content, connection); - if (expectErrors) { - connection.setAutoCommit(false); - } - transaction.commit(); - - } catch (SQLException e) { - throw new PersistenceException("Failed to run script", e); - - } finally { - transaction.end(); - } + private void runScript(boolean useAutoCommit, String content, String scriptName) { + server().script().runScript(scriptName, content, useAutoCommit); } @IgnorePlatform({Platform.ORACLE, Platform.NUODB}) @@ -188,21 +163,29 @@ public class DbMigrationTest extends BaseTestCase { } private void cleanup(String ... tables) { + + final boolean sqlServer = isSqlServer(); + final boolean postgres = isPostgres(); + StringBuilder sb = new StringBuilder(); for (String table : tables) { // simple and stupid try to execute all commands on all dialects. - sb.append("alter table ").append(table).append(" set ( system_versioning = OFF );\n"); - sb.append("alter table ").append(table).append(" drop system versioning;\n"); - sb.append("drop table ").append(table).append(";\n"); - sb.append("drop table ").append(table).append(" cascade;\n"); - sb.append("drop table ").append(table).append("_history;\n"); - sb.append("drop table ").append(table).append("_history cascade;\n"); + if (sqlServer) { + sb.append("alter table ").append(table).append(" set ( system_versioning = OFF );\n"); + sb.append("alter table ").append(table).append(" drop system versioning;\n"); + } + if (postgres) { + sb.append("drop table ").append(table).append(" cascade;\n"); + sb.append("drop table ").append(table).append("_history cascade;\n"); + } else { + sb.append("drop table ").append(table).append(";\n"); + sb.append("drop table ").append(table).append("_history;\n"); + } sb.append("drop view ").append(table).append("_with_history;\n"); sb.append("drop sequence ").append(table).append("_seq;\n"); } runScript(true, sb.toString(), "cleanup"); runScript(true, sb.toString(), "cleanup"); - } } diff --git a/src/test/java/io/ebeaninternal/server/deploy/IndexDefinitionTest.java b/src/test/java/io/ebeaninternal/server/deploy/IndexDefinitionTest.java new file mode 100644 index 000000000..cf206edaf --- /dev/null +++ b/src/test/java/io/ebeaninternal/server/deploy/IndexDefinitionTest.java @@ -0,0 +1,41 @@ +package io.ebeaninternal.server.deploy; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class IndexDefinitionTest { + + private static final String[] simpleCol1 = new String[]{"one"}; + private static final String[] simpleCol2 = new String[]{"one","two"}; + private static final String[] formulaCol1 = new String[]{"lower(one)"}; + private static final String[] formulaCol2 = new String[]{"one","lower(two)"}; + + @Test + public void isUniqueConstraint_TRUE_when_simpleMultiColumn() { + assertTrue(new IndexDefinition(simpleCol1).isUniqueConstraint()); + assertTrue(new IndexDefinition(simpleCol2).isUniqueConstraint()); + } + + @Test + public void isUniqueConstraint_NOT_when_columnWithFormula() { + assertFalse(new IndexDefinition(formulaCol1).isUniqueConstraint()); + assertFalse(new IndexDefinition(formulaCol2).isUniqueConstraint()); + } + + @Test + public void isUniqueConstraint_NOT_when_concurrentTrue() { + assertFalse(new IndexDefinition(simpleCol1, "name", true, null, true, null).isUniqueConstraint()); + } + + @Test + public void isUniqueConstraint_NOT_when_definitionNotEmpty() { + assertFalse(new IndexDefinition(simpleCol1, "name", true, null, false, "create index foo").isUniqueConstraint()); + } + + @Test + public void isUniqueConstraint_TRUE_otherwise() { + assertTrue(new IndexDefinition(simpleCol1, "name", true, null, false, "").isUniqueConstraint()); + assertTrue(new IndexDefinition(simpleCol1, "name", true, null, false, null).isUniqueConstraint()); + } +} diff --git a/src/test/java/misc/migration/v1_0/OtoChild.java b/src/test/java/misc/migration/v1_0/OtoChild.java index f6d23ca9e..23bc8f569 100644 --- a/src/test/java/misc/migration/v1_0/OtoChild.java +++ b/src/test/java/misc/migration/v1_0/OtoChild.java @@ -8,7 +8,9 @@ import javax.persistence.Table; import static io.ebean.annotation.Platform.POSTGRES; -@Index(columnNames = "lower(name)", platforms = POSTGRES) +@Index(platforms = POSTGRES, name = "idxd_migtest_0", definition = "create index idxd_migtest_0 on migtest_oto_child using hash (upper(name)) where upper(name) = 'JIM'") +@Index(platforms = POSTGRES, columnNames = {"lower(name)","id"}, concurrent = true) +@Index(platforms = POSTGRES, columnNames = "lower(name)") @Entity @Table(name = "migtest_oto_child") public class OtoChild { diff --git a/src/test/java/org/tests/model/basic/TSDetail.java b/src/test/java/org/tests/model/basic/TSDetail.java index 0d9de6d1b..2898ad07e 100644 --- a/src/test/java/org/tests/model/basic/TSDetail.java +++ b/src/test/java/org/tests/model/basic/TSDetail.java @@ -1,6 +1,7 @@ package org.tests.model.basic; import io.ebean.annotation.Index; +import io.ebean.annotation.Platform; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -13,7 +14,8 @@ import javax.validation.constraints.Size; /** * A basic entity to test simple things. */ -//@Index(name = "t_detail_foo_name", unique = true, columnNames = "lower(name)") +@Index(name = "ix_t_detail_with_other_namexxxyy_lowername", unique = true, columnNames = "lower(name)", concurrent = true, platforms = Platform.POSTGRES) +@Index(name = "ix_t_detail_with_other_namexxxyy_defn", platforms = Platform.POSTGRES, definition = "create index ix_t_detail_with_other_namexxxyy_defn on t_detail_with_other_namexxxyy using hash (lower(name)) where lower(name) like 'r%'") @Entity @Table(name = "t_detail_with_other_namexxxyy") public class TSDetail { diff --git a/src/test/java/org/tests/saveassociation/TestSaveSamePK.java b/src/test/java/org/tests/saveassociation/TestSaveSamePK.java index fc9efabeb..e2a21562d 100644 --- a/src/test/java/org/tests/saveassociation/TestSaveSamePK.java +++ b/src/test/java/org/tests/saveassociation/TestSaveSamePK.java @@ -1,11 +1,12 @@ package org.tests.saveassociation; import io.ebean.BaseTestCase; -import io.ebean.Ebean; +import io.ebean.DB; +import org.junit.Test; import org.tests.model.basic.TSDetail; import org.tests.model.basic.TSMaster; -import org.junit.Assert; -import org.junit.Test; + +import static org.junit.Assert.assertNotNull; public class TestSaveSamePK extends BaseTestCase { @@ -13,22 +14,22 @@ public class TestSaveSamePK extends BaseTestCase { public void test() { // delete in case we are running multiple times without full db drop - Ebean.delete(TSMaster.class, 10000); + DB.delete(TSMaster.class, 10000); TSMaster m0 = new TSMaster(); m0.setId(10000); m0.setName("master1"); - Ebean.save(m0); + DB.save(m0); - TSDetail tsDetail = new TSDetail("master1 detail1"); + TSDetail tsDetail = new TSDetail("m4 d1"); tsDetail.setId(10000); tsDetail.setMaster(m0); - Ebean.save(tsDetail); + DB.save(tsDetail); - TSDetail fetchedDetail = Ebean.find(TSDetail.class).setId(10000).fetch("master").findOne(); + TSDetail fetchedDetail = DB.find(TSDetail.class).setId(10000).fetch("master").findOne(); - Assert.assertNotNull(fetchedDetail); + assertNotNull(fetchedDetail); } } diff --git a/src/test/resources/dbmigration/migrationtest/model/1.0__initial.model.xml b/src/test/resources/dbmigration/migrationtest/model/1.0__initial.model.xml index 15c6708fc..68661ce4c 100644 --- a/src/test/resources/dbmigration/migrationtest/model/1.0__initial.model.xml +++ b/src/test/resources/dbmigration/migrationtest/model/1.0__initial.model.xml @@ -115,6 +115,8 @@ + + \ No newline at end of file diff --git a/src/test/resources/dbmigration/migrationtest/model/1.1.model.xml b/src/test/resources/dbmigration/migrationtest/model/1.1.model.xml index 55116e4ec..e165e05d8 100644 --- a/src/test/resources/dbmigration/migrationtest/model/1.1.model.xml +++ b/src/test/resources/dbmigration/migrationtest/model/1.1.model.xml @@ -89,6 +89,8 @@ + + diff --git a/src/test/resources/dbmigration/migrationtest/postgres/1.0__initial.sql b/src/test/resources/dbmigration/migrationtest/postgres/1.0__initial.sql index 22f1bae2d..fa4d80c2c 100644 --- a/src/test/resources/dbmigration/migrationtest/postgres/1.0__initial.sql +++ b/src/test/resources/dbmigration/migrationtest/postgres/1.0__initial.sql @@ -163,6 +163,8 @@ create table migtest_oto_master ( create index ix_migtest_e_basic_indextest1 on migtest_e_basic (indextest1); create index ix_migtest_e_basic_indextest5 on migtest_e_basic (indextest5); +create index idxd_migtest_0 on migtest_oto_child using hash (upper(name)) where upper(name) = 'JIM'; +create index concurrently ix_migtest_oto_child_lowername_id on migtest_oto_child (lower(name),id); create index ix_migtest_oto_child_lowername on migtest_oto_child (lower(name)); create index ix_migtest_fk_cascade_one_id on migtest_fk_cascade (one_id); alter table migtest_fk_cascade add constraint fk_migtest_fk_cascade_one_id foreign key (one_id) references migtest_fk_cascade_one (id) on delete cascade on update cascade; diff --git a/src/test/resources/dbmigration/migrationtest/postgres/1.1.sql b/src/test/resources/dbmigration/migrationtest/postgres/1.1.sql index e6f801cf5..34ec2956d 100644 --- a/src/test/resources/dbmigration/migrationtest/postgres/1.1.sql +++ b/src/test/resources/dbmigration/migrationtest/postgres/1.1.sql @@ -105,6 +105,8 @@ create index ix_migtest_e_basic_indextest6 on migtest_e_basic (indextest6); create index ix_migtest_oto_child_name on migtest_oto_child (name); drop index if exists ix_migtest_e_basic_indextest1; drop index if exists ix_migtest_e_basic_indextest5; +drop index if exists idxd_migtest_0; +drop index concurrently if exists ix_migtest_oto_child_lowername_id; drop index if exists ix_migtest_oto_child_lowername; create index ix_migtest_mtm_c_migtest_mtm_m_migtest_mtm_c on migtest_mtm_c_migtest_mtm_m (migtest_mtm_c_id); alter table migtest_mtm_c_migtest_mtm_m add constraint fk_migtest_mtm_c_migtest_mtm_m_migtest_mtm_c foreign key (migtest_mtm_c_id) references migtest_mtm_c (id) on delete restrict on update restrict;