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 80da05f97..8969e7c7d 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java +++ b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java @@ -389,14 +389,7 @@ public class BaseTableDdl implements TableDdl { protected void writeInlineForeignKey(DdlWrite write, Column column) throws IOException { - String references = column.getReferences(); - int pos = references.lastIndexOf('.'); - if (pos == -1) { - throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]"); - } - String refTableName = references.substring(0, pos); - String refColumnName = references.substring(pos + 1); - String fkConstraint = platformDdl.tableInlineForeignKey(new String[]{column.getName()}, refTableName, new String[]{refColumnName}); + String fkConstraint = platformDdl.tableInlineForeignKey(new WriteForeignKey(null, column)); write.apply().append(",").newLine().append(" ").append(fkConstraint); } @@ -404,11 +397,7 @@ public class BaseTableDdl implements TableDdl { List foreignKey = createTable.getForeignKey(); for (ForeignKey key : foreignKey) { - String refTableName = key.getRefTableName(); - String[] cols = toColumnNamesSplit(key.getColumnNames()); - String[] refColumns = toColumnNamesSplit(key.getRefColumnNames()); - - String fkConstraint = platformDdl.tableInlineForeignKey(cols, refTableName, refColumns); + String fkConstraint = platformDdl.tableInlineForeignKey(new WriteForeignKey(null, key)); write.apply().append(",").newLine().append(" ").append(fkConstraint); } } @@ -433,61 +422,42 @@ public class BaseTableDdl implements TableDdl { List foreignKey = createTable.getForeignKey(); for (ForeignKey key : foreignKey) { - - String refTableName = key.getRefTableName(); - String fkName = key.getName(); - String[] cols = toColumnNamesSplit(key.getColumnNames()); - String[] refColumns = toColumnNamesSplit(key.getRefColumnNames()); - - writeForeignKey(write, fkName, tableName, cols, refTableName, refColumns, key.getIndexName()); + writeForeignKey(write, new WriteForeignKey(tableName, key)); } } protected void writeForeignKey(DdlWrite write, String tableName, Column column) throws IOException { - - String fkName = column.getForeignKeyName(); - String references = column.getReferences(); - int pos = references.lastIndexOf('.'); - if (pos == -1) { - throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]"); - } - String refTableName = references.substring(0, pos); - String refColumnName = references.substring(pos + 1); - - String[] cols = {column.getName()}; - String[] refCols = {refColumnName}; - - writeForeignKey(write, fkName, tableName, cols, refTableName, refCols, column.getForeignKeyIndex()); + writeForeignKey(write, new WriteForeignKey(tableName, column)); } - protected void writeForeignKey(DdlWrite write, String fkName, String tableName, String[] columns, String refTable, String[] refColumns, String indexName) throws IOException { + protected void writeForeignKey(DdlWrite write, WriteForeignKey request) throws IOException { - tableName = lowerTableName(tableName); + String tableName = lowerTableName(request.table()); DdlBuffer fkeyBuffer = write.applyForeignKeys(); - alterTableAddForeignKey(fkeyBuffer, fkName, tableName, columns, refTable, refColumns); + alterTableAddForeignKey(fkeyBuffer, request); - if (indexName != null) { + if (request.indexName() != null) { // no matching unique constraint so add the index - fkeyBuffer.append(platformDdl.createIndex(indexName, tableName, columns)).endOfStatement(); + fkeyBuffer.append(platformDdl.createIndex(request.indexName(), tableName, request.cols())).endOfStatement(); } fkeyBuffer.end(); write.dropAllForeignKeys() - .append(platformDdl.alterTableDropForeignKey(tableName, fkName)).endOfStatement(); + .append(platformDdl.alterTableDropForeignKey(tableName, request.fkName())).endOfStatement(); - if (indexName != null) { + if (request.indexName() != null) { write.dropAllForeignKeys() - .append(platformDdl.dropIndex(indexName, tableName)).endOfStatement(); + .append(platformDdl.dropIndex(request.indexName(), tableName)).endOfStatement(); } write.dropAllForeignKeys().end(); } - protected void alterTableAddForeignKey(DdlBuffer buffer, String fkName, String tableName, String[] columns, String refTable, String[] refColumns) throws IOException { + protected void alterTableAddForeignKey(DdlBuffer buffer, WriteForeignKey request) throws IOException { - String fkConstraint = platformDdl.alterTableAddForeignKey(tableName, fkName, columns, refTable, refColumns); + String fkConstraint = platformDdl.alterTableAddForeignKey(request); if (fkConstraint != null && !fkConstraint.isEmpty()) { buffer.append(fkConstraint).endOfStatement(); } @@ -554,7 +524,7 @@ public class BaseTableDdl implements TableDdl { for (UniqueConstraint uniqueConstraint : uniqueConstraints) { if (inlineUniqueWhenNull) { String uqName = uniqueConstraint.getName(); - String[] columns = toColumnNamesSplit(uniqueConstraint.getColumnNames()); + String[] columns = SplitColumns.split(uniqueConstraint.getColumnNames()); apply.append(",").newLine(); apply.append(" constraint ").append(uqName).append(" unique"); appendColumns(columns, apply); @@ -625,13 +595,6 @@ public class BaseTableDdl implements TableDdl { return cols; } - /** - * Return as an array of string column names. - */ - protected String[] toColumnNamesSplit(String columns) { - return columns.split(","); - } - /** * Convert the table lower case. */ @@ -662,7 +625,7 @@ public class BaseTableDdl implements TableDdl { @Override public void generate(DdlWrite writer, CreateIndex createIndex) throws IOException { - String[] cols = toColumnNamesSplit(createIndex.getColumns()); + String[] cols = SplitColumns.split(createIndex.getColumns()); writer.apply() .append(platformDdl.createIndex(createIndex.getIndexName(), createIndex.getTableName(), cols)) .endOfStatement(); @@ -931,22 +894,9 @@ public class BaseTableDdl implements TableDdl { } } - protected void alterColumnAddForeignKey(DdlWrite writer, AlterColumn alterColumn) throws IOException { - String tableName = alterColumn.getTableName(); - String fkName = alterColumn.getForeignKeyName(); - String[] cols = {alterColumn.getColumnName()}; - String references = alterColumn.getReferences(); - int pos = references.lastIndexOf('.'); - if (pos == -1) { - throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]"); - } - String refTableName = references.substring(0, pos); - String refColumnName = references.substring(pos + 1); - String[] refCols = {refColumnName}; - - alterTableAddForeignKey(writer.apply(), fkName, tableName, cols, refTableName, refCols); + alterTableAddForeignKey(writer.apply(), new WriteForeignKey(alterColumn)); } protected void alterColumnDropForeignKey(DdlWrite writer, AlterColumn alter) throws IOException { diff --git a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/Oracle10Ddl.java b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/Oracle10Ddl.java index 2e4ae9b9c..2071a94e2 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/Oracle10Ddl.java +++ b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/Oracle10Ddl.java @@ -1,5 +1,6 @@ package io.ebeaninternal.dbmigration.ddlgeneration.platform; +import io.ebean.annotation.ConstraintMode; import io.ebean.config.dbplatform.DatabasePlatform; /** @@ -14,7 +15,6 @@ public class Oracle10Ddl extends PlatformDdl { this.dropConstraintIfExists = "drop constraint"; this.dropIndexIfExists = "drop index "; this.dropTableCascade = " cascade constraints purge"; - this.foreignKeyRestrict = ""; this.alterColumn = "modify"; this.columnSetNotnull = "not null"; this.columnSetNull = "null"; @@ -22,4 +22,20 @@ public class Oracle10Ddl extends PlatformDdl { this.identitySuffix = " generated always as identity"; } + @Override + protected void appendForeignKeyOnUpdate(StringBuilder buffer, ConstraintMode mode) { + // do nothing, no on update clause for oracle + } + + @Override + protected void appendForeignKeyMode(StringBuilder buffer, String onMode, ConstraintMode mode) { + switch (mode) { + case SET_NULL: + case CASCADE: + super.appendForeignKeyMode(buffer, onMode, mode); + default: + // do nothing, defaults to RESTRICT effectively + } + } + } 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 c870608f2..3088e49c1 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl.java +++ b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl.java @@ -1,11 +1,13 @@ package io.ebeaninternal.dbmigration.ddlgeneration.platform; +import io.ebean.annotation.ConstraintMode; import io.ebean.config.DbConstraintNaming; import io.ebean.config.ServerConfig; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.DbDefaultValue; import io.ebean.config.dbplatform.DbIdentity; import io.ebean.config.dbplatform.IdType; +import io.ebean.util.StringHelper; import io.ebeaninternal.dbmigration.ddlgeneration.BaseDdlHandler; import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer; import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler; @@ -17,7 +19,6 @@ import io.ebeaninternal.dbmigration.migration.Column; import io.ebeaninternal.dbmigration.migration.DropHistoryTable; import io.ebeaninternal.dbmigration.migration.IdentityType; import io.ebeaninternal.dbmigration.model.MTable; -import io.ebean.util.StringHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,7 +63,8 @@ public class PlatformDdl { */ protected String dropSequenceIfExists = "drop sequence if exists "; - protected String foreignKeyRestrict = "on delete restrict on update restrict"; + protected String foreignKeyOnDelete = "on delete"; + protected String foreignKeyOnUpdate = "on update"; protected String identitySuffix = " auto_increment"; @@ -351,37 +353,68 @@ public class PlatformDdl { /** * Return the foreign key constraint when used inline with create table. */ - public String tableInlineForeignKey(String[] columns, String refTable, String[] refColumns) { + public String tableInlineForeignKey(WriteForeignKey request) { StringBuilder buffer = new StringBuilder(90); buffer.append("foreign key"); - appendColumns(columns, buffer); - buffer.append(" references ").append(lowerTableName(refTable)); - appendColumns(refColumns, buffer); - appendWithSpace(foreignKeyRestrict, buffer); + appendColumns(request.cols(), buffer); + buffer.append(" references ").append(lowerTableName(request.refTable())); + appendColumns(request.refCols(), buffer); + appendForeignKeySuffix(request, buffer); return buffer.toString(); } /** * Add foreign key. */ - public String alterTableAddForeignKey(String tableName, String fkName, String[] columns, String refTable, String[] refColumns) { + public String alterTableAddForeignKey(WriteForeignKey request) { StringBuilder buffer = new StringBuilder(90); buffer - .append("alter table ").append(tableName) - .append(" add constraint ").append(fkName) + .append("alter table ").append(lowerTableName(request.table())) + .append(" add constraint ").append(request.fkName()) .append(" foreign key"); - appendColumns(columns, buffer); + appendColumns(request.cols(), buffer); buffer .append(" references ") - .append(lowerTableName(refTable)); - appendColumns(refColumns, buffer); - appendWithSpace(foreignKeyRestrict, buffer); - + .append(lowerTableName(request.refTable())); + appendColumns(request.refCols(), buffer); + appendForeignKeySuffix(request, buffer); return buffer.toString(); } + protected void appendForeignKeySuffix(WriteForeignKey request, StringBuilder buffer) { + appendForeignKeyOnDelete(buffer, withDefault(request.onDelete())); + appendForeignKeyOnUpdate(buffer, withDefault(request.onDelete())); + } + + protected ConstraintMode withDefault(ConstraintMode mode) { + return (mode == null || mode == ConstraintMode.GLOBAL_DEFAULT) ? ConstraintMode.RESTRICT : mode; + } + + protected void appendForeignKeyOnDelete(StringBuilder buffer, ConstraintMode mode) { + appendForeignKeyMode(buffer, foreignKeyOnDelete, mode); + } + + protected void appendForeignKeyOnUpdate(StringBuilder buffer, ConstraintMode mode) { + appendForeignKeyMode(buffer, foreignKeyOnUpdate, mode); + } + + protected void appendForeignKeyMode(StringBuilder buffer, String onMode, ConstraintMode mode) { + buffer.append(" ").append(onMode).append(" ").append(translate(mode)); + } + + protected String translate(ConstraintMode mode) { + switch(mode) { + case SET_NULL: return "set null"; + case SET_DEFAULT: return "set default"; + case RESTRICT: return "restrict"; + case CASCADE: return "cascade"; + default: + throw new IllegalStateException("Unknown mode "+mode); + } + } + /** * Drop a unique constraint from the table (Sometimes this is an index). */ diff --git a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/SQLiteDdl.java b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/SQLiteDdl.java index 0f555d097..bd2b41830 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/SQLiteDdl.java +++ b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/SQLiteDdl.java @@ -3,8 +3,6 @@ package io.ebeaninternal.dbmigration.ddlgeneration.platform; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer; -import java.io.IOException; - /** * DB2 platform specific DDL. */ @@ -17,17 +15,17 @@ public class SQLiteDdl extends PlatformDdl { } @Override - public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException { + public void addTableComment(DdlBuffer apply, String tableName, String tableComment) { // not supported } @Override - public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException { + public void addColumnComment(DdlBuffer apply, String table, String column, String comment) { // not supported } @Override - public String alterTableAddForeignKey(String tableName, String fkName, String[] columns, String refTable, String[] refColumns) { + public String alterTableAddForeignKey(WriteForeignKey request) { // not supported return null; } diff --git a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/SplitColumns.java b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/SplitColumns.java new file mode 100644 index 000000000..a1c83c4e2 --- /dev/null +++ b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/SplitColumns.java @@ -0,0 +1,12 @@ +package io.ebeaninternal.dbmigration.ddlgeneration.platform; + +public class SplitColumns { + + /** + * Return as an array of string column names. + */ + public static String[] split(String columns) { + return columns.split(","); + } + +} 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 5fd7d9175..16b541d88 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/SqlServerDdl.java +++ b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/SqlServerDdl.java @@ -1,5 +1,6 @@ package io.ebeaninternal.dbmigration.ddlgeneration.platform; +import io.ebean.annotation.ConstraintMode; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer; import io.ebeaninternal.dbmigration.migration.AlterColumn; @@ -14,7 +15,6 @@ public class SqlServerDdl extends PlatformDdl { public SqlServerDdl(DatabasePlatform platform) { super(platform); this.identitySuffix = " identity(1,1)"; - this.foreignKeyRestrict = ""; this.alterTableIfExists = ""; this.addColumn = "add"; this.inlineUniqueWhenNullable = false; @@ -23,6 +23,13 @@ public class SqlServerDdl extends PlatformDdl { this.historyDdl = new SqlServerHistoryDdl(); } + @Override + protected void appendForeignKeyMode(StringBuilder buffer, String onMode, ConstraintMode mode) { + if (mode != ConstraintMode.RESTRICT) { + super.appendForeignKeyMode(buffer, onMode, mode); + } + } + @Override public String dropTable(String tableName) { StringBuilder buffer = new StringBuilder(); diff --git a/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/WriteForeignKey.java b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/WriteForeignKey.java new file mode 100644 index 000000000..f9fab239d --- /dev/null +++ b/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/WriteForeignKey.java @@ -0,0 +1,100 @@ +package io.ebeaninternal.dbmigration.ddlgeneration.platform; + +import io.ebean.annotation.ConstraintMode; +import io.ebeaninternal.dbmigration.migration.AlterColumn; +import io.ebeaninternal.dbmigration.migration.Column; +import io.ebeaninternal.dbmigration.migration.ForeignKey; + +class WriteForeignKey { + + private final String fkName; + private final String tableName; + private final String[] cols; + private String refTableName; + private String[] refCols; + private final String indexName; + private final ConstraintMode onDelete; + private final ConstraintMode onUpdate; + + WriteForeignKey(AlterColumn alterColumn) { + this.tableName = alterColumn.getTableName(); + this.indexName = alterColumn.getForeignKeyIndex(); + this.fkName = alterColumn.getForeignKeyName(); + this.cols = new String[]{alterColumn.getColumnName()}; + setReferences(alterColumn.getReferences()); + this.onDelete = modeOf(alterColumn.getForeignKeyOnDelete()); + this.onUpdate = modeOf(alterColumn.getForeignKeyOnUpdate()); + } + + WriteForeignKey(String tableName, ForeignKey key) { + this.tableName = tableName; + 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(); + this.fkName = column.getForeignKeyName(); + this.cols = new String[]{column.getName()}; + setReferences(column.getReferences()); + this.onDelete = modeOf(column.getForeignKeyOnDelete()); + this.onUpdate = modeOf(column.getForeignKeyOnUpdate()); + } + + private void setReferences(String references) { + int pos = references.lastIndexOf('.'); + if (pos == -1) { + throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]"); + } + this.refTableName = references.substring(0, pos); + String refColumnName = references.substring(pos + 1); + this.refCols = new String[]{refColumnName}; + } + + private String[] toCols(String columns) { + return SplitColumns.split(columns); + } + + private ConstraintMode modeOf(String value) { + return (value == null) ? null : ConstraintMode.valueOf(value); + } + + public String table() { + return tableName; + } + + public String[] cols() { + return cols; + } + + public String indexName() { + return indexName; + } + + public String fkName() { + return fkName; + } + + public String refTable() { + return refTableName; + } + + public String[] refCols() { + return refCols; + } + + public ConstraintMode onDelete() { + return onDelete; + } + + public ConstraintMode onUpdate() { + return onUpdate; + } + +} diff --git a/src/main/java/io/ebeaninternal/dbmigration/migration/AlterColumn.java b/src/main/java/io/ebeaninternal/dbmigration/migration/AlterColumn.java index 4e2ab2b02..47f9a413b 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/migration/AlterColumn.java +++ b/src/main/java/io/ebeaninternal/dbmigration/migration/AlterColumn.java @@ -1,14 +1,13 @@ package io.ebeaninternal.dbmigration.migration; -import java.util.ArrayList; -import java.util.List; - import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; +import java.util.ArrayList; +import java.util.List; /** @@ -40,6 +39,8 @@ import javax.xml.bind.annotation.XmlType; * <attribute name="references" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="foreignKeyName" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="foreignKeyIndex" type="{http://www.w3.org/2001/XMLSchema}string" /> + * <attribute name="foreignKeyOnDelete" type="{http://www.w3.org/2001/XMLSchema}string" /> + * <attribute name="foreignKeyOnUpdate" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="dropForeignKey" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="dropForeignKeyIndex" type="{http://www.w3.org/2001/XMLSchema}string" /> * </restriction> @@ -99,6 +100,10 @@ public class AlterColumn { protected String foreignKeyName; @XmlAttribute(name = "foreignKeyIndex") protected String foreignKeyIndex; + @XmlAttribute(name = "foreignKeyOnDelete") + protected String foreignKeyOnDelete; + @XmlAttribute(name = "foreignKeyOnUpdate") + protected String foreignKeyOnUpdate; @XmlAttribute(name = "dropForeignKey") protected String dropForeignKey; @XmlAttribute(name = "dropForeignKeyIndex") @@ -504,6 +509,46 @@ public class AlterColumn { this.foreignKeyIndex = value; } + /** + * Gets the value of the foreignKeyOnDelete property. + * + * @return possible object is + * {@link String } + */ + public String getForeignKeyOnDelete() { + return foreignKeyOnDelete; + } + + /** + * Sets the value of the foreignKeyOnDelete property. + * + * @param value allowed object is + * {@link String } + */ + public void setForeignKeyOnDelete(String value) { + this.foreignKeyOnDelete = value; + } + + /** + * Gets the value of the foreignKeyOnUpdate property. + * + * @return possible object is + * {@link String } + */ + public String getForeignKeyOnUpdate() { + return foreignKeyOnUpdate; + } + + /** + * Sets the value of the foreignKeyOnUpdate property. + * + * @param value allowed object is + * {@link String } + */ + public void setForeignKeyOnUpdate(String value) { + this.foreignKeyOnUpdate = value; + } + /** * Gets the value of the dropForeignKey property. * diff --git a/src/main/java/io/ebeaninternal/dbmigration/migration/Column.java b/src/main/java/io/ebeaninternal/dbmigration/migration/Column.java index a2385b2a9..0aefdfbc8 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/migration/Column.java +++ b/src/main/java/io/ebeaninternal/dbmigration/migration/Column.java @@ -1,14 +1,13 @@ package io.ebeaninternal.dbmigration.migration; -import java.util.ArrayList; -import java.util.List; - import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; +import java.util.ArrayList; +import java.util.List; /** @@ -34,6 +33,8 @@ import javax.xml.bind.annotation.XmlType; * <attribute name="references" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="foreignKeyName" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="foreignKeyIndex" type="{http://www.w3.org/2001/XMLSchema}string" /> + * <attribute name="foreignKeyOnDelete" type="{http://www.w3.org/2001/XMLSchema}string" /> + * <attribute name="foreignKeyOnUpdate" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="comment" type="{http://www.w3.org/2001/XMLSchema}string" /> * </restriction> * </complexContent> @@ -80,6 +81,10 @@ public class Column { protected String foreignKeyName; @XmlAttribute(name = "foreignKeyIndex") protected String foreignKeyIndex; + @XmlAttribute(name = "foreignKeyOnDelete") + protected String foreignKeyOnDelete; + @XmlAttribute(name = "foreignKeyOnUpdate") + protected String foreignKeyOnUpdate; @XmlAttribute(name = "comment") protected String comment; @@ -378,6 +383,46 @@ public class Column { this.foreignKeyIndex = value; } + /** + * Gets the value of the foreignOnDelete property. + * + * @return possible object is + * {@link String } + */ + public String getForeignKeyOnDelete() { + return foreignKeyOnDelete; + } + + /** + * Sets the value of the foreignKeyOnDelete property. + * + * @param value allowed object is + * {@link String } + */ + public void setForeignKeyOnDelete(String value) { + this.foreignKeyOnDelete = value; + } + + /** + * Gets the value of the foreignOnUpdate property. + * + * @return possible object is + * {@link String } + */ + public String getForeignKeyOnUpdate() { + return foreignKeyOnUpdate; + } + + /** + * Sets the value of the foreignKeyOnUpdate property. + * + * @param value allowed object is + * {@link String } + */ + public void setForeignKeyOnUpdate(String value) { + this.foreignKeyOnUpdate = value; + } + /** * Gets the value of the comment property. * diff --git a/src/main/java/io/ebeaninternal/dbmigration/migration/ForeignKey.java b/src/main/java/io/ebeaninternal/dbmigration/migration/ForeignKey.java index 8aa3763d3..37d314207 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/migration/ForeignKey.java +++ b/src/main/java/io/ebeaninternal/dbmigration/migration/ForeignKey.java @@ -21,6 +21,8 @@ import javax.xml.bind.annotation.XmlType; * <attribute name="refColumnNames" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="refTableName" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="indexName" type="{http://www.w3.org/2001/XMLSchema}string" /> + * <attribute name="onDelete" type="{http://www.w3.org/2001/XMLSchema}string" /> + * <attribute name="onUpdate" type="{http://www.w3.org/2001/XMLSchema}string" /> * </restriction> * </complexContent> * </complexType> @@ -41,7 +43,10 @@ public class ForeignKey { protected String refTableName; @XmlAttribute(name = "indexName") protected String indexName; - + @XmlAttribute(name = "onDelete") + protected String onDelete; + @XmlAttribute(name = "onUpdate") + protected String onUpdate; /** * Gets the value of the name property. * @@ -142,4 +147,44 @@ public class ForeignKey { this.indexName = 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; + } + } diff --git a/src/main/java/io/ebeaninternal/dbmigration/model/MColumn.java b/src/main/java/io/ebeaninternal/dbmigration/model/MColumn.java index 1ed8ff067..f9eb80a8c 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/model/MColumn.java +++ b/src/main/java/io/ebeaninternal/dbmigration/model/MColumn.java @@ -1,13 +1,14 @@ package io.ebeaninternal.dbmigration.model; -import java.util.List; - +import io.ebean.annotation.ConstraintMode; import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp; import io.ebeaninternal.dbmigration.migration.AlterColumn; import io.ebeaninternal.dbmigration.migration.Column; import io.ebeaninternal.dbmigration.migration.DdlScript; import io.ebeaninternal.server.deploy.DbMigrationInfo; +import java.util.List; + /** * A column in the logical model. */ @@ -21,6 +22,8 @@ public class MColumn { private String references; private String foreignKeyName; private String foreignKeyIndex; + private ConstraintMode fkeyOnDelete; + private ConstraintMode fkeyOnUpdate; private String comment; private boolean historyExclude; @@ -55,6 +58,8 @@ public class MColumn { this.references = column.getReferences(); this.foreignKeyName = column.getForeignKeyName(); this.foreignKeyIndex = column.getForeignKeyIndex(); + this.fkeyOnDelete = fkeyMode(column.getForeignKeyOnDelete()); + this.fkeyOnUpdate = fkeyMode(column.getForeignKeyOnUpdate()); this.notnull = Boolean.TRUE.equals(column.isNotnull()); this.primaryKey = Boolean.TRUE.equals(column.isPrimaryKey()); this.identity = Boolean.TRUE.equals(column.isIdentity()); @@ -63,6 +68,10 @@ public class MColumn { this.historyExclude = Boolean.TRUE.equals(column.isHistoryExclude()); } + private ConstraintMode fkeyMode(String mode) { + return (mode == null) ? null : ConstraintMode.valueOf(mode); + } + public MColumn(String name, String type) { this.name = name; this.type = type; @@ -89,6 +98,8 @@ public class MColumn { copy.comment = comment; copy.foreignKeyName = foreignKeyName; copy.foreignKeyIndex = foreignKeyIndex; + copy.fkeyOnUpdate = fkeyOnUpdate; + copy.fkeyOnDelete = fkeyOnDelete; copy.historyExclude = historyExclude; copy.notnull = notnull; copy.primaryKey = primaryKey; @@ -154,6 +165,11 @@ public class MColumn { this.foreignKeyIndex = foreignKeyIndex; } + public void setForeignKeyModes(ConstraintMode onDelete, ConstraintMode onUpdate) { + this.fkeyOnDelete = onDelete; + this.fkeyOnUpdate = onUpdate; + } + public String getDefaultValue() { return defaultValue; } @@ -196,7 +212,7 @@ public class MColumn { /** * Set unique specifically for OneToOne mapping. - * We need special DDL for this case for MsSqlServer. + * We need special DDL for this case for SqlServer. */ public void setUniqueOneToOne(String uniqueOneToOne) { this.uniqueOneToOne = uniqueOneToOne; @@ -260,6 +276,8 @@ public class MColumn { c.setReferences(references); c.setForeignKeyName(foreignKeyName); c.setForeignKeyIndex(foreignKeyIndex); + c.setForeignKeyOnDelete(fkeyModeOf(fkeyOnDelete)); + c.setForeignKeyOnUpdate(fkeyModeOf(fkeyOnUpdate)); c.setDefaultValue(defaultValue); c.setComment(comment); c.setUnique(unique); @@ -286,6 +304,10 @@ public class MColumn { return c; } + private String fkeyModeOf(ConstraintMode mode) { + return (mode == null) ? null : mode.name(); + } + protected static boolean different(String val1, String val2) { return (val1 == null) ? val2 != null : !val1.equals(val2); } 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 ba90cdd1c..b5b631458 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java +++ b/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java @@ -11,6 +11,7 @@ import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; import io.ebeaninternal.server.deploy.IndexDefinition; import io.ebeaninternal.server.deploy.InheritInfo; +import io.ebeaninternal.server.deploy.PropertyForeignKey; import io.ebeaninternal.server.deploy.TableJoin; import io.ebeaninternal.server.deploy.TableJoinColumn; import io.ebeaninternal.server.deploy.id.ImportedId; @@ -156,6 +157,8 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor { List modelColumns = new ArrayList<>(columns.length); + PropertyForeignKey foreignKey = p.getForeignKey(); + MCompoundForeignKey compoundKey = null; if (columns.length > 1) { // compound foreign key @@ -180,15 +183,22 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor { col.setDbMigrationInfos(p.getDbMigrationInfos()); col.setDefaultValue(p.getDbColumnDefault()); if (columns.length == 1) { - // single references column (put it on the column) - String refTable = importedProperty.getBeanDescriptor().getBaseTable(); - if (refTable == null) { - // odd case where an EmbeddedId only has 1 property - refTable = p.getTargetDescriptor().getBaseTable(); + if (p.hasForeignKey()) { + // single references column (put it on the column) + String refTable = importedProperty.getBeanDescriptor().getBaseTable(); + if (refTable == null) { + // odd case where an EmbeddedId only has 1 property + refTable = p.getTargetDescriptor().getBaseTable(); + } + col.setReferences(refTable + "." + refColumn); + col.setForeignKeyName(determineForeignKeyConstraintName(col.getName())); + if (p.hasForeignKeyIndex()) { + col.setForeignKeyIndex(determineForeignKeyIndexName(col.getName())); + } + if (foreignKey != null) { + col.setForeignKeyModes(foreignKey.getOnDelete(), foreignKey.getOnUpdate()); + } } - col.setReferences(refTable + "." + refColumn); - col.setForeignKeyName(determineForeignKeyConstraintName(col.getName())); - col.setForeignKeyIndex(determineForeignKeyIndexName(col.getName())); } else { compoundKey.addColumnPair(dbCol, refColumn); } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java index d523bb739..134d4c3e6 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -3,6 +3,7 @@ package io.ebeaninternal.server.deploy; import io.ebean.BackgroundExecutor; import io.ebean.Model; import io.ebean.RawSqlBuilder; +import io.ebean.annotation.ConstraintMode; import io.ebean.bean.BeanCollection; import io.ebean.bean.EntityBean; import io.ebean.config.EncryptKey; @@ -1156,7 +1157,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap { // get the mappedBy property DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); if (mappedProp == null) { - String m = "Error on " + prop.getFullBeanName(); m += " Can not find mappedBy property [" + mappedBy + "] "; m += "in [" + targetDesc + "]"; @@ -1179,6 +1179,19 @@ public class BeanDescriptorManager implements BeanDescriptorMap { otherTableJoin.copyTo(tableJoin, true, tableJoin.getTable()); } + PropertyForeignKey foreignKey = mappedAssocOne.getForeignKey(); + if (foreignKey != null) { + ConstraintMode onDelete = foreignKey.getOnDelete(); + switch (onDelete) { + case SET_DEFAULT: + case SET_NULL: + case CASCADE: { + // turn off cascade delete when we are using the foreign + // key constraint to cascade the delete or set null + prop.getCascadeInfo().setDelete(false); + } + } + } } /** diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index 2bc1bd27c..04e953aca 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -41,6 +41,8 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { private final boolean primaryKeyExport; + private final PropertyForeignKey foreignKey; + private AssocOneHelp localHelp; protected final BeanProperty[] embeddedProps; @@ -69,6 +71,7 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { super(descriptor, deploy); + foreignKey = deploy.getForeignKey(); primaryKeyExport = deploy.isPrimaryKeyExport(); importedPrimaryKey = deploy.isImportedPrimaryKey(); oneToOne = deploy.isOneToOne(); @@ -309,6 +312,18 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { } } + public PropertyForeignKey getForeignKey() { + return foreignKey; + } + + public boolean hasForeignKey() { + return foreignKey == null || !foreignKey.isNoConstraint(); + } + + public boolean hasForeignKeyIndex() { + return foreignKey == null || !foreignKey.isNoIndex(); + } + /** * Return true if this a OneToOne property. Otherwise assumed ManyToOne. */ diff --git a/src/main/java/io/ebeaninternal/server/deploy/PropertyForeignKey.java b/src/main/java/io/ebeaninternal/server/deploy/PropertyForeignKey.java new file mode 100644 index 000000000..ab5b75111 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/deploy/PropertyForeignKey.java @@ -0,0 +1,35 @@ +package io.ebeaninternal.server.deploy; + +import io.ebean.annotation.ConstraintMode; +import io.ebean.annotation.DbForeignKey; + +public class PropertyForeignKey { + + private final boolean noIndex; + private final boolean noConstraint; + private final ConstraintMode onDelete; + private final ConstraintMode onUpdate; + + public PropertyForeignKey(DbForeignKey dbForeignKey) { + this.noIndex = dbForeignKey.noIndex(); + this.noConstraint = dbForeignKey.noConstraint(); + this.onDelete = dbForeignKey.onDelete(); + this.onUpdate = dbForeignKey.onUpdate(); + } + + public boolean isNoIndex() { + return noIndex; + } + + public boolean isNoConstraint() { + return noConstraint; + } + + public ConstraintMode getOnDelete() { + return onDelete; + } + + public ConstraintMode getOnUpdate() { + return onUpdate; + } +} diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java index 9e257def2..93c57efb7 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java @@ -1,5 +1,7 @@ package io.ebeaninternal.server.deploy.meta; +import io.ebeaninternal.server.deploy.PropertyForeignKey; + import javax.persistence.CascadeType; /** @@ -21,6 +23,8 @@ public class DeployBeanPropertyAssocOne extends DeployBeanPropertyAssoc { private String columnPrefix; + private PropertyForeignKey foreignKey; + /** * Create the property. */ @@ -148,4 +152,12 @@ public class DeployBeanPropertyAssocOne extends DeployBeanPropertyAssoc { cascadeInfo.setType(CascadeType.ALL); } } + + public void setForeignKey(PropertyForeignKey foreignKey) { + this.foreignKey = foreignKey; + } + + public PropertyForeignKey getForeignKey() { + return foreignKey; + } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java index 4e5b04e36..6294c5e82 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java @@ -1,10 +1,12 @@ package io.ebeaninternal.server.deploy.parse; +import io.ebean.annotation.DbForeignKey; import io.ebean.annotation.FetchPreference; import io.ebean.annotation.Where; import io.ebean.config.NamingConvention; import io.ebeaninternal.server.deploy.BeanDescriptorManager; import io.ebeaninternal.server.deploy.BeanTable; +import io.ebeaninternal.server.deploy.PropertyForeignKey; import io.ebeaninternal.server.deploy.meta.DeployBeanProperty; import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc; import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; @@ -89,6 +91,11 @@ public class AnnotationAssocOnes extends AnnotationParser { prop.setNullable(false); } + DbForeignKey dbForeignKey = get(prop, DbForeignKey.class); + if (dbForeignKey != null){ + prop.setForeignKey(new PropertyForeignKey(dbForeignKey)); + } + Where where = get(prop, Where.class); if (where != null) { // not expecting this to be used on assoc one properties diff --git a/src/main/resources/ebean-dbmigration-1.0.xsd b/src/main/resources/ebean-dbmigration-1.0.xsd index 4233f28ef..ade3fc9a7 100644 --- a/src/main/resources/ebean-dbmigration-1.0.xsd +++ b/src/main/resources/ebean-dbmigration-1.0.xsd @@ -136,6 +136,8 @@ + + @@ -221,6 +223,8 @@ + + @@ -271,6 +275,8 @@ + + diff --git a/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/Oracle10DdlTest.java b/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/Oracle10DdlTest.java new file mode 100644 index 000000000..efd8c395c --- /dev/null +++ b/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/Oracle10DdlTest.java @@ -0,0 +1,54 @@ +package io.ebeaninternal.dbmigration.ddlgeneration.platform; + +import io.ebean.annotation.ConstraintMode; +import io.ebean.config.dbplatform.oracle.OraclePlatform; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class Oracle10DdlTest { + + Oracle10Ddl create() { + return new Oracle10Ddl(new OraclePlatform()); + } + + @Test + public void appendForeignKeyOnDelete_expectEmtpy_when_nullRestrictSetDefault() { + + Oracle10Ddl oracle = create(); + + StringBuilder sb = new StringBuilder(); + oracle.appendForeignKeyOnDelete(sb, oracle.withDefault(null)); + assertThat(sb.toString()).isEqualTo(""); + + sb = new StringBuilder(); + oracle.appendForeignKeyOnDelete(sb, ConstraintMode.RESTRICT); + assertThat(sb.toString()).isEqualTo(""); + + + sb = new StringBuilder(); + oracle.appendForeignKeyOnDelete(sb, ConstraintMode.SET_DEFAULT); + assertThat(sb.toString()).isEqualTo(""); + } + + @Test + public void appendForeignKeyOnDelete_setNull() { + + Oracle10Ddl oracle = create(); + + StringBuilder sb = new StringBuilder(); + oracle.appendForeignKeyOnDelete(sb, ConstraintMode.SET_NULL); + assertThat(sb.toString()).isEqualTo(" on delete set null"); + } + + @Test + public void appendForeignKeyOnDelete_cascade() { + + Oracle10Ddl oracle = create(); + + StringBuilder sb = new StringBuilder(); + oracle.appendForeignKeyOnDelete(sb, ConstraintMode.CASCADE); + assertThat(sb.toString()).isEqualTo(" on delete cascade"); + } + +} diff --git a/src/test/java/org/tests/cascade/TestOrderedList.java b/src/test/java/org/tests/cascade/TestOrderedList.java index 9baf7a601..4b8540adb 100644 --- a/src/test/java/org/tests/cascade/TestOrderedList.java +++ b/src/test/java/org/tests/cascade/TestOrderedList.java @@ -27,7 +27,12 @@ public class TestOrderedList extends BaseTestCase { List sql = LoggedSqlCollector.current(); assertThat(sql.size()).isGreaterThan(1); assertThat(sql.get(0)).contains("insert into om_ordered_master"); - assertThat(sql.get(1)).contains("insert into om_ordered_detail (name, version, sort_order, master_id) values (?,?,?,?)"); + boolean hasId = sql.get(1).contains(" (id, name"); + if (hasId) { + assertThat(sql.get(1)).contains("insert into om_ordered_detail (id, name, version, sort_order, master_id) values (?,?,?,?,?)"); + } else { + assertThat(sql.get(1)).contains("insert into om_ordered_detail (name, version, sort_order, master_id) values (?,?,?,?)"); + } // update without any changes Ebean.save(master); diff --git a/src/test/java/org/tests/ddl/DfkCascade.java b/src/test/java/org/tests/ddl/DfkCascade.java new file mode 100644 index 000000000..18f4f8201 --- /dev/null +++ b/src/test/java/org/tests/ddl/DfkCascade.java @@ -0,0 +1,50 @@ +package org.tests.ddl; + +import io.ebean.annotation.ConstraintMode; +import io.ebean.annotation.DbForeignKey; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +@Entity +public class DfkCascade { + + @Id + long id; + + String name; + + @ManyToOne + @DbForeignKey(onDelete = ConstraintMode.CASCADE) + DfkCascadeOne one; + + public DfkCascade(String name, DfkCascadeOne one) { + this.name = name; + this.one = one; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public DfkCascadeOne getOne() { + return one; + } + + public void setOne(DfkCascadeOne one) { + this.one = one; + } +} diff --git a/src/test/java/org/tests/ddl/DfkCascadeOne.java b/src/test/java/org/tests/ddl/DfkCascadeOne.java new file mode 100644 index 000000000..fa3c14c2c --- /dev/null +++ b/src/test/java/org/tests/ddl/DfkCascadeOne.java @@ -0,0 +1,47 @@ +package org.tests.ddl; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import java.util.List; + +@Entity +public class DfkCascadeOne { + + @Id + long id; + + String name; + + @OneToMany(mappedBy = "one", cascade = CascadeType.ALL) + List details; + + public DfkCascadeOne(String name) { + this.name = name; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getDetails() { + return details; + } + + public void setDetails(List details) { + this.details = details; + } +} diff --git a/src/test/java/org/tests/ddl/DfkNone.java b/src/test/java/org/tests/ddl/DfkNone.java new file mode 100644 index 000000000..995ba990d --- /dev/null +++ b/src/test/java/org/tests/ddl/DfkNone.java @@ -0,0 +1,49 @@ +package org.tests.ddl; + +import io.ebean.annotation.DbForeignKey; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +@Entity +public class DfkNone { + + @Id + long id; + + String name; + + @ManyToOne + @DbForeignKey(noConstraint = true) + DfkOne one; + + public DfkNone(String name, DfkOne one) { + this.name = name; + this.one = one; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public DfkOne getOne() { + return one; + } + + public void setOne(DfkOne one) { + this.one = one; + } +} diff --git a/src/test/java/org/tests/ddl/DfkOne.java b/src/test/java/org/tests/ddl/DfkOne.java new file mode 100644 index 000000000..84bb826d8 --- /dev/null +++ b/src/test/java/org/tests/ddl/DfkOne.java @@ -0,0 +1,33 @@ +package org.tests.ddl; + +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +public class DfkOne { + + @Id + long id; + + String name; + + public DfkOne(String name) { + this.name = name; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/test/java/org/tests/ddl/DfkSetNull.java b/src/test/java/org/tests/ddl/DfkSetNull.java new file mode 100644 index 000000000..5f55e3f0b --- /dev/null +++ b/src/test/java/org/tests/ddl/DfkSetNull.java @@ -0,0 +1,50 @@ +package org.tests.ddl; + +import io.ebean.annotation.ConstraintMode; +import io.ebean.annotation.DbForeignKey; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +@Entity +public class DfkSetNull { + + @Id + long id; + + String name; + + @ManyToOne + @DbForeignKey(onDelete = ConstraintMode.SET_NULL) + DfkOne one; + + public DfkSetNull(String name, DfkOne one) { + this.name = name; + this.one = one; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public DfkOne getOne() { + return one; + } + + public void setOne(DfkOne one) { + this.one = one; + } +} diff --git a/src/test/java/org/tests/ddl/TestForeignKeyModes.java b/src/test/java/org/tests/ddl/TestForeignKeyModes.java new file mode 100644 index 000000000..d0e8bdccf --- /dev/null +++ b/src/test/java/org/tests/ddl/TestForeignKeyModes.java @@ -0,0 +1,77 @@ +package org.tests.ddl; + +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import org.ebeantest.LoggedSqlCollector; +import org.junit.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestForeignKeyModes extends BaseTestCase { + + @Test + public void none() { + + DfkOne one = new DfkOne("one"); + Ebean.save(one); + + DfkNone none = new DfkNone("none", one); + Ebean.save(none); + + // fails unless there is no Foreign key ... + Ebean.delete(one); + + DfkNone found = Ebean.find(DfkNone.class, none.getId()); + assertThat(found).isNotNull(); + // we still reference one ... even though it does not exist anymore + assertThat(found.getOne()).isNotNull(); + } + + + @Test + public void setNullOnDelete() { + + DfkOne one = new DfkOne("one2"); + Ebean.save(one); + + DfkSetNull other = new DfkSetNull("none", one); + Ebean.save(other); + + // success with ... fkey value set to null + Ebean.delete(one); + + DfkSetNull found = Ebean.find(DfkSetNull.class, other.getId()); + assertThat(found).isNotNull(); + assertThat(found.getOne()).isNull(); + + } + + @Test + public void onDeleteCascade() { + + DfkCascadeOne one = new DfkCascadeOne("one3"); + + + DfkCascade other = new DfkCascade("cascade1", one); + one.getDetails().add(other); + one.getDetails().add(new DfkCascade("cascade2", one)); + one.getDetails().add(new DfkCascade("cascade3", one)); + + Ebean.save(one); + + + LoggedSqlCollector.start(); + Ebean.delete(one); + + List sql = LoggedSqlCollector.stop(); + + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("delete from dfk_cascade_one where id=?"); + + DfkCascade found = Ebean.find(DfkCascade.class, other.getId()); + assertThat(found).isNull(); + + } +} diff --git a/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoin.java b/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoin.java index b2ab2d1c6..0b207f938 100644 --- a/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoin.java +++ b/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoin.java @@ -35,7 +35,7 @@ public class TestOneToOnePrimaryKeyJoin extends BaseTestCase { OtoPrime found = query.findOne(); assertThat(found).isNotNull(); - assertThat(sqlOf(query)).contains("select t0.pid, t0.name, t0.version, t0.pid from oto_prime t0 where t0.pid = ?") + assertThat(sqlOf(query, 10)).contains("select t0.pid, t0.name, t0.version, t0.pid from oto_prime t0 where t0.pid = ?") .as("we don't join to oto_prime_extra"); assertThat(found.getName()).isEqualTo("p" + desc); @@ -47,7 +47,7 @@ public class TestOneToOnePrimaryKeyJoin extends BaseTestCase { OtoPrime oneWith = queryWithFetch.findOne(); assertThat(oneWith).isNotNull(); - assertThat(sqlOf(queryWithFetch)).contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version from oto_prime t0 join oto_prime_extra t1 on t1.eid = t0.pid where t0.pid = ?") + assertThat(sqlOf(queryWithFetch, 10)).contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version from oto_prime t0 join oto_prime_extra t1 on t1.eid = t0.pid where t0.pid = ?") .as("we join to oto_prime_extra"); diff --git a/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinBidi.java b/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinBidi.java index 8becdfe02..7903abc02 100644 --- a/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinBidi.java +++ b/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinBidi.java @@ -35,7 +35,7 @@ public class TestOneToOnePrimaryKeyJoinBidi extends BaseTestCase { OtoUBPrime found = query.findOne(); assertThat(found).isNotNull(); - assertThat(sqlOf(query)).contains("select t0.pid, t0.name, t0.version, t0.pid from oto_ubprime t0 where t0.pid = ?") + assertThat(sqlOf(query, 10)).contains("select t0.pid, t0.name, t0.version, t0.pid from oto_ubprime t0 where t0.pid = ?") .as("we don't join to oto_ubprime_extra"); assertThat(found.getName()).isEqualTo("u" + desc); @@ -47,7 +47,7 @@ public class TestOneToOnePrimaryKeyJoinBidi extends BaseTestCase { OtoUBPrime oneWith = queryWithFetch.findOne(); assertThat(oneWith).isNotNull(); - assertThat(sqlOf(queryWithFetch)).contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version, t1.eid from oto_ubprime t0 left join oto_ubprime_extra t1 on t1.eid = t0.pid where t0.pid = ?") + assertThat(sqlOf(queryWithFetch, 10)).contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version, t1.eid from oto_ubprime t0 left join oto_ubprime_extra t1 on t1.eid = t0.pid where t0.pid = ?") .as("we join to oto_prime_extra"); diff --git a/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinOptional.java b/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinOptional.java index 64835b68b..259991b85 100644 --- a/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinOptional.java +++ b/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinOptional.java @@ -35,7 +35,7 @@ public class TestOneToOnePrimaryKeyJoinOptional extends BaseTestCase { OtoUPrime found = query.findOne(); assertThat(found).isNotNull(); - assertThat(sqlOf(query)).contains("select t0.pid, t0.name, t0.version, t0.pid from oto_uprime t0 where t0.pid = ?") + assertThat(sqlOf(query, 4)).contains("select t0.pid, t0.name, t0.version, t0.pid from oto_uprime t0 where t0.pid = ?") .as("we don't join to oto_uprime_extra"); assertThat(found.getName()).isEqualTo("u" + desc); @@ -47,7 +47,7 @@ public class TestOneToOnePrimaryKeyJoinOptional extends BaseTestCase { OtoUPrime oneWith = queryWithFetch.findOne(); assertThat(oneWith).isNotNull(); - assertThat(sqlOf(queryWithFetch)).contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version from oto_uprime t0 left join oto_uprime_extra t1 on t1.eid = t0.pid where t0.pid = ?") + assertThat(sqlOf(queryWithFetch, 6)).contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version from oto_uprime t0 left join oto_uprime_extra t1 on t1.eid = t0.pid where t0.pid = ?") .as("we join to oto_prime_extra"); diff --git a/src/test/java/org/tests/update/EPersonOnline.java b/src/test/java/org/tests/update/EPersonOnline.java index 7b1f2bb88..232226652 100644 --- a/src/test/java/org/tests/update/EPersonOnline.java +++ b/src/test/java/org/tests/update/EPersonOnline.java @@ -7,7 +7,6 @@ import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import javax.validation.constraints.Size; - import java.time.Instant; @Entity @@ -21,7 +20,7 @@ public class EPersonOnline { @Size(max=127) String email; - boolean online; + boolean onlineStatus; @WhenModified Instant whenUpdated; @@ -42,12 +41,12 @@ public class EPersonOnline { this.email = email; } - public boolean isOnline() { - return online; + public boolean isOnlineStatus() { + return onlineStatus; } - public void setOnline(boolean online) { - this.online = online; + public void setOnlineStatus(boolean onlineStatus) { + this.onlineStatus = onlineStatus; } public Instant getWhenUpdated() { diff --git a/src/test/java/org/tests/update/TestSqlUpdateUpsert.java b/src/test/java/org/tests/update/TestSqlUpdateUpsert.java index 92aa0dd3f..eeb761b4b 100644 --- a/src/test/java/org/tests/update/TestSqlUpdateUpsert.java +++ b/src/test/java/org/tests/update/TestSqlUpdateUpsert.java @@ -15,7 +15,7 @@ public class TestSqlUpdateUpsert extends BaseTestCase { @Test public void h2Merge() { - String sql = "merge into e_person_online (email, online, when_updated) key(email) values (?, ?, now())"; + String sql = "merge into e_person_online (email, online_status, when_updated) key(email) values (?, ?, now())"; String email = "baz@one.com"; @@ -28,9 +28,9 @@ public class TestSqlUpdateUpsert extends BaseTestCase { EPersonOnline found = Ebean.find(EPersonOnline.class, key); assertThat(found).isNotNull(); assertThat(found.getEmail()).isEqualTo(email); - assertThat(found.isOnline()).isTrue(); + assertThat(found.isOnlineStatus()).isTrue(); - String sqlNamed = "merge into e_person_online (email, online, when_updated) key(email) values (:email, :online, now())"; + String sqlNamed = "merge into e_person_online (email, online_status, when_updated) key(email) values (:email, :online, now())"; SqlUpdate sqlUpdate2 = Ebean.createSqlUpdate(sqlNamed) .setGetGeneratedKeys(true) @@ -45,7 +45,7 @@ public class TestSqlUpdateUpsert extends BaseTestCase { assertThat(found2).isNotNull(); assertThat(found2.getId()).isEqualTo(key); assertThat(found2.getEmail()).isEqualTo(email); - assertThat(found2.isOnline()).isFalse(); + assertThat(found2.isOnlineStatus()).isFalse(); assertThat(found2.getWhenUpdated()).isGreaterThan(found.getWhenUpdated()); } @@ -53,7 +53,7 @@ public class TestSqlUpdateUpsert extends BaseTestCase { @Test public void postgresUpsert() { - String sql = "insert into e_person_online (email, online, when_updated) values (?, ?, now()) on conflict (email) do update set when_updated=now(), online = ?"; + String sql = "insert into e_person_online (email, online_status, when_updated) values (?, ?, now()) on conflict (email) do update set when_updated=now(), online_status = ?"; String email = "foo@one.com"; @@ -67,10 +67,10 @@ public class TestSqlUpdateUpsert extends BaseTestCase { EPersonOnline found = Ebean.find(EPersonOnline.class, key); assertThat(found).isNotNull(); assertThat(found.getEmail()).isEqualTo("foo@one.com"); - assertThat(found.isOnline()).isTrue(); + assertThat(found.isOnlineStatus()).isTrue(); - String sqlNamed = "insert into e_person_online (email, online, when_updated) values (:email, :online, now()) on conflict (email) do update set when_updated=now(), online = :online"; + String sqlNamed = "insert into e_person_online (email, online_status, when_updated) values (:email, :online, now()) on conflict (email) do update set when_updated=now(), online_status = :online"; SqlUpdate sqlUpdate2 = Ebean.createSqlUpdate(sqlNamed) .setGetGeneratedKeys(true) .setParameter("email", email) @@ -82,7 +82,7 @@ public class TestSqlUpdateUpsert extends BaseTestCase { assertThat(found2).isNotNull(); assertThat(found2.getId()).isEqualTo(key); assertThat(found2.getEmail()).isEqualTo("foo@one.com"); - assertThat(found2.isOnline()).isFalse(); + assertThat(found2.isOnlineStatus()).isFalse(); assertThat(found2.getWhenUpdated()).isGreaterThan(found.getWhenUpdated()); } @@ -93,7 +93,7 @@ public class TestSqlUpdateUpsert extends BaseTestCase { String email = "bar@one.com"; - String sql = "insert into e_person_online (email, online, when_updated) values (?, ?, current_time) on duplicate key update when_updated=current_time, online = ?"; + String sql = "insert into e_person_online (email, online_status, when_updated) values (?, ?, current_time) on duplicate key update when_updated=current_time, online_status = ?"; SqlUpdate sqlUpdate = Ebean.createSqlUpdate(sql) .setGetGeneratedKeys(true) .setParameter(1, email) @@ -106,10 +106,10 @@ public class TestSqlUpdateUpsert extends BaseTestCase { EPersonOnline found = Ebean.find(EPersonOnline.class, key); assertThat(found).isNotNull(); assertThat(found.getEmail()).isEqualTo("bar@one.com"); - assertThat(found.isOnline()).isTrue(); + assertThat(found.isOnlineStatus()).isTrue(); - String sqlNamed = "insert into e_person_online (email, online, when_updated) values (:email, :online, current_time) on duplicate key update when_updated=current_time, online = :online"; + String sqlNamed = "insert into e_person_online (email, online_status, when_updated) values (:email, :online, current_time) on duplicate key update when_updated=current_time, online_status = :online"; SqlUpdate sqlUpdate2 = Ebean.createSqlUpdate(sqlNamed) .setGetGeneratedKeys(true) .setParameter("email", email) @@ -122,7 +122,7 @@ public class TestSqlUpdateUpsert extends BaseTestCase { assertThat(found2).isNotNull(); assertThat(found2.getId()).isEqualTo(key); assertThat(found2.getEmail()).isEqualTo("bar@one.com"); - assertThat(found2.isOnline()).isFalse(); + assertThat(found2.isOnlineStatus()).isFalse(); } }