提交 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)) ...@@ -639,7 +639,7 @@ CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))
"Commands (DDL)","CREATE TRIGGER"," "Commands (DDL)","CREATE TRIGGER","
CREATE TRIGGER [ IF NOT EXISTS ] newTriggerName { BEFORE | AFTER | INSTEAD OF } CREATE TRIGGER [ IF NOT EXISTS ] newTriggerName { BEFORE | AFTER | INSTEAD OF }
{ INSERT | UPDATE | DELETE | SELECT | ROLLBACK } [,...] ON tableName [ FOR EACH ROW ] { 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. Creates a new trigger.
The trigger class must be public and implement ""org.h2.api.Trigger"". The trigger class must be public and implement ""org.h2.api.Trigger"".
...@@ -647,6 +647,9 @@ Inner classes are not supported. ...@@ -647,6 +647,9 @@ Inner classes are not supported.
The class must be available in the classpath of the database engine 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). (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, BEFORE triggers are called after data conversion is made, default values are set,
null and length constraint checks have been made; null and length constraint checks have been made;
but before other constraints have been checked. but before other constraints have been checked.
...@@ -682,7 +685,8 @@ The schema name does not need to be specified when creating the trigger. ...@@ -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. 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"," "Commands (DDL)","CREATE USER","
CREATE USER [ IF NOT EXISTS ] newUserName CREATE USER [ IF NOT EXISTS ] newUserName
......
...@@ -50,6 +50,7 @@ Change Log ...@@ -50,6 +50,7 @@ Change Log
the logic that cleared the flag could never be reached, resulting in performance degradation. the logic that cleared the flag could never be reached, resulting in performance degradation.
Reported by Alexander Nesterov. Reported by Alexander Nesterov.
</li><li>Issue 552: Implement BIT_AND and BIT_OR aggregate functions. </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> </li></ul>
<h2>Version 1.4.184 Beta (2014-12-19)</h2> <h2>Version 1.4.184 Beta (2014-12-19)</h2>
......
...@@ -4544,8 +4544,12 @@ public class Parser { ...@@ -4544,8 +4544,12 @@ public class Parser {
command.setQueueSize(readPositiveInt()); command.setQueueSize(readPositiveInt());
} }
command.setNoWait(readIf("NOWAIT")); command.setNoWait(readIf("NOWAIT"));
if (readIf("AS")) {
command.setTriggerSource(readString());
} else {
read("CALL"); read("CALL");
command.setTriggerClassName(readUniqueIdentifier()); command.setTriggerClassName(readUniqueIdentifier());
}
return command; return command;
} }
......
...@@ -32,6 +32,7 @@ public class CreateTrigger extends SchemaCommand { ...@@ -32,6 +32,7 @@ public class CreateTrigger extends SchemaCommand {
private boolean noWait; private boolean noWait;
private String tableName; private String tableName;
private String triggerClassName; private String triggerClassName;
private String triggerSource;
private boolean force; private boolean force;
private boolean onRollback; private boolean onRollback;
...@@ -51,6 +52,10 @@ public class CreateTrigger extends SchemaCommand { ...@@ -51,6 +52,10 @@ public class CreateTrigger extends SchemaCommand {
this.triggerClassName = triggerClassName; this.triggerClassName = triggerClassName;
} }
public void setTriggerSource(String triggerSource) {
this.triggerSource = triggerSource;
}
public void setTypeMask(int typeMask) { public void setTypeMask(int typeMask) {
this.typeMask = typeMask; this.typeMask = typeMask;
} }
...@@ -106,7 +111,10 @@ public class CreateTrigger extends SchemaCommand { ...@@ -106,7 +111,10 @@ public class CreateTrigger extends SchemaCommand {
trigger.setRowBased(rowBased); trigger.setRowBased(rowBased);
trigger.setTypeMask(typeMask); trigger.setTypeMask(typeMask);
trigger.setOnRollback(onRollback); trigger.setOnRollback(onRollback);
if(this.triggerClassName != null)
trigger.setTriggerClassName(triggerClassName, force); trigger.setTriggerClassName(triggerClassName, force);
else
trigger.setTriggerSource(triggerSource, force);
db.addSchemaObject(session, trigger); db.addSchemaObject(session, trigger);
table.addTrigger(trigger); table.addTrigger(trigger);
return 0; return 0;
......
...@@ -5,12 +5,14 @@ ...@@ -5,12 +5,14 @@
*/ */
package org.h2.schema; package org.h2.schema;
import java.lang.reflect.Method;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.api.Trigger; import org.h2.api.Trigger;
import org.h2.command.Parser; import org.h2.command.Parser;
import org.h2.engine.Constants;
import org.h2.engine.DbObject; import org.h2.engine.DbObject;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.message.DbException; import org.h2.message.DbException;
...@@ -18,7 +20,9 @@ import org.h2.message.Trace; ...@@ -18,7 +20,9 @@ import org.h2.message.Trace;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.table.Table; import org.h2.table.Table;
import org.h2.util.JdbcUtils; import org.h2.util.JdbcUtils;
import org.h2.util.SourceCompiler;
import org.h2.util.StatementBuilder; import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
import org.h2.value.DataType; import org.h2.value.DataType;
import org.h2.value.Value; import org.h2.value.Value;
...@@ -43,6 +47,7 @@ public class TriggerObject extends SchemaObjectBase { ...@@ -43,6 +47,7 @@ public class TriggerObject extends SchemaObjectBase {
private boolean noWait; private boolean noWait;
private Table table; private Table table;
private String triggerClassName; private String triggerClassName;
private String triggerSource;
private Trigger triggerCallback; private Trigger triggerCallback;
public TriggerObject(Schema schema, int id, String name, Table table) { public TriggerObject(Schema schema, int id, String name, Table table) {
...@@ -66,7 +71,12 @@ public class TriggerObject extends SchemaObjectBase { ...@@ -66,7 +71,12 @@ public class TriggerObject extends SchemaObjectBase {
try { try {
Session sysSession = database.getSystemSession(); Session sysSession = database.getSystemSession();
Connection c2 = sysSession.createConnection(false); 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 = (Trigger) obj;
triggerCallback.init(c2, getSchema().getName(), getName(), triggerCallback.init(c2, getSchema().getName(), getName(),
table.getName(), before, typeMask); table.getName(), before, typeMask);
...@@ -74,7 +84,25 @@ public class TriggerObject extends SchemaObjectBase { ...@@ -74,7 +84,25 @@ public class TriggerObject extends SchemaObjectBase {
// try again later // try again later
triggerCallback = null; triggerCallback = null;
throw DbException.get(ErrorCode.ERROR_CREATING_TRIGGER_OBJECT_3, e, getName(), 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 { ...@@ -86,7 +114,23 @@ public class TriggerObject extends SchemaObjectBase {
* should be ignored * should be ignored
*/ */
public void setTriggerClassName(String triggerClassName, boolean force) { 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.triggerClassName = triggerClassName;
this.triggerSource = source;
try { try {
load(); load();
} catch (DbException e) { } catch (DbException e) {
...@@ -120,7 +164,7 @@ public class TriggerObject extends SchemaObjectBase { ...@@ -120,7 +164,7 @@ public class TriggerObject extends SchemaObjectBase {
triggerCallback.fire(c2, null, null); triggerCallback.fire(c2, null, null);
} catch (Throwable e) { } catch (Throwable e) {
throw DbException.get(ErrorCode.ERROR_EXECUTING_TRIGGER_3, e, getName(), throw DbException.get(ErrorCode.ERROR_EXECUTING_TRIGGER_3, e, getName(),
triggerClassName, e.toString()); triggerClassName != null ? triggerClassName : "..source..", e.toString());
} finally { } finally {
session.setLastScopeIdentity(identity); session.setLastScopeIdentity(identity);
if (type != Trigger.SELECT) { if (type != Trigger.SELECT) {
...@@ -283,7 +327,10 @@ public class TriggerObject extends SchemaObjectBase { ...@@ -283,7 +327,10 @@ public class TriggerObject extends SchemaObjectBase {
} else { } else {
buff.append(" QUEUE ").append(queueSize); buff.append(" QUEUE ").append(queueSize);
} }
if (triggerClassName != null)
buff.append(" CALL ").append(Parser.quoteIdentifier(triggerClassName)); buff.append(" CALL ").append(Parser.quoteIdentifier(triggerClassName));
else
buff.append(" AS ").append(StringUtils.quoteStringSQL(triggerSource));
return buff.toString(); return buff.toString();
} }
...@@ -335,6 +382,7 @@ public class TriggerObject extends SchemaObjectBase { ...@@ -335,6 +382,7 @@ public class TriggerObject extends SchemaObjectBase {
} }
table = null; table = null;
triggerClassName = null; triggerClassName = null;
triggerSource = null;
triggerCallback = null; triggerCallback = null;
invalidate(); invalidate();
} }
...@@ -371,6 +419,10 @@ public class TriggerObject extends SchemaObjectBase { ...@@ -371,6 +419,10 @@ public class TriggerObject extends SchemaObjectBase {
return triggerClassName; return triggerClassName;
} }
public String getTriggerSource() {
return triggerSource;
}
/** /**
* Close the trigger. * Close the trigger.
*/ */
......
...@@ -46,6 +46,7 @@ public class TestTriggersConstraints extends TestBase implements Trigger { ...@@ -46,6 +46,7 @@ public class TestTriggersConstraints extends TestBase implements Trigger {
testViewTrigger(); testViewTrigger();
testTriggerBeforeSelect(); testTriggerBeforeSelect();
testTriggerAlterTable(); testTriggerAlterTable();
testTriggerAsSource();
testTriggers(); testTriggers();
testConstraints(); testConstraints();
testCheckConstraintErrorMessage(); testCheckConstraintErrorMessage();
...@@ -386,19 +387,39 @@ public class TestTriggersConstraints extends TestBase implements Trigger { ...@@ -386,19 +387,39 @@ public class TestTriggersConstraints extends TestBase implements Trigger {
} }
private void testTriggerAlterTable() throws SQLException { 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"); Connection conn = getConnection("trigger");
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
stat.execute("DROP TABLE IF EXISTS TEST"); stat.execute("DROP TABLE IF EXISTS TEST");
stat.execute("create sequence seq"); stat.execute("create sequence seq");
stat.execute("create table test(id int primary key)"); stat.execute("create table test(id int primary key)");
assertSingleValue(stat, "call seq.nextval", 1); assertSingleValue(stat, callSeq, 1);
conn.setAutoCommit(false); conn.setAutoCommit(false);
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 \"" + stat.execute("create trigger test_upd before insert on test call \"" +
TestTriggerAlterTable.class.getName() + "\""); TestTriggerAlterTable.class.getName() + "\"");
}
stat.execute("insert into test values(1)"); 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"); 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 sequence seq");
stat.execute("drop table test"); stat.execute("drop table test");
conn.close(); conn.close();
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论