|
| 1 | +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
| 2 | +From: Mustang <mustang@opensearch.local> |
| 3 | +Date: Wed, 6 May 2026 00:00:00 -0700 |
| 4 | +Subject: [PATCH] CALCITE-3745: TCCL-chained classloader for Janino parent CL |
| 5 | + |
| 6 | +Introduce a TcclChainedClassLoader utility that resolves classes via the |
| 7 | +thread context classloader first, falling back to the Calcite-local CL |
| 8 | +if a name is not found on TCCL. Every site that configures Janino's |
| 9 | +parent classloader (EnumerableInterpretable, JaninoRexCompiler, |
| 10 | +RexExecutable, JaninoRelMetadataProvider) now uses the chained loader. |
| 11 | + |
| 12 | +This keeps Calcite's internal types always resolvable while making |
| 13 | +child-plugin UDFs visible when the host (OpenSearch's extendedPlugins) |
| 14 | +sets TCCL to the child classloader. |
| 15 | +--- |
| 16 | + .../enumerable/EnumerableInterpretable.java | 3 +- |
| 17 | + .../interpreter/JaninoRexCompiler.java | 3 +- |
| 18 | + .../metadata/JaninoRelMetadataProvider.java | 4 +- |
| 19 | + .../org/apache/calcite/rex/RexExecutable.java | 4 +- |
| 20 | + .../calcite/util/TcclChainedClassLoader.java | 61 +++++++++++++++++++ |
| 21 | + 5 files changed, 71 insertions(+), 4 deletions(-) |
| 22 | + create mode 100644 core/src/main/java/org/apache/calcite/util/TcclChainedClassLoader.java |
| 23 | + |
| 24 | +diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableInterpretable.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableInterpretable.java |
| 25 | +index 5f32ab1..1c9ce19 100644 |
| 26 | +--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableInterpretable.java |
| 27 | ++++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableInterpretable.java |
| 28 | +@@ -145,7 +145,8 @@ static Bindable getBindable(ClassDeclaration expr, String classBody, int fieldCo |
| 29 | + "Unable to instantiate java compiler", e); |
| 30 | + } |
| 31 | + final ISimpleCompiler compiler = compilerFactory.newSimpleCompiler(); |
| 32 | +- compiler.setParentClassLoader(classLoader); |
| 33 | ++ compiler.setParentClassLoader( |
| 34 | ++ org.apache.calcite.util.TcclChainedClassLoader.chain(classLoader)); |
| 35 | + final String s = "public final class " + expr.name + " implements " |
| 36 | + + (fieldCount == 1 |
| 37 | + ? Bindable.class.getCanonicalName() + ", " + Typed.class.getCanonicalName() |
| 38 | +diff --git a/core/src/main/java/org/apache/calcite/interpreter/JaninoRexCompiler.java b/core/src/main/java/org/apache/calcite/interpreter/JaninoRexCompiler.java |
| 39 | +index bca4f85..d6de426 100644 |
| 40 | +--- a/core/src/main/java/org/apache/calcite/interpreter/JaninoRexCompiler.java |
| 41 | ++++ b/core/src/main/java/org/apache/calcite/interpreter/JaninoRexCompiler.java |
| 42 | +@@ -211,7 +211,8 @@ static Scalar.Producer getScalar(ClassDeclaration expr, String s) |
| 43 | + IClassBodyEvaluator cbe = compilerFactory.newClassBodyEvaluator(); |
| 44 | + cbe.setClassName(expr.name); |
| 45 | + cbe.setImplementedInterfaces(new Class[] {Scalar.Producer.class}); |
| 46 | +- cbe.setParentClassLoader(classLoader); |
| 47 | ++ cbe.setParentClassLoader( |
| 48 | ++ org.apache.calcite.util.TcclChainedClassLoader.chain(classLoader)); |
| 49 | + if (CalciteSystemProperty.DEBUG.value()) { |
| 50 | + // Add line numbers to the generated janino class |
| 51 | + cbe.setDebuggingInformation(true, true, true); |
| 52 | +diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java b/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java |
| 53 | +index 135b11e..34a5e4b 100644 |
| 54 | +--- a/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java |
| 55 | ++++ b/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java |
| 56 | +@@ -157,7 +157,9 @@ static <MH extends MetadataHandler<?>> MH compile(String className, |
| 57 | + } |
| 58 | + |
| 59 | + final ISimpleCompiler compiler = compilerFactory.newSimpleCompiler(); |
| 60 | +- compiler.setParentClassLoader(JaninoRexCompiler.class.getClassLoader()); |
| 61 | ++ compiler.setParentClassLoader( |
| 62 | ++ org.apache.calcite.util.TcclChainedClassLoader.chain( |
| 63 | ++ JaninoRexCompiler.class.getClassLoader())); |
| 64 | + |
| 65 | + if (CalciteSystemProperty.DEBUG.value()) { |
| 66 | + // Add line numbers to the generated janino class |
| 67 | +diff --git a/core/src/main/java/org/apache/calcite/rex/RexExecutable.java b/core/src/main/java/org/apache/calcite/rex/RexExecutable.java |
| 68 | +index 8828654..1e91951 100644 |
| 69 | +--- a/core/src/main/java/org/apache/calcite/rex/RexExecutable.java |
| 70 | ++++ b/core/src/main/java/org/apache/calcite/rex/RexExecutable.java |
| 71 | +@@ -60,7 +60,9 @@ public RexExecutable(String code, Object reason) { |
| 72 | + cbe.setClassName(GENERATED_CLASS_NAME); |
| 73 | + cbe.setExtendedClass(Utilities.class); |
| 74 | + cbe.setImplementedInterfaces(new Class[] {Function1.class, Serializable.class}); |
| 75 | +- cbe.setParentClassLoader(RexExecutable.class.getClassLoader()); |
| 76 | ++ cbe.setParentClassLoader( |
| 77 | ++ org.apache.calcite.util.TcclChainedClassLoader.chain( |
| 78 | ++ RexExecutable.class.getClassLoader())); |
| 79 | + cbe.cook(new Scanner(null, new StringReader(code))); |
| 80 | + Class c = cbe.getClazz(); |
| 81 | + //noinspection unchecked |
| 82 | +diff --git a/core/src/main/java/org/apache/calcite/util/TcclChainedClassLoader.java b/core/src/main/java/org/apache/calcite/util/TcclChainedClassLoader.java |
| 83 | +new file mode 100644 |
| 84 | +index 0000000..259d71c |
| 85 | +--- /dev/null |
| 86 | ++++ b/core/src/main/java/org/apache/calcite/util/TcclChainedClassLoader.java |
| 87 | +@@ -0,0 +1,61 @@ |
| 88 | ++/* |
| 89 | ++ * Licensed to the Apache Software Foundation (ASF) under one or more |
| 90 | ++ * contributor license agreements. See the NOTICE file distributed with |
| 91 | ++ * this work for additional information regarding copyright ownership. |
| 92 | ++ * The ASF licenses this file to you under the Apache License, Version 2.0 |
| 93 | ++ * (the "License"); you may not use this file except in compliance with |
| 94 | ++ * the License. You may obtain a copy of the License at |
| 95 | ++ * |
| 96 | ++ * http://www.apache.org/licenses/LICENSE-2.0 |
| 97 | ++ * |
| 98 | ++ * Unless required by applicable law or agreed to in writing, software |
| 99 | ++ * distributed under the License is distributed on an "AS IS" BASIS, |
| 100 | ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 101 | ++ * See the License for the specific language governing permissions and |
| 102 | ++ * limitations under the License. |
| 103 | ++ */ |
| 104 | ++package org.apache.calcite.util; |
| 105 | ++ |
| 106 | ++/** |
| 107 | ++ * CALCITE-3745 (OpenSearch patch): helper to build a classloader that |
| 108 | ++ * prefers the thread context classloader for name resolution but falls back |
| 109 | ++ * to a supplied Calcite-local classloader for Calcite's own internal types. |
| 110 | ++ * |
| 111 | ++ * <p>When Calcite is embedded under a parent plugin classloader (e.g. in |
| 112 | ++ * OpenSearch's {@code extendedPlugins} layout), child plugins register UDFs |
| 113 | ++ * that end up referenced by name in Janino-generated code. The default |
| 114 | ++ * {@code SomeCalciteClass.class.getClassLoader()} cannot see those UDFs. |
| 115 | ++ * Using TCCL alone breaks in contexts where TCCL is a stripped-down |
| 116 | ++ * classloader that has no view of Calcite's own internal types. Chaining |
| 117 | ++ * solves both cases. |
| 118 | ++ */ |
| 119 | ++public final class TcclChainedClassLoader { |
| 120 | ++ private TcclChainedClassLoader() {} |
| 121 | ++ |
| 122 | ++ /** |
| 123 | ++ * Returns a classloader that resolves classes by consulting the thread |
| 124 | ++ * context classloader first, then falling back to {@code fallback}. If |
| 125 | ++ * TCCL is unset or identical to {@code fallback}, the fallback is |
| 126 | ++ * returned unchanged. |
| 127 | ++ */ |
| 128 | ++ public static ClassLoader chain(ClassLoader fallback) { |
| 129 | ++ final ClassLoader tccl = Thread.currentThread().getContextClassLoader(); |
| 130 | ++ if (tccl == null || tccl == fallback) { |
| 131 | ++ return fallback; |
| 132 | ++ } |
| 133 | ++ return new ClassLoader(fallback) { |
| 134 | ++ @Override protected Class<?> loadClass(String name, boolean resolve) |
| 135 | ++ throws ClassNotFoundException { |
| 136 | ++ try { |
| 137 | ++ Class<?> c = tccl.loadClass(name); |
| 138 | ++ if (resolve) { |
| 139 | ++ resolveClass(c); |
| 140 | ++ } |
| 141 | ++ return c; |
| 142 | ++ } catch (ClassNotFoundException e) { |
| 143 | ++ return super.loadClass(name, resolve); |
| 144 | ++ } |
| 145 | ++ } |
| 146 | ++ }; |
| 147 | ++ } |
| 148 | ++} |
| 149 | +-- |
| 150 | +2.50.1 (Apple Git-155) |
| 151 | + |
0 commit comments