diff --git a/conf/ivysettings.xml b/conf/ivysettings.xml
index bda842a89bb07710fdcd7180a00833a7388ada8f..ccf12b7b18c7d78017a0b52c34e1c9923a3ec47b 100644
--- a/conf/ivysettings.xml
+++ b/conf/ivysettings.xml
@@ -1,4 +1,3 @@
-
+
+
+
+
-
+
+
diff --git a/itests/custom-udfs/pom.xml b/itests/custom-udfs/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d97dfea34b0ae368cef59562e26cadde8224edf7
--- /dev/null
+++ b/itests/custom-udfs/pom.xml
@@ -0,0 +1,62 @@
+
+
+
+ 4.0.0
+
+ org.apache.hive
+ hive-it
+ 2.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ hive-it-custom-udfs
+ pom
+ Hive Integration - Custom udfs
+ Custom udfs used in hive itest can be defined under this module
+
+
+ ../..
+
+
+
+
+ udf-classloader-util
+ udf-classloader-udf1
+ udf-classloader-udf2
+
+
+
+
+
+ org.apache.hive
+ hive-exec
+ ${project.version}
+ true
+
+
+ org.apache.hadoop
+ hadoop-common
+ ${hadoop.version}
+ true
+
+
+
diff --git a/itests/custom-udfs/udf-classloader-udf1/pom.xml b/itests/custom-udfs/udf-classloader-udf1/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a4e75ad858896ea1b93d7e4db3ebd75791b6789b
--- /dev/null
+++ b/itests/custom-udfs/udf-classloader-udf1/pom.xml
@@ -0,0 +1,43 @@
+
+
+
+ 4.0.0
+
+ org.apache.hive
+ hive-it-custom-udfs
+ 2.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ org.apache.hive.hive-it-custom-udfs
+ udf-classloader-udf1
+ jar
+ Hive Integration - Custom UDFs - udf-classloader-udf1
+
+
+
+ org.apache.hive.hive-it-custom-udfs
+ udf-classloader-util
+ ${project.version}
+
+
+
+
+ ../../..
+
+
+
diff --git a/itests/custom-udfs/udf-classloader-udf1/src/main/java/hive/it/custom/udfs/UDF1.java b/itests/custom-udfs/udf-classloader-udf1/src/main/java/hive/it/custom/udfs/UDF1.java
new file mode 100644
index 0000000000000000000000000000000000000000..681619049ee99b7efd21f2ef13954f5ebf5e350c
--- /dev/null
+++ b/itests/custom-udfs/udf-classloader-udf1/src/main/java/hive/it/custom/udfs/UDF1.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package hive.it.custom.udfs;
+
+import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
+import org.apache.hadoop.hive.ql.metadata.HiveException;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
+
+
+public class UDF1 extends GenericUDF {
+ public UDF1() {
+ Util util = new Util();
+ System.out.println(
+ "constructor: " + getClass().getSimpleName() + " classloader: " + getClass().getClassLoader() + ", " + util + " classloader: "
+ + util.getClass().getClassLoader());
+ }
+
+ @Override
+ public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
+ Util util = new Util();
+ System.out.println(
+ "initialize: " + getClass().getSimpleName() + " classloader: " + getClass().getClassLoader() + ", " + util + " classloader: "
+ + util.getClass().getClassLoader());
+ return PrimitiveObjectInspectorFactory.javaStringObjectInspector;
+ }
+
+ @Override
+ public Object evaluate(DeferredObject[] arguments) throws HiveException {
+ Util util = new Util();
+ System.out.println(
+ "evaluate: " + getClass().getSimpleName() + " classloader: " + getClass().getClassLoader() + ", " + util + " classloader: "
+ + util.getClass().getClassLoader());
+ return getClass().getSimpleName();
+ }
+
+ @Override
+ public String getDisplayString(String[] children) {
+ return getClass().getName();
+ }
+}
diff --git a/itests/custom-udfs/udf-classloader-udf2/pom.xml b/itests/custom-udfs/udf-classloader-udf2/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e9f01b4f6ac049da6052e0970ffdaca2c12ad596
--- /dev/null
+++ b/itests/custom-udfs/udf-classloader-udf2/pom.xml
@@ -0,0 +1,43 @@
+
+
+
+ 4.0.0
+
+ org.apache.hive
+ hive-it-custom-udfs
+ 2.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ org.apache.hive.hive-it-custom-udfs
+ udf-classloader-udf2
+ jar
+ Hive Integration - Custom UDFs - udf-classloader-udf2
+
+
+
+ org.apache.hive.hive-it-custom-udfs
+ udf-classloader-util
+ ${project.version}
+
+
+
+
+ ../../..
+
+
+
diff --git a/itests/custom-udfs/udf-classloader-udf2/src/main/java/hive/it/custom/udfs/UDF2.java b/itests/custom-udfs/udf-classloader-udf2/src/main/java/hive/it/custom/udfs/UDF2.java
new file mode 100644
index 0000000000000000000000000000000000000000..4bb8772d0e09a190ffc78d310ef7d43602055c31
--- /dev/null
+++ b/itests/custom-udfs/udf-classloader-udf2/src/main/java/hive/it/custom/udfs/UDF2.java
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package hive.it.custom.udfs;
+
+import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
+import org.apache.hadoop.hive.ql.metadata.HiveException;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
+
+
+public class UDF2 extends GenericUDF {
+ public UDF2() {
+ Util util = new Util();
+ System.out.println(
+ "constructor: " + getClass().getSimpleName() + " classloader: " + getClass().getClassLoader() + ", " + util
+ + " classloader: "
+ + util.getClass().getClassLoader());
+ }
+
+ @Override
+ public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
+ Util util = new Util();
+ System.out.println(
+ "initialize: " + getClass().getSimpleName() + " classloader: " + getClass().getClassLoader() + ", " + util
+ + " classloader: "
+ + util.getClass().getClassLoader());
+ return PrimitiveObjectInspectorFactory.javaStringObjectInspector;
+ }
+
+ @Override
+ public Object evaluate(DeferredObject[] arguments) throws HiveException {
+ Util util = new Util();
+ System.out.println(
+ "evaluate: " + getClass().getSimpleName() + " classloader: " + getClass().getClassLoader() + ", " + util
+ + " classloader: "
+ + util.getClass().getClassLoader());
+ return getClass().getSimpleName();
+ }
+
+ @Override
+ public String getDisplayString(String[] children) {
+ return getClass().getName();
+ }
+}
diff --git a/itests/custom-udfs/udf-classloader-util/pom.xml b/itests/custom-udfs/udf-classloader-util/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..68d4c5918a5b7b8abc87c6c7a9c44cc2d8545edc
--- /dev/null
+++ b/itests/custom-udfs/udf-classloader-util/pom.xml
@@ -0,0 +1,35 @@
+
+
+
+ 4.0.0
+
+ org.apache.hive
+ hive-it-custom-udfs
+ 2.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ org.apache.hive.hive-it-custom-udfs
+ udf-classloader-util
+ jar
+ Hive Integration - Custom UDFs - udf-classloader-util
+
+
+ ../../..
+
+
+
diff --git a/itests/custom-udfs/udf-classloader-util/src/main/java/hive/it/custom/udfs/Util.java b/itests/custom-udfs/udf-classloader-util/src/main/java/hive/it/custom/udfs/Util.java
new file mode 100644
index 0000000000000000000000000000000000000000..8881ec70ec4fb78c9335bff4403d8fa760c3dddb
--- /dev/null
+++ b/itests/custom-udfs/udf-classloader-util/src/main/java/hive/it/custom/udfs/Util.java
@@ -0,0 +1,25 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package hive.it.custom.udfs;
+
+public class Util {
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+}
diff --git a/itests/pom.xml b/itests/pom.xml
index 0686f1fd58c2be26b2ee645c4e244159aec565e5..bcf89aaec2533438da2d4093bc66d01d02a3ec22 100644
--- a/itests/pom.xml
+++ b/itests/pom.xml
@@ -33,6 +33,7 @@
custom-serde
+ custom-udfs
hcatalog-unit
hive-unit
util
diff --git a/itests/qtest/pom.xml b/itests/qtest/pom.xml
index 8db6fb04d0a5d4600bc23543a0215d31c1cd0648..c95ae66c4bc55b984917185e8b63a7a1204e7d2a 100644
--- a/itests/qtest/pom.xml
+++ b/itests/qtest/pom.xml
@@ -656,7 +656,6 @@
initScript="${initScript}"
cleanupScript="q_test_cleanup.sql"/>
-
diff --git a/ql/src/java/org/apache/hadoop/hive/ql/exec/UDFClassLoader.java b/ql/src/java/org/apache/hadoop/hive/ql/exec/UDFClassLoader.java
new file mode 100644
index 0000000000000000000000000000000000000000..c3638035e3da41db2bd0838636e201e461d116d8
--- /dev/null
+++ b/ql/src/java/org/apache/hadoop/hive/ql/exec/UDFClassLoader.java
@@ -0,0 +1,70 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.hive.ql.exec;
+
+import com.google.common.base.Preconditions;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import org.apache.hadoop.hive.ql.session.SessionState;
+
+
+/**
+ * {@link UDFClassLoader} is used to dynamically register
+ * udf (and related) jars
+ *
+ * This was introducted to fix HIVE-11878
+ *
+ * Each session will have its own instance of {@link UDFClassLoader}
+ * This is to support HiveServer2 where there can be multiple
+ * active sessions. Addition/removal of jars/resources in one
+ * session should not affect other sessions.
+ */
+public class UDFClassLoader extends URLClassLoader {
+ private boolean isClosed;
+
+ public UDFClassLoader(URL[] urls) {
+ super(urls);
+ isClosed = false;
+ }
+
+ public UDFClassLoader(URL[] urls, ClassLoader parent) {
+ super(urls, parent);
+ isClosed = false;
+ }
+
+ @Override
+ public void addURL(URL url) {
+ Preconditions.checkState(!isClosed, getClass().getSimpleName() + " is already closed");
+ super.addURL(url);
+ }
+
+ /**
+ * See {@link URLClassLoader#close}
+ */
+ public boolean isClosed() {
+ return isClosed;
+ }
+
+ @Override
+ public void close() throws IOException {
+ isClosed = true;
+ super.close();
+ }
+}
diff --git a/ql/src/java/org/apache/hadoop/hive/ql/exec/Utilities.java b/ql/src/java/org/apache/hadoop/hive/ql/exec/Utilities.java
index de2eb984159526048e8dacf71d3ff8b0647394a3..76b3c597f540d37194d1356fc40f35dde01e5fc9 100644
--- a/ql/src/java/org/apache/hadoop/hive/ql/exec/Utilities.java
+++ b/ql/src/java/org/apache/hadoop/hive/ql/exec/Utilities.java
@@ -18,6 +18,9 @@
package org.apache.hadoop.hive.ql.exec;
+import java.util.ArrayList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
import java.beans.DefaultPersistenceDelegate;
import java.beans.Encoder;
import java.beans.ExceptionListener;
@@ -195,7 +198,6 @@
import org.apache.hadoop.util.Progressable;
import org.apache.hadoop.util.Shell;
import org.apache.hive.common.util.ReflectionUtil;
-import org.slf4j.Logger;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
@@ -2279,6 +2281,19 @@ private static URL urlFromPathString(String onestr) {
return result;
}
+ private static boolean useExistingClassLoader(ClassLoader cl) {
+ if (!(cl instanceof UDFClassLoader)) {
+ // Cannot use the same classloader if it is not an instance of {@code UDFClassLoader}
+ return false;
+ }
+ final UDFClassLoader udfClassLoader = (UDFClassLoader) cl;
+ if (udfClassLoader.isClosed()) {
+ // The classloader may have been closed, Cannot add to the same instance
+ return false;
+ }
+ return true;
+ }
+
/**
* Add new elements to the classpath.
*
@@ -2286,24 +2301,28 @@ private static URL urlFromPathString(String onestr) {
* Array of classpath elements
*/
public static ClassLoader addToClassPath(ClassLoader cloader, String[] newPaths) throws Exception {
- URLClassLoader loader = (URLClassLoader) cloader;
- List curPath = Arrays.asList(loader.getURLs());
- ArrayList newPath = new ArrayList();
-
- // get a list with the current classpath components
- for (URL onePath : curPath) {
- newPath.add(onePath);
+ final URLClassLoader loader = (URLClassLoader) cloader;
+ if (useExistingClassLoader(cloader)) {
+ final UDFClassLoader udfClassLoader = (UDFClassLoader) loader;
+ for (String path : newPaths) {
+ udfClassLoader.addURL(urlFromPathString(path));
+ }
+ return udfClassLoader;
+ } else {
+ return createUDFClassLoader(loader, newPaths);
}
- curPath = newPath;
+ }
+ public static ClassLoader createUDFClassLoader(URLClassLoader loader, String[] newPaths) {
+ final Set curPathsSet = Sets.newHashSet(loader.getURLs());
+ final List curPaths = Lists.newArrayList(curPathsSet);
for (String onestr : newPaths) {
- URL oneurl = urlFromPathString(onestr);
- if (oneurl != null && !curPath.contains(oneurl)) {
- curPath.add(oneurl);
+ final URL oneurl = urlFromPathString(onestr);
+ if (oneurl != null && !curPathsSet.contains(oneurl)) {
+ curPaths.add(oneurl);
}
}
-
- return new URLClassLoader(curPath.toArray(new URL[0]), loader);
+ return new UDFClassLoader(curPaths.toArray(new URL[0]), loader);
}
/**
@@ -2324,13 +2343,13 @@ public static void removeFromClassPath(String[] pathsToRemove) throws Exception
}
}
JavaUtils.closeClassLoader(loader);
-//this loader is closed, remove it from cached registry loaders to avoid remove it again.
+ // This loader is closed, remove it from cached registry loaders to avoid removing it again.
Registry reg = SessionState.getRegistry();
if(reg != null) {
reg.removeFromUDFLoaders(loader);
}
- loader = new URLClassLoader(newPath.toArray(new URL[0]));
+ loader = new UDFClassLoader(newPath.toArray(new URL[0]));
curThread.setContextClassLoader(loader);
SessionState.get().getConf().setClassLoader(loader);
}
diff --git a/ql/src/java/org/apache/hadoop/hive/ql/session/SessionState.java b/ql/src/java/org/apache/hadoop/hive/ql/session/SessionState.java
index ff875df98e1dd64a8af3ad22f4b38dbc1d6a1923..f0ac5f6848f73ae1d1fddedec5c19297a0487b12 100644
--- a/ql/src/java/org/apache/hadoop/hive/ql/session/SessionState.java
+++ b/ql/src/java/org/apache/hadoop/hive/ql/session/SessionState.java
@@ -362,7 +362,12 @@ public SessionState(HiveConf conf, String userName) {
if (StringUtils.isEmpty(conf.getVar(HiveConf.ConfVars.HIVESESSIONID))) {
conf.setVar(HiveConf.ConfVars.HIVESESSIONID, makeSessionId());
}
- parentLoader = JavaUtils.getClassLoader();
+ // Using system classloader as the parent. Using thread context
+ // classloader as parent can pollute the session. See HIVE-11878
+ parentLoader = SessionState.class.getClassLoader();
+ // Make sure that each session has its own UDFClassloader. For details see {@link UDFClassLoader}
+ final ClassLoader currentLoader = Utilities.createUDFClassLoader((URLClassLoader) parentLoader, new String[]{});
+ this.conf.setClassLoader(currentLoader);
}
public void setCmd(String cmdString) {
@@ -1273,7 +1278,7 @@ private static String getURLType(String value) throws URISyntaxException {
return scheme;
}
- List resolveAndDownload(ResourceType t, String value, boolean convertToUnix) throws URISyntaxException,
+ protected List resolveAndDownload(ResourceType t, String value, boolean convertToUnix) throws URISyntaxException,
IOException {
URI uri = createURI(value);
if (getURLType(value).equals("file")) {
diff --git a/ql/src/test/queries/clientpositive/udf_classloader.q b/ql/src/test/queries/clientpositive/udf_classloader.q
new file mode 100644
index 0000000000000000000000000000000000000000..82678c21bf1cca29675618486fc06dd4620c7c95
--- /dev/null
+++ b/ql/src/test/queries/clientpositive/udf_classloader.q
@@ -0,0 +1,16 @@
+ADD JAR ${system:maven.local.repository}/org/apache/hive/hive-it-custom-udfs/udf-classloader-udf1/${system:hive.version}/udf-classloader-udf1-${system:hive.version}.jar;
+ADD JAR ${system:maven.local.repository}/org/apache/hive/hive-it-custom-udfs/udf-classloader-util/${system:hive.version}/udf-classloader-util-${system:hive.version}.jar;
+ADD JAR ${system:maven.local.repository}/org/apache/hive/hive-it-custom-udfs/udf-classloader-udf2/${system:hive.version}/udf-classloader-udf2-${system:hive.version}.jar;
+
+CREATE TEMPORARY FUNCTION f1 AS 'hive.it.custom.udfs.UDF1';
+CREATE TEMPORARY FUNCTION f2 AS 'hive.it.custom.udfs.UDF2';
+
+-- udf-classloader-udf1.jar contains f1 which relies on udf-classloader-util.jar,
+-- similiary udf-classloader-udf2.jar contains f2 which also relies on udf-classloader-util.jar.
+SELECT f1(*), f2(*) FROM SRC limit 1;
+
+DELETE JAR ${system:maven.local.repository}/org/apache/hive/hive-it-custom-udfs/udf-classloader-udf2/${system:hive.version}/udf-classloader-udf2-${system:hive.version}.jar;
+SELECT f1(*) FROM SRC limit 1;
+
+ADD JAR ${system:maven.local.repository}/org/apache/hive/hive-it-custom-udfs/udf-classloader-udf2/${system:hive.version}/udf-classloader-udf2-${system:hive.version}.jar;
+SELECT f2(*) FROM SRC limit 1;
diff --git a/ql/src/test/queries/clientpositive/udf_classloader_dynamic_dependency_resolution.q b/ql/src/test/queries/clientpositive/udf_classloader_dynamic_dependency_resolution.q
new file mode 100644
index 0000000000000000000000000000000000000000..2ceeaa32f3c0b9425d01af2d7325cbde15435caf
--- /dev/null
+++ b/ql/src/test/queries/clientpositive/udf_classloader_dynamic_dependency_resolution.q
@@ -0,0 +1,16 @@
+ADD JAR ivy://org.apache.hive.hive-it-custom-udfs:udf-classloader-udf1:+;
+ADD JAR ivy://org.apache.hive.hive-it-custom-udfs:udf-classloader-util:+;
+ADD JAR ivy://org.apache.hive.hive-it-custom-udfs:udf-classloader-udf2:+;
+
+CREATE TEMPORARY FUNCTION f1 AS 'hive.it.custom.udfs.UDF1';
+CREATE TEMPORARY FUNCTION f2 AS 'hive.it.custom.udfs.UDF2';
+
+-- udf-classloader-udf1.jar contains f1 which relies on udf-classloader-util.jar,
+-- similiary udf-classloader-udf2.jar contains f2 which also relies on udf-classloader-util.jar.
+SELECT f1(*), f2(*) FROM SRC limit 1;
+
+DELETE JAR ivy://org.apache.hive.hive-it-custom-udfs:udf-classloader-udf2:+;
+SELECT f1(*) FROM SRC limit 1;
+
+ADD JAR ivy://org.apache.hive.hive-it-custom-udfs:udf-classloader-udf2:+;
+SELECT f2(*) FROM SRC limit 1;
diff --git a/ql/src/test/results/clientpositive/udf_classloader.q.out b/ql/src/test/results/clientpositive/udf_classloader.q.out
new file mode 100644
index 0000000000000000000000000000000000000000..031d88afd3df28a38d23be89434ebcb697e4465e
--- /dev/null
+++ b/ql/src/test/results/clientpositive/udf_classloader.q.out
@@ -0,0 +1,43 @@
+PREHOOK: query: CREATE TEMPORARY FUNCTION f1 AS 'hive.it.custom.udfs.UDF1'
+PREHOOK: type: CREATEFUNCTION
+PREHOOK: Output: f1
+POSTHOOK: query: CREATE TEMPORARY FUNCTION f1 AS 'hive.it.custom.udfs.UDF1'
+POSTHOOK: type: CREATEFUNCTION
+POSTHOOK: Output: f1
+PREHOOK: query: CREATE TEMPORARY FUNCTION f2 AS 'hive.it.custom.udfs.UDF2'
+PREHOOK: type: CREATEFUNCTION
+PREHOOK: Output: f2
+POSTHOOK: query: CREATE TEMPORARY FUNCTION f2 AS 'hive.it.custom.udfs.UDF2'
+POSTHOOK: type: CREATEFUNCTION
+POSTHOOK: Output: f2
+PREHOOK: query: -- udf-classloader-udf1.jar contains f1 which relies on udf-classloader-util.jar,
+-- similiary udf-classloader-udf2.jar contains f2 which also relies on udf-classloader-util.jar.
+SELECT f1(*), f2(*) FROM SRC limit 1
+PREHOOK: type: QUERY
+PREHOOK: Input: default@src
+#### A masked pattern was here ####
+POSTHOOK: query: -- udf-classloader-udf1.jar contains f1 which relies on udf-classloader-util.jar,
+-- similiary udf-classloader-udf2.jar contains f2 which also relies on udf-classloader-util.jar.
+SELECT f1(*), f2(*) FROM SRC limit 1
+POSTHOOK: type: QUERY
+POSTHOOK: Input: default@src
+#### A masked pattern was here ####
+UDF1 UDF2
+PREHOOK: query: SELECT f1(*) FROM SRC limit 1
+PREHOOK: type: QUERY
+PREHOOK: Input: default@src
+#### A masked pattern was here ####
+POSTHOOK: query: SELECT f1(*) FROM SRC limit 1
+POSTHOOK: type: QUERY
+POSTHOOK: Input: default@src
+#### A masked pattern was here ####
+UDF1
+PREHOOK: query: SELECT f2(*) FROM SRC limit 1
+PREHOOK: type: QUERY
+PREHOOK: Input: default@src
+#### A masked pattern was here ####
+POSTHOOK: query: SELECT f2(*) FROM SRC limit 1
+POSTHOOK: type: QUERY
+POSTHOOK: Input: default@src
+#### A masked pattern was here ####
+UDF2
diff --git a/ql/src/test/results/clientpositive/udf_classloader_dynamic_dependency_resolution.q.out b/ql/src/test/results/clientpositive/udf_classloader_dynamic_dependency_resolution.q.out
new file mode 100644
index 0000000000000000000000000000000000000000..031d88afd3df28a38d23be89434ebcb697e4465e
--- /dev/null
+++ b/ql/src/test/results/clientpositive/udf_classloader_dynamic_dependency_resolution.q.out
@@ -0,0 +1,43 @@
+PREHOOK: query: CREATE TEMPORARY FUNCTION f1 AS 'hive.it.custom.udfs.UDF1'
+PREHOOK: type: CREATEFUNCTION
+PREHOOK: Output: f1
+POSTHOOK: query: CREATE TEMPORARY FUNCTION f1 AS 'hive.it.custom.udfs.UDF1'
+POSTHOOK: type: CREATEFUNCTION
+POSTHOOK: Output: f1
+PREHOOK: query: CREATE TEMPORARY FUNCTION f2 AS 'hive.it.custom.udfs.UDF2'
+PREHOOK: type: CREATEFUNCTION
+PREHOOK: Output: f2
+POSTHOOK: query: CREATE TEMPORARY FUNCTION f2 AS 'hive.it.custom.udfs.UDF2'
+POSTHOOK: type: CREATEFUNCTION
+POSTHOOK: Output: f2
+PREHOOK: query: -- udf-classloader-udf1.jar contains f1 which relies on udf-classloader-util.jar,
+-- similiary udf-classloader-udf2.jar contains f2 which also relies on udf-classloader-util.jar.
+SELECT f1(*), f2(*) FROM SRC limit 1
+PREHOOK: type: QUERY
+PREHOOK: Input: default@src
+#### A masked pattern was here ####
+POSTHOOK: query: -- udf-classloader-udf1.jar contains f1 which relies on udf-classloader-util.jar,
+-- similiary udf-classloader-udf2.jar contains f2 which also relies on udf-classloader-util.jar.
+SELECT f1(*), f2(*) FROM SRC limit 1
+POSTHOOK: type: QUERY
+POSTHOOK: Input: default@src
+#### A masked pattern was here ####
+UDF1 UDF2
+PREHOOK: query: SELECT f1(*) FROM SRC limit 1
+PREHOOK: type: QUERY
+PREHOOK: Input: default@src
+#### A masked pattern was here ####
+POSTHOOK: query: SELECT f1(*) FROM SRC limit 1
+POSTHOOK: type: QUERY
+POSTHOOK: Input: default@src
+#### A masked pattern was here ####
+UDF1
+PREHOOK: query: SELECT f2(*) FROM SRC limit 1
+PREHOOK: type: QUERY
+PREHOOK: Input: default@src
+#### A masked pattern was here ####
+POSTHOOK: query: SELECT f2(*) FROM SRC limit 1
+POSTHOOK: type: QUERY
+POSTHOOK: Input: default@src
+#### A masked pattern was here ####
+UDF2