提交 769b2932 authored 作者: Thomas Mueller's avatar Thomas Mueller

User defined functions can be created with source code.

上级 4abe8639
...@@ -3772,8 +3772,12 @@ public class Parser { ...@@ -3772,8 +3772,12 @@ public class Parser {
command.setAliasName(name); command.setAliasName(name);
command.setIfNotExists(ifNotExists); command.setIfNotExists(ifNotExists);
command.setDeterministic(readIf("DETERMINISTIC")); command.setDeterministic(readIf("DETERMINISTIC"));
read("FOR"); if (readIf("AS")) {
command.setJavaClassMethod(readUniqueIdentifier()); command.setSource(readString());
} else {
read("FOR");
command.setJavaClassMethod(readUniqueIdentifier());
}
return command; return command;
} }
......
...@@ -25,6 +25,7 @@ public class CreateFunctionAlias extends DefineCommand { ...@@ -25,6 +25,7 @@ public class CreateFunctionAlias extends DefineCommand {
private boolean deterministic; private boolean deterministic;
private boolean ifNotExists; private boolean ifNotExists;
private boolean force; private boolean force;
private String source;
public CreateFunctionAlias(Session session) { public CreateFunctionAlias(Session session) {
super(session); super(session);
...@@ -40,7 +41,12 @@ public class CreateFunctionAlias extends DefineCommand { ...@@ -40,7 +41,12 @@ public class CreateFunctionAlias extends DefineCommand {
} }
} else { } else {
int id = getObjectId(false, true); int id = getObjectId(false, true);
FunctionAlias functionAlias = new FunctionAlias(db, id, aliasName, javaClassMethod, force); FunctionAlias functionAlias;
if (javaClassMethod != null) {
functionAlias = FunctionAlias.newInstance(db, id, aliasName, javaClassMethod, force);
} else {
functionAlias = FunctionAlias.newInstanceFromSource(db, id, aliasName, source, force);
}
functionAlias.setDeterministic(deterministic); functionAlias.setDeterministic(deterministic);
db.addDatabaseObject(session, functionAlias); db.addDatabaseObject(session, functionAlias);
} }
...@@ -67,4 +73,8 @@ public class CreateFunctionAlias extends DefineCommand { ...@@ -67,4 +73,8 @@ public class CreateFunctionAlias extends DefineCommand {
this.deterministic = deterministic; this.deterministic = deterministic;
} }
public void setSource(String source) {
this.source = source;
}
} }
...@@ -491,6 +491,11 @@ public class Constants { ...@@ -491,6 +491,11 @@ public class Constants {
*/ */
public static final boolean BLOB_SEARCH = false; public static final boolean BLOB_SEARCH = false;
/**
* The package name of user defined classes.
*/
public static final String USER_PACKAGE = "org.h2.dynamic";
private Constants() { private Constants() {
// utility class // utility class
} }
......
...@@ -65,6 +65,7 @@ import org.h2.util.NetUtils; ...@@ -65,6 +65,7 @@ import org.h2.util.NetUtils;
import org.h2.util.New; import org.h2.util.New;
import org.h2.util.ObjectArray; import org.h2.util.ObjectArray;
import org.h2.util.SmallLRUCache; import org.h2.util.SmallLRUCache;
import org.h2.util.SourceCompiler;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import org.h2.util.TempFileDeleter; import org.h2.util.TempFileDeleter;
import org.h2.value.CompareMode; import org.h2.value.CompareMode;
...@@ -165,16 +166,14 @@ public class Database implements DataHandler { ...@@ -165,16 +166,14 @@ public class Database implements DataHandler {
private TempFileDeleter tempFileDeleter = TempFileDeleter.getInstance(); private TempFileDeleter tempFileDeleter = TempFileDeleter.getInstance();
private PageStore pageStore; private PageStore pageStore;
private boolean usePageStoreSet, usePageStore; private boolean usePageStoreSet, usePageStore;
private Properties reconnectLastLock; private Properties reconnectLastLock;
private volatile long reconnectCheckNext; private volatile long reconnectCheckNext;
private volatile boolean reconnectChangePending; private volatile boolean reconnectChangePending;
private volatile boolean checkpointAllowed; private volatile boolean checkpointAllowed;
private volatile boolean checkpointRunning; private volatile boolean checkpointRunning;
private int cacheSize; private int cacheSize;
private boolean compactFully; private boolean compactFully;
private SourceCompiler compiler;
public Database(String name, ConnectionInfo ci, String cipher) throws SQLException { public Database(String name, ConnectionInfo ci, String cipher) throws SQLException {
this.compareMode = CompareMode.getInstance(null, 0); this.compareMode = CompareMode.getInstance(null, 0);
...@@ -2498,4 +2497,11 @@ public class Database implements DataHandler { ...@@ -2498,4 +2497,11 @@ public class Database implements DataHandler {
this.compactFully = compactFully; this.compactFully = compactFully;
} }
public SourceCompiler getCompiler() {
if (compiler == null) {
compiler = new SourceCompiler();
}
return compiler;
}
} }
...@@ -21,6 +21,7 @@ import org.h2.message.Trace; ...@@ -21,6 +21,7 @@ import org.h2.message.Trace;
import org.h2.table.Table; import org.h2.table.Table;
import org.h2.util.ClassUtils; import org.h2.util.ClassUtils;
import org.h2.util.ObjectArray; import org.h2.util.ObjectArray;
import org.h2.util.SourceCompiler;
import org.h2.util.StatementBuilder; import org.h2.util.StatementBuilder;
import org.h2.value.DataType; import org.h2.value.DataType;
import org.h2.value.Value; import org.h2.value.Value;
...@@ -36,20 +37,37 @@ public class FunctionAlias extends DbObjectBase { ...@@ -36,20 +37,37 @@ public class FunctionAlias extends DbObjectBase {
private String className; private String className;
private String methodName; private String methodName;
private String source;
private JavaMethod[] javaMethods; private JavaMethod[] javaMethods;
private boolean deterministic; private boolean deterministic;
public FunctionAlias(Database db, int id, String name, String javaClassMethod, boolean force) throws SQLException { private FunctionAlias(Database db, int id, String name) {
initDbObjectBase(db, id, name, Trace.FUNCTION); initDbObjectBase(db, id, name, Trace.FUNCTION);
}
public static FunctionAlias newInstance(Database db, int id, String name, String javaClassMethod, boolean force) throws SQLException {
FunctionAlias alias = new FunctionAlias(db, id, name);
int paren = javaClassMethod.indexOf('('); int paren = javaClassMethod.indexOf('(');
int lastDot = javaClassMethod.lastIndexOf('.', paren < 0 ? javaClassMethod.length() : paren); int lastDot = javaClassMethod.lastIndexOf('.', paren < 0 ? javaClassMethod.length() : paren);
if (lastDot < 0) { if (lastDot < 0) {
throw Message.getSQLException(ErrorCode.SYNTAX_ERROR_1, javaClassMethod); throw Message.getSQLException(ErrorCode.SYNTAX_ERROR_1, javaClassMethod);
} }
className = javaClassMethod.substring(0, lastDot); alias.className = javaClassMethod.substring(0, lastDot);
methodName = javaClassMethod.substring(lastDot + 1); alias.methodName = javaClassMethod.substring(lastDot + 1);
alias.init(force);
return alias;
}
public static FunctionAlias newInstanceFromSource(Database db, int id, String name, String source, boolean force) throws SQLException {
FunctionAlias alias = new FunctionAlias(db, id, name);
alias.source = source;
alias.init(force);
return alias;
}
private void init(boolean force) throws SQLException {
try { try {
// at least try to load the class, otherwise the data type is not // at least try to compile the class, otherwise the data type is not
// initialized if it could be // initialized if it could be
load(); load();
} catch (SQLException e) { } catch (SQLException e) {
...@@ -63,6 +81,29 @@ public class FunctionAlias extends DbObjectBase { ...@@ -63,6 +81,29 @@ public class FunctionAlias extends DbObjectBase {
if (javaMethods != null) { if (javaMethods != null) {
return; return;
} }
if (source != null) {
loadFromSource();
} else {
loadClass();
}
}
private void loadFromSource() throws SQLException {
SourceCompiler compiler = database.getCompiler();
String className = Constants.USER_PACKAGE + "." + getName();
compiler.setSource(className, source);
try {
Method m = compiler.getMethod(className);
JavaMethod method = new JavaMethod(m, 0);
javaMethods = new JavaMethod[] {
method
};
} catch (Exception e) {
throw Message.getSQLException(ErrorCode.SYNTAX_ERROR_1, e, source);
}
}
private void loadClass() throws SQLException {
Class< ? > javaClass = ClassUtils.loadUserClass(className); Class< ? > javaClass = ClassUtils.loadUserClass(className);
Method[] methods = javaClass.getMethods(); Method[] methods = javaClass.getMethods();
ObjectArray<JavaMethod> list = ObjectArray.newInstance(); ObjectArray<JavaMethod> list = ObjectArray.newInstance();
......
/*
* Copyright 2004-2009 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.util;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import org.h2.message.InternalException;
/**
* This class allows to convert source code to a class. It uses one class loader
* per class.
*/
public class SourceCompiler {
private static final Class< ? > JAVAC_SUN;
HashMap<String, String> sources = New.hashMap();
HashMap<String, Class< ? >> compiled = New.hashMap();
private String compileDir = System.getProperty("java.io.tmpdir");
static {
Class< ? > clazz;
try {
clazz = Class.forName("com.sun.tools.javac.Main");
} catch (Exception e) {
clazz = null;
}
JAVAC_SUN = clazz;
}
/**
* Set the source code for the specified class.
* This will reset all compiled classes.
*
* @param className the class name
* @param source the source code
*/
public void setSource(String className, String source) {
sources.put(className, source);
compiled.clear();
}
/**
* Get the class object for the given name.
*
* @param name the class name
* @return the class
*/
private Class< ? > getClass(String name) throws ClassNotFoundException {
Class< ? > clazz = compiled.get(name);
if (clazz != null) {
return clazz;
}
ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) {
public Class< ? > findClass(String name) throws ClassNotFoundException {
Class< ? > clazz = compiled.get(name);
if (clazz == null) {
String source = sources.get(name);
String packageName = null;
int idx = name.lastIndexOf('.');
String className;
if (idx >= 0) {
packageName = name.substring(0, idx);
className = name.substring(idx + 1);
} else {
className = name;
}
byte[] data = javacCompile(packageName, className, source);
if (data == null) {
clazz = findSystemClass(name);
} else {
clazz = defineClass(name, data, 0, data.length);
compiled.put(name, clazz);
}
}
return clazz;
}
};
return classLoader.loadClass(name);
}
/**
* Get the first public static method of the given class.
*
* @param className the class name
* @return the method name
*/
public Method getMethod(String className) throws ClassNotFoundException {
Class< ? > clazz = getClass(className);
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
int modifiers = m.getModifiers();
if (Modifier.isPublic(modifiers)) {
if (Modifier.isStatic(modifiers)) {
return m;
}
}
}
return null;
}
/**
* 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"
* in a separate process.
*
* @param packageName the package name
* @param className the class name
* @param source the source code
* @return the class file
*/
byte[] javacCompile(String packageName, String className, String source) {
File dir = new File(compileDir);
if (packageName != null) {
dir = new File(dir, packageName.replace('.', '/'));
try {
FileUtils.mkdirs(dir);
} catch (IOException e) {
throw new InternalException(e);
}
}
File javaFile = new File(dir, className + ".java");
File classFile = new File(dir, className + ".class");
try {
PrintWriter out = new PrintWriter(new FileWriter(javaFile));
classFile.delete();
int endImport = source.indexOf("@CODE");
String importCode = "import java.util.*;\n" +
"import java.math.*;\n" +
"import java.sql.*;\n";
if (packageName != null) {
importCode = "package " + packageName + ";\n" + importCode;
}
if (endImport >= 0) {
importCode = source.substring(0, endImport);
source = source.substring("@CODE".length() + endImport);
}
out.println(importCode);
out.println("public class "+ className +" {\n" +
" public static " +
source + "\n" +
"}\n");
out.close();
if (JAVAC_SUN != null) {
javacSun(javaFile);
} else {
javacProcess(javaFile);
}
byte[] data = new byte[(int) classFile.length()];
DataInputStream in = new DataInputStream(new FileInputStream(classFile));
in.readFully(data);
in.close();
return data;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
javaFile.delete();
classFile.delete();
}
}
private void javacProcess(File javaFile) {
exec("javac",
"-sourcepath", compileDir,
"-d", compileDir,
javaFile.getAbsolutePath());
}
private int exec(String... args) {
ByteArrayOutputStream buff = new ByteArrayOutputStream();
try {
Process p = Runtime.getRuntime().exec(args);
copyInThread(p.getInputStream(), buff);
copyInThread(p.getErrorStream(), buff);
p.waitFor();
byte[] err = buff.toByteArray();
if (err.length != 0) {
throw new RuntimeException("Compile error: " + StringUtils.utf8Decode(err));
}
return p.exitValue();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void copyInThread(final InputStream in, final OutputStream out) {
new Thread() {
public void run() {
try {
while (true) {
int x = in.read();
if (x < 0) {
return;
}
if (out != null) {
out.write(x);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} .start();
}
private void javacSun(File javaFile) {
PrintStream old = System.err;
ByteArrayOutputStream buff = new ByteArrayOutputStream();
PrintStream temp = new PrintStream(buff);
try {
System.setErr(temp);
Method compile = JAVAC_SUN.getMethod("compile", String[].class);
Object javac = JAVAC_SUN.newInstance();
compile.invoke(javac, (Object) new String[] {
"-sourcepath", compileDir,
"-d", compileDir,
javaFile.getAbsolutePath() });
byte[] err = buff.toByteArray();
if (err.length != 0) {
throw new RuntimeException("Compile error: " + StringUtils.utf8Decode(err));
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
System.setErr(old);
}
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论