mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#374 - Add @History support with MySql - refactor extracting DbTriggerBasedHistoryDdl
This commit is contained in:
@@ -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.
|
||||
@@ -114,14 +114,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
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);
|
||||
@@ -157,6 +150,10 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -415,35 +412,6 @@ public class BaseTableDdl implements TableDdl {
|
||||
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.
|
||||
*/
|
||||
|
||||
+241
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ public class MySqlDdl extends PlatformDdl {
|
||||
super(platformTypes, dbIdentity);
|
||||
this.alterColumn = "modify";
|
||||
this.dropUniqueConstraint = "drop index";
|
||||
this.historyDdl = new MySqlHistoryDdl();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlWrite;
|
||||
import com.avaje.ebean.dbmigration.model.MTable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* MySql history support using DB triggers to maintain a history table.
|
||||
*/
|
||||
public class MySqlHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
|
||||
|
||||
public MySqlHistoryDdl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dropTriggers(DdlBuffer buffer, String baseTable) throws IOException {
|
||||
|
||||
buffer.append("drop trigger ").append(updateTriggerName(baseTable)).endOfStatement();
|
||||
buffer.append("drop trigger ").append(deleteTriggerName(baseTable)).endOfStatement();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createTriggers(DdlWrite writer, MTable table) throws IOException {
|
||||
|
||||
String baseTableName = table.getName();
|
||||
String historyTableName = historyTableName(baseTableName);
|
||||
List<String> includedColumns = includedColumnNames(table);
|
||||
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
|
||||
addBeforeUpdate(apply, updateTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
|
||||
addBeforeDelete(apply, deleteTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void regenerateHistoryTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
|
||||
|
||||
String baseTableName = table.getName();
|
||||
String historyTableName = historyTableName(baseTableName);
|
||||
List<String> includedColumns = includedColumnNames(table);
|
||||
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
|
||||
apply.append("-- Regenerated ").newLine();
|
||||
apply.append("-- changes: ").append(update.description()).newLine();
|
||||
// lock the base table while we drop and recreate the triggers
|
||||
apply.append("lock tables ").append(baseTableName).append(" write").endOfStatement();
|
||||
dropTriggers(apply, baseTableName);
|
||||
addBeforeUpdate(apply, updateTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
|
||||
addBeforeDelete(apply, deleteTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
|
||||
apply.append("unlock tables").endOfStatement();
|
||||
|
||||
// put a reverted version into the rollback buffer
|
||||
update.toRevertedColumns(includedColumns);
|
||||
|
||||
DdlBuffer rollback = writer.rollback();
|
||||
rollback.append("-- Revert regenerated ").newLine();
|
||||
rollback.append("-- revert changes: ").append(update.description()).newLine();
|
||||
// lock the base table while we drop and recreate the triggers
|
||||
rollback.append("lock tables ").append(baseTableName).append(" write").endOfStatement();
|
||||
dropTriggers(rollback, baseTableName);
|
||||
addBeforeUpdate(rollback, updateTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
|
||||
addBeforeDelete(rollback, deleteTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
|
||||
rollback.append("unlock tables").endOfStatement();
|
||||
|
||||
}
|
||||
|
||||
private void addBeforeUpdate(DdlBuffer apply, String triggerName, String baseTable, String historyTable, List<String> includedColumns) throws IOException {
|
||||
|
||||
apply
|
||||
.append("delimiter $$").newLine()
|
||||
.append("create trigger ").append(triggerName).append(" before update on ").append(baseTable)
|
||||
.append(" for each row begin").newLine();
|
||||
appendInsertIntoHistory(apply, historyTable, includedColumns);
|
||||
apply
|
||||
.append(" set NEW.").append(sysPeriod).append("_start = now(6)").endOfStatement().newLine()
|
||||
.append("end$$").newLine();
|
||||
}
|
||||
|
||||
private void addBeforeDelete(DdlBuffer apply, String triggerName, String baseTable, String historyTable, List<String> includedColumns) throws IOException {
|
||||
|
||||
apply
|
||||
.append("delimiter $$").newLine()
|
||||
.append("create trigger ").append(triggerName).append(" before delete on ").append(baseTable)
|
||||
.append(" for each row begin").newLine();
|
||||
appendInsertIntoHistory(apply, historyTable, includedColumns);
|
||||
apply.append("end$$").newLine();
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@ import java.io.IOException;
|
||||
public class NoHistorySupportDdl implements PlatformHistoryDdl {
|
||||
|
||||
@Override
|
||||
public void configure(ServerConfig serverConfig) {
|
||||
public void configure(ServerConfig serverConfig, PlatformDdl platformDdl) {
|
||||
// does nothing
|
||||
}
|
||||
|
||||
|
||||
@@ -6,16 +6,19 @@ import com.avaje.ebean.config.dbplatform.DbIdentity;
|
||||
import com.avaje.ebean.config.dbplatform.DbTypeMap;
|
||||
import com.avaje.ebean.config.dbplatform.IdType;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.BaseDdlHandler;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlHandler;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlWrite;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.util.PlatformTypeConverter;
|
||||
import com.avaje.ebean.dbmigration.migration.AddHistoryTable;
|
||||
import com.avaje.ebean.dbmigration.migration.AlterColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.Column;
|
||||
import com.avaje.ebean.dbmigration.migration.DropHistoryTable;
|
||||
import com.avaje.ebean.dbmigration.migration.IdentityType;
|
||||
import com.avaje.ebean.dbmigration.model.MTable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Controls the DDL generation for a specific database platform.
|
||||
@@ -84,7 +87,7 @@ public class PlatformDdl {
|
||||
* Set configuration options.
|
||||
*/
|
||||
public void configure(ServerConfig serverConfig) {
|
||||
historyDdl.configure(serverConfig);
|
||||
historyDdl.configure(serverConfig, this);
|
||||
naming = serverConfig.getConstraintNaming();
|
||||
}
|
||||
|
||||
@@ -111,6 +114,38 @@ public class PlatformDdl {
|
||||
return columnDefn + identitySuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write all the table columns converting to platform types as necessary.
|
||||
*/
|
||||
public void writeTableColumns(DdlBuffer apply, List<Column> columns, boolean useIdentity) throws IOException {
|
||||
for (int i = 0; i < columns.size(); i++) {
|
||||
apply.newLine();
|
||||
writeColumnDefinition(apply, columns.get(i), useIdentity);
|
||||
if (i < columns.size() - 1) {
|
||||
apply.append(",");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = convert(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
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the drop foreign key clause.
|
||||
*/
|
||||
@@ -333,4 +368,10 @@ public class PlatformDdl {
|
||||
return naming.lowerName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Null safe Boolean true test.
|
||||
*/
|
||||
protected boolean isTrue(Boolean value) {
|
||||
return Boolean.TRUE.equals(value);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ public interface PlatformHistoryDdl {
|
||||
/**
|
||||
* Configure typically reading the
|
||||
*/
|
||||
void configure(ServerConfig serverConfig);
|
||||
void configure(ServerConfig serverConfig, PlatformDdl platformDdl);
|
||||
|
||||
/**
|
||||
* Add history support to the table using platform specific mechanism.
|
||||
|
||||
+65
-173
@@ -1,151 +1,57 @@
|
||||
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 class PostgresHistoryDdl implements PlatformHistoryDdl {
|
||||
public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
|
||||
private DbConstraintNaming constraintNaming;
|
||||
|
||||
private String sysPeriod;
|
||||
|
||||
private String viewSuffix;
|
||||
|
||||
private String historySuffix;
|
||||
|
||||
public PostgresHistoryDdl() {
|
||||
this.currentTimestamp = "current_timestamp";
|
||||
}
|
||||
|
||||
/**
|
||||
* Use Postgres create table like to create the history table.
|
||||
*/
|
||||
@Override
|
||||
public void configure(ServerConfig serverConfig) {
|
||||
this.sysPeriod = serverConfig.getAsOfSysPeriod();
|
||||
this.viewSuffix = serverConfig.getAsOfViewSuffix();
|
||||
this.historySuffix = serverConfig.getHistoryTableSuffix();
|
||||
this.constraintNaming = serverConfig.getConstraintNaming();
|
||||
}
|
||||
|
||||
@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)");
|
||||
}
|
||||
addStoredFunction(writer, table, update);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
|
||||
|
||||
String baseTable = dropHistoryTable.getBaseTable();
|
||||
|
||||
// drop in appropriate order
|
||||
dropTriggersEtc(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 {
|
||||
protected void createHistoryTable(DdlBuffer apply, MTable table) throws IOException {
|
||||
|
||||
String baseTable = table.getName();
|
||||
String whenCreatedColumn = table.getWhenCreatedColumn();
|
||||
|
||||
// rollback changes in appropriate order
|
||||
dropTriggersEtc(writer.rollback(), baseTable);
|
||||
dropHistoryTableEtc(writer.rollback(), baseTable);
|
||||
|
||||
addHistoryTable(writer, table, whenCreatedColumn);
|
||||
addStoredFunction(writer, table, null);
|
||||
addTrigger(writer, table);
|
||||
apply
|
||||
.append("create table ").append(baseTable).append(historySuffix)
|
||||
.append("(like ").append(baseTable).append(")").endOfStatement();
|
||||
}
|
||||
|
||||
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 void dropTriggersEtc(DdlBuffer buffer, String baseTable) throws IOException {
|
||||
|
||||
// rollback trigger then function
|
||||
buffer.append("drop trigger if exists ").append(triggerName(baseTable)).append(" on ").append(baseTable).append(" cascade").endOfStatement();
|
||||
buffer.append("drop function if exists ").append(procedureName(baseTable)).append("()").endOfStatement();
|
||||
buffer.end();
|
||||
}
|
||||
|
||||
protected void addHistoryTable(DdlWrite writer, MTable table, String whenCreatedColumn) throws IOException {
|
||||
|
||||
String baseTableName = table.getName();
|
||||
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
|
||||
if (whenCreatedColumn == null) {
|
||||
// effective history start as at current timestamp
|
||||
whenCreatedColumn = "current_timestamp";
|
||||
}
|
||||
|
||||
/**
|
||||
* Use Postgres range type rather than start and end timestamps.
|
||||
*/
|
||||
@Override
|
||||
protected void addSysPeriodColumns(DdlBuffer apply, String baseTableName, String whenCreatedColumn) throws IOException {
|
||||
apply
|
||||
.append("alter table ").append(baseTableName)
|
||||
.append(" add column ").append(sysPeriod).append(" tstzrange not null default tstzrange(").append(whenCreatedColumn).append(", null)")
|
||||
.append(" add column ").append(sysPeriod).append(" tstzrange not null default tstzrange(").append(currentTimestamp).append(", null)")
|
||||
.endOfStatement();
|
||||
|
||||
apply
|
||||
.append("create table ").append(baseTableName).append(historySuffix)
|
||||
.append(" (like ").append(baseTableName).append(")")
|
||||
.endOfStatement();
|
||||
|
||||
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();
|
||||
if (whenCreatedColumn != null) {
|
||||
apply.append("update table ").append(baseTableName).append(" set ")
|
||||
.append(sysPeriod).append(" = tstzrange(").append(whenCreatedColumn).append(", null)").endOfStatement();
|
||||
}
|
||||
}
|
||||
|
||||
protected void dropHistoryTableEtc(DdlBuffer buffer, String baseTableName) throws IOException {
|
||||
|
||||
buffer.append("drop view ").append(baseTableName).append(viewSuffix).endOfStatement();
|
||||
@Override
|
||||
protected void dropSysPeriodColumns(DdlBuffer buffer, String baseTableName) throws IOException {
|
||||
buffer.append("alter table ").append(baseTableName).append(" drop column ").append(sysPeriod).endOfStatement();
|
||||
buffer.append("drop table ").append(baseTableName).append(historySuffix).endOfStatement().end();
|
||||
}
|
||||
|
||||
protected void addTrigger(DdlWrite writer, MTable table) throws IOException {
|
||||
@Override
|
||||
protected void createTriggers(DdlWrite writer, MTable table) throws IOException {
|
||||
|
||||
String baseTableName = table.getName();
|
||||
String procedureName = procedureName(baseTableName);
|
||||
@@ -154,10 +60,49 @@ public class PostgresHistoryDdl implements PlatformHistoryDdl {
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
apply
|
||||
.append("create trigger ").append(triggerName).newLine()
|
||||
.append(" before insert or update or delete on ").append(baseTableName).newLine()
|
||||
.append(" before update or delete on ").append(baseTableName).newLine()
|
||||
.append(" for each row execute procedure ").append(procedureName).append("();").newLine().newLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dropTriggers(DdlBuffer buffer, String baseTable) throws IOException {
|
||||
// rollback trigger then function
|
||||
buffer.append("drop trigger if exists ").append(triggerName(baseTable)).append(" on ").append(baseTable).append(" cascade").endOfStatement();
|
||||
buffer.append("drop function if exists ").append(procedureName(baseTable)).append("()").endOfStatement();
|
||||
buffer.end();
|
||||
}
|
||||
|
||||
protected void addFunction(DdlBuffer apply, String procedureName, String historyTable, List<String> includedColumns) throws IOException {
|
||||
apply
|
||||
.append("create or replace function ").append(procedureName).append("() returns trigger as $$").newLine()
|
||||
.append("begin").newLine();
|
||||
apply
|
||||
.append(" if (TG_OP = 'UPDATE') then").newLine();
|
||||
appendInsertIntoHistory(apply, historyTable, includedColumns);
|
||||
apply
|
||||
.append(" NEW.").append(sysPeriod).append(" = tstzrange(CURRENT_TIMESTAMP,null);").newLine()
|
||||
.append(" return new;").newLine().newLine();
|
||||
apply
|
||||
.append(" elsif (TG_OP = 'DELETE') then").newLine();
|
||||
appendInsertIntoHistory(apply, historyTable, includedColumns);
|
||||
apply
|
||||
.append(" return old;").newLine().newLine();
|
||||
apply
|
||||
.append(" end if;").newLine()
|
||||
.append("end;").newLine()
|
||||
.append("$$ LANGUAGE plpgsql;").newLine();
|
||||
|
||||
apply.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void regenerateHistoryTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
|
||||
|
||||
// just replace the stored function with 'create or replace'
|
||||
addStoredFunction(writer, table, update);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addStoredFunction(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
|
||||
|
||||
String procedureName = procedureName(table.getName());
|
||||
@@ -187,67 +132,14 @@ public class PostgresHistoryDdl implements PlatformHistoryDdl {
|
||||
}
|
||||
}
|
||||
|
||||
private void addFunction(DdlBuffer apply, String procedureName, String historyTable, List<String> includedColumns) throws IOException {
|
||||
apply
|
||||
.append("create or replace function ").append(procedureName).append("() returns trigger as $$").newLine()
|
||||
.append("begin").newLine();
|
||||
apply
|
||||
.append(" if (TG_OP = 'INSERT') then").newLine()
|
||||
.append(" NEW.").append(sysPeriod).append(" = tstzrange(CURRENT_TIMESTAMP,null);").newLine()
|
||||
.append(" return new;").newLine().newLine();
|
||||
apply
|
||||
.append(" elsif (TG_OP = 'UPDATE') then").newLine();
|
||||
appendInsertIntoHistory(apply, historyTable, includedColumns);
|
||||
apply
|
||||
.append(" NEW.").append(sysPeriod).append(" = tstzrange(CURRENT_TIMESTAMP,null);").newLine()
|
||||
.append(" return new;").newLine().newLine();
|
||||
apply
|
||||
.append(" elsif (TG_OP = 'DELETE') then").newLine();
|
||||
appendInsertIntoHistory(apply, historyTable, includedColumns);
|
||||
apply
|
||||
.append(" return old;").newLine().newLine();
|
||||
|
||||
apply
|
||||
.append(" end if;").newLine()
|
||||
.append("end;").newLine()
|
||||
.append("$$ LANGUAGE plpgsql;").newLine();
|
||||
|
||||
apply.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void appendInsertIntoHistory(DdlBuffer buffer, String historyTable, List<String> columns) throws IOException {
|
||||
|
||||
buffer.append(" insert into ").append(historyTable).append(" (").append(sysPeriod).append(",");
|
||||
appendColumnNames(buffer, columns, "");
|
||||
buffer.append(") values (tstzrange(lower(OLD.").append(sysPeriod).append("), CURRENT_TIMESTAMP), ");
|
||||
buffer.append(") values (tstzrange(lower(OLD.").append(sysPeriod).append("), current_timestamp), ");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user