Unverified 提交 7365ba5c authored 作者: Noel Grandin's avatar Noel Grandin 提交者: GitHub

Merge pull request #704 from ilm-informatique/javascript

added Javascript support for Triggers' source
...@@ -735,6 +735,9 @@ The class must be available in the classpath of the database engine ...@@ -735,6 +735,9 @@ 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, javax.script.ScriptEngineManager can be used to create an instance of ""org.h2.api.Trigger"".
Currently javascript (included in every JRE) and ruby (with JRuby) are supported.
In that case the source must begin respectively with ""//javascript"" or ""#ruby"".
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 +776,8 @@ This command commits an open transaction in this connection. ...@@ -773,6 +776,8 @@ 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_JS BEFORE INSERT ON TEST AS $$//javascript\nreturn new Packages.MyTrigger(""constructorParam""); $$;
CREATE TRIGGER TRIG_RUBY BEFORE INSERT ON TEST AS $$#ruby\nJava::MyPackage::MyTrigger.new(""constructorParam"") $$;
" "
"Commands (DDL)","CREATE USER"," "Commands (DDL)","CREATE USER","
CREATE USER [ IF NOT EXISTS ] newUserName CREATE USER [ IF NOT EXISTS ] newUserName
......
...@@ -94,11 +94,15 @@ public class TriggerObject extends SchemaObjectBase { ...@@ -94,11 +94,15 @@ 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); if (SourceCompiler.isJavaxScriptSource(triggerSource)) {
if (m.getParameterTypes().length > 0) { return (Trigger) compiler.getCompiledScript(fullClassName).eval();
throw new IllegalStateException("No parameters are allowed for a trigger"); } else {
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);
} }
return (Trigger) m.invoke(null);
} catch (DbException e) { } catch (DbException e) {
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
......
...@@ -24,6 +24,12 @@ import java.net.URI; ...@@ -24,6 +24,12 @@ 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.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
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 +39,7 @@ import javax.tools.JavaFileObject.Kind; ...@@ -33,6 +39,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 +72,11 @@ public class SourceCompiler { ...@@ -65,6 +72,11 @@ public class SourceCompiler {
*/ */
final HashMap<String, Class<?>> compiled = New.hashMap(); final HashMap<String, Class<?>> compiled = New.hashMap();
/**
* The class name to compiled scripts map.
*/
final Map<String, CompiledScript> compiledScripts = New.hashMap();
/** /**
* Whether to use the ToolProvider.getSystemJavaCompiler(). * Whether to use the ToolProvider.getSystemJavaCompiler().
*/ */
...@@ -168,6 +180,43 @@ public class SourceCompiler { ...@@ -168,6 +180,43 @@ 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");
}
private static boolean isRubySource(String source) {
return source.startsWith("#ruby");
}
/**
* Whether the passed source can be compiled using {@link javax.script.ScriptEngineManager}.
*
* @param source the source to test.
* @return <code>true</code> if {@link #getCompiledScript(String)} can be called.
*/
public static boolean isJavaxScriptSource(String source) {
return isJavascriptSource(source) || isRubySource(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 if (isRubySource(source))
lang = "ruby";
else
throw new IllegalStateException("Unknown 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.
* *
......
...@@ -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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论