io.ebeaninternal.dbmigration.ddlgeneration.platform.DbTriggerBasedHistoryDdl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ebean-ddlgen Show documentation
Show all versions of ebean-ddlgen Show documentation
DDL and DB Migration generation
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.DbConstraintNaming;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
import io.ebeaninternal.dbmigration.model.MColumn;
import io.ebeaninternal.dbmigration.model.MTable;
import java.io.IOException;
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 sysPeriodType = "datetime(6)";
protected String now = "now(6)";
protected String sysPeriodEndValue = "now(6)";
DbTriggerBasedHistoryDdl() {
}
@Override
public void configure(DatabaseConfig config, PlatformDdl platformDdl) {
this.platformDdl = platformDdl;
this.sysPeriod = config.getAsOfSysPeriod();
this.viewSuffix = config.getAsOfViewSuffix();
this.historySuffix = config.getHistoryTableSuffix();
this.constraintNaming = config.getConstraintNaming();
this.sysPeriodStart = sysPeriod + "_start";
this.sysPeriodEnd = sysPeriod + "_end";
}
@Override
public void updateTriggers(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)");
}
updateTriggers(writer, table, update);
}
/**
* Replace the existing triggers/stored procedures/views for history table support given the included columns.
*/
protected abstract void updateHistoryTriggers(DbTriggerUpdate triggerUpdate) throws IOException;
/**
* Process the HistoryTableUpdate which can result in changes to the apply, rollback
* and drop scripts.
*/
protected void updateTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
writer.applyHistoryTrigger().append("-- changes: ").append(update.description()).newLine();
updateHistoryTriggers(createDbTriggerUpdate(writer, table));
}
protected DbTriggerUpdate createDbTriggerUpdate(DdlWrite writer, MTable table) {
List columns = columnNamesForApply(table);
String baseTableName = table.getName();
String historyTableName = historyTableName(baseTableName);
return new DbTriggerUpdate(baseTableName, historyTableName, writer, columns);
}
@Override
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
String baseTable = dropHistoryTable.getBaseTable();
// drop in appropriate order
dropTriggers(writer.applyDropDependencies(), baseTable);
dropHistoryTableEtc(writer.applyDropDependencies(), 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();
dropTriggers(writer.dropAll(), baseTable);
dropHistoryTableEtc(writer.dropAll(), baseTable);
addHistoryTable(writer, table, whenCreatedColumn);
createStoredFunction(writer, table);
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 createStoredFunction(DdlWrite writer, MTable table) throws IOException {
// do nothing
}
protected String normalise(String tableName) {
return constraintNaming.normaliseTable(tableName);
}
protected String historyTableName(String baseTableName) {
return baseTableName + historySuffix;
}
protected String procedureName(String baseTableName) {
return 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.applyHistoryView();
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(now).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(platformDdl.getCreateTableCommandPrefix()).append(" ").append(table.getName()).append(historySuffix).append("(").newLine();
Collection cols = table.allColumns();
for (MColumn column : cols) {
if (!column.isDraftOnly()) {
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);
buffer.append(" ");
buffer.append(platformDdl.lowerColumnName(columnName), 29);
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();
}
/**
* For postgres/h2/mysql we need to drop and recreate the view. Well, we could add columns to the end of the view
* but otherwise we need to drop and create it.
*/
protected void recreateHistoryView(DbTriggerUpdate update) throws IOException {
DdlBuffer buffer = update.dropDependencyBuffer();
// we need to drop the view early/first before any changes to the tables etc
buffer.append("drop view if exists ").append(update.getBaseTable()).append(viewSuffix).endOfStatement();
// recreate the view after all ddl modifications - the view requires ALL columns, also the historyExclude ones.
createWithHistoryView(update.historyViewBuffer(), update.getBaseTable());
}
protected void appendSysPeriodColumns(DdlBuffer apply, String prefix) throws IOException {
appendColumnName(apply, prefix, sysPeriodStart);
appendColumnName(apply, prefix, sysPeriodEnd);
}
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 {
platformDdl.alterTableDropColumn(buffer, baseTableName, sysPeriodStart);
platformDdl.alterTableDropColumn(buffer, baseTableName, sysPeriodEnd);
}
protected void appendInsertIntoHistory(DdlBuffer buffer, String historyTable, List 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(sysPeriodEndValue).append(",");
appendColumnNames(buffer, columns, "OLD.");
buffer.append(");").newLine();
}
void appendColumnNames(DdlBuffer buffer, List 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));
}
}
/**
* Append a single column to the buffer if it is not null.
*/
void appendColumnName(DdlBuffer buffer, String prefix, String columnName) throws IOException {
if (columnName != null) {
buffer.append(prefix).append(columnName);
}
}
/**
* Return the column names included in history for the apply script.
*
* Note that dropped columns are actually still included at this point as they are going
* to be removed from the history handling when the drop script runs that also deletes
* the column.
*
*/
List columnNamesForApply(MTable table) {
return table.allHistoryColumns(true);
}
}