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