提交 5632b50a authored 作者: sylvain's avatar sylvain

added Javascript support for Triggers' source

上级 079785dc
...@@ -735,6 +735,7 @@ The class must be available in the classpath of the database engine ...@@ -735,6 +735,7 @@ The class must be available in the classpath of the database engine
The sourceCodeString must define a single method with no parameters that returns ""org.h2.api.Trigger"". The sourceCodeString must define a single method with no parameters that returns ""org.h2.api.Trigger"".
See CREATE ALIAS for requirements regarding the compilation. See CREATE ALIAS for requirements regarding the compilation.
Alternatively, it can be a javascript (see javax.script.ScriptEngineManager) that returns ""org.h2.api.Trigger"". In that case the source must begin by ""//javascript"".
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;
...@@ -773,6 +774,7 @@ This command commits an open transaction in this connection. ...@@ -773,6 +774,7 @@ 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""); } $$; CREATE TRIGGER TRIG_SRC BEFORE INSERT ON TEST AS $$org.h2.api.Trigger create() { return new MyTrigger(""constructorParam""); } $$;
CREATE TRIGGER TRIG_SCRIPT BEFORE INSERT ON TEST AS $$//javascript\nreturn new Packages.MyTrigger(""constructorParam""); $$;
" "
"Commands (DDL)","CREATE USER"," "Commands (DDL)","CREATE USER","
CREATE USER [ IF NOT EXISTS ] newUserName CREATE USER [ IF NOT EXISTS ] newUserName
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
*/ */
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;
...@@ -94,11 +93,7 @@ public class TriggerObject extends SchemaObjectBase { ...@@ -94,11 +93,7 @@ public class TriggerObject extends SchemaObjectBase {
String fullClassName = Constants.USER_PACKAGE + ".trigger." + getName(); String fullClassName = Constants.USER_PACKAGE + ".trigger." + getName();
compiler.setSource(fullClassName, triggerSource); compiler.setSource(fullClassName, triggerSource);
try { try {
Method m = compiler.getMethod(fullClassName); return (Trigger) compiler.invoke(fullClassName);
if (m.getParameterTypes().length > 0) {
throw new IllegalStateException("No parameters are allowed for a trigger");
}
return (Trigger) m.invoke(null);
} catch (DbException e) { } catch (DbException e) {
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
......
...@@ -24,6 +24,14 @@ import java.net.URI; ...@@ -24,6 +24,14 @@ import java.net.URI;
import java.security.SecureClassLoader; import java.security.SecureClassLoader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import javax.tools.FileObject; import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager; import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler; import javax.tools.JavaCompiler;
...@@ -33,6 +41,7 @@ import javax.tools.JavaFileObject.Kind; ...@@ -33,6 +41,7 @@ import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject; import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager; import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider; import javax.tools.ToolProvider;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
...@@ -65,6 +74,8 @@ public class SourceCompiler { ...@@ -65,6 +74,8 @@ public class SourceCompiler {
*/ */
final HashMap<String, Class<?>> compiled = New.hashMap(); final HashMap<String, Class<?>> compiled = New.hashMap();
final Map<String, CompiledScript> compiledScripts = New.hashMap();
/** /**
* Whether to use the ToolProvider.getSystemJavaCompiler(). * Whether to use the ToolProvider.getSystemJavaCompiler().
*/ */
...@@ -168,6 +179,32 @@ public class SourceCompiler { ...@@ -168,6 +179,32 @@ public class SourceCompiler {
return source.startsWith("//groovy") || source.startsWith("@groovy"); return source.startsWith("//groovy") || source.startsWith("@groovy");
} }
private static boolean isJavascriptSource(String source) {
return source.startsWith("//javascript");
}
// whether the passed source should be compiled using javax.script.ScriptEngineManager
private static boolean isJavaxScriptSource(String source) {
return isJavascriptSource(source);
}
public CompiledScript getCompiledScript(String packageAndClassName) throws ScriptException {
CompiledScript compiledScript = compiledScripts.get(packageAndClassName);
if (compiledScript == null) {
String source = sources.get(packageAndClassName);
final String lang;
if (isJavascriptSource(source))
lang = "javascript";
else
throw new IllegalStateException("Unkown language for " + source);
final Compilable jsEngine = (Compilable) new ScriptEngineManager().getEngineByName(lang);
compiledScript = jsEngine.compile(source);
compiledScripts.put(packageAndClassName, compiledScript);
}
return compiledScript;
}
/** /**
* Get the first public static method of the given class. * Get the first public static method of the given class.
* *
...@@ -189,6 +226,24 @@ public class SourceCompiler { ...@@ -189,6 +226,24 @@ public class SourceCompiler {
return null; return null;
} }
public Object invoke(String className, final Object... args) throws Exception {
String source = sources.get(className);
if (isJavaxScriptSource(source)) {
final Bindings bindings = new SimpleBindings();
int i = 0;
for (final Object arg : args) {
bindings.put("arg" + i, arg);
i++;
}
return this.getCompiledScript(className).eval(bindings);
} else {
final Method m = this.getMethod(className);
if (m.getParameterTypes().length != args.length)
throw new IllegalStateException("Wrong number of parameters, " + args.length + " were pased but " + m.getParameterTypes().length + " were needed");
return m.invoke(null, args);
}
}
/** /**
* Compile the given class. This method tries to use the class * Compile the given class. This method tries to use the class
* "com.sun.tools.javac.Main" if available. If not, it tries to run "javac" * "com.sun.tools.javac.Main" if available. If not, it tries to run "javac"
......
...@@ -12,6 +12,7 @@ import java.sql.SQLException; ...@@ -12,6 +12,7 @@ import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.api.Trigger; import org.h2.api.Trigger;
import org.h2.engine.Session; import org.h2.engine.Session;
...@@ -50,6 +51,7 @@ public class TestTriggersConstraints extends TestBase implements Trigger { ...@@ -50,6 +51,7 @@ public class TestTriggersConstraints extends TestBase implements Trigger {
testTriggerBeforeSelect(); testTriggerBeforeSelect();
testTriggerAlterTable(); testTriggerAlterTable();
testTriggerAsSource(); testTriggerAsSource();
testTriggerAsJavascript();
testTriggers(); testTriggers();
testConstraints(); testConstraints();
testCheckConstraintErrorMessage(); testCheckConstraintErrorMessage();
...@@ -471,15 +473,20 @@ public class TestTriggersConstraints extends TestBase implements Trigger { ...@@ -471,15 +473,20 @@ public class TestTriggersConstraints extends TestBase implements Trigger {
private void testTriggerAlterTable() throws SQLException { private void testTriggerAlterTable() throws SQLException {
deleteDb("trigger"); deleteDb("trigger");
testTrigger(false); testTrigger(null);
} }
private void testTriggerAsSource() throws SQLException { private void testTriggerAsSource() throws SQLException {
deleteDb("trigger"); deleteDb("trigger");
testTrigger(true); testTrigger("java");
}
private void testTriggerAsJavascript() throws SQLException {
deleteDb("trigger");
testTrigger("javascript");
} }
private void testTrigger(final boolean asSource) throws SQLException { private void testTrigger(final String sourceLang) throws SQLException {
final String callSeq = "call seq.nextval"; final String callSeq = "call seq.nextval";
Connection conn = getConnection("trigger"); Connection conn = getConnection("trigger");
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
...@@ -490,12 +497,18 @@ public class TestTriggersConstraints extends TestBase implements Trigger { ...@@ -490,12 +497,18 @@ public class TestTriggersConstraints extends TestBase implements Trigger {
conn.setAutoCommit(false); conn.setAutoCommit(false);
Trigger t = new org.h2.test.db.TestTriggersConstraints.TestTriggerAlterTable(); Trigger t = new org.h2.test.db.TestTriggersConstraints.TestTriggerAlterTable();
t.close(); t.close();
if (asSource) { if ("java".equals(sourceLang)) {
String triggerClassName = this.getClass().getName() + "." String triggerClassName = this.getClass().getName() + "."
+ TestTriggerAlterTable.class.getSimpleName(); + TestTriggerAlterTable.class.getSimpleName();
stat.execute("create trigger test_upd before insert on test " stat.execute("create trigger test_upd before insert on test "
+ "as $$org.h2.api.Trigger create() " + "{ return new " + "as $$org.h2.api.Trigger create() " + "{ return new "
+ triggerClassName + "(); } $$"); + triggerClassName + "(); } $$");
} else if ("javascript".equals(sourceLang)) {
String triggerClassName = this.getClass().getName() + "."
+ TestTriggerAlterTable.class.getSimpleName();
final String body = "//javascript\nnew Packages." + triggerClassName + "();";
stat.execute("create trigger test_upd before insert on test as $$"
+ body + " $$");
} else { } 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() + "\"");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论