提交 b02f651e authored 作者: noelgrandin@gmail.com's avatar noelgrandin@gmail.com

Allow declaring triggers as source code (like functions). Patch by Sylvain CUAZ.

上级 53c4629a
......@@ -639,7 +639,7 @@ CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))
"Commands (DDL)","CREATE TRIGGER","
CREATE TRIGGER [ IF NOT EXISTS ] newTriggerName { BEFORE | AFTER | INSTEAD OF }
{ INSERT | UPDATE | DELETE | SELECT | ROLLBACK } [,...] ON tableName [ FOR EACH ROW ]
[ QUEUE int ] [ NOWAIT ] CALL triggeredClassName
[ QUEUE int ] [ NOWAIT ] { CALL triggeredClassName | AS sourceCodeString }
","
Creates a new trigger.
The trigger class must be public and implement ""org.h2.api.Trigger"".
......@@ -647,6 +647,9 @@ Inner classes are not supported.
The class must be available in the classpath of the database engine
(when using the server mode, it must be in the classpath of the server).
The sourceCodeString must define a single method with no parameters that returns ""org.h2.api.Trigger"".
See CREATE ALIAS for requirements regarding the compilation.
BEFORE triggers are called after data conversion is made, default values are set,
null and length constraint checks have been made;
but before other constraints have been checked.
......@@ -682,7 +685,8 @@ The schema name does not need to be specified when creating the trigger.
This command commits an open transaction in this connection.
","
CREATE TRIGGER TRIG_INS BEFORE INSERT ON TEST FOR EACH ROW CALL ""MyTrigger""
CREATE TRIGGER TRIG_INS BEFORE INSERT ON TEST FOR EACH ROW CALL ""MyTrigger"";
CREATE TRIGGER TRIG_SRC BEFORE INSERT ON TEST AS $$org.h2.api.Trigger create(){ return new MyTrigger(""constructorParam""); } $$;
"
"Commands (DDL)","CREATE USER","
CREATE USER [ IF NOT EXISTS ] newUserName
......
......@@ -50,6 +50,7 @@ Change Log
the logic that cleared the flag could never be reached, resulting in performance degradation.
Reported by Alexander Nesterov.
</li><li>Issue 552: Implement BIT_AND and BIT_OR aggregate functions.
</li><li>Allow declaring triggers as source code (like functions). Patch by Sylvain CUAZ.
</li></ul>
<h2>Version 1.4.184 Beta (2014-12-19)</h2>
......
......@@ -4544,8 +4544,12 @@ public class Parser {
command.setQueueSize(readPositiveInt());
}
command.setNoWait(readIf("NOWAIT"));
read("CALL");
command.setTriggerClassName(readUniqueIdentifier());
if (readIf("AS")) {
command.setTriggerSource(readString());
} else {
read("CALL");
command.setTriggerClassName(readUniqueIdentifier());
}
return command;
}
......
......@@ -32,6 +32,7 @@ public class CreateTrigger extends SchemaCommand {
private boolean noWait;
private String tableName;
private String triggerClassName;
private String triggerSource;
private boolean force;
private boolean onRollback;
......@@ -51,6 +52,10 @@ public class CreateTrigger extends SchemaCommand {
this.triggerClassName = triggerClassName;
}
public void setTriggerSource(String triggerSource) {
this.triggerSource = triggerSource;
}
public void setTypeMask(int typeMask) {
this.typeMask = typeMask;
}
......@@ -106,7 +111,10 @@ public class CreateTrigger extends SchemaCommand {
trigger.setRowBased(rowBased);
trigger.setTypeMask(typeMask);
trigger.setOnRollback(onRollback);
trigger.setTriggerClassName(triggerClassName, force);
if(this.triggerClassName != null)
trigger.setTriggerClassName(triggerClassName, force);
else
trigger.setTriggerSource(triggerSource, force);
db.addSchemaObject(session, trigger);
table.addTrigger(trigger);
return 0;
......
......@@ -5,12 +5,14 @@
*/
package org.h2.schema;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import org.h2.api.ErrorCode;
import org.h2.api.Trigger;
import org.h2.command.Parser;
import org.h2.engine.Constants;
import org.h2.engine.DbObject;
import org.h2.engine.Session;
import org.h2.message.DbException;
......@@ -18,7 +20,9 @@ import org.h2.message.Trace;
import org.h2.result.Row;
import org.h2.table.Table;
import org.h2.util.JdbcUtils;
import org.h2.util.SourceCompiler;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
import org.h2.value.DataType;
import org.h2.value.Value;
......@@ -43,6 +47,7 @@ public class TriggerObject extends SchemaObjectBase {
private boolean noWait;
private Table table;
private String triggerClassName;
private String triggerSource;
private Trigger triggerCallback;
public TriggerObject(Schema schema, int id, String name, Table table) {
......@@ -66,7 +71,12 @@ public class TriggerObject extends SchemaObjectBase {
try {
Session sysSession = database.getSystemSession();
Connection c2 = sysSession.createConnection(false);
Object obj = JdbcUtils.loadUserClass(triggerClassName).newInstance();
final Object obj;
if (triggerClassName != null) {
obj = JdbcUtils.loadUserClass(triggerClassName).newInstance();
} else {
obj = loadFromSource();
}
triggerCallback = (Trigger) obj;
triggerCallback.init(c2, getSchema().getName(), getName(),
table.getName(), before, typeMask);
......@@ -74,7 +84,25 @@ public class TriggerObject extends SchemaObjectBase {
// try again later
triggerCallback = null;
throw DbException.get(ErrorCode.ERROR_CREATING_TRIGGER_OBJECT_3, e, getName(),
triggerClassName, e.toString());
triggerClassName != null ? triggerClassName : "..source..", e.toString());
}
}
private Trigger loadFromSource() {
final SourceCompiler compiler = database.getCompiler();
synchronized (compiler) {
final String fullClassName = Constants.USER_PACKAGE + ".trigger." + getName();
compiler.setSource(fullClassName, triggerSource);
try {
final Method m = compiler.getMethod(fullClassName);
if (m.getParameterTypes().length > 0)
throw new IllegalStateException("No parameters are allowed for a trigger");
return (Trigger) m.invoke(null);
} catch (DbException e) {
throw e;
} catch (Exception e) {
throw DbException.get(ErrorCode.SYNTAX_ERROR_1, e, triggerSource);
}
}
}
......@@ -86,7 +114,23 @@ public class TriggerObject extends SchemaObjectBase {
* should be ignored
*/
public void setTriggerClassName(String triggerClassName, boolean force) {
this.setTriggerAction(triggerClassName, null, force);
}
/**
* Set the trigger source code and compile it if possible.
*
* @param source the source code of a method returning a {@link Trigger}
* @param force whether exceptions (due to syntax error)
* should be ignored
*/
public void setTriggerSource(String source, boolean force) {
this.setTriggerAction(null, source, force);
}
private void setTriggerAction(String triggerClassName, String source, boolean force) {
this.triggerClassName = triggerClassName;
this.triggerSource = source;
try {
load();
} catch (DbException e) {
......@@ -118,9 +162,9 @@ public class TriggerObject extends SchemaObjectBase {
Value identity = session.getLastScopeIdentity();
try {
triggerCallback.fire(c2, null, null);
} catch (Throwable e) {
} catch (Throwable e) {
throw DbException.get(ErrorCode.ERROR_EXECUTING_TRIGGER_3, e, getName(),
triggerClassName, e.toString());
triggerClassName != null ? triggerClassName : "..source..", e.toString());
} finally {
session.setLastScopeIdentity(identity);
if (type != Trigger.SELECT) {
......@@ -283,7 +327,10 @@ public class TriggerObject extends SchemaObjectBase {
} else {
buff.append(" QUEUE ").append(queueSize);
}
buff.append(" CALL ").append(Parser.quoteIdentifier(triggerClassName));
if (triggerClassName != null)
buff.append(" CALL ").append(Parser.quoteIdentifier(triggerClassName));
else
buff.append(" AS ").append(StringUtils.quoteStringSQL(triggerSource));
return buff.toString();
}
......@@ -335,6 +382,7 @@ public class TriggerObject extends SchemaObjectBase {
}
table = null;
triggerClassName = null;
triggerSource = null;
triggerCallback = null;
invalidate();
}
......@@ -371,6 +419,10 @@ public class TriggerObject extends SchemaObjectBase {
return triggerClassName;
}
public String getTriggerSource() {
return triggerSource;
}
/**
* Close the trigger.
*/
......
......@@ -46,6 +46,7 @@ public class TestTriggersConstraints extends TestBase implements Trigger {
testViewTrigger();
testTriggerBeforeSelect();
testTriggerAlterTable();
testTriggerAsSource();
testTriggers();
testConstraints();
testCheckConstraintErrorMessage();
......@@ -386,19 +387,39 @@ public class TestTriggersConstraints extends TestBase implements Trigger {
}
private void testTriggerAlterTable() throws SQLException {
deleteDb("trigger");
testTrigger(false);
}
private void testTriggerAsSource() throws SQLException {
deleteDb("trigger");
testTrigger(true);
}
private void testTrigger(final boolean asSource) throws SQLException {
final String callSeq = "call seq.nextval";
Connection conn = getConnection("trigger");
Statement stat = conn.createStatement();
stat.execute("DROP TABLE IF EXISTS TEST");
stat.execute("create sequence seq");
stat.execute("create table test(id int primary key)");
assertSingleValue(stat, "call seq.nextval", 1);
assertSingleValue(stat, callSeq, 1);
conn.setAutoCommit(false);
stat.execute("create trigger test_upd before insert on test call \"" +
if (asSource) {
String triggerClassName = this.getClass().getName() + "." +
TestTriggerAlterTable.class.getSimpleName();
stat.execute(
"create trigger test_upd before insert on test " +
"as $$org.h2.api.Trigger create() " +
"{ return new " + triggerClassName + "(\"seq\"); } $$");
} else {
stat.execute("create trigger test_upd before insert on test call \"" +
TestTriggerAlterTable.class.getName() + "\"");
}
stat.execute("insert into test values(1)");
assertSingleValue(stat, "call seq.nextval", 3);
assertSingleValue(stat, callSeq, 3);
stat.execute("alter table test add column name varchar");
assertSingleValue(stat, "call seq.nextval", 4);
assertSingleValue(stat, callSeq, 4);
stat.execute("drop sequence seq");
stat.execute("drop table test");
conn.close();
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论