Feature/ddl migration (#1111)

* REFACTOR: use functions from ddl-help

* NEW: Default values are written to DDL

* fix testcase for sqlserver as default constraints will get names in future

* Reading DbMigration

* ADD: DbmIgrationGenerateTest

* Tables must be created first

* Sanity checks and some getters

* DdlMigration gets applied now

* ADD: Testcase and reference migration scripts

* no effective code changes: add comments
This commit is contained in:
Roland Praml
2017-09-08 21:47:33 +12:00
committed by Rob Bygrave
parent 73b3d1d977
commit dd8fff0277
90 changed files with 2767 additions and 70 deletions
@@ -0,0 +1,23 @@
package io.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to specify a default value for DDL-generation & Migration.
* This annotation is <b>EXPERMIENTAL</b> and may change.
*
* TODO: Move this annotation to eben-annotation package
*
* @author Roland Praml, FOCONIS AG
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DbDefault {
/**
* The defaultValue for the column.
*/
String value();
}
@@ -0,0 +1,65 @@
package io.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import io.ebean.Platform;
/**
* Annotation to specify details for DDL &amp; Migration-generation. (e.g. defaults/renames/...)
* This annotation is <b>EXPERMIENTAL</b> and may change.
*
* @author Roland Praml, FOCONIS AG
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Repeatable(DbMigration.List.class)
public @interface DbMigration {
/**
* DdlScripts that will be executed before the 'alter' command.
*
* You may write a custom update routine here.
* If you do not specify an SQL here, and this will alter the table
* to a non-null column, ebean will autogenerate a statement from
* default value like this:
* <pre>
* UPDATE table SET column = 'foo' WHERE column IS NULL
* </pre>
*/
String[] preAlter() default {};
/**
* DdlScript that will be executed after the 'alter' command
*/
String[] postAlter() default {};
/**
* DdlScript that will be executed before the 'add' command
*/
String[] preAdd() default {};
/**
* DdlScript that will be executed after the 'add' command.
* You may write certain update scripts here.
*/
String[] postAdd() default {};
// TODO: Do we need preDrop / postDrop?
Platform[] platforms() default {};
/**
* Repeatable support for {@link Formula}.
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@interface List {
DbMigration[] value() default {};
}
}
@@ -287,6 +287,9 @@ public class DatabasePlatform {
* Create and return a DDL handler for generating DDL scripts.
*/
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
if (platformDdl == null) {
throw new IllegalStateException("Platform " + getName() + " has no DDL Handler");
}
return platformDdl.createDdlHandler(serverConfig);
}
@@ -1,8 +1,13 @@
package io.ebean.config.dbplatform;
import java.sql.Types;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.xml.bind.DatatypeConverter;
import io.ebean.annotation.DbDefault;
/**
* DB Column default values mapping to database platform specific literals.
*/
@@ -22,6 +27,13 @@ public class DbDefaultValue {
* The key for the NOW / current timestamp.
*/
public static final String NOW = "now";
/**
* The 'null' literal.
*/
public static final String NULL = "null";
protected Map<String, String> map = new LinkedHashMap<>();
@@ -63,8 +75,128 @@ public class DbDefaultValue {
if (dbDefaultLiteral == null) {
return null;
}
if (dbDefaultLiteral.startsWith("$RAW:")) {
return dbDefaultLiteral.substring(5);
}
String val = map.get(dbDefaultLiteral);
return val != null ? val : dbDefaultLiteral;
}
/**
* This method checks & convert the {@link DbDefault#value()} to a valid SQL literal.
*
* This is mainly to quote string literals and verify integer/dates for correctness.
* <p>
* Note: There are some special cases:
* </p>
* <ul>
* <li>Normal Quoting: <code>@DbDefault("User's default")</code> on a String propery
* returns: <code>default 'User''s default'</code><br/>
* (the same on an integer property will throw a NumberFormatException)</li>
* <li>Special case null: <code>@DbDefault("null")</code> will return this: <code>default null</code><br/>
* If you need really the String "null", you have to specify <code>@DbDefault("'null'")</code>
* which gives you the <code>default 'null'</code> statement.</li>
* <li>Any statement, that begins and ends with single quote will not be checked or get quoted again.</li>
* <li>A statement that begins with "$RAW:", e.g <code>@DbDefault("$RAW:N'SANDNES'")</code> will lead to
* a <code>default N'SANDNES'</code> in DDL. Note that this is platform specific!</li>
* </ul>
*/
public static String toSqlLiteral(String defaultValue, Class<?> propertyType, int sqlType) {
if (propertyType == null
|| defaultValue == null
|| NULL.equals(defaultValue)
|| (defaultValue.startsWith("'") && defaultValue.endsWith("'"))
|| (defaultValue.startsWith("$RAW:"))) {
return defaultValue;
}
if (Boolean.class.isAssignableFrom(propertyType) || Boolean.TYPE.isAssignableFrom(propertyType)) {
return toBooleanLiteral(defaultValue);
}
if (Number.class.isAssignableFrom(propertyType)
|| Byte.TYPE.equals(propertyType)
|| Short.TYPE.equals(propertyType)
|| Integer.TYPE.equals(propertyType)
|| Long.TYPE.equals(propertyType)
|| Float.TYPE.equals(propertyType)
|| Double.TYPE.equals(propertyType)
|| (propertyType.isEnum() && sqlType == Types.INTEGER)) {
Double.valueOf(defaultValue); // verify if it is a number
return defaultValue;
}
// check if it is a date/time - in all other cases return quoted defaultValue
switch (sqlType) {
// date
case Types.DATE:
return toDateLiteral(defaultValue);
// time
case Types.TIME:
case Types.TIME_WITH_TIMEZONE:
return toTimeLiteral(defaultValue);
// timestamp
case Types.TIMESTAMP:
case Types.TIMESTAMP_WITH_TIMEZONE:
return toDateTimeLiteral(defaultValue);
default:
return toTextLiteral(defaultValue); // do not check other datatypes
}
}
/**
* Checks if specified value is either 'true' or 'false'. The literal is translated later.
*/
private static String toBooleanLiteral(String value) {
if (DbDefaultValue.FALSE.equals(value) || DbDefaultValue.TRUE.equals(value)) {
return value;
}
throw new IllegalArgumentException("'" + value + "' is not a valid value for boolean");
}
/**
* This adds single qoutes around the <code>value</code> and doubles single quotes.
* "User's home" will return "'User''s home'"
*/
private static String toTextLiteral(String value) {
StringBuilder sb = new StringBuilder(value.length()+10);
sb.append('\'');
for (int i = 0; i < value.length(); i++) {
char ch = value.charAt(i);
if (ch == '\'') {
sb.append("''");
} else {
sb.append(ch);
}
}
sb.append('\'');
return sb.toString();
}
private static String toDateLiteral(String value) {
if (NOW.equals(value)) {
return value; // this will get translated later
}
DatatypeConverter.parseDate(value); // verify
return toTextLiteral(value);
}
private static String toTimeLiteral(String value) {
if (NOW.equals(value)) {
return value; // this will get translated later
}
DatatypeConverter.parseTime(value); // verify
return toTextLiteral(value);
}
private static String toDateTimeLiteral(String value) {
if (NOW.equals(value)) {
return value; // this will get translated later
}
DatatypeConverter.parseDateTime(value); // verify
return toTextLiteral(value);
}
}
@@ -36,6 +36,11 @@ public class BaseDdlHandler implements DdlHandler {
for (Object change : changeSetChildren) {
if (change instanceof CreateTable) {
generate(writer, (CreateTable) change);
}
}
for (Object change : changeSetChildren) {
if (change instanceof CreateTable) {
// ignore
} else if (change instanceof DropTable) {
generate(writer, (DropTable) change);
} else if (change instanceof AddTableComment) {
@@ -54,6 +59,8 @@ public class BaseDdlHandler implements DdlHandler {
generate(writer, (AddHistoryTable) change);
} else if (change instanceof DropHistoryTable) {
generate(writer, (DropHistoryTable) change);
} else {
throw new IllegalArgumentException("Unsupported change: " + change);
}
}
}
@@ -15,6 +15,7 @@ import io.ebean.dbmigration.migration.AlterColumn;
import io.ebean.dbmigration.migration.Column;
import io.ebean.dbmigration.migration.CreateIndex;
import io.ebean.dbmigration.migration.CreateTable;
import io.ebean.dbmigration.migration.DdlScript;
import io.ebean.dbmigration.migration.DropColumn;
import io.ebean.dbmigration.migration.DropHistoryTable;
import io.ebean.dbmigration.migration.DropIndex;
@@ -27,6 +28,8 @@ import io.ebean.util.StringHelper;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -70,6 +73,119 @@ public class BaseTableDdl implements TableDdl {
*/
protected Map<String, HistoryTableUpdate> regenerateHistoryTriggers = new LinkedHashMap<>();
private boolean strict;
/**
* Helper class that is used to execute the migration ddl before and after the migration action.
*/
private class DdlMigrationHelp {
private List<String> before;
private List<String> after;
private String tableName;
private String columnName;
private String defaultValue;
/**
* Constructor for DdlMigrationHelp when adding a NEW column.
*/
DdlMigrationHelp(String tableName, Column column) throws IOException {
this.tableName = tableName;
this.columnName = column.getName();
this.defaultValue = platformDdl.convertDefaultValue(column.getDefaultValue());
boolean alterNotNull = Boolean.TRUE.equals(column.isNotnull());
if (column.getBefore().isEmpty() && alterNotNull && defaultValue == null) {
handleStrictError("non-null column has no default value: " + tableName + "." + columnName);
}
before = getScriptsForPlatform(column.getBefore(), platformDdl.getPlatform().getName());
after = getScriptsForPlatform(column.getAfter(), platformDdl.getPlatform().getName());
}
/**
* Constructor for DdlMigrationHelp when altering a column.
*/
DdlMigrationHelp(AlterColumn alter) throws IOException {
this.tableName = alter.getTableName();
this.columnName = alter.getColumnName();
String tmp = alter.getDefaultValue() != null ? alter.getDefaultValue() : alter.getCurrentDefaultValue();
this.defaultValue = platformDdl.convertDefaultValue(tmp);
boolean alterNotNull = Boolean.TRUE.equals(alter.isNotnull());
// here we add the platform's default update script
if (alter.getBefore().isEmpty() && alterNotNull) {
if (defaultValue == null) {
handleStrictError("non-null column has no default value: " + tableName + "." + columnName);
}
before = Arrays.asList(platformDdl.getUpdateNullWithDefault());
} else {
before = getScriptsForPlatform(alter.getBefore(), platformDdl.getPlatform().getName());
}
after = getScriptsForPlatform(alter.getAfter(), platformDdl.getPlatform().getName());
}
public void writeBefore(DdlBuffer buffer) throws IOException {
if (!before.isEmpty()) {
buffer.end();
}
for (String ddlScript : before) {
buffer.append(translate(ddlScript, tableName, columnName, this.defaultValue));
buffer.endOfStatement();
}
}
public void writeAfter(DdlBuffer buffer) throws IOException {
// here we run postmigration scripts
for (String ddlScript : after) {
buffer.append(translate(ddlScript, tableName, columnName, defaultValue));
buffer.endOfStatement();
}
if (!after.isEmpty()) {
buffer.end();
}
}
private List<String> getScriptsForPlatform(List<DdlScript> scripts, String searchPlatform) {
List<String> ret = Collections.emptyList();
for (DdlScript script : scripts) {
if (script.getPlatforms() == null || script.getPlatforms().isEmpty()) {
ret = script.getDdl();
} else for (String platform : StringHelper.splitNames(script.getPlatforms())) {
if (platform.equals(searchPlatform)) {
return script.getDdl();
}
}
}
return ret;
}
/**
* Replaces Table name (${table}), Column name (${column}) and default value (${default}) in DDL.
*/
private String translate(String ddl, String tableName, String columnName, String defaultValue) {
String ret = StringHelper.replaceString(ddl, "${table}", tableName);
ret = StringHelper.replaceString(ret, "${column}", columnName);
return StringHelper.replaceString(ret, "${default}", defaultValue);
}
private void handleStrictError(String message) {
if (strict) {
throw new IllegalArgumentException(message);
} else {
System.err.println("Error in DDL: " + message);
}
}
public String getDefaultValue() {
return defaultValue;
}
}
/**
* Construct with a naming convention and platform specific DDL.
*/
@@ -79,6 +195,7 @@ public class BaseTableDdl implements TableDdl {
this.historyTableSuffix = serverConfig.getHistoryTableSuffix();
this.platformDdl = platformDdl;
this.platformDdl.configure(serverConfig);
this.strict = true; // TODO RPr serverConfig.getMigrationConfig().isStrict();
}
/**
@@ -649,7 +766,9 @@ public class BaseTableDdl implements TableDdl {
*/
@Override
public void generate(DdlWrite writer, AlterColumn alterColumn) throws IOException {
DdlMigrationHelp ddlHelp = new DdlMigrationHelp(alterColumn);
ddlHelp.writeBefore(writer.apply());
if (isTrue(alterColumn.isHistoryExclude())) {
regenerateHistoryTriggers(alterColumn.getTableName(), HistoryTableUpdate.Change.EXCLUDE, alterColumn.getColumnName());
} else if (isFalse(alterColumn.isHistoryExclude())) {
@@ -702,6 +821,7 @@ public class BaseTableDdl implements TableDdl {
// add constraint last (after potential type change)
addCheckConstraint(writer, alterColumn);
}
ddlHelp.writeAfter(writer.apply());
}
private void alterColumnComment(DdlWrite writer, AlterColumn alterColumn) throws IOException {
@@ -856,11 +976,11 @@ public class BaseTableDdl implements TableDdl {
}
protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable) throws IOException {
String ddl = platformDdl.alterTableAddColumn(tableName, column, onHistoryTable);
if (hasValue(ddl)) {
buffer.append(ddl);
buffer.endOfStatement();
}
DdlMigrationHelp help = new DdlMigrationHelp(tableName, column);
help.writeBefore(buffer);
platformDdl.alterTableAddColumn(buffer, tableName, column, onHistoryTable, help.getDefaultValue());
help.writeAfter(buffer);
}
protected boolean isFalse(Boolean value) {
@@ -59,7 +59,7 @@ public class MySqlDdl extends PlatformDdl {
@Override
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
String suffix = isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
// use alter
return "alter table " + tableName + " alter " + columnName + " " + suffix;
@@ -67,7 +67,9 @@ public class MySqlDdl extends PlatformDdl {
@Override
public String alterColumnBaseAttributes(AlterColumn alter) {
if (DdlHelp.isDropDefault(alter.getDefaultValue())) {
return null;
}
String tableName = alter.getTableName();
String columnName = alter.getColumnName();
String type = alter.getType() != null ? alter.getType() : alter.getCurrentType();
@@ -87,6 +87,8 @@ public class PlatformDdl {
protected String columnSetNotnull = "set not null";
protected String columnSetNull = "set null";
protected String updateNullWithDefault = "update ${table} set ${column} = ${default} where ${column} is null";
/**
* Set false for MsSqlServer to allow multiple nulls for OneToOne mapping.
@@ -205,7 +207,7 @@ public class PlatformDdl {
/**
* Convert the DB column default literal to platform specific.
*/
private String convertDefaultValue(String dbDefault) {
public String convertDefaultValue(String dbDefault) {
return dbDefaultValue.convert(dbDefault);
}
@@ -379,25 +381,38 @@ public class PlatformDdl {
return buffer.toString();
}
public String alterTableAddColumn(String tableName, Column column, boolean onHistoryTable) throws IOException {
public void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable, String defaultValue) throws IOException {
String convertedType = convert(column.getType(), false);
StringBuilder buffer = new StringBuilder(90);
buffer.append("alter table ").append(tableName)
.append(' ').append(addColumn).append(' ').append(column.getName())
.append(' ').append(convertedType);
.append(" ").append(addColumn).append(" ").append(column.getName())
.append(" ").append(convertedType);
if (!onHistoryTable) {
if (isTrue(column.isNotnull())) {
buffer.append(" not null");
}
if (!StringHelper.isNull(column.getCheckConstraint())) {
buffer.append(" constraint ").append(column.getCheckConstraintName());
buffer.append(" ").append(column.getCheckConstraint());
if (defaultValue != null) {
if (typeContainsDefault(convertedType)) {
System.err.println("Cannot set default value for '" + tableName + "." + column.getName() + "'");
} else {
buffer.append(" default ");
buffer.append(defaultValue);
}
}
buffer.endOfStatement();
// check constraints cannot be added in one statement for h2
if (!StringHelper.isNull(column.getCheckConstraint())) {
String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(), column.getCheckConstraint());
buffer.append(ddl).endOfStatement();
}
} else {
buffer.endOfStatement();
}
return buffer.toString();
}
/**
@@ -440,19 +455,11 @@ public class PlatformDdl {
return "alter table " + tableName + " " + addConstraint + " " + checkConstraintName + " " + checkConstraint;
}
/**
* Return true if the default value is the special DROP DEFAULT value.
*/
public boolean isDropDefault(String defaultValue) {
return "DROP DEFAULT".equals(defaultValue);
}
/**
* Alter column setting the default value.
*/
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
String suffix = isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix;
}
@@ -505,6 +512,13 @@ public class PlatformDdl {
return naming.lowerColumnName(name);
}
public DatabasePlatform getPlatform() {
return platform;
}
public String getUpdateNullWithDefault() {
return updateNullWithDefault;
}
/**
* Null safe Boolean true test.
@@ -103,16 +103,19 @@ public class SqlServerDdl extends PlatformDdl {
@Override
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
if (isDropDefault(defaultValue)) {
return "-- alter table " + tableName + " drop constraint <unknown> -- find the appropriate constraint for default value on column " + columnName;
if (DdlHelp.isDropDefault(defaultValue)) {
return "alter table " + tableName + " drop constraint df_" + tableName + "_" + columnName;
} else {
return "alter table " + tableName + " add default " + defaultValue + " for " + columnName;
return "alter table " + tableName + " add constraint df_" + tableName + "_" + columnName
+ " default " + defaultValue + " for " + columnName;
}
}
@Override
public String alterColumnBaseAttributes(AlterColumn alter) {
if (DdlHelp.isDropDefault(alter.getDefaultValue())) {
return null;
}
String tableName = alter.getTableName();
String columnName = alter.getColumnName();
String type = alter.getType() != null ? alter.getType() : alter.getCurrentType();
@@ -1,8 +1,12 @@
package io.ebean.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;
@@ -44,10 +48,17 @@ import javax.xml.bind.annotation.XmlType;
* </pre>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "")
@XmlType(name = "", propOrder = {
"before", "after"
})
@XmlRootElement(name = "alterColumn")
public class AlterColumn {
@XmlElement(required = false)
protected List<DdlScript> before;
@XmlElement(required = false)
protected List<DdlScript> after;
@XmlAttribute(name = "columnName", required = true)
protected String columnName;
@XmlAttribute(name = "tableName", required = true)
@@ -533,4 +544,17 @@ public class AlterColumn {
this.dropForeignKeyIndex = value;
}
public List<DdlScript> getBefore() {
if (before == null) {
before = new ArrayList<>();
}
return before;
}
public List<DdlScript> getAfter() {
if (after == null) {
after = new ArrayList<>();
}
return after;
}
}
@@ -1,8 +1,12 @@
package io.ebean.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 javax.xml.bind.annotation.XmlValue;
@@ -39,13 +43,16 @@ import javax.xml.bind.annotation.XmlValue;
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"content"
"before", "after"
})
@XmlRootElement(name = "column")
public class Column {
@XmlValue
protected String content;
@XmlElement(required = false)
protected List<DdlScript> before;
@XmlElement(required = false)
protected List<DdlScript> after;
@XmlAttribute(name = "name", required = true)
protected String name;
@XmlAttribute(name = "type", required = true)
@@ -76,27 +83,22 @@ public class Column {
protected String foreignKeyIndex;
@XmlAttribute(name = "comment")
protected String comment;
/**
* Gets the value of the content property.
*
* @return possible object is
* {@link String }
*/
public String getContent() {
return content;
public List<DdlScript> getBefore() {
if (before == null) {
before = new ArrayList<>();
}
return before;
}
/**
* Sets the value of the content property.
*
* @param value allowed object is
* {@link String }
*/
public void setContent(String value) {
this.content = value;
public List<DdlScript> getAfter() {
if (after == null) {
after = new ArrayList<>();
}
return after;
}
/**
* Gets the value of the name property.
*
@@ -0,0 +1,73 @@
package io.ebean.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.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
/**
* <p>Java class for anonymous complex type.
* <p>
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <pre>
* TODO @Rob: Can this generated automatically?
* </pre>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"ddl"
})
@XmlRootElement(name = "ddl-script")
public class DdlScript {
@XmlValue
protected List<String> ddl;
@XmlAttribute(name = "platforms")
protected String platforms;
/**
* Gets the value of the value property.
*
* @return possible object is
* {@link String }
*/
public List<String> getDdl() {
if (ddl == null) {
ddl = new ArrayList<>();
}
return ddl;
}
public void setDdl(List<String> ddl) {
this.ddl = ddl;
}
/**
* Gets the value of the platforms property.
*
* @return possible object is
* {@link String }
*/
public String getPlatforms() {
return platforms;
}
/**
* Sets the value of the platforms property.
*
* @param value allowed object is
* {@link String }
*/
public void setPlatforms(String value) {
this.platforms = value;
}
}
@@ -1,8 +1,14 @@
package io.ebean.dbmigration.model;
import java.util.List;
import javax.sound.midi.MidiDevice.Info;
import io.ebean.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebean.dbmigration.migration.AlterColumn;
import io.ebean.dbmigration.migration.Column;
import io.ebean.dbmigration.migration.DdlScript;
import io.ebeaninternal.server.deploy.DbMigrationInfo;
/**
* A column in the logical model.
@@ -38,6 +44,8 @@ public class MColumn {
private AlterColumn alterColumn;
private boolean draftOnly;
private List<DbMigrationInfo> dbMigrationInfos;
public MColumn(Column column) {
this.name = column.getName();
@@ -78,6 +86,7 @@ public class MColumn {
copy.checkConstraint = checkConstraint;
copy.checkConstraintName = checkConstraintName;
copy.defaultValue = defaultValue;
copy.dbMigrationInfos = dbMigrationInfos;
copy.references = references;
copy.comment = comment;
copy.foreignKeyName = foreignKeyName;
@@ -257,6 +266,24 @@ public class MColumn {
c.setComment(comment);
c.setUnique(unique);
c.setUniqueOneToOne(uniqueOneToOne);
if (dbMigrationInfos != null) {
for (DbMigrationInfo info : dbMigrationInfos) {
if (!info.getPreAdd().isEmpty()) {
DdlScript script = new DdlScript();
script.setDdl(info.getPreAdd());
script.setPlatforms(info.joinPlatforms());
c.getBefore().add(script);
}
if (!info.getPostAdd().isEmpty()) {
DdlScript script = new DdlScript();
script.setDdl(info.getPostAdd());
script.setPlatforms(info.joinPlatforms());
c.getAfter().add(script);
}
}
}
return c;
}
@@ -281,6 +308,24 @@ public class MColumn {
if (tableWithHistory) {
alterColumn.setWithHistory(Boolean.TRUE);
}
if (dbMigrationInfos != null) {
for (DbMigrationInfo info : dbMigrationInfos) {
if (!info.getPreAlter().isEmpty()) {
DdlScript script = new DdlScript();
script.setDdl(info.getPreAlter());
script.setPlatforms(info.joinPlatforms());
alterColumn.getBefore().add(script);
}
if (!info.getPostAlter().isEmpty()) {
DdlScript script = new DdlScript();
script.setDdl(info.getPostAlter());
script.setPlatforms(info.joinPlatforms());
alterColumn.getAfter().add(script);
}
}
}
}
return alterColumn;
}
@@ -291,6 +336,8 @@ public class MColumn {
*/
public void compare(ModelDiff modelDiff, MTable table, MColumn newColumn) {
this.dbMigrationInfos = newColumn.dbMigrationInfos;
boolean tableWithHistory = table.isWithHistory();
String tableName = table.getName();
@@ -383,6 +430,10 @@ public class MColumn {
}
}
public void setDbMigrationInfos(List<DbMigrationInfo> dbMigrationInfos) {
this.dbMigrationInfos = dbMigrationInfos;
}
/**
* Apply changes based on the AlterColumn request.
*/
@@ -177,7 +177,8 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
String refColumn = importedProperty.getDbColumn();
MColumn col = table.addColumn(dbCol, columnDefn, !p.isNullable());
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();
@@ -232,6 +233,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
}
} else {
col.setDefaultValue(p.getDbColumnDefault());
col.setDbMigrationInfos(p.getDbMigrationInfos());
if (!p.isNullable() || p.isDDLNotNull()) {
col.setNotnull(true);
}
@@ -2,9 +2,11 @@ package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.ValuePair;
import io.ebean.annotation.DbDefault;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.config.EncryptKey;
import io.ebean.config.dbplatform.DbDefaultValue;
import io.ebean.config.dbplatform.DbEncryptFunction;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.plugin.Property;
@@ -232,6 +234,7 @@ public class BeanProperty implements ElPropertyValue, Property {
* DB Column default value for DDL definition (FALSE, NOW etc).
*/
final String dbColumnDefault;
final List<DbMigrationInfo> dbMigrationInfos;
/**
* Database DDL column comment.
@@ -303,7 +306,8 @@ public class BeanProperty implements ElPropertyValue, Property {
this.dbLength = deploy.getDbLength();
this.dbScale = deploy.getDbScale();
this.dbColumnDefn = InternString.intern(deploy.getDbColumnDefn());
this.dbColumnDefault = deploy.getDbColumnDefault();
this.dbColumnDefault = DbDefaultValue.toSqlLiteral(deploy.getDbColumnDefault(), deploy.getPropertyType(), deploy.getDbType());
this.dbMigrationInfos = deploy.getDbMigrationInfos();
this.inherited = false;// deploy.isInherited();
this.owningType = deploy.getOwningType();
@@ -414,6 +418,7 @@ public class BeanProperty implements ElPropertyValue, Property {
this.dbScale = source.getDbScale();
this.dbColumnDefn = InternString.intern(source.getDbColumnDefn());
this.dbColumnDefault = source.dbColumnDefault;
this.dbMigrationInfos = source.dbMigrationInfos;
this.inherited = source.isInherited();
this.owningType = source.owningType;
@@ -1033,6 +1038,13 @@ public class BeanProperty implements ElPropertyValue, Property {
return dbColumnDefn != null ? null : dbColumnDefault;
}
/**
* Return the DDL-Migration Infos
*/
public List<DbMigrationInfo> getDbMigrationInfos() {
return dbMigrationInfos;
}
/**
* Return the bean Field associated with this property.
*/
@@ -0,0 +1,71 @@
package io.ebeaninternal.server.deploy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import io.ebean.Platform;
/**
* Class to hold the DDL-migration information that is needed to do correct alters.
*
* @author Roland Praml, FOCONIS AG
*/
public class DbMigrationInfo {
private final List<String> preAdd;
private final List<String> postAdd;
private final List<String> preAlter;
private final List<String> postAlter;
private final List<Platform> platforms;
public DbMigrationInfo(String[] preAdd, String[] postAdd, String[] preAlter, String[] postAlter, Platform[] platforms) {
this.preAdd = toList(preAdd);
this.postAdd = toList(postAdd);
this.preAlter = toList(preAlter);
this.postAlter = toList(postAlter);
this.platforms = toList(platforms);
}
private <T> List<T> toList(T[] scripts) {
if (scripts.length == 0) {
return Collections.emptyList();
} else {
return Collections.unmodifiableList(Arrays.asList(scripts));
}
}
public List<String> getPreAdd() {
return preAdd;
}
public List<String> getPostAdd() {
return postAdd;
}
public List<String> getPreAlter() {
return preAlter;
}
public List<String> getPostAlter() {
return postAlter;
}
public List<Platform> getPlatforms() {
return platforms;
}
public String joinPlatforms() {
if (platforms.isEmpty()) {
return null;
} else {
StringBuilder sb = new StringBuilder();
for (Platform p : platforms) {
if (sb.length() > 0) {
sb.append(',');
}
sb.append(p.name().toLowerCase());
}
return sb.toString();
}
}
}
@@ -17,6 +17,7 @@ import io.ebean.config.dbplatform.DbEncryptFunction;
import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.DeployDocPropertyOptions;
import io.ebeaninternal.server.deploy.DbMigrationInfo;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.deploy.parse.AnnotationBase;
import io.ebeaninternal.server.el.ElPropertyValue;
@@ -34,7 +35,9 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
@@ -221,6 +224,8 @@ public class DeployBeanProperty {
private String dbColumnDefault;
private List<DbMigrationInfo> dbMigrationInfos;
public DeployBeanProperty(DeployBeanDescriptor<?> desc, Class<?> propertyType, ScalarType<?> scalarType, ScalarTypeConverter<?, ?> typeConverter) {
this.desc = desc;
this.propertyType = propertyType;
@@ -949,7 +954,9 @@ public class DeployBeanProperty {
public void checkPrimitiveBoolean() {
if (boolean.class.equals(propertyType) && !softDelete) {
this.nullable = false;
this.dbColumnDefault = DbDefaultValue.FALSE;
if (dbColumnDefault == null) {
this.dbColumnDefault = DbDefaultValue.FALSE;
}
} else if (!id && !versionColumn && PRIMITIVE_NUMBER_TYPES.contains(propertyType)) {
this.nullable = false;
@@ -1003,6 +1010,10 @@ public class DeployBeanProperty {
return dbColumnDefault;
}
public void setDbColumnDefault(String dbColumnDefault) {
this.dbColumnDefault = dbColumnDefault;
}
public void setTenantId() {
this.tenantId = true;
this.nullable = false;
@@ -1013,4 +1024,15 @@ public class DeployBeanProperty {
public boolean isTenantId() {
return tenantId;
}
public void addDbMigrationInfo(DbMigrationInfo info) {
if (dbMigrationInfos == null) {
dbMigrationInfos = new ArrayList<>();
}
dbMigrationInfos.add(info);
}
public List<DbMigrationInfo> getDbMigrationInfos() {
return dbMigrationInfos;
}
}
@@ -7,6 +7,7 @@ import io.ebean.config.dbplatform.DbEncrypt;
import io.ebean.config.dbplatform.DbEncryptFunction;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebeaninternal.server.deploy.DbMigrationInfo;
import io.ebeaninternal.server.deploy.IndexDefinition;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
@@ -131,6 +132,7 @@ public class AnnotationFields extends AnnotationParser {
}
initWhoProperties(prop);
readDbMigration(prop);
}
private void initWhoProperties(DeployBeanProperty prop) {
@@ -287,14 +289,20 @@ public class AnnotationFields extends AnnotationParser {
if (get(prop, HistoryExclude.class) != null) {
prop.setExcludedFromHistory();
}
Length length = get(prop, Length.class);
if (length != null) {
prop.setDbLength(length.value());
}
io.ebean.annotation.NotNull nonNull = get(prop, io.ebean.annotation.NotNull.class);
if (nonNull != null) {
prop.setNullable(false);
}
readDbMigration(prop);
if (validationAnnotations) {
NotNull notNull = get(prop, NotNull.class);
if (notNull != null && isEbeanValidationGroups(notNull.groups())) {
@@ -346,6 +354,17 @@ public class AnnotationFields extends AnnotationParser {
}
}
private void readDbMigration(DeployBeanProperty prop) {
DbDefault dbDefault = get(prop, DbDefault.class);
if (dbDefault != null) {
prop.setDbColumnDefault(dbDefault.value());
}
Set<DbMigration> dbMigration = getAll(prop, DbMigration.class);
dbMigration.forEach(ann -> prop.addDbMigrationInfo(
new DbMigrationInfo(ann.preAdd(), ann.postAdd(), ann.preAlter(), ann.postAlter(), ann.platforms())));
}
private void addIndex(DeployBeanProperty prop, Index index) {
String[] columnNames;
if (index.columnNames().length == 0) {