Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DB Migration with helper stored procedures (for MySql and SQL Server) #1451

Merged
merged 2 commits into from
Jul 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java
Original file line number Diff line number Diff line change
Expand Up @@ -314,24 +314,28 @@ private void configurePlatforms() {
private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform) throws IOException {

if (dbPlatform != null) {
ExtraDdl extraDdl = ExtraDdlXmlReader.read("/extra-ddl.xml");
if (extraDdl != null) {
List<DdlScript> ddlScript = extraDdl.getDdlScript();
for (DdlScript script : ddlScript) {
if (!script.isDrop() && ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
generateExtraDdl(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltin());
generateExtraDdl(migrationDir, dbPlatform, ExtraDdlXmlReader.read());
}
}

private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform, ExtraDdl extraDdl) throws IOException {
if (extraDdl != null) {
List<DdlScript> ddlScript = extraDdl.getDdlScript();
for (DdlScript script : ddlScript) {
if (!script.isDrop() && ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
writeExtraDdl(migrationDir, script);
}
}
}
}
}

/**
* Write (or override) the "repeatable" migration script.
*/
private void writeExtraDdl(File migrationDir, DdlScript script) throws IOException {

String fullName = repeatableMigrationName(script.getName());
String fullName = repeatableMigrationName(script.isInit(), script.getName());

logger.info("writing repeatable script {}", fullName);

Expand All @@ -342,8 +346,18 @@ private void writeExtraDdl(File migrationDir, DdlScript script) throws IOExcepti
}
}

private String repeatableMigrationName(String scriptName) {
return "R__" + scriptName.replace(' ', '_') + migrationConfig.getApplySuffix();


private String repeatableMigrationName(boolean init, String scriptName) {
StringBuilder sb = new StringBuilder();
if (init) {
sb.append("I__");
} else {
sb.append("R__");
}
sb.append(scriptName.replace(' ', '_'));
sb.append(migrationConfig.getApplySuffix());
return sb.toString();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ public String alterTableDropForeignKey(String tableName, String fkName) {
return "alter table " + tableName + " drop foreign key " + maxConstraintName(fkName);
}

/**
* It is rather complex to delete a column on MySql as there must not exist any foreign keys.
* That's why we call a user stored procedure here
*/
@Override
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {

buffer.append("CALL usp_ebean_drop_column('").append(tableName).append("', '").append(columnName).append("')").endOfStatement();
}

@Override
public String alterTableDropConstraint(String tableName, String constraintName) {
// drop constraint not supported in MySQL 5.7 and 8.0 but starting with MariaDB 10.2.1 CHECK is evaluated
Expand All @@ -61,7 +71,7 @@ public String alterColumnNotnull(String tableName, String columnName, boolean no
@Override
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {

String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + convertDefaultValue(defaultValue);

// use alter
return "alter table " + tableName + " alter " + columnName + " " + suffix;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import io.ebean.annotation.ConstraintMode;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.server.persist.platform.MultiValueBind;

import java.io.IOException;

Expand Down Expand Up @@ -137,13 +139,7 @@ public String alterColumnDefaultValue(String tableName, String columnName, Strin
// a rather complex statement.
StringBuilder sb = new StringBuilder();
if (DdlHelp.isDropDefault(defaultValue)) {
sb.append("delimiter $$\n");
sb.append("DECLARE @Tmp nvarchar(200);");
sb.append("select @Tmp = t1.name from sys.default_constraints t1\n");
sb.append(" join sys.columns t2 on t1.object_id = t2.default_object_id\n");
sb.append(" where t1.parent_object_id = OBJECT_ID('").append(tableName)
.append("') and t2.name = '").append(columnName).append("';\n");
sb.append("if @Tmp is not null EXEC('alter table ").append(tableName).append(" drop constraint ' + @Tmp)$$");
sb.append("EXEC usp_ebean_drop_default_constraint ").append(tableName).append(", ").append(columnName);
} else {
sb.append("alter table ").append(tableName);
sb.append(" add default ").append(convertDefaultValue(defaultValue)).append(" for ").append(columnName);
Expand Down Expand Up @@ -200,25 +196,50 @@ public void addColumnComment(DdlBuffer apply, String table, String column, Strin

/**
* It is rather complex to delete a column on SqlServer as there must not exist any references
* (constraints, default values, indices and foreign keys). The list is not yet complete, as
* indices over multiple columns will not yet deleted.
* (This may be changed to delete all refering objects by using the sys.* tables later)
* (constraints, default values, indices and foreign keys). That's why we call a user stored procedure here
*/
@Override
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
buffer.append("-- drop column ").append(tableName).append(".").append(columnName).endOfStatement();

buffer.append(alterTableDropUniqueConstraint(tableName, naming.uniqueConstraintName(tableName, columnName)));
buffer.endOfStatement();
buffer.append(alterColumnDefaultValue(tableName, columnName, DdlHelp.DROP_DEFAULT));
buffer.endOfStatement();
buffer.append(alterTableDropConstraint(tableName, naming.checkConstraintName(tableName, columnName)));
buffer.endOfStatement();
buffer.append(dropIndex(naming.indexName(tableName, columnName), tableName));
buffer.endOfStatement();
buffer.append(alterTableDropForeignKey(tableName, naming.foreignKeyConstraintName(tableName, columnName)));
buffer.endOfStatement();
super.alterTableDropColumn(buffer, tableName, columnName);

buffer.append("EXEC usp_ebean_drop_column ").append(tableName).append(", ").append(columnName).endOfStatement();
}

/**
* This writes the multi value datatypes needed for {@link MultiValueBind}
*/
@Override
public void generateProlog(DdlWrite write) throws IOException {
super.generateProlog(write);

generateTVPDefinitions(write, "bigint");
generateTVPDefinitions(write, "float");
generateTVPDefinitions(write, "bit");
generateTVPDefinitions(write, "date");
generateTVPDefinitions(write, "time");
//generateTVPDefinitions(write, "datetime2");
generateTVPDefinitions(write, "uniqueidentifier");
generateTVPDefinitions(write, "nvarchar(max)");

}

private void generateTVPDefinitions(DdlWrite write, String definition) throws IOException {
int pos = definition.indexOf('(');
String name = pos == -1 ? definition : definition.substring(0, pos);

dropTVP(write.dropAll(), name);
//TVPs are included in "I__create_procs.sql"
//createTVP(write.apply(), name, definition);
}

private void dropTVP(DdlBuffer ddl, String name) throws IOException {
ddl.append("if exists (select name from sys.types where name = 'ebean_").append(name)
.append("_tvp') drop type ebean_").append(name).append("_tvp").endOfStatement();
}

private void createTVP(DdlBuffer ddl, String name, String definition) throws IOException {
ddl.append("if not exists (select name from sys.types where name = 'ebean_").append(name)
.append("_tvp') create type ebean_").append(name).append("_tvp as table (c1 ").append(definition).append(")")
.endOfStatement();
}

}
17 changes: 17 additions & 0 deletions src/main/java/io/ebeaninternal/dbmigration/model/CurrentModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@
import io.ebeaninternal.dbmigration.model.build.ModelBuildBeanVisitor;
import io.ebeaninternal.dbmigration.model.build.ModelBuildContext;
import io.ebeaninternal.dbmigration.model.visitor.VisitAllUsing;
import io.ebeaninternal.extraddl.model.DdlScript;
import io.ebeaninternal.extraddl.model.ExtraDdl;
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;

import java.io.IOException;
import java.util.List;

/**
* Reads EbeanServer bean descriptors to build the current model.
Expand Down Expand Up @@ -111,6 +115,19 @@ public String getCreateDdl() throws IOException {
if (header != null && !header.isEmpty()) {
ddl.append(header).append('\n');
}

ExtraDdl extraDdl = ExtraDdlXmlReader.readBuiltin();
if (extraDdl != null) {
List<DdlScript> ddlScript = extraDdl.getDdlScript();
for (DdlScript script : ddlScript) {
if (script.isInit() && ExtraDdlXmlReader.matchPlatform(server.getDatabasePlatform().getName(), script.getPlatforms())) {
ddl.append("-- init script " + script.getName()).append('\n');
ddl.append(script.getValue());
}
}
}


ddl.append(write.apply().getBuffer());
ddl.append(write.applyForeignKeys().getBuffer());
ddl.append(write.applyHistoryView().getBuffer());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,13 @@ public void visitScalar(BeanProperty p) {
}
} else {
col.setDefaultValue(p.getDbColumnDefault());
col.setDbMigrationInfos(p.getDbMigrationInfos());
if (!p.isNullable() || p.isDDLNotNull()) {
col.setNotnull(true);
}
}

col.setDbMigrationInfos(p.getDbMigrationInfos());

if (p.isUnique() && !p.isId()) {
col.setUnique(determineUniqueConstraintName(col.getName()));
indexSetAdd(col.getName());
Expand Down
16 changes: 15 additions & 1 deletion src/main/java/io/ebeaninternal/extraddl/model/DdlScript.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public class DdlScript {
protected String platforms;
@XmlAttribute(name = "drop")
protected boolean drop;

@XmlAttribute(name = "init")
protected boolean init;
/**
* Gets the value of the value property.
*
Expand Down Expand Up @@ -114,4 +115,17 @@ public void setDrop(boolean drop) {
this.drop = drop;
}

/**
* Return if this a init script.
*/
public boolean isInit() {
return init;
}

/**
* Sets that this is a init script.
*/
public void setInit(boolean init) {
this.init = init;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,24 @@ private static String genericPlatformMatch(String platformName) {
}
}

/**
* Read the builtin extra ddl. (Stored procedures, tvp types etc)
*/
public static ExtraDdl readBuiltin() {
return read("/io/ebeaninternal/dbmigration/builtin-extra-ddl.xml");
}

/**
* Read the extra ddl.
*/
public static ExtraDdl read() {
return read("/extra-ddl.xml");
}

/**
* Read and return a ExtraDdl from an xml document at the given resource path.
*/
public static ExtraDdl read(String resourcePath) {
private static ExtraDdl read(String resourcePath) {

try (InputStream is = ExtraDdlXmlReader.class.getResourceAsStream(resourcePath)) {
if (is == null) {
Expand Down
Loading