001package io.ebeaninternal.dbmigration.ddlgeneration.platform; 002 003import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer; 004import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite; 005import io.ebeaninternal.dbmigration.model.MTable; 006 007import java.util.List; 008 009/** 010 * Uses DB triggers to maintain a history table. 011 */ 012public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl { 013 014 PostgresHistoryDdl() { 015 this.now = "current_timestamp"; 016 this.sysPeriodEndValue = "current_timestamp"; 017 } 018 019 /** 020 * Use Postgres create table like to create the history table. 021 */ 022 @Override 023 protected void createHistoryTable(DdlBuffer apply, MTable table) { 024 apply.append("create table ").append(historyTableName(table.getName())) 025 .append("(like ").append(table.getName()).append(")").endOfStatement(); 026 } 027 028 /** 029 * Use Postgres range type rather than start and end timestamps. 030 */ 031 @Override 032 protected void addSysPeriodColumns(DdlWrite writer, String baseTableName, String whenCreatedColumn) { 033 platformDdl.alterTableAddColumn(writer, baseTableName, sysPeriod, "tstzrange not null", "tstzrange(" + now + ", null)"); 034 if (whenCreatedColumn != null) { 035 writer.applyPostAlter() 036 .append("update ").append(baseTableName).append(" set ") 037 .append(sysPeriod).append(" = tstzrange(").append(whenCreatedColumn).append(", null)").endOfStatement(); 038 } 039 } 040 041 @Override 042 protected void appendSysPeriodColumns(DdlBuffer apply, String prefix) { 043 appendColumnName(apply, prefix, sysPeriod); 044 } 045 046 @Override 047 protected void dropSysPeriodColumns(DdlWrite writer, String baseTableName) { 048 platformDdl.alterTableDropColumn(writer, baseTableName, sysPeriod); 049 } 050 051 @Override 052 protected void createTriggers(DdlBuffer buffer, String baseTableName, List<String> columnNames) { 053 String procedureName = procedureName(baseTableName); 054 String triggerName = triggerName(baseTableName); 055 createOrReplaceFunction(buffer, procedureName, historyTableName(baseTableName), columnNames); 056 buffer 057 .append("create trigger ").append(triggerName).newLine() 058 .append(" before update or delete on ").append(baseTableName).newLine() 059 .append(" for each row execute procedure ").append(procedureName).append("();").newLine().newLine(); 060 } 061 062 @Override 063 protected void dropTriggers(DdlBuffer buffer, String baseTable) { 064 // rollback trigger then function 065 buffer.append("drop trigger if exists ").append(triggerName(baseTable)).append(" on ").append(baseTable).append(" cascade").endOfStatement(); 066 buffer.append("drop function if exists ").append(procedureName(baseTable)).append("()").endOfStatement(); 067 buffer.end(); 068 } 069 070 protected void createOrReplaceFunction(DdlBuffer apply, String procedureName, String historyTable, List<String> includedColumns) { 071 apply 072 .append("create or replace function ").append(procedureName).append("() returns trigger as $$").newLine(); 073 074 apply.append("declare").newLine() 075 .append(" lowerTs timestamptz;").newLine() 076 .append(" upperTs timestamptz;").newLine(); 077 078 apply.append("begin").newLine() 079 .append(" lowerTs = lower(OLD.sys_period);").newLine() 080 .append(" upperTs = greatest(lowerTs + '1 microsecond',current_timestamp);").newLine(); 081 082 apply 083 .append(" if (TG_OP = 'UPDATE') then").newLine(); 084 appendInsertIntoHistory(apply, historyTable, includedColumns); 085 apply 086 .append(" NEW.").append(sysPeriod).append(" = tstzrange(upperTs,null);").newLine() 087 .append(" return new;").newLine(); 088 apply 089 .append(" elsif (TG_OP = 'DELETE') then").newLine(); 090 appendInsertIntoHistory(apply, historyTable, includedColumns); 091 apply 092 .append(" return old;").newLine(); 093 apply 094 .append(" end if;").newLine() 095 .append("end;").newLine() 096 .append("$$ LANGUAGE plpgsql;").newLine(); 097 098 apply.end(); 099 } 100 101 @Override 102 protected void appendInsertIntoHistory(DdlBuffer buffer, String historyTable, List<String> columns) { 103 buffer.append(" insert into ").append(historyTable).append(" (").append(sysPeriod).append(","); 104 appendColumnNames(buffer, columns, ""); 105 buffer.append(") values (tstzrange(lowerTs,upperTs), "); 106 appendColumnNames(buffer, columns, "OLD."); 107 buffer.append(");").newLine(); 108 } 109 110 @Override 111 protected String procedureName(String baseTableName) { 112 return normalise(baseTableName) + "_history_version"; 113 } 114 115 @Override 116 protected String triggerName(String baseTableName) { 117 return normalise(baseTableName) + "_history_upd"; 118 } 119 120 @Override 121 protected String updateTriggerName(String baseTableName) { 122 return normalise(baseTableName) + "_history_upd"; 123 } 124 125 @Override 126 protected String deleteTriggerName(String baseTableName) { 127 return normalise(baseTableName) + "_history_del"; 128 } 129}