All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hsqldb.sample.TriggerSample Maven / Gradle / Ivy

There is a newer version: 2.7.2
Show newest version
/* Copyright (c) 2001-2019, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb.sample;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;

import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.StringUtil;
import org.hsqldb.trigger.Trigger;

// peterhudson@users 20020130 - patch 478657 by peterhudson - new class
// fredt@users 20030727 - signature altered
// campbell-burnet@users 20040315 - sample updated

/**
 * Sample code for use of triggers in hsqldb.

* * This class org.hsqldb.sample package, but a typical implementation is in * users's class hierarchy. * * SQL to invoke is:

* CREATE TRIGGER triggerSample BEFORE|AFTER INSERT|UPDATE|DELETE * ON myTable [FOR EACH ROW] [QUEUE n] [NOWAIT] CALL "myPackage.trigClass"
* * This will create a thread that will wait for its firing event to occur; * when this happens, the trigger's thread runs the 'trigClass.fire' * Note that this is still in the same Java Virtual Machine as the * database, so make sure the fired method does not hang.

* * There is a queue of events waiting to be run by each trigger thread. * This is particularly useful for 'FOR EACH ROW' triggers, when a large * number of trigger events occur in rapid succession, without the trigger * thread getting a chance to run. If the queue becomes full, subsequent * additions to it cause the database engine to suspend awaiting space * in the queue. Take great care to avoid this situation if the trigger * action involves accessing the database, as deadlock will occur. * This can be avoided either by ensuring the QUEUE parameter makes a large * enough queue, or by using the NOWAIT parameter, which causes a new * trigger event to overwrite the most recent event in the queue. * The default queue size is 1024.

* * Ensure that "myPackage.trigClass" is present in the classpath which * you use to start hsql.

* * If the method wants to access the database, it must establish * a JDBC connection.

* * When the 'fire' method is called, it is passed the following arguments:

* * fire (int type, String trigName, String tabName, Object oldRow[], * Object[] newRow)

* * where 'type' is one of the values enumerated in the Trigger interface and * the 'oldRow'/'newRow' pair represents the rows acted on. The first * length - 1 array slots contain column values and the final slot contains * either null or the value of the internally assigned row identity, if * the concerned table has no primary key. The final slot must _never_ be * modified.

* * The mapping of row classes to database types is specified in * /doc/hsqlSyntax.html#Datatypes.

* * To be done: * *

    *
  1. Implement the "jdbc:default:connection: URL to provide transparent * and portable access to internal connections for use in triggers and * stored procedures. * *
  2. Implement declarative column to trigger method argument * mapping, conditional execution (WHEN clause), etc. * *
  3. Investigate and refine synchronous and asynchronous trigger models.

    * * Because certain combinations of trigger create parameters cause the * individual triggered actions of a multirow update to run in different * threads, it is possible for an 'after' trigger to run before its * corresponding 'before' trigger; the acceptability and implications * of this needs to be investigated, documented and the behaviour of * the engine fully specified. * *

  4. Investigate and implement the SQL 200n specified execution stack under * arbitrary triggered action and SQL-invoked routine call graphs. *
* * @author Peter Hudson * @author Campbell Burnet (campbell-burnet@users dot sourceforge.net) * @version 1.7.2 * @since 1.7.0 */ public class TriggerSample implements Trigger { static final PrintWriter out = new PrintWriter(System.out); static final String drv = "org.hsqldb.jdbc.JDBCDriver"; static final String url = "jdbc:hsqldb:mem:trigger-sample"; static final String usr = "SA"; static final String pwd = ""; static final String impl = TriggerSample.class.getName(); static final String tn = "trig_test"; static final String drop_test_table_stmt = "DROP TABLE " + tn + " IF EXISTS"; static final String create_test_table_stmt = "CREATE TABLE " + tn + "(id INTEGER PRIMARY KEY, value VARCHAR(20))"; static final String drop_audit_table_stmt = "DROP TABLE audit IF EXISTS"; static final String create_audit_table_stmt = "CREATE TABLE audit(" + "id INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1), " + "op VARCHAR(6), " + "tn VARCHAR(20), " + "ors LONGVARCHAR, " + "nrs LONGVARCHAR, " + "ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP)"; static final String audit_insert_stmt = "INSERT INTO audit(op, tn, ors, nrs) VALUES(?, ?, ?, ?)"; /** * A sample HSQLDB Trigger interface implementation.

* * This sample prints information about the firing trigger and records * actions in an audit table.

* * The techniques used here are simplified dramatically for demonstration * purposes and are in no way recommended as a model upon which to build * actual installations involving triggered actions. * * @param typ trigger type * @param trn trigger name * @param tn table name * @param or old row * @param nr new row */ public void fire(int typ, String trn, String tn, Object[] or, Object[] nr) { synchronized (TriggerSample.class) { String ors = or == null ? "null" : StringUtil.arrayToString(or); String nrs = nr == null ? "null" : StringUtil.arrayToString(nr); out.println("----------------------------------------"); out.println(getTriggerDescriptor(trn, typ, tn)); out.println("old row : " + ors); out.println("new row : " + nrs); out.flush(); if ("TRIG_TEST".equals(tn)) { switch (typ) { case INSERT_BEFORE_ROW : { // Business rule: ID shall be less than 11. // (Marti DiBergi, we love you ;-) // You can cast row[i] given your knowledge of what // the table format is: final int ID = ((Number) nr[0]).intValue(); doAssert(ID < 11, "ID < 11"); break; } case UPDATE_BEFORE_ROW : { // Business rule: ignore update of VALUE 'unchangable'. if ("unchangable".equals(or[1])) { nr[1] = or[1]; // short-circuit the update } // !!!Warning!!! // The engine does not check the class of substituted // values; it's up to you to use the correct class. // For example, this will cause database curruption: // nr[1] = Integer.valueOf(5); break; } } } doAuditStep(typ, tn, ors, nrs); } } private static void doAssert(boolean b, String msg) { if (b) { // do nothing } else { throw Error.error(ErrorCode.GENERAL_ERROR, msg); } } private static void doAuditStep(int typ, String tn, String ors, String nrs) { Connection conn; PreparedStatement stmt; switch (typ) { case INSERT_AFTER_ROW : case UPDATE_AFTER_ROW : case DELETE_AFTER_ROW : { try { conn = getConnection(); stmt = conn.prepareStatement(audit_insert_stmt); stmt.setString(1, getOperationSpec(typ)); stmt.setString(2, tn); stmt.setString(3, ors); stmt.setString(4, nrs); stmt.executeUpdate(); conn.close(); } catch (SQLException se) { se.printStackTrace(); } } } } public static String getWhenSpec(int type) { switch (type) { case INSERT_BEFORE_ROW : case UPDATE_BEFORE_ROW : case DELETE_BEFORE_ROW : { return "BEFORE"; } case INSERT_AFTER : case INSERT_AFTER_ROW : case UPDATE_AFTER : case UPDATE_AFTER_ROW : case DELETE_AFTER : case DELETE_AFTER_ROW : { return "AFTER"; } default : { return ""; } } } public static String getOperationSpec(int type) { switch (type) { case INSERT_AFTER : case INSERT_AFTER_ROW : case INSERT_BEFORE_ROW : { return "INSERT"; } case UPDATE_AFTER : case UPDATE_AFTER_ROW : case UPDATE_BEFORE_ROW : { return "UPDATE"; } case DELETE_AFTER : case DELETE_AFTER_ROW : case DELETE_BEFORE_ROW : { return "DELETE"; } default : { return ""; } } } public static String getQueueSpec(int qs) { return (qs < 0) ? "" : ("QUEUE " + qs); } public static String getForEachSpec(int type) { switch (type) { case INSERT_BEFORE_ROW : case INSERT_AFTER_ROW : case UPDATE_BEFORE_ROW : case UPDATE_AFTER_ROW : case DELETE_AFTER_ROW : case DELETE_BEFORE_ROW : { return "FOR EACH ROW"; } default : { return "FOR EACH STATEMENT"; } } } public static String getTriggerDDL(String trn, int typ, String tab, int qs, String impl) throws SQLException { StringBuilder sb = new StringBuilder(); sb.append("CREATE TRIGGER "); sb.append(trn); sb.append(' '); sb.append(getWhenSpec(typ)); sb.append(' '); sb.append(getOperationSpec(typ)); sb.append(" ON "); sb.append(tab); sb.append(' '); sb.append(getForEachSpec(typ)); sb.append(' '); sb.append(getQueueSpec(qs)); sb.append(" CALL \""); sb.append(impl); sb.append("\""); return sb.toString(); } public static String getTriggerDescriptor(String trn, int typ, String tab) { StringBuilder sb = new StringBuilder(); sb.append("TRIGGER : "); sb.append(trn); sb.append(' '); sb.append(getWhenSpec(typ)); sb.append(' '); sb.append(getOperationSpec(typ)); sb.append(" ON "); sb.append(tab); sb.append(' '); sb.append(getForEachSpec(typ)); return sb.toString(); } private static Connection getConnection() throws SQLException { try { Class.forName(drv); return DriverManager.getConnection(url, usr, pwd); } catch (SQLException se) { throw se; } catch (Exception e) { throw new SQLException(e.toString()); } } private static void createTrigger(Statement stmt, String trn, int typ) throws SQLException { stmt.execute(getTriggerDDL(trn, typ, tn, 0, impl)); } private static void setup() throws SQLException { Connection conn = getConnection(); Statement stmt = conn.createStatement(); stmt.execute(drop_test_table_stmt); stmt.execute(create_test_table_stmt); stmt.execute(drop_audit_table_stmt); stmt.execute(create_audit_table_stmt); createTrigger(stmt, "tibr_" + tn, INSERT_BEFORE_ROW); createTrigger(stmt, "tia_" + tn, INSERT_AFTER); createTrigger(stmt, "tiar_" + tn, INSERT_AFTER_ROW); createTrigger(stmt, "tubr_" + tn, UPDATE_BEFORE_ROW); createTrigger(stmt, "tua_" + tn, UPDATE_AFTER); createTrigger(stmt, "tuar_" + tn, UPDATE_AFTER_ROW); createTrigger(stmt, "tdbr_" + tn, DELETE_BEFORE_ROW); createTrigger(stmt, "tda_" + tn, DELETE_AFTER); createTrigger(stmt, "tdar_" + tn, DELETE_AFTER_ROW); stmt.close(); conn.close(); } private static void doSomeWork() throws SQLException { Connection conn = getConnection(); Statement stmt = conn.createStatement(); conn.setAutoCommit(false); stmt.execute("INSERT INTO trig_test VALUES (1, 'hello')"); stmt.execute("INSERT INTO trig_test VALUES (2, 'now what?')"); stmt.execute("INSERT INTO trig_test VALUES (3, 'unchangable')"); stmt.execute("INSERT INTO trig_test VALUES (4, 'goodbye')"); conn.commit(); dumpTable("trig_test"); stmt.execute("UPDATE trig_test SET value = 'all done'"); conn.commit(); dumpTable("trig_test"); stmt.execute("DELETE FROM trig_test"); conn.rollback(); dumpTable("trig_test"); try { stmt.execute("INSERT INTO trig_test VALUES(11, 'whatever')"); } catch (SQLException se) { se.printStackTrace(); } stmt.execute("INSERT INTO trig_test VALUES(10, 'whatever')"); conn.commit(); dumpTable("trig_test"); stmt.close(); conn.close(); } private static void dumpTable(String tn) throws SQLException { Connection conn = getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select * from " + tn); ResultSetMetaData rsmd = rs.getMetaData(); int count = rsmd.getColumnCount(); out.println(); out.println("****************************************"); out.println("DUMP FOR TABLE: " + tn); out.println("****************************************"); out.flush(); while (rs.next()) { out.print("["); for (int i = 1; i <= count; i++) { out.print(rs.getString(i)); if (i < count) { out.print(" : "); } } out.println("]"); } out.println(); out.flush(); rs.close(); stmt.close(); conn.close(); } private static void runSample() throws SQLException { setup(); doSomeWork(); dumpTable("audit"); } public static void main(String[] args) throws SQLException { runSample(); } } /* test SQL CREATE CACHED TABLE trig_test (int_field integer) CREATE TRIGGER ins_before BEFORE INSERT ON trig_test CALL "org.hsqldb.sample.TriggerSample" CREATE TRIGGER ins_after AFTER INSERT ON trig_test CALL "org.hsqldb.sample.TriggerSample" CREATE TRIGGER upd_before BEFORE UPDATE ON trig_test CALL "org.hsqldb.sample.TriggerSample" CREATE TRIGGER upd_after AFTER UPDATE ON trig_test CALL "org.hsqldb.sample.TriggerSample" CREATE TRIGGER upd_before_row BEFORE UPDATE ON trig_test FOR EACH ROW CALL "org.hsqldb.sample.TriggerSample" CREATE TRIGGER upd_after_row AFTER UPDATE ON trig_test FOR EACH ROW CALL "org.hsqldb.sample.TriggerSample" CREATE TRIGGER del_before BEFORE DELETE ON trig_test CALL "org.hsqldb.sample.TriggerSample" CREATE TRIGGER del_after AFTER DELETE ON trig_test CALL "org.hsqldb.sample.TriggerSample" CREATE TRIGGER del_before_row BEFORE DELETE ON trig_test FOR EACH ROW CALL "org.hsqldb.sample.TriggerSample" CREATE TRIGGER del_after_row AFTER DELETE ON trig_test FOR EACH ROW CALL "org.hsqldb.sample.TriggerSample" INSERT INTO trig_test VALUES (1) INSERT INTO trig_test VALUES (2) INSERT INTO trig_test VALUES (3) UPDATE trig_test SET int_field = int_field + 3 DELETE FROM trig_test */





© 2015 - 2024 Weber Informatics LLC | Privacy Policy