提交 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
The sourceCodeString must define a single method with no parameters that returns ""org.h2.api.Trigger"".
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,
null and length constraint checks have been made;
......@@ -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_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","
CREATE USER [ IF NOT EXISTS ] newUserName
......
......@@ -5,7 +5,6 @@
*/
package org.h2.schema;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
......@@ -94,11 +93,7 @@ public class TriggerObject extends SchemaObjectBase {
String fullClassName = Constants.USER_PACKAGE + ".trigger." + getName();
compiler.setSource(fullClassName, triggerSource);
try {
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);
return (Trigger) compiler.invoke(fullClassName);
} catch (DbException e) {
throw e;
} catch (Exception e) {
......
......@@ -24,6 +24,14 @@ import java.net.URI;
import java.security.SecureClassLoader;
import java.util.ArrayList;
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.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
......@@ -33,6 +41,7 @@ import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import org.h2.api.ErrorCode;
import org.h2.engine.Constants;
import org.h2.engine.SysProperties;
......@@ -65,6 +74,8 @@ public class SourceCompiler {
*/
final HashMap<String, Class<?>> compiled = New.hashMap();
final Map<String, CompiledScript> compiledScripts = New.hashMap();
/**
* Whether to use the ToolProvider.getSystemJavaCompiler().
*/
......@@ -168,6 +179,32 @@ public class SourceCompiler {
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.
*
......@@ -189,6 +226,24 @@ public class SourceCompiler {
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
* "com.sun.tools.javac.Main" if available. If not, it tries to run "javac"
......
......@@ -12,6 +12,7 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.HashSet;
import org.h2.api.ErrorCode;
import org.h2.api.Trigger;
import org.h2.engine.Session;
......@@ -50,6 +51,7 @@ public class TestTriggersConstraints extends TestBase implements Trigger {
testTriggerBeforeSelect();
testTriggerAlterTable();
testTriggerAsSource();
testTriggerAsJavascript();
testTriggers();
testConstraints();
testCheckConstraintErrorMessage();
......@@ -471,15 +473,20 @@ public class TestTriggersConstraints extends TestBase implements Trigger {
private void testTriggerAlterTable() throws SQLException {
deleteDb("trigger");
testTrigger(false);
testTrigger(null);
}
private void testTriggerAsSource() throws SQLException {
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";
Connection conn = getConnection("trigger");
Statement stat = conn.createStatement();
......@@ -490,12 +497,18 @@ public class TestTriggersConstraints extends TestBase implements Trigger {
conn.setAutoCommit(false);
Trigger t = new org.h2.test.db.TestTriggersConstraints.TestTriggerAlterTable();
t.close();
if (asSource) {
if ("java".equals(sourceLang)) {
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 + "(); } $$");
} 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 {
stat.execute("create trigger test_upd before insert on test call \""
+ TestTriggerAlterTable.class.getName() + "\"");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论