Skip to content

Commit

Permalink
#374 - Add @history support with MySql - refactor extracting DbTrigge…
Browse files Browse the repository at this point in the history
…rBasedHistoryDdl
  • Loading branch information
rbygrave committed Aug 17, 2015
1 parent 24971bc commit 98ec00a
Show file tree
Hide file tree
Showing 8 changed files with 444 additions and 207 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public class BaseTableDdl implements TableDdl {
* Base tables that have associated history tables that need their triggers/functions regenerated as
* columns have been added, removed, included or excluded.
*/
protected Map<String,HistoryTableUpdate> regenerateHistoryTriggers = new LinkedHashMap<String,HistoryTableUpdate>();
protected Map<String, HistoryTableUpdate> regenerateHistoryTriggers = new LinkedHashMap<String, HistoryTableUpdate>();

/**
* Construct with a naming convention and platform specific DDL.
Expand Down Expand Up @@ -114,14 +114,7 @@ public void generate(DdlWrite writer, CreateTable createTable) throws IOExceptio

DdlBuffer apply = writer.apply();
apply.append("create table ").append(tableName).append(" (");
for (int i = 0; i < columns.size(); i++) {
apply.newLine();
writeColumnDefinition(apply, columns.get(i), useIdentity);
if (i < columns.size() - 1) {
apply.append(",");
}
}

writeTableColumns(apply, columns, useIdentity);
writeCheckConstraints(apply, createTable);
writeUniqueConstraints(apply, createTable);
writeCompoundUniqueConstraints(apply, createTable);
Expand Down Expand Up @@ -157,6 +150,10 @@ public void generate(DdlWrite writer, CreateTable createTable) throws IOExceptio

}

private void writeTableColumns(DdlBuffer apply, List<Column> columns, boolean useIdentity) throws IOException {
platformDdl.writeTableColumns(apply, columns, useIdentity);
}

/**
* Specific handling of OneToOne unique constraints for MsSqlServer.
* For all other DB platforms these unique constraints are done inline as per normal.
Expand Down Expand Up @@ -415,35 +412,6 @@ protected String lowerName(String name) {
return naming.lowerName(name);
}

/**
* Write the column definition to the create table statement.
*/
protected void writeColumnDefinition(DdlBuffer buffer, Column column, boolean useIdentity) throws IOException {

boolean identityColumn = useIdentity && isTrue(column.isPrimaryKey());
String platformType = convertToPlatformType(column.getType(), identityColumn);

buffer.append(" ");
buffer.append(lowerName(column.getName()), 30);
buffer.append(platformType);
if (isTrue(column.isNotnull()) || isTrue(column.isPrimaryKey())) {
buffer.append(" not null");
}

// add check constraints later as we really want to give them a nice name
// so that the database can potentially provide a nice SQL error
}

/**
* Convert the expected logical type into a platform specific one.
* <p>
* For example clob -> text for postgres.
* </p>
*/
protected String convertToPlatformType(String type, boolean identity) {
return platformDdl.convert(type, identity);
}

/**
* Return the list of columns that make the primary key.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package com.avaje.ebean.dbmigration.ddlgeneration.platform;

import com.avaje.ebean.config.DbConstraintNaming;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
import com.avaje.ebean.dbmigration.ddlgeneration.DdlWrite;
import com.avaje.ebean.dbmigration.migration.AddHistoryTable;
import com.avaje.ebean.dbmigration.migration.DropHistoryTable;
import com.avaje.ebean.dbmigration.model.MColumn;
import com.avaje.ebean.dbmigration.model.MTable;

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

/**
* Uses DB triggers to maintain a history table.
*/
public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {

protected DbConstraintNaming constraintNaming;

protected PlatformDdl platformDdl;

protected String sysPeriod;
protected String sysPeriodStart;
protected String sysPeriodEnd;

protected String viewSuffix;
protected String historySuffix;


protected String currentTimestamp = "now(6)";
protected String sysPeriodType = "datetime(6)";

public DbTriggerBasedHistoryDdl() {
}

@Override
public void configure(ServerConfig serverConfig, PlatformDdl platformDdl) {
this.platformDdl = platformDdl;
this.sysPeriod = serverConfig.getAsOfSysPeriod();
this.viewSuffix = serverConfig.getAsOfViewSuffix();
this.historySuffix = serverConfig.getHistoryTableSuffix();
this.constraintNaming = serverConfig.getConstraintNaming();

this.sysPeriodStart = sysPeriod+"_start";
this.sysPeriodEnd = sysPeriod+"_end";
}

@Override
public void regenerateHistoryTriggers(DdlWrite writer, HistoryTableUpdate update) throws IOException {

MTable table = writer.getTable(update.getBaseTable());
if (table == null) {
throw new IllegalStateException("MTable "+update.getBaseTable()+" not found in writer? (required for history DDL)");
}
regenerateHistoryTriggers(writer, table, update);
}

protected abstract void regenerateHistoryTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException;

@Override
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {

String baseTable = dropHistoryTable.getBaseTable();

// drop in appropriate order
dropTriggers(writer.dropHistory(), baseTable);
dropHistoryTableEtc(writer.dropHistory(), baseTable);
}


@Override
public void addHistoryTable(DdlWrite writer, AddHistoryTable addHistoryTable) throws IOException {

String baseTable = addHistoryTable.getBaseTable();
MTable table = writer.getTable(baseTable);
if (table == null) {
throw new IllegalStateException("MTable "+baseTable+" not found in writer? (required for history DDL)");
}

createWithHistory(writer, table);
}

@Override
public void createWithHistory(DdlWrite writer, MTable table) throws IOException {

String baseTable = table.getName();
String whenCreatedColumn = table.getWhenCreatedColumn();

// rollback changes in appropriate order
dropTriggers(writer.rollback(), baseTable);
dropHistoryTableEtc(writer.rollback(), baseTable);

addHistoryTable(writer, table, whenCreatedColumn);
addStoredFunction(writer, table, null);
createTriggers(writer, table);
}

protected abstract void createTriggers(DdlWrite writer, MTable table) throws IOException;

protected abstract void dropTriggers(DdlBuffer buffer, String baseTable) throws IOException;

protected void addStoredFunction(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
// do nothing
}

protected String normalise(String tableName) {
return constraintNaming.normaliseTable(tableName);
}

protected String historyTableName(String baseTableName) {
return normalise(baseTableName) + historySuffix;
}

protected String procedureName(String baseTableName) {
return normalise(baseTableName) + "_history_version";
}

protected String triggerName(String baseTableName) {
return normalise(baseTableName) + "_history_upd";
}

protected String updateTriggerName(String baseTableName) {
return normalise(baseTableName) + "_history_upd";
}

protected String deleteTriggerName(String baseTableName) {
return normalise(baseTableName) + "_history_del";
}

protected void addHistoryTable(DdlWrite writer, MTable table, String whenCreatedColumn) throws IOException {

String baseTableName = table.getName();

DdlBuffer apply = writer.applyHistory();

addSysPeriodColumns(apply, baseTableName, whenCreatedColumn);
createHistoryTable(apply, table);
createWithHistoryView(apply, baseTableName);
}

protected void addSysPeriodColumns(DdlBuffer apply, String baseTableName, String whenCreatedColumn) throws IOException {

apply.append("alter table ").append(baseTableName).append(" add column ")
.append(sysPeriodStart).append(" ").append(sysPeriodType).append(" default ").append(currentTimestamp).endOfStatement();
apply.append("alter table ").append(baseTableName).append(" add column ")
.append(sysPeriodEnd).append(" ").append(sysPeriodType).endOfStatement();

if (whenCreatedColumn != null) {
apply.append("update ").append(baseTableName).append(" set ").append(sysPeriodStart).append(" = ").append(whenCreatedColumn).endOfStatement();
}
}

protected void createHistoryTable(DdlBuffer apply, MTable table) throws IOException {

apply.append("create table ").append(table.getName()).append(historySuffix).append("(").newLine();

Collection<MColumn> cols = table.getColumns().values();
for (MColumn column : cols) {
writeColumnDefinition(apply, column.getName(), column.getType());
apply.append(",").newLine();
}
writeColumnDefinition(apply, sysPeriodStart, sysPeriodType);
apply.append(",").newLine();
writeColumnDefinition(apply, sysPeriodEnd, sysPeriodType);
apply.newLine().append(")").endOfStatement();
}

/**
* Write the column definition to the create table statement.
*/
protected void writeColumnDefinition(DdlBuffer buffer, String columnName, String type) throws IOException {

String platformType = platformDdl.convert(type, false);
buffer.append(" ");
buffer.append(platformDdl.lowerName(columnName), 30);
buffer.append(platformType);
}

protected void createWithHistoryView(DdlBuffer apply, String baseTableName) throws IOException {

apply
.append("create view ").append(baseTableName).append(viewSuffix)
.append(" as select * from ").append(baseTableName)
.append(" union all select * from ").append(baseTableName).append(historySuffix)
.endOfStatement().end();
}

protected void dropHistoryTableEtc(DdlBuffer buffer, String baseTableName) throws IOException {

buffer.append("drop view ").append(baseTableName).append(viewSuffix).endOfStatement();
dropSysPeriodColumns(buffer, baseTableName);
buffer.append("drop table ").append(baseTableName).append(historySuffix).endOfStatement().end();
}

protected void dropSysPeriodColumns(DdlBuffer buffer, String baseTableName) throws IOException {
buffer.append("alter table ").append(baseTableName).append(" drop column ").append(sysPeriodStart).endOfStatement();
buffer.append("alter table ").append(baseTableName).append(" drop column ").append(sysPeriodEnd).endOfStatement();
}

//protected abstract void addFunction(DdlBuffer apply, String procedureName, String historyTable, List<String> includedColumns) throws IOException;

protected void appendInsertIntoHistory(DdlBuffer buffer, String historyTable, List<String> columns) throws IOException {

buffer.append(" insert into ").append(historyTable).append(" (").append(sysPeriodStart).append(",").append(sysPeriodEnd).append(",");
appendColumnNames(buffer, columns, "");
buffer.append(") values (OLD.").append(sysPeriodStart).append(", ").append(currentTimestamp).append(",");
appendColumnNames(buffer, columns, "OLD.");
buffer.append(");").newLine();
}

protected void appendColumnNames(DdlBuffer buffer, List<String> columns, String columnPrefix) throws IOException {

for (int i=0; i< columns.size(); i++) {
if (i > 0) {
buffer.append(", ");
}
buffer.append(columnPrefix);
buffer.append(columns.get(i));
}
}

/**
* Return the list of included columns in order.
*/
protected List<String> includedColumnNames(MTable table) throws IOException {

Collection<MColumn> columns = table.getColumns().values();
List<String> includedColumns = new ArrayList<String>(columns.size());

for (MColumn column : columns) {
if (!column.isHistoryExclude()) {
includedColumns.add(column.getName());
}
}
return includedColumns;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public MySqlDdl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
super(platformTypes, dbIdentity);
this.alterColumn = "modify";
this.dropUniqueConstraint = "drop index";
this.historyDdl = new MySqlHistoryDdl();
}

/**
Expand Down
Loading

0 comments on commit 98ec00a

Please sign in to comment.