001package io.ebean.config.dbplatform.h2; 002 003import org.h2.api.Trigger; 004import org.slf4j.Logger; 005import org.slf4j.LoggerFactory; 006 007import java.sql.*; 008import java.util.Arrays; 009 010/** 011 * H2 database trigger used to populate history tables to support the @History feature. 012 */ 013public class H2HistoryTrigger implements Trigger { 014 015 private static final Logger logger = LoggerFactory.getLogger(H2HistoryTrigger.class); 016 017 /** 018 * Hardcoding the column and history table suffix for now. Not sure how to get that 019 * configuration into the trigger instance nicely as it is instantiated by H2. 020 */ 021 private static final String SYS_PERIOD_START = "SYS_PERIOD_START"; 022 private static final String SYS_PERIOD_END = "SYS_PERIOD_END"; 023 private static final String HISTORY_SUFFIX = "_history"; 024 025 /** 026 * SQL to insert into the history table. 027 */ 028 private String insertHistorySql; 029 030 /** 031 * Position of SYS_PERIOD_START column in the Object[]. 032 */ 033 private int effectStartPosition; 034 035 /** 036 * Position of SYS_PERIOD_END column in the Object[]. 037 */ 038 private int effectEndPosition; 039 040 @Override 041 public void init(Connection conn, String schemaName, String triggerName, String tableName, boolean before, int type) throws SQLException { 042 // get the columns for the table 043 ResultSet rs = conn.getMetaData().getColumns(null, schemaName, tableName, null); 044 045 // build the insert into history table SQL 046 StringBuilder insertSql = new StringBuilder(150); 047 insertSql.append("insert into ").append(schemaName).append(".").append(tableName).append(HISTORY_SUFFIX).append(" ("); 048 049 int count = 0; 050 while (rs.next()) { 051 if (++count > 1) { 052 insertSql.append(","); 053 } 054 String columnName = rs.getString("COLUMN_NAME"); 055 if (columnName.equalsIgnoreCase(SYS_PERIOD_START)) { 056 this.effectStartPosition = count - 1; 057 } else if (columnName.equalsIgnoreCase(SYS_PERIOD_END)) { 058 this.effectEndPosition = count - 1; 059 } 060 insertSql.append(columnName); 061 } 062 insertSql.append(") values ("); 063 for (int i = 0; i < count; i++) { 064 if (i > 0) { 065 insertSql.append(","); 066 } 067 insertSql.append("?"); 068 } 069 insertSql.append(");"); 070 071 this.insertHistorySql = insertSql.toString(); 072 logger.debug("History table insert sql: {}", insertHistorySql); 073 } 074 075 @Override 076 public void fire(Connection connection, Object[] oldRow, Object[] newRow) throws SQLException { 077 if (oldRow != null) { 078 // a delete or update event 079 Timestamp now = new Timestamp(System.currentTimeMillis()); 080 oldRow[effectEndPosition] = now; 081 if (newRow != null) { 082 // update event. Set the effective start timestamp to now. 083 newRow[effectStartPosition] = now; 084 } 085 if (logger.isDebugEnabled()) { 086 logger.debug("History insert: {}", Arrays.toString(oldRow)); 087 } 088 insertIntoHistory(connection, oldRow); 089 } 090 } 091 092 /** 093 * Insert the data into the history table. 094 */ 095 private void insertIntoHistory(Connection connection, Object[] oldRow) throws SQLException { 096 try (PreparedStatement stmt = connection.prepareStatement(insertHistorySql)) { 097 for (int i = 0; i < oldRow.length; i++) { 098 stmt.setObject(i + 1, oldRow[i]); 099 } 100 stmt.executeUpdate(); 101 } 102 } 103 104 @Override 105 public void close() throws SQLException { 106 // do nothing 107 } 108 109 @Override 110 public void remove() throws SQLException { 111 // do nothing 112 } 113}