在calcite中可以通过自定义函数来实现丰富的算子,但是往往需要将自定义的函数包与Calcite集成在一起,才能使用相应的算子功能,由于业务需求多变,函数包可能面临频繁的更改,如果需要更改生效,则需要将Calcite的代码重新打包,并重启jvm才能获取最新的算子功能。那么能否更改现状,更新完函数包,通过api调用刷新注册函数呢?
首先我们引入一个外部的函数包。
D:\maven-3.6.3\repository\com\kanyun\func\1.0-SNAPSHOT\func-1.0-SNAPSHOT.jar
该包中只包含一个class。
package com.kanyun.func.string;
import java.util.Stack;
public class StringFuncUtil {
/**
* 字符串反转
*
* @param s
* @return
*/
public static String reverse(String s) {
char[] str = s.toCharArray();
Stack<Character> stack = new Stack<>();
for (char aStr1 : str) {
stack.push(aStr1);
}
StringBuilder reversed = new StringBuilder();
while (!stack.isEmpty()) {
reversed.append(stack.pop());
}
return reversed.toString();
}
}
可以看到com.kanyun.func.string.StringFuncUtil#reverse() 就是我们自定义的算子。
此时我们需要将该函数注册到calcite中。
由于该函数并不在我们calcite服务的classpath下,所以是无法直接进行注册的。
//java.lang.ClassNotFoundException: com.kanyun.func.string.StringFuncUtil
Class.forName("com.kanyun.func.string.StringFuncUtil")
因此第一步我们需要先加载这个函数包,最简单的形式是使用URLClassLoader来加载这个函数包,这里我自定义了一个类加载器来加载这个函数包
package com.kanyun.sql.func;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
/**
* 外部函数类加载器,单例模式。保证该类加载器在jvm中只存在一个实例
* 此处配置类加载器主要是用来外部函数(非ClassPath中的jar)
*/
public class ExternalFuncClassLoader extends URLClassLoader {
private static ExternalFuncClassLoader externalFuncClassLoader;
/**
* 私有的构造方法,禁止外部通过new方法创建类加载器实例
* @param urls
* @param parent
*/
private ExternalFuncClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
/**
* 创建自定义类加载器实例
*
* @param urls
* @param parent
* @return
*/
public static ExternalFuncClassLoader newInstance(URL[] urls, ClassLoader parent) {
if (externalFuncClassLoader == null) {
// 如果传递的父加载器为null,则继承线程上下文类加载器
externalFuncClassLoader = new ExternalFuncClassLoader(urls, parent == null ? Thread.currentThread().getContextClassLoader() : parent);
}
return externalFuncClassLoader;
}
/**
* 添加jar
*
* @param files
* @throws MalformedURLException
*/
public void addJars(List<File> files) throws MalformedURLException {
for (File file : files) {
super.addURL(file.toURI().toURL());
}
}
/**
* 获取类加载器实例
* @return
*/
public static ExternalFuncClassLoader getInstance() {
if (externalFuncClassLoader == null) {
URL[] urls = {};
externalFuncClassLoader = new ExternalFuncClassLoader(urls, Thread.currentThread().getContextClassLoader() );
}
return externalFuncClassLoader;
}
}
需要注意这个自定义类加载器是一个单例模式,这也就意味着,我们在创建和使用这个类加载器时是同一个对象。
创建类加载器
/**
* 从文件URL获取类加载器
* 一次加载所有的外部函数,使用同一个类加载器,而不是加载一个外部jar就创建一个类加载器
*
* @param files
* @return
* @throws MalformedURLException
*/
protected ClassLoader createClassLoaderFromFile(List<File> files) throws MalformedURLException {
URL[] urls = new URL[files.size()];
for (int i = 0; i < files.size(); i++) {
urls[i] = files.get(i).toURI().toURL();
}
ExternalFuncClassLoader urlClassLoader = ExternalFuncClassLoader.newInstance(urls, ClassLoader.getSystemClassLoader());
return urlClassLoader;
}
使用创建出来的ClassLoader来解析jar包中的内容,并缓存其中的由public static修饰的方法。
在上面的函数包中,它将缓存com.kanyun.func.string.StringFuncUtil#reverse()方法来作为自定义函数使用!
/**
* 解析jar包
* 并取出需要的函数,这里仅取出 public static 修饰的方法
*
* @param jarFile
* @param classLoader
* @throws ClassNotFoundException
*/
private void parseJar(JarFile jarFile, ClassLoader classLoader) throws ClassNotFoundException {
log.info("准备解析jar文件:{}", jarFile.getName());
// 得到jar包中的元素,包括目录
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
// 判断是否是目录
if (jarEntry.isDirectory()) continue;
String fullName = jarEntry.getName();
// 判断文件是否是class文件
if (!fullName.endsWith(".class")) continue;
String className = fullName.substring(0, fullName.length() - 6).replaceAll("/", ".");
Class<?> clazz;
if (classLoader == null) {
// 对于当前非jvm进程的类路径来说,不指定类加载器是会报错的:java.lang.ClassNotFoundException,因为默认的类加载器是AppClassLoader,它加载classPath下的jar
clazz = Class.forName(className);
} else {
clazz = Class.forName(className, true, classLoader);
}
cacheFunc(clazz, userDefineFunctions);
}
}
calcite自定义函数注册
/**
* 注册函数
*/
public static void registerFunction(SchemaPlus schemaPlus) {
log.debug("=======Schema:[{}],开始注册函数======", schemaPlus.getName());
Set<Map.Entry<String, Class>> entries = userDefineFunctions.entrySet();
entries.addAll(innerDefineFunctions.entrySet());
for (Map.Entry<String, Class> entry : entries) {
log.debug("待注册的函数信息:[{}.{}()]", entry.getValue().getName(), entry.getKey());
try {
// 第一个参数为在SQL中使用的函数名,第二个参数是传入类及类的方法名所创建的函数实例.
// 例如:自定义的函数为com.kanyun.fun.CustomFunc#applyDate() 但是在写SQL时希望把函数名写为APPLY_DATE,此时第一个参数应为APPLY_DATE
schemaPlus.add(entry.getKey(), ScalarFunctionImpl.create(entry.getValue(), entry.getKey()));
} catch (Exception e) {
log.error("函数注册异常!:", e);
}
}
}
自此我们的代码还算完美,实现了自定义函数的注册功能。
那么我们实际使用下这个自定义函数看一下:
# reverse()即为我们自定义的算子
select id,reverse(title) from DB1.girl_pic
发现报错了。
java.sql.SQLException: Error while executing SQL "select id,reverse(title) from DB1.girl_pic": Error while compiling generated Java code:
public org.apache.calcite.linq4j.Enumerable bind(final org.apache.calcite.DataContext root) {
final org.apache.calcite.linq4j.Enumerable _inputEnumerable = org.apache.calcite.schema.Schemas.enumerable((org.apache.calcite.schema.ScannableTable) root.getRootSchema().getSubSchema("DB1").getTable("girl_pic"), root);
return new org.apache.calcite.linq4j.AbstractEnumerable(){
public org.apache.calcite.linq4j.Enumerator enumerator() {
return new org.apache.calcite.linq4j.Enumerator(){
public final org.apache.calcite.linq4j.Enumerator inputEnumerator = _inputEnumerable.enumerator();
public void reset() {
inputEnumerator.reset();
}
public boolean moveNext() {
return inputEnumerator.moveNext();
}
public void close() {
inputEnumerator.close();
}
public Object current() {
final Object[] current = (Object[]) inputEnumerator.current();
return new Object[] {
current[0],
com.kanyun.func.string.StringFuncUtil.reverse(current[1] == null ? (String) null : current[1].toString())};
}
};
}
};
}
public Class getElementType() {
return java.lang.Object[].class;
}
at org.apache.calcite.avatica.Helper.createException(Helper.java:56) ~[avatica-core-1.23.0.jar:1.23.0]
at org.apache.calcite.avatica.Helper.createException(Helper.java:41) ~[avatica-core-1.23.0.jar:1.23.0]
at org.apache.calcite.avatica.AvaticaStatement.executeInternal(AvaticaStatement.java:164) ~[avatica-core-1.23.0.jar:1.23.0]
at org.apache.calcite.avatica.AvaticaStatement.executeQuery(AvaticaStatement.java:228) ~[avatica-core-1.23.0.jar:1.23.0]
at com.kanyun.sql.SqlExecutor.execute(SqlExecutor.java:137) ~[classes/:?]
at com.kanyun.ui.event.ExecuteSqlService$1.call(ExecuteSqlService.java:69) ~[classes/:?]
at com.kanyun.ui.event.ExecuteSqlService$1.call(ExecuteSqlService.java:66) ~[classes/:?]
at javafx.concurrent.Task$TaskCallable.call(Task.java:1423) ~[jfxrt.jar:?]
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266) ~[?:1.8.0_261]
at java.util.concurrent.FutureTask.run(FutureTask.java) ~[?:1.8.0_261]
at javafx.concurrent.Service.lambda$null$6(Service.java:725) ~[jfxrt.jar:?]
at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_261]
at javafx.concurrent.Service.lambda$executeTask$7(Service.java:724) ~[jfxrt.jar:?]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[?:1.8.0_261]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[?:1.8.0_261]
at java.lang.Thread.run(Thread.java:748) ~[?:1.8.0_261]
Caused by: java.lang.RuntimeException: Error while compiling generated Java code:
public org.apache.calcite.linq4j.Enumerable bind(final org.apache.calcite.DataContext root) {
final org.apache.calcite.linq4j.Enumerable _inputEnumerable = org.apache.calcite.schema.Schemas.enumerable((org.apache.calcite.schema.ScannableTable) root.getRootSchema().getSubSchema("DB1").getTable("girl_pic"), root);
return new org.apache.calcite.linq4j.AbstractEnumerable(){
public org.apache.calcite.linq4j.Enumerator enumerator() {
return new org.apache.calcite.linq4j.Enumerator(){
public final org.apache.calcite.linq4j.Enumerator inputEnumerator = _inputEnumerable.enumerator();
public void reset() {
inputEnumerator.reset();
}
public boolean moveNext() {
return inputEnumerator.moveNext();
}
public void close() {
inputEnumerator.close();
}
public Object current() {
final Object[] current = (Object[]) inputEnumerator.current();
return new Object[] {
current[0],
com.kanyun.func.string.StringFuncUtil.reverse(current[1] == null ? (String) null : current[1].toString())};
}
};
}
};
}
public Class getElementType() {
return java.lang.Object[].class;
}
at org.apache.calcite.avatica.Helper.wrap(Helper.java:37) ~[avatica-core-1.23.0.jar:1.23.0]
at org.apache.calcite.adapter.enumerable.EnumerableInterpretable.toBindable(EnumerableInterpretable.java:128) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.prepare.CalcitePrepareImpl$CalcitePreparingStmt.implement(CalcitePrepareImpl.java:1159) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.prepare.Prepare.prepareSql(Prepare.java:324) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.prepare.Prepare.prepareSql(Prepare.java:220) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.prepare.CalcitePrepareImpl.prepare2_(CalcitePrepareImpl.java:665) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.prepare.CalcitePrepareImpl.prepare_(CalcitePrepareImpl.java:519) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.prepare.CalcitePrepareImpl.prepareSql(CalcitePrepareImpl.java:487) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.jdbc.CalciteConnectionImpl.parseQuery(CalciteConnectionImpl.java:236) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.jdbc.CalciteMetaImpl.prepareAndExecute(CalciteMetaImpl.java:621) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.avatica.AvaticaConnection.prepareAndExecuteInternal(AvaticaConnection.java:677) ~[avatica-core-1.23.0.jar:1.23.0]
at org.apache.calcite.avatica.AvaticaStatement.executeInternal(AvaticaStatement.java:157) ~[avatica-core-1.23.0.jar:1.23.0]
... 13 more
Caused by: org.codehaus.commons.compiler.CompileException: Line 24, Column 57: Unknown variable or type "com.kanyun.func.string.StringFuncUtil"
at org.codehaus.janino.UnitCompiler.compileError(UnitCompiler.java:12969) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.getType2(UnitCompiler.java:7162) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.access$14300(UnitCompiler.java:236) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$23.visitPackage(UnitCompiler.java:6647) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$23.visitPackage(UnitCompiler.java:6644) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.Java$Package.accept(Java.java:4627) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.getType(UnitCompiler.java:6644) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.getType2(UnitCompiler.java:7157) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.access$18100(UnitCompiler.java:236) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$26.visitAmbiguousName(UnitCompiler.java:6748) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$26.visitAmbiguousName(UnitCompiler.java:6747) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.Java$AmbiguousName.accept(Java.java:4603) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.getType(UnitCompiler.java:6747) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.access$15100(UnitCompiler.java:236) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$25.visitLvalue(UnitCompiler.java:6708) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$25.visitLvalue(UnitCompiler.java:6705) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.Java$Lvalue.accept(Java.java:4528) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.getType(UnitCompiler.java:6705) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.access$14400(UnitCompiler.java:236) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$23.visitRvalue(UnitCompiler.java:6653) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$23.visitRvalue(UnitCompiler.java:6644) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.Java$Rvalue.accept(Java.java:4495) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.getType(UnitCompiler.java:6644) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.findIMethod(UnitCompiler.java:9355) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileGet2(UnitCompiler.java:5195) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.access$9300(UnitCompiler.java:236) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$16.visitMethodInvocation(UnitCompiler.java:4698) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$16.visitMethodInvocation(UnitCompiler.java:4674) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.Java$MethodInvocation.accept(Java.java:5470) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileGet(UnitCompiler.java:4674) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileGetValue(UnitCompiler.java:5817) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.access$3800(UnitCompiler.java:236) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$7.visitRvalue(UnitCompiler.java:2729) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$7.visitRvalue(UnitCompiler.java:2717) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.Java$Rvalue.accept(Java.java:4498) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile(UnitCompiler.java:2717) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileGetValue(UnitCompiler.java:5787) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileGet2(UnitCompiler.java:5760) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.access$9900(UnitCompiler.java:236) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$16.visitNewInitializedArray(UnitCompiler.java:4709) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$16.visitNewInitializedArray(UnitCompiler.java:4674) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.Java$NewInitializedArray.accept(Java.java:5770) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileGet(UnitCompiler.java:4674) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileGetValue(UnitCompiler.java:5817) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:2811) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.access$2800(UnitCompiler.java:236) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$6.visitReturnStatement(UnitCompiler.java:1573) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$6.visitReturnStatement(UnitCompiler.java:1558) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.Java$ReturnStatement.accept(Java.java:3888) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile(UnitCompiler.java:1558) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileStatements(UnitCompiler.java:1644) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:3621) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile(UnitCompiler.java:3292) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileDeclaredMethods(UnitCompiler.java:1430) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileDeclaredMethods(UnitCompiler.java:1403) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:829) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:1026) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:996) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.access$200(UnitCompiler.java:236) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$3.visitAnonymousClassDeclaration(UnitCompiler.java:420) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$3.visitAnonymousClassDeclaration(UnitCompiler.java:418) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.Java$AnonymousClassDeclaration.accept(Java.java:1377) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile(UnitCompiler.java:418) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileGet2(UnitCompiler.java:5675) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.access$9700(UnitCompiler.java:236) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$16.visitNewAnonymousClassInstance(UnitCompiler.java:4707) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$16.visitNewAnonymousClassInstance(UnitCompiler.java:4674) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.Java$NewAnonymousClassInstance.accept(Java.java:5635) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileGet(UnitCompiler.java:4674) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileGetValue(UnitCompiler.java:5817) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:2811) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.access$2800(UnitCompiler.java:236) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$6.visitReturnStatement(UnitCompiler.java:1573) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$6.visitReturnStatement(UnitCompiler.java:1558) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.Java$ReturnStatement.accept(Java.java:3888) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile(UnitCompiler.java:1558) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileStatements(UnitCompiler.java:1644) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:3621) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile(UnitCompiler.java:3292) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileDeclaredMethods(UnitCompiler.java:1430) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileDeclaredMethods(UnitCompiler.java:1403) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:829) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:1026) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:996) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.access$200(UnitCompiler.java:236) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$3.visitAnonymousClassDeclaration(UnitCompiler.java:420) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$3.visitAnonymousClassDeclaration(UnitCompiler.java:418) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.Java$AnonymousClassDeclaration.accept(Java.java:1377) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile(UnitCompiler.java:418) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileGet2(UnitCompiler.java:5675) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.access$9700(UnitCompiler.java:236) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$16.visitNewAnonymousClassInstance(UnitCompiler.java:4707) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$16.visitNewAnonymousClassInstance(UnitCompiler.java:4674) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.Java$NewAnonymousClassInstance.accept(Java.java:5635) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileGet(UnitCompiler.java:4674) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileGetValue(UnitCompiler.java:5817) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:2811) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.access$2800(UnitCompiler.java:236) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$6.visitReturnStatement(UnitCompiler.java:1573) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$6.visitReturnStatement(UnitCompiler.java:1558) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.Java$ReturnStatement.accept(Java.java:3888) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile(UnitCompiler.java:1558) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileStatements(UnitCompiler.java:1644) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:3621) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile(UnitCompiler.java:3292) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileDeclaredMethods(UnitCompiler.java:1430) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileDeclaredMethods(UnitCompiler.java:1403) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:829) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:442) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.access$400(UnitCompiler.java:236) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$3.visitPackageMemberClassDeclaration(UnitCompiler.java:422) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$3.visitPackageMemberClassDeclaration(UnitCompiler.java:418) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.Java$PackageMemberClassDeclaration.accept(Java.java:1688) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile(UnitCompiler.java:418) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:392) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.access$000(UnitCompiler.java:236) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$2.visitCompilationUnit(UnitCompiler.java:363) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler$2.visitCompilationUnit(UnitCompiler.java:361) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.Java$CompilationUnit.accept(Java.java:371) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.UnitCompiler.compileUnit(UnitCompiler.java:361) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.SimpleCompiler.cook(SimpleCompiler.java:264) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.SimpleCompiler.compileToClassLoader(SimpleCompiler.java:517) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.SimpleCompiler.cook(SimpleCompiler.java:241) ~[janino-3.1.8.jar:?]
at org.codehaus.janino.SimpleCompiler.cook(SimpleCompiler.java:219) ~[janino-3.1.8.jar:?]
at org.codehaus.commons.compiler.Cookable.cook(Cookable.java:82) ~[commons-compiler-3.1.8.jar:?]
at org.codehaus.commons.compiler.Cookable.cook(Cookable.java:77) ~[commons-compiler-3.1.8.jar:?]
at org.apache.calcite.adapter.enumerable.EnumerableInterpretable.compileToBindable(EnumerableInterpretable.java:174) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.adapter.enumerable.EnumerableInterpretable.getBindable(EnumerableInterpretable.java:168) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.adapter.enumerable.EnumerableInterpretable.toBindable(EnumerableInterpretable.java:125) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.prepare.CalcitePrepareImpl$CalcitePreparingStmt.implement(CalcitePrepareImpl.java:1159) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.prepare.Prepare.prepareSql(Prepare.java:324) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.prepare.Prepare.prepareSql(Prepare.java:220) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.prepare.CalcitePrepareImpl.prepare2_(CalcitePrepareImpl.java:665) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.prepare.CalcitePrepareImpl.prepare_(CalcitePrepareImpl.java:519) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.prepare.CalcitePrepareImpl.prepareSql(CalcitePrepareImpl.java:487) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.jdbc.CalciteConnectionImpl.parseQuery(CalciteConnectionImpl.java:236) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.jdbc.CalciteMetaImpl.prepareAndExecute(CalciteMetaImpl.java:621) ~[calcite-core-1.34.0.jar:1.34.0]
at org.apache.calcite.avatica.AvaticaConnection.prepareAndExecuteInternal(AvaticaConnection.java:677) ~[avatica-core-1.23.0.jar:1.23.0]
at org.apache.calcite.avatica.AvaticaStatement.executeInternal(AvaticaStatement.java:157) ~[avatica-core-1.23.0.jar:1.23.0]
... 13 more
2023-08-09 10:00:14.114 ERROR [JavaFX Application Thread] c.k.u.e.ExecuteSqlService : 异步SQL执行任务异常:
这个错误我们分两部分看:
第一部分:
java.sql.SQLException: Error while executing SQL "select id,reverse(title) from DB1.girl_pic": Error while compiling generated Java code:
...................省略报错
java.sql.SQLException: Error while executing SQL "select id,reverse(title) from DB1.girl_pic": Error while compiling generated Java code:
public org.apache.calcite.linq4j.Enumerable bind(final org.apache.calcite.DataContext root) {
意思是janino在将sql编译成java代码报错(calcite利用janino生成代码这块不再细说了)
第二部分:
........省略报错信息
Caused by: org.codehaus.commons.compiler.CompileException: Line 24, Column 57: Unknown variable or type "com.kanyun.func.string.StringFuncUtil"
at org.codehaus.janino.UnitCompiler.compileError(UnitCompiler.java:12969) ~[janino-3.1.8.jar:?]
........省略报错信息
意思是报错的原因是未找到变量:com.kanyun.func.string.StringFuncUtil而这个变量正是我们自定义函数所在的类!
经过调试发现报错的位置是:类org.codehaus.janino.UnitCompiler#getType()
private IType
getType(Atom a) throws CompileException {
IType result = (IType) a.accept(new AtomVisitor<IType, CompileException>() {
// 此处报错
@Override public IType
visitPackage(Package p) throws CompileException { return UnitCompiler.this.getType2(p); }
@Override @Nullable public IType
visitType(Type t) throws CompileException { return UnitCompiler.this.getType(t); }
@Override @Nullable public IType
visitRvalue(Rvalue rv) throws CompileException { return UnitCompiler.this.getType(rv); }
@Override @Nullable public IType
visitConstructorInvocation(ConstructorInvocation ci) throws CompileException {
return UnitCompiler.this.getType2(ci);
}
});
assert result != null;
return result;
}
其中Atom的参数即为我们自定义函数所在的类
其中Atom是一个典型的访问者模式
而报错的这个地方是janino框架,janino简介
因此猜想这个报错原因是加载不到我们自定义函数所在的类:com.kanyun.func.string.StringFuncUtil
虽然我们通过自定义类加载器加载了自定义的外部函数包,但是janino执行代码的时候使用的不一定是我们自定义的类加载器,所以有可能加载不到,经过调试发现,当前执行代码块的类加载器是AppClassLoader,也即Atom(我们的自定义类加载器是AppClassLoader)
但实际上这个类并不是AppClassLoader来加载的,而是我们使用上面的自定义类加载器加载的。
此时忽然想到自己的业务代码调用Calcite执行SQL是异步的,或许janino并不能使用自定义的类加载器。
不过既然找到了报错的地方是janino框架,那么Calcite又是如何调用Janino的呢?
经过一段时间查阅资料,发现在calcite 模块的类org.apache.calcite.rel.metadata.JaninoRelMetadataProvider#compile()方法调用了Janino
而调用了org.apache.calcite.rel.metadata.JaninoRelMetadataProvider#compile()的方法是,
org.apache.calcite.rel.metadata.JaninoRelMetadataProvider#generateCompileAndInstantiate()方法
private static <MH extends MetadataHandler<?>> MH generateCompileAndInstantiate(
Class<MH> handlerClass,
List<? extends MetadataHandler<? extends Metadata>> handlers) {
final List<? extends MetadataHandler<? extends Metadata>> uniqueHandlers = handlers.stream()
.distinct()
.collect(Collectors.toList());
// 生成Handler,字符串拼接生成代码
RelMetadataHandlerGeneratorUtil.HandlerNameAndGeneratedCode handlerNameAndGeneratedCode =
RelMetadataHandlerGeneratorUtil.generateHandler(handlerClass, uniqueHandlers);
try {
// 最终编译上面的handler
return compile(handlerNameAndGeneratedCode.getHandlerName(),
handlerNameAndGeneratedCode.getGeneratedCode(), handlerClass, uniqueHandlers);
} catch (CompileException | IOException e) {
throw new RuntimeException("Error compiling:\n"
+ handlerNameAndGeneratedCode.getGeneratedCode(), e);
}
}
从方法名可以看出是生成和编译实例类(该方法在旧版本的calcite中是load3()方法)
RelMetadataQuery这个类看名字就可以判断出它是用于获取元数据的。但是获取具体元数据的方法实现并不是直接依靠RelMetadataQuery。RelMetadataQuery对象有很多handler对象,获取某一方面类型的元数据都需要依靠对应的handler。
在calcite的源码中是找不到这些handler的实现的,只能找到这些handler的接口。 calcite内部很多地方使用了动态代码生成和编译。这些handler了也不例外也是在运行时生成具体实现并且编译后实例化对象的。 而这些handler的代码生成都在JaninoRelMetadataProvider#
load3
方法中实现
这里我使用的Calcite版本是:1.34.0
跑题了,类加载的问题还没解决,我们重点还是看一下org.apache.calcite.rel.metadata.JaninoRelMetadataProvider#compile()方法
static <MH extends MetadataHandler<?>> MH compile(String className,
String generatedCode, Class<MH> handlerClass,
List<? extends Object> argList) throws CompileException, IOException {
// 定义编译器工厂类(janino模块)
final ICompilerFactory compilerFactory;
ClassLoader classLoader =
Objects.requireNonNull(JaninoRelMetadataProvider.class.getClassLoader(), "classLoader");
try {
// 获取编译器工厂类(janino模块)
compilerFactory = CompilerFactoryFactory.getDefaultCompilerFactory(classLoader);
} catch (Exception e) {
throw new IllegalStateException(
"Unable to instantiate java compiler", e);
}
// 创建一个新的编译器(janino模块) ISimpleCompiler 是一个接口
final ISimpleCompiler compiler = compilerFactory.newSimpleCompiler();
// 设置创建出来的编译器的父类加载器(janino模块,重要)
compiler.setParentClassLoader(JaninoRexCompiler.class.getClassLoader());
if (CalciteSystemProperty.DEBUG.value()) {
// Add line numbers to the generated janino class
compiler.setDebuggingInformation(true, true, true);
System.out.println(generatedCode);
}
compiler.cook(generatedCode);
final Constructor constructor;
final Object o;
try {
constructor = compiler.getClassLoader().loadClass(className)
.getDeclaredConstructors()[0];
o = constructor.newInstance(argList.toArray());
} catch (InstantiationException
| IllegalAccessException
| InvocationTargetException
| ClassNotFoundException e) {
throw new RuntimeException(e);
}
return handlerClass.cast(o);
}
上面的代码描述了Calcite调用Janino编译代码的过程,其中有一个非常重要步骤:就是设置编译器的父类加载器的。通过debug我们发现编译器的具体实现是 org.codehaus.janino.SimpleCompiler类。同时我们查看下设置父类加载器的代码
@Override public void
setParentClassLoader(@Nullable ClassLoader parentClassLoader) {
this.parentClassLoader = (
parentClassLoader != null
? parentClassLoader
: Thread.currentThread().getContextClassLoader()
);
}
通过这个方法的代码,我们发现了一个成员变量,即:org.codehaus.janino.SimpleCompiler#parentClassLoader 同时发现它有一个默认的实现,就是使用的是线程上下文类加载器
这也就意味着如果我们调用执行SQL之前先设置线程上下文类加载器,那么这个成员变量的值就应还是我们设置的线程上下文类加载器,还记得之前我们自定义的类加载器吗?由于自定义类加载器使用了单例模式,因此保证加载外部自定义的类加载器与我们将要设置的线程上下文类加载器是同一个实例,同时为了验证上下文类加载器是否设置成功,还应该在执行SQL之前验证一下类的加载是否存在问题。
......省略代码
// 由于sql执行是异步操作,因此需要在此处重新设置线程上下文类加载器
Thread.currentThread().setContextClassLoader(ExternalFuncClassLoader.getInstance());
// 验证上下文类加载器是否设置成功(不报错即为设置成功)
Class<?> clazz = Class.forName("com.kanyun.func.string.StringFuncUtil", false, Thread.currentThread().getContextClassLoader());
......省略代码
// 执行SQL
ResultSet resultSet = statement.executeQuery(sql);
此时继续执行SQL,由于我们在执行SQL前添加了验证代码,执行时是通过的,同时我们还在
org.codehaus.janino.SimpleCompiler#parentClassLoader属性上添加了断点,可以观察到
它的属性值已经是我们自定义的类加载器了
但是在执行SQL过程中发现还是报错了。并且错误与之前一致。依然还是找不到的类的问题
再次debug发现报错位置的的类加载器依然是AppclassLoader。可是在执行SQL之前明明已经设置了线程上下文类加载器了啊。
回到刚才设置父类加载器的位置通过观察发现:
org.codehaus.janino.SimpleCompiler#setParentClassLoader()方法可能会更改默认parentClassLoader
打上断点调试:
通过断点发现,org.codehaus.janino.SimpleCompiler#parentClassLoader属性的值被setParentClassLoader()方法给改变了。
查下setParentClassLoader()方法被谁调用了,由于上面已经找过,因此很容易找到调用位置。
org.apache.calcite.rel.metadata.JaninoRelMetadataProvider#compile()调用
通过在此处打断点,发现代码确实执行到了此处。而且发现org.apache.calcite.rel.metadata.JaninoRelMetadataProvider类例属于Calcite模块。
其调用setParentClassLoader()方法的参数则是org.apache.calcite.interpreter.JaninoRexCompiler的类加载器。
这也同时说明了org.apache.calcite.interpreter.JaninoRexCompile的类加载器是AppClassLoader
而org.apache.calcite.interpreter.JaninoRexCompile也属于Calcite模块。
由上面的代码发现,janino的
org.codehaus.janino.SimpleCompiler#parentClassLoader是获取到了我们的自定义类加载器,但是由于外部调用了org.codehaus.janino.SimpleCompiler#setParentClassLoader()改变了org.codehaus.janino.SimpleCompiler#parentClassLoader属性。 因此思路就比较清晰了 1:类org.apache.calcite.interpreter.JaninoRexCompiler的加载时机 2:是否可以更改org.apache.calcite.interpreter.JaninoRexCompiler的类加载器
通过排查并没有找到org.apache.calcite.interpreter.JaninoRexCompiler是什么时候被加载的
此时问题就比较尴尬了,我们使用的自定义类加载器虽然可以加载解析注册自定义的函数,但是当janino去编译生成代码的时候又使用了AppClassLoader,导致无法找到类,因此如何使AppClassLoader能够加载到外部的函数就成了首要问题。
首先我们看一下AppClassloader的源码,发现AppClassloader是一个静态内部类
在sun.misc.Launcher类下
由于AppClassloader继承自URLClassLoader,因此我们可以将AppClassLoader的实例强转为URLClassLoader类型,然后反射调用addURL方法。
因此在使用自定义类加载器加载解析注册自定义的外部函数的同时,也需要使用反射的形式使AppClassLoader能够加载到外部的自定义函数。最后试一下效果!成功执行了SQL