DbMigration supports altering UniqueConstraints and foreign keys (#1279)

* NEW: Ebean reads version from pop.properties and prints it at start up

* DDL generation and migration can append a header and platformMigration is prepared to append epilog & prolog to scripts

* FIX: Set identity only, if it is not the platform default type

* Updated reference scripts

* ADD basic support for foreign key migration

* DbMigration detects alter foreign keys

* tests
This commit is contained in:
Roland Praml
2018-02-26 14:24:52 +13:00
committed by Rob Bygrave
parent a64a244745
commit da7bb834e7
70 changed files with 1806 additions and 80 deletions
@@ -6,7 +6,9 @@ import io.ebeaninternal.dbmigration.ddlgeneration.platform.PlatformDdl;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.AddTableComment;
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.ChangeSet;
import io.ebeaninternal.dbmigration.migration.CreateIndex;
import io.ebeaninternal.dbmigration.migration.CreateTable;
@@ -59,6 +61,10 @@ public class BaseDdlHandler implements DdlHandler {
generate(writer, (AddHistoryTable) change);
} else if (change instanceof DropHistoryTable) {
generate(writer, (DropHistoryTable) change);
} else if (change instanceof AddUniqueConstraint) {
generate(writer, (AddUniqueConstraint) change);
} else if (change instanceof AlterForeignKey) {
generate(writer, (AlterForeignKey) change);
} else {
throw new IllegalArgumentException("Unsupported change: " + change);
}
@@ -125,4 +131,13 @@ public class BaseDdlHandler implements DdlHandler {
tableDdl.generate(writer, dropIndex);
}
@Override
public void generate(DdlWrite writer, AddUniqueConstraint constraint) throws IOException {
tableDdl.generate(writer, constraint);
}
@Override
public void generate(DdlWrite writer, AlterForeignKey alterForeignKey) throws IOException {
tableDdl.generate(writer, alterForeignKey);
}
}
@@ -3,7 +3,9 @@ package io.ebeaninternal.dbmigration.ddlgeneration;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.AddTableComment;
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.ChangeSet;
import io.ebeaninternal.dbmigration.migration.CreateIndex;
import io.ebeaninternal.dbmigration.migration.CreateTable;
@@ -41,6 +43,10 @@ public interface DdlHandler {
void generate(DdlWrite writer, DropIndex dropIndex) throws IOException;
void generate(DdlWrite writer, AddUniqueConstraint constraint) throws IOException;
void generate(DdlWrite writer, AlterForeignKey alterForeignKey) throws IOException;
void generateProlog(DdlWrite write) throws IOException;
void generateEpilog(DdlWrite write) throws IOException;
@@ -3,7 +3,9 @@ package io.ebeaninternal.dbmigration.ddlgeneration;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.AddTableComment;
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.CreateIndex;
import io.ebeaninternal.dbmigration.migration.CreateTable;
import io.ebeaninternal.dbmigration.migration.DropColumn;
@@ -69,6 +71,16 @@ public interface TableDdl {
void generate(DdlWrite writer, DropIndex dropIndex) throws IOException;
/**
* Write add unique constraint.
*/
void generate(DdlWrite writer, AddUniqueConstraint constraint) throws IOException;
/**
* Writes alter foreign key statements.
* @throws IOException
*/
void generate(DdlWrite writer, AlterForeignKey alterForeignKey) throws IOException;
/**
* Generate any extra DDL such as stored procedures or TableValueParameters.
*/
@@ -13,7 +13,9 @@ import io.ebeaninternal.dbmigration.ddlgeneration.platform.util.IndexSet;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.AddTableComment;
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.Column;
import io.ebeaninternal.dbmigration.migration.CreateIndex;
import io.ebeaninternal.dbmigration.migration.CreateTable;
@@ -330,7 +332,7 @@ public class BaseTableDdl implements TableDdl {
}
String[] columnNames = {col.getName()};
write.apply()
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, Boolean.TRUE.equals(col.isNotnull())))
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, Boolean.TRUE.equals(col.isNotnull()) ? null : columnNames))
.endOfStatement();
write.dropAllForeignKeys()
@@ -340,9 +342,10 @@ public class BaseTableDdl implements TableDdl {
for (UniqueConstraint constraint : externalCompoundUnique) {
String uqName = constraint.getName();
String[] columnNames = StringHelper.delimitedToArray(constraint.getColumnNames(), ",", false);
String[] columnNames = SplitColumns.split(constraint.getColumnNames());
String[] nullableColumns = SplitColumns.split(constraint.getNullableColumns());
write.apply()
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, false)) // TODO: check if nullable
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, nullableColumns))
.endOfStatement();
write.dropAllForeignKeys()
@@ -639,6 +642,40 @@ public class BaseTableDdl implements TableDdl {
.append(platformDdl.dropIndex(dropIndex.getIndexName(), dropIndex.getTableName()))
.endOfStatement();
}
@Override
public void generate(DdlWrite writer, AddUniqueConstraint constraint) throws IOException {
if (DdlHelp.isDropConstraint(constraint.getColumnNames())) {
String ddl = platformDdl.alterTableDropUniqueConstraint(constraint.getTableName(), constraint.getConstraintName());
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
} else {
String[] cols = SplitColumns.split(constraint.getColumnNames());
String[] nullableColumns = SplitColumns.split(constraint.getNullableColumns());
String ddl = platformDdl.alterTableAddUniqueConstraint(constraint.getTableName(), constraint.getConstraintName(), cols, nullableColumns);
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
}
}
@Override
public void generate(DdlWrite writer, AlterForeignKey alterForeignKey) throws IOException {
if (DdlHelp.isDropForeignKey(alterForeignKey.getColumnNames())) {
String ddl = platformDdl.alterTableDropForeignKey(alterForeignKey.getTableName(),
alterForeignKey.getName());
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
} else {
String ddl = platformDdl.alterTableAddForeignKey(new WriteForeignKey(alterForeignKey));
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
}
}
/**
* Add add history table DDL.
@@ -932,7 +969,7 @@ public class BaseTableDdl implements TableDdl {
String[] cols = {alter.getColumnName()};
boolean notNull = alter.isNotnull() != null ? alter.isNotnull() : Boolean.TRUE.equals(alter.isNotnull());
writer.apply()
.append(platformDdl.alterTableAddUniqueConstraint(alter.getTableName(), uqName, cols, notNull))
.append(platformDdl.alterTableAddUniqueConstraint(alter.getTableName(), uqName, cols, notNull ? null : cols))
.endOfStatement();
writer.dropAllForeignKeys()
@@ -18,12 +18,12 @@ public class DB2Ddl extends PlatformDdl {
}
@Override
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, boolean notNull) {
if (notNull) {
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, true);
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
if (nullableColumns == null || nullableColumns.length == 0) {
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
} else {
// Hmm: Complex workaround: https://www.ibm.com/developerworks/mydeveloperworks/blogs/SQLTips4DB2LUW/entry/unique_where_not_null_indexes26?lang=en
return "-- NOT SUPPORTED " + super.alterTableAddUniqueConstraint(tableName, uqName, columns, true);
return "-- NOT SUPPORTED " + super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
}
}
@@ -5,18 +5,35 @@ public class DdlHelp {
public static final String DROP_COMMENT = "DROP COMMENT";
/**
* Return true if the default value is the special DROP DEFAULT value.
*/
public static boolean isDropDefault(String defaultValue) {
return DROP_DEFAULT.equals(defaultValue);
}
public static final String DROP_CONSTRAINT = "DROP CONSTRAINT";
public static final String DROP_FOREIGN_KEY = "DROP FOREIGN KEY";
/**
* Return true if the default value is the special DROP DEFAULT value.
*/
public static boolean isDropComment(String comment) {
return DROP_COMMENT.equals(comment);
public static boolean isDropDefault(String value) {
return DROP_DEFAULT.equals(value);
}
/**
* Return true if the default value is the special DROP COMMENT value.
*/
public static boolean isDropComment(String value) {
return DROP_COMMENT.equals(value);
}
/**
* Return true if the default value is the special DROP CONSTRAINT value.
*/
public static boolean isDropConstraint(String value) {
return DROP_CONSTRAINT.equals(value);
}
/**
* Return true if the default value is the special DROP FOREIGN KEY value.
*/
public static boolean isDropForeignKey(String value) {
return DROP_FOREIGN_KEY.equals(value);
}
}
@@ -108,6 +108,15 @@ public class MySqlDdl extends PlatformDdl {
*/
@Override
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException {
if (DdlHelp.isDropComment(tableComment)) {
tableComment = "";
}
apply.append(String.format("alter table %s comment = '%s'", tableName, tableComment)).endOfStatement();
}
@Override
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
// alter comment currently not supported as it requires to repeat whole column definition
}
}
@@ -22,6 +22,16 @@ public class Oracle10Ddl extends PlatformDdl {
this.identitySuffix = " generated always as identity";
}
@Override
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
if (nullableColumns == null || nullableColumns.length == 0) {
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
} else {
// Hmm: https://stackoverflow.com/questions/11893134/oracle-create-unique-index-but-ignore-nulls
return "-- NOT YET IMPLEMENTED: " + super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
}
}
@Override
protected void appendForeignKeyOnUpdate(StringBuilder buffer, ConstraintMode mode) {
// do nothing, no on update clause for oracle
@@ -432,7 +432,7 @@ public class PlatformDdl {
* <p>
* Overridden by MsSqlServer for specific null handling on unique constraints.
*/
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, boolean notNull) {
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
StringBuilder buffer = new StringBuilder(90);
buffer.append("alter table ").append(tableName).append(" add constraint ").append(uqName).append(" unique ");
@@ -63,9 +63,9 @@ public class SqlServerDdl extends PlatformDdl {
* MsSqlServer specific null handling on unique constraints.
*/
@Override
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, boolean notNull) {
if (notNull) {
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, notNull);
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
if (nullableColumns == null || nullableColumns.length == 0) {
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
}
if (uqName == null) {
throw new NullPointerException();
@@ -82,7 +82,7 @@ public class SqlServerDdl extends PlatformDdl {
}
sb.append(") where");
String sep = " ";
for (String column : columns) {
for (String column : nullableColumns) {
sb.append(sep).append(column).append(" is not null");
sep = " and ";
}
@@ -2,6 +2,7 @@ package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.annotation.ConstraintMode;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.Column;
import io.ebeaninternal.dbmigration.migration.ForeignKey;
@@ -37,6 +38,17 @@ class WriteForeignKey {
this.onUpdate = modeOf(key.getOnUpdate());
}
WriteForeignKey(AlterForeignKey key) {
this.tableName = key.getTableName();
this.indexName = key.getIndexName();
this.fkName = key.getName();
this.cols = toCols(key.getColumnNames());
this.refTableName = key.getRefTableName();
this.refCols = toCols(key.getRefColumnNames());
this.onDelete = modeOf(key.getOnDelete());
this.onUpdate = modeOf(key.getOnUpdate());
}
WriteForeignKey(String tableName, Column column) {
this.tableName = tableName;
this.indexName = column.getForeignKeyIndex();
@@ -0,0 +1,137 @@
package io.ebeaninternal.dbmigration.migration;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
* <p>
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <pre>
* TODO
* </pre>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "")
@XmlRootElement(name = "addUniqueConstraint")
public class AddUniqueConstraint {
@XmlAttribute(name = "constraintName", required = true)
protected String constraintName;
@XmlAttribute(name = "tableName", required = true)
protected String tableName;
@XmlAttribute(name = "columnNames", required = true)
protected String columnNames;
@XmlAttribute(name = "nullableColumns", required = true)
protected String nullableColumns;
@XmlAttribute(name = "oneToOne", required = false)
protected Boolean oneToOne;
/**
* Gets the value of the constraintName property.
*
* @return possible object is
* {@link String }
*/
public String getConstraintName() {
return constraintName;
}
/**
* Sets the value of the constraintName property.
*
* @param value allowed object is
* {@link String }
*/
public void setConstraintName(String value) {
this.constraintName = value;
}
/**
* Gets the value of the tableName property.
*
* @return possible object is
* {@link String }
*/
public String getTableName() {
return tableName;
}
/**
* Sets the value of the tableName property.
*
* @param value allowed object is
* {@link String }
*/
public void setTableName(String tableName) {
this.tableName = tableName;
}
/**
* Gets the value of the columnNames property.
*
* @return possible object is
* {@link String }
*/
public String getColumnNames() {
return columnNames;
}
/**
* Sets the value of the columnNames property.
*
* @param value allowed object is
* {@link String }
*/
public void setColumnNames(String value) {
this.columnNames = value;
}
/**
* Gets the value of the nullableColumns property.
*
* @return possible object is
* {@link String }
*/
public String getNullableColumns() {
return nullableColumns;
}
/**
* Sets the value of the nullableColumns property.
*
* @param value allowed object is
* {@link String }
*/
public void setNullableColumns(String value) {
this.nullableColumns = value;
}
/**
* Gets the value of the oneToOne property.
*
* @return true if oneToOne was set
*/
public boolean isOneToOne() {
return Boolean.TRUE.equals(oneToOne);
}
/**
* Sets the value of the oneToOne property.
*
* @param value boolean
*/
public void setOneToOne(boolean oneToOne) {
this.oneToOne = oneToOne;
}
}
@@ -0,0 +1,199 @@
package io.ebeaninternal.dbmigration.migration;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
* <p>
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <pre>
* TODO
* </pre>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "")
@XmlRootElement(name = "foreignKey")
public class AlterForeignKey {
@XmlAttribute(name = "name", required = true)
protected String name;
@XmlAttribute(name = "columnNames", required = true)
protected String columnNames;
@XmlAttribute(name = "refColumnNames", required = true)
protected String refColumnNames;
@XmlAttribute(name = "refTableName", required = true)
protected String refTableName;
@XmlAttribute(name = "indexName")
protected String indexName;
@XmlAttribute(name = "tableName", required = true)
protected String tableName;
@XmlAttribute(name = "onDelete")
protected String onDelete;
@XmlAttribute(name = "onUpdate")
protected String onUpdate;
/**
* Gets the value of the name property.
*
* @return possible object is
* {@link String }
*/
public String getName() {
return name;
}
/**
* Sets the value of the name property.
*
* @param value allowed object is
* {@link String }
*/
public void setName(String value) {
this.name = value;
}
/**
* Gets the value of the columnNames property.
*
* @return possible object is
* {@link String }
*/
public String getColumnNames() {
return columnNames;
}
/**
* Sets the value of the columnNames property.
*
* @param value allowed object is
* {@link String }
*/
public void setColumnNames(String value) {
this.columnNames = value;
}
/**
* Gets the value of the refColumnNames property.
*
* @return possible object is
* {@link String }
*/
public String getRefColumnNames() {
return refColumnNames;
}
/**
* Sets the value of the refColumnNames property.
*
* @param value allowed object is
* {@link String }
*/
public void setRefColumnNames(String value) {
this.refColumnNames = value;
}
/**
* Gets the value of the refTableName property.
*
* @return possible object is
* {@link String }
*/
public String getRefTableName() {
return refTableName;
}
/**
* Sets the value of the refTableName property.
*
* @param value allowed object is
* {@link String }
*/
public void setRefTableName(String value) {
this.refTableName = value;
}
/**
* Gets the value of the indexName property.
*
* @return possible object is
* {@link String }
*/
public String getIndexName() {
return indexName;
}
/**
* Sets the value of the indexName property.
*
* @param value allowed object is
* {@link String }
*/
public void setIndexName(String value) {
this.indexName = value;
}
/**
* Gets the value of the tableName property.
*
* @return possible object is
* {@link String }
*/
public String getTableName() {
return tableName;
}
/**
* Sets the value of the tableName property.
*
* @param value allowed object is
* {@link String }
*/
public void setTableName(String value) {
this.tableName = value;
}
/**
* Gets the value of the onDelete property.
*
* @return possible object is
* {@link String }
*/
public String getOnDelete() {
return onDelete;
}
/**
* Sets the value of the onDelete property.
*
* @param value allowed object is
* {@link String }
*/
public void setOnDelete(String value) {
this.onDelete = value;
}
/**
* Gets the value of the onUpdate property.
*
* @return possible object is
* {@link String }
*/
public String getOnUpdate() {
return onUpdate;
}
/**
* Sets the value of the onUpdate property.
*
* @param value allowed object is
* {@link String }
*/
public void setOnUpdate(String value) {
this.onUpdate = value;
}
}
@@ -57,7 +57,9 @@ public class ChangeSet {
@XmlElement(name = "alterColumn", type = AlterColumn.class),
@XmlElement(name = "renameColumn", type = RenameColumn.class),
@XmlElement(name = "createIndex", type = CreateIndex.class),
@XmlElement(name = "dropIndex", type = DropIndex.class)
@XmlElement(name = "dropIndex", type = DropIndex.class),
@XmlElement(name = "addUniqueConstraint", type = AddUniqueConstraint.class),
@XmlElement(name = "alterForeignKey", type = AlterForeignKey.class),
})
protected List<Object> changeSetChildren;
@XmlAttribute(name = "type", required = true)
@@ -194,5 +194,17 @@ public class ObjectFactory {
return new DdlScript();
}
/**
* Create an instance of {@link CompoundUniqueConstraint }
*/
public AddUniqueConstraint createCompoundUniqueConstraint() {
return new AddUniqueConstraint();
}
/**
* Create an instance of {@link AddUniqueConstraint }
*/
public AlterForeignKey createAlterForeignKey() {
return new AlterForeignKey();
}
}
@@ -30,9 +30,16 @@ public class UniqueConstraint {
@XmlAttribute(name = "name", required = true)
protected String name;
@XmlAttribute(name = "columnNames", required = true)
protected String columnNames;
@XmlAttribute(name = "oneToOne", required = false)
protected Boolean oneToOne;
@XmlAttribute(name = "nullableColumns", required = true)
protected String nullableColumns;
/**
* Gets the value of the name property.
*
@@ -72,5 +79,43 @@ public class UniqueConstraint {
public void setColumnNames(String value) {
this.columnNames = value;
}
/**
* Gets the value of the oneToOne property.
*
* @return true if oneToOne was set
*/
public boolean isOneToOne() {
return Boolean.TRUE.equals(oneToOne);
}
/**
* Sets the value of the oneToOne property.
*
* @param value boolean
*/
public void setOneToOne(boolean oneToOne) {
this.oneToOne = oneToOne;
}
/**
* Gets the value of the nullableColumns property.
*
* @return possible object is
* {@link String }
*/
public String getNullableColumns() {
return nullableColumns;
}
/**
* Sets the value of the nullableColumns property.
*
* @param value allowed object is
* {@link String }
*/
public void setNullableColumns(String value) {
this.nullableColumns = value;
}
}
@@ -404,7 +404,9 @@ public class MColumn {
alter.setCheckConstraint(newColumn.checkConstraint);
}
}
if (different(references, newColumn.references)) {
if (different(references, newColumn.references)
|| hasValue(newColumn.references) && fkeyOnDelete != newColumn.fkeyOnDelete
|| hasValue(newColumn.references) && fkeyOnUpdate != newColumn.fkeyOnUpdate) {
// foreign key change
AlterColumn alter = getAlterColumn(tableName, tableWithHistory);
if (hasValue(foreignKeyName)) {
@@ -418,6 +420,12 @@ public class MColumn {
alter.setReferences(newColumn.references);
alter.setForeignKeyName(newColumn.foreignKeyName);
alter.setForeignKeyIndex(newColumn.foreignKeyIndex);
if (newColumn.fkeyOnDelete != null) {
alter.setForeignKeyOnDelete(fkeyModeOf(newColumn.fkeyOnDelete));
}
if (newColumn.fkeyOnUpdate != null) {
alter.setForeignKeyOnUpdate(fkeyModeOf(newColumn.fkeyOnUpdate));
}
}
}
@@ -513,6 +521,11 @@ public class MColumn {
comment = null;
}
}
if (hasValue(alterColumn.getForeignKeyOnDelete())) {
fkeyOnDelete = fkeyMode(alterColumn.getForeignKeyOnDelete());
}
if (hasValue(alterColumn.getForeignKeyOnUpdate())) {
fkeyOnUpdate = fkeyMode(alterColumn.getForeignKeyOnUpdate());
}
}
}
@@ -1,9 +1,12 @@
package io.ebeaninternal.dbmigration.model;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.ForeignKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* A unique constraint for multiple columns.
@@ -46,6 +49,32 @@ public class MCompoundForeignKey {
fk.setRefTableName(referenceTable);
return fk;
}
/**
* Create and return an AlterForeignKey migration element.
*/
public AlterForeignKey addForeignKey(String tableName) {
AlterForeignKey fk = new AlterForeignKey();
fk.setName(name);
fk.setIndexName(indexName);
fk.setColumnNames(toColumnNames(columns));
fk.setRefColumnNames(toColumnNames(referenceColumns));
fk.setRefTableName(referenceTable);
fk.setTableName(tableName);
return fk;
}
/**
* Create and return an AlterForeignKey migration element.
*/
public AlterForeignKey dropForeignKey(String tableName) {
AlterForeignKey fk = new AlterForeignKey();
fk.setName(name);
fk.setIndexName(indexName);
fk.setColumnNames(DdlHelp.DROP_FOREIGN_KEY);
fk.setTableName(tableName);
return fk;
}
/**
* Add a counter to the foreign key and index names to avoid duplication.
@@ -99,4 +128,24 @@ public class MCompoundForeignKey {
return sb.toString();
}
@Override
public int hashCode() {
return Objects.hash(columns, indexName, name, referenceColumns, referenceTable);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof MCompoundForeignKey))
return false;
MCompoundForeignKey other = (MCompoundForeignKey) obj;
return Objects.equals(columns, other.columns)
&& Objects.equals(indexName, other.indexName)
&& Objects.equals(name, other.name)
&& Objects.equals(referenceColumns, other.referenceColumns)
&& Objects.equals(referenceTable, other.referenceTable);
}
}
@@ -1,5 +1,11 @@
package io.ebeaninternal.dbmigration.model;
import java.util.Arrays;
import java.util.Objects;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
import io.ebeaninternal.dbmigration.migration.UniqueConstraint;
/**
* A unique constraint for multiple columns.
* <p>
@@ -20,6 +26,8 @@ public class MCompoundUniqueConstraint {
* The columns combined to be unique.
*/
private final String[] columns;
private String[] nullableColumns;
public MCompoundUniqueConstraint(String[] columns, boolean oneToOne, String name) {
this.name = name;
@@ -47,4 +55,78 @@ public class MCompoundUniqueConstraint {
public String getName() {
return name;
}
public UniqueConstraint getUniqueConstraint() {
UniqueConstraint uq = new UniqueConstraint();
uq.setName(getName());
uq.setColumnNames(join(columns));
uq.setNullableColumns(join(nullableColumns));
uq.setOneToOne(isOneToOne());
return uq;
}
/**
* Return a AddUniqueConstraint migration for this constraint.
*/
public AddUniqueConstraint addUniqueConstraint(String tableName) {
AddUniqueConstraint create = new AddUniqueConstraint();
create.setConstraintName(getName());
create.setTableName(tableName);
create.setColumnNames(join(columns));
create.setNullableColumns(join(nullableColumns));
create.setOneToOne(isOneToOne());
return create;
}
/**
* Create a AddUniqueConstraint migration with 'DROP CONSTRAINT' set for this index.
*/
public AddUniqueConstraint dropUniqueConstraint(String tableName) {
AddUniqueConstraint dropUniqueConstraint = new AddUniqueConstraint();
dropUniqueConstraint.setConstraintName(name);
dropUniqueConstraint.setTableName(tableName);
dropUniqueConstraint.setColumnNames(DdlHelp.DROP_CONSTRAINT);
dropUniqueConstraint.setNullableColumns(join(nullableColumns));
return dropUniqueConstraint;
}
public void setNullableColumns(String[] nullableColumns) {
if (nullableColumns != null && nullableColumns.length == 0) {
this.nullableColumns = null;
} else {
this.nullableColumns = nullableColumns;
}
}
private String join(String[] arr) {
if (arr == null) {
return "";
}
StringBuilder sb = new StringBuilder(50);
for (int i = 0; i < arr.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(arr[i]);
}
return sb.toString();
}
@Override
public int hashCode() {
return Arrays.hashCode(columns) + 31 * Objects.hash(name, oneToOne);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof MCompoundUniqueConstraint)) {
return false;
}
MCompoundUniqueConstraint other = (MCompoundUniqueConstraint) obj;
return Arrays.equals(columns, other.columns)
&& Arrays.equals(nullableColumns, other.nullableColumns)
&& Objects.equals(name, other.name)
&& oneToOne == other.oneToOne;
}
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.dbmigration.model;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.SplitColumns;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.AddTableComment;
@@ -10,6 +11,7 @@ import io.ebeaninternal.dbmigration.migration.CreateTable;
import io.ebeaninternal.dbmigration.migration.DropColumn;
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
import io.ebeaninternal.dbmigration.migration.DropTable;
import io.ebeaninternal.dbmigration.migration.ForeignKey;
import io.ebeaninternal.dbmigration.migration.IdentityType;
import io.ebeaninternal.dbmigration.migration.UniqueConstraint;
import org.slf4j.Logger;
@@ -165,9 +167,36 @@ public class MTable {
for (Column column : cols) {
addColumn(column);
}
List<UniqueConstraint> uqConstraints = createTable.getUniqueConstraint();
for (UniqueConstraint uq : uqConstraints) {
MCompoundUniqueConstraint mUq = new MCompoundUniqueConstraint(
SplitColumns.split(uq.getColumnNames()), uq.isOneToOne(), uq.getName());
mUq.setNullableColumns(SplitColumns.split(uq.getNullableColumns()));
uniqueConstraints.add(mUq);
}
for (ForeignKey fk : createTable.getForeignKey()) {
if (DdlHelp.isDropForeignKey(fk.getColumnNames())) {
removeForeignKey(fk.getName());
} else {
addForeignKey(fk.getName(), fk.getRefTableName(), fk.getIndexName(), fk.getColumnNames(), fk.getRefColumnNames());
}
}
}
public void addForeignKey(String name, String refTableName, String indexName, String columnNames,
String refColumnNames) {
MCompoundForeignKey foreignKey = new MCompoundForeignKey(name, refTableName, indexName);
String[] cols = SplitColumns.split(columnNames);
String[] refCols = SplitColumns.split(refColumnNames);
for (int i = 0; i < cols.length && i < refCols.length; i++) {
foreignKey.addColumnPair(cols[i], refCols[i]);
}
addForeignKey(foreignKey);
}
/**
* Construct typically from EbeanServer meta data.
*/
@@ -236,17 +265,7 @@ public class MTable {
}
for (MCompoundUniqueConstraint constraint : uniqueConstraints) {
UniqueConstraint uq = new UniqueConstraint();
uq.setName(constraint.getName());
String[] columns = constraint.getColumns();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < columns.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(columns[i]);
}
uq.setColumnNames(sb.toString());
UniqueConstraint uq = constraint.getUniqueConstraint();
createTable.getUniqueConstraint().add(uq);
}
@@ -271,6 +290,25 @@ public class MTable {
}
}
compareColumns(modelDiff, newTable);
if (MColumn.different(comment, newTable.comment)) {
AddTableComment addTableComment = new AddTableComment();
addTableComment.setName(name);
if (newTable.comment == null) {
addTableComment.setComment(DdlHelp.DROP_COMMENT);
} else {
addTableComment.setComment(newTable.comment);
}
modelDiff.addTableComment(addTableComment);
}
compareCompoundKeys(modelDiff, newTable);
compareUniqueKeys(modelDiff, newTable);
}
private void compareColumns(ModelDiff modelDiff, MTable newTable) {
addColumn = null;
Map<String, MColumn> newColumnMap = newTable.getColumns();
@@ -303,16 +341,39 @@ public class MTable {
if (addColumn != null) {
modelDiff.addAddColumn(addColumn);
}
}
if (MColumn.different(comment, newTable.comment)) {
AddTableComment addTableComment = new AddTableComment();
addTableComment.setName(name);
if (newTable.comment == null) {
addTableComment.setComment(DdlHelp.DROP_COMMENT);
} else {
addTableComment.setComment(newTable.comment);
}
modelDiff.addTableComment(addTableComment);
private void compareCompoundKeys(ModelDiff modelDiff, MTable newTable) {
List<MCompoundForeignKey> newKeys = new ArrayList<>(newTable.getCompoundKeys());
List<MCompoundForeignKey> currentKeys = new ArrayList<>(getCompoundKeys());
// remove keys that have not changed
currentKeys.removeAll(newTable.getCompoundKeys());
newKeys.removeAll(getCompoundKeys());
for (MCompoundForeignKey currentKey : currentKeys) {
modelDiff.addAlterForeignKey(currentKey.dropForeignKey(name));
}
for (MCompoundForeignKey newKey : newKeys) {
modelDiff.addAlterForeignKey(newKey.addForeignKey(name));
}
}
private void compareUniqueKeys(ModelDiff modelDiff, MTable newTable) {
List<MCompoundUniqueConstraint> newKeys = new ArrayList<>(newTable.getUniqueConstraints());
List<MCompoundUniqueConstraint> currentKeys = new ArrayList<>(getUniqueConstraints());
// remove keys that have not changed
currentKeys.removeAll(newTable.getUniqueConstraints());
newKeys.removeAll(getUniqueConstraints());
for (MCompoundUniqueConstraint currentKey: currentKeys) {
modelDiff.addUniqueConstraint(currentKey.dropUniqueConstraint(name));
}
for (MCompoundUniqueConstraint newKey: newKeys) {
modelDiff.addUniqueConstraint(newKey.addUniqueConstraint(name));
}
}
@@ -656,6 +717,29 @@ public class MTable {
return draftTableName + "." + references.substring(lastDot + 1);
}
/**
* This method adds information which columns are nullable or not to the compound indices.
*/
public void updateCompoundIndices() {
for (MCompoundUniqueConstraint uniq : uniqueConstraints) {
List<String> nullableColumns = new ArrayList<>();
for (String columnName : uniq.getColumns()) {
MColumn col = getColumn(columnName);
if (col == null) {
throw new IllegalStateException("Column '" + columnName + "' not found in table " + getName());
}
if (!col.isNotnull()) {
nullableColumns.add(columnName);
}
}
uniq.setNullableColumns(nullableColumns.toArray(new String[nullableColumns.size()]));
}
}
public void removeForeignKey(String name) {
compoundKeys.removeIf(fk -> fk.getName().equals(name));
}
/**
* Clear the indexes on the foreign keys as they are covered by unique constraints.
*/
@@ -1,10 +1,13 @@
package io.ebeaninternal.dbmigration.model;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.SplitColumns;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.AddTableComment;
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.ChangeSet;
import io.ebeaninternal.dbmigration.migration.ChangeSetType;
import io.ebeaninternal.dbmigration.migration.CreateIndex;
@@ -134,6 +137,10 @@ public class ModelContainer {
applyChange((AddHistoryTable) change);
} else if (change instanceof DropHistoryTable) {
applyChange((DropHistoryTable) change);
} else if (change instanceof AddUniqueConstraint) {
applyChange((AddUniqueConstraint) change);
} else if (change instanceof AlterForeignKey) {
applyChange((AlterForeignKey) change);
} else if (change instanceof AddTableComment) {
applyChange((AddTableComment) change);
} else {
@@ -166,6 +173,34 @@ public class ModelContainer {
table.setWithHistory(false);
}
private void applyChange(AddUniqueConstraint change) {
MTable table = tables.get(change.getTableName());
if (table == null) {
throw new IllegalStateException("Table [" + change.getTableName() + "] does not exist in model?");
}
if (DdlHelp.isDropConstraint(change.getColumnNames())) {
table.getUniqueConstraints().removeIf(constraint -> constraint.getName().equals(change.getConstraintName()));
} else {
MCompoundUniqueConstraint constraint = new MCompoundUniqueConstraint(
SplitColumns.split(change.getColumnNames()), change.isOneToOne(), change.getConstraintName());
constraint.setNullableColumns(SplitColumns.split(change.getNullableColumns()));
table.getUniqueConstraints().add(constraint);
}
}
private void applyChange(AlterForeignKey change) {
MTable table = tables.get(change.getTableName());
if (table == null) {
throw new IllegalStateException("Table [" + change.getName() + "] does not exist in model?");
}
if (DdlHelp.isDropForeignKey(change.getColumnNames())) {
table.removeForeignKey(change.getName());
} else {
table.addForeignKey(change.getName(), change.getRefTableName(), change.getIndexName(), change.getColumnNames(),
change.getRefColumnNames());
}
}
private void applyChange(AddTableComment change) {
MTable table = tables.get(change.getName());
if (table == null) {
@@ -3,7 +3,9 @@ package io.ebeaninternal.dbmigration.model;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.AddTableComment;
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.ChangeSet;
import io.ebeaninternal.dbmigration.migration.ChangeSetType;
import io.ebeaninternal.dbmigration.migration.CreateIndex;
@@ -245,4 +247,18 @@ public class ModelDiff {
public void addTableComment(AddTableComment addTableComment) {
applyChanges.add(addTableComment);
}
/**
* Adds (or drops) a unique constraint to the 'apply' changes.
*/
public void addUniqueConstraint(AddUniqueConstraint addUniqueConstraint) {
applyChanges.add(addUniqueConstraint);
}
/**
* Adds (or drops) a foreign key constraint to the 'apply' changes.
*/
public void addAlterForeignKey(AlterForeignKey alterForeignKey) {
applyChanges.add(alterForeignKey);
}
}
@@ -105,6 +105,8 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
}
addDraftTable();
table.updateCompoundIndices();
}
/**
@@ -273,6 +273,10 @@ class CQueryBuilder {
SqlLimitResponse s = buildSql(sqlSelect, request, predicates, sqlTree);
String sql = s.getSql();
if (hasMany || query.isRawSql()) {
int pos = sql.lastIndexOf(" order by "); // remove order by - mssql does not accept order by in subqueries
if (pos != -1) {
sql = sql.substring(0, pos);
}
sql = "select count(*) from ( " + sql + ")";
if (selectCountWithAlias) {
sql += " as c";