http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/main/java/org/apache/drill/exec/testing/store/NoWriteLocalStore.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/testing/store/NoWriteLocalStore.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/testing/store/NoWriteLocalStore.java
index ff14f6d..58ec3ea 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/testing/store/NoWriteLocalStore.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/testing/store/NoWriteLocalStore.java
@@ -20,17 +20,32 @@ package org.apache.drill.exec.testing.store;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
+import org.apache.drill.common.concurrent.AutoCloseableLock;
+import org.apache.drill.exec.exception.VersionMismatchException;
 import org.apache.drill.exec.store.sys.BasePersistentStore;
 import org.apache.drill.exec.store.sys.PersistentStoreMode;
+import org.apache.drill.exec.store.sys.store.DataChangeVersion;
 
 public class NoWriteLocalStore<V> extends BasePersistentStore<V> {
+  private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
+  private final AutoCloseableLock readLock = new 
AutoCloseableLock(readWriteLock.readLock());
+  private final AutoCloseableLock writeLock = new 
AutoCloseableLock(readWriteLock.writeLock());
   private final ConcurrentMap<String, V> store = Maps.newConcurrentMap();
+  private final AtomicInteger version = new AtomicInteger();
 
+  @Override
   public void delete(final String key) {
-    store.remove(key);
+    try (AutoCloseableLock lock = writeLock.open()) {
+      store.remove(key);
+      version.incrementAndGet();
+    }
   }
 
   @Override
@@ -40,27 +55,59 @@ public class NoWriteLocalStore<V> extends 
BasePersistentStore<V> {
 
   @Override
   public V get(final String key) {
-    return store.get(key);
+    return get(key, null);
+  }
+
+  @Override
+  public V get(final String key, final DataChangeVersion dataChangeVersion) {
+    try (AutoCloseableLock lock = readLock.open()) {
+      if (dataChangeVersion != null) {
+        dataChangeVersion.setVersion(version.get());
+      }
+      return store.get(key);
+    }
   }
 
   @Override
   public void put(final String key, final V value) {
-    store.put(key, value);
+    put(key, value, null);
+  }
+
+  @Override
+  public void put(final String key, final V value, final DataChangeVersion 
dataChangeVersion) {
+    try (AutoCloseableLock lock = writeLock.open()) {
+      if (dataChangeVersion != null && dataChangeVersion.getVersion() != 
version.get()) {
+        throw new VersionMismatchException("Version mismatch detected", 
dataChangeVersion.getVersion());
+      }
+      store.put(key, value);
+      version.incrementAndGet();
+    }
   }
 
   @Override
   public boolean putIfAbsent(final String key, final V value) {
-    final V old = store.putIfAbsent(key, value);
-    return value != old;
+    try (AutoCloseableLock lock = writeLock.open()) {
+      final V old = store.putIfAbsent(key, value);
+      if (old == null) {
+        version.incrementAndGet();
+        return true;
+      }
+      return false;
+    }
   }
 
   @Override
   public Iterator<Map.Entry<String, V>> getRange(final int skip, final int 
take) {
-    return Iterables.limit(Iterables.skip(store.entrySet(), skip), 
take).iterator();
+    try (AutoCloseableLock lock = readLock.open()) {
+      return Iterables.limit(Iterables.skip(store.entrySet(), skip), 
take).iterator();
+    }
   }
 
   @Override
   public void close() throws Exception {
-    store.clear();
+    try (AutoCloseableLock lock = writeLock.open()) {
+      store.clear();
+      version.set(0);
+    }
   }
 }

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/main/java/org/apache/drill/exec/util/JarUtil.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/util/JarUtil.java 
b/exec/java-exec/src/main/java/org/apache/drill/exec/util/JarUtil.java
new file mode 100644
index 0000000..3157223
--- /dev/null
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/util/JarUtil.java
@@ -0,0 +1,33 @@
+/**
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.drill.exec.util;
+
+public class JarUtil {
+
+  /**
+   * Generates source jar name based on binary jar name.
+   * It is expected binary and source have standard naming convention.
+   *
+   * @param binary binary jar name
+   * @return source jar name
+   */
+  public static String getSourceName(String binary) {
+    return binary.replace(".jar", "-sources.jar");
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/main/resources/drill-module.conf
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/resources/drill-module.conf 
b/exec/java-exec/src/main/resources/drill-module.conf
index d8f4759b..60dcf15 100644
--- a/exec/java-exec/src/main/resources/drill-module.conf
+++ b/exec/java-exec/src/main/resources/drill-module.conf
@@ -45,6 +45,12 @@ drill.client: {
   supports-complex-types: true
 }
 
+// Directory is used as base for temporary storage of Dynamic UDF jars.
+// Set this property if you want to have custom temporary directory, instead 
of generated at runtime.
+// By default ${DRILL_TMP_DIR} is used if set.
+// drill.tmp-dir: "/tmp"
+// drill.tmp-dir: ${?DRILL_TMP_DIR}
+
 drill.exec: {
   cluster-id: "drillbits1"
   rpc: {
@@ -189,6 +195,29 @@ drill.exec: {
   debug: {
     return_error_for_failure_in_cancelled_fragments: false
   }
+  udf: {
+    retry-attempts: 5,
+    directory: {
+      // Set this property if custom file system should be used to create 
remote directories, ex: fs: "file:///".
+      // fs: "",
+      // Set this property if custom absolute root should be used for remote 
directories, ex: root: "/app/drill".
+      // root: "",
+
+      // Relative base directory for all udf directories (local and remote).
+      base: ${drill.exec.zk.root}"/udf",
+
+      // Relative path to local udf directory.
+      // Generated temporary directory is used as root unless ${DRILL_TMP_DIR} 
or ${drill.tmp-dir} is set.
+      local: ${drill.exec.udf.directory.base}"/local"
+
+      // Relative path to all remote udf directories.
+      // Directories are created under default file system unless 
${drill.exec.udf.directory.fs} is set.
+      // User home directory is used as root unless 
${drill.exec.udf.directory.root} is set.
+      staging: ${drill.exec.udf.directory.base}"/staging",
+      registry: ${drill.exec.udf.directory.base}"/registry",
+      tmp: ${drill.exec.udf.directory.base}"/tmp",
+    }
+  }
 }
 
 drill.jdbc: {

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/java/org/apache/drill/BaseTestQuery.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/java/org/apache/drill/BaseTestQuery.java 
b/exec/java-exec/src/test/java/org/apache/drill/BaseTestQuery.java
index 3eded52..e528d0e 100644
--- a/exec/java-exec/src/test/java/org/apache/drill/BaseTestQuery.java
+++ b/exec/java-exec/src/test/java/org/apache/drill/BaseTestQuery.java
@@ -64,7 +64,6 @@ import org.junit.runner.Description;
 
 import com.google.common.base.Charsets;
 import com.google.common.base.Preconditions;
-import com.google.common.io.Files;
 import com.google.common.io.Resources;
 
 public class BaseTestQuery extends ExecTest {
@@ -423,19 +422,6 @@ public class BaseTestQuery extends ExecTest {
     return file.getPath();
   }
 
-  /**
-   * Create a temp directory to store the given <i>dirName</i>
-   * @param dirName
-   * @return Full path including temp parent directory and given directory 
name.
-   */
-  public static String getTempDir(final String dirName) {
-    final File dir = Files.createTempDir();
-    dir.deleteOnExit();
-
-    return dir.getAbsolutePath() + File.separator + dirName;
-  }
-
-
   protected static void setSessionOption(final String option, final String 
value) {
     try {
       runSQL(String.format("alter session set `%s` = %s", option, value));

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/java/org/apache/drill/TestDynamicUDFSupport.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/TestDynamicUDFSupport.java 
b/exec/java-exec/src/test/java/org/apache/drill/TestDynamicUDFSupport.java
new file mode 100644
index 0000000..ae73a3d
--- /dev/null
+++ b/exec/java-exec/src/test/java/org/apache/drill/TestDynamicUDFSupport.java
@@ -0,0 +1,801 @@
+/**
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.drill;
+
+import com.google.common.collect.Lists;
+import mockit.Deencapsulation;
+import org.apache.drill.common.config.CommonConstants;
+import org.apache.drill.common.config.DrillConfig;
+import org.apache.drill.common.exceptions.UserRemoteException;
+import org.apache.drill.common.util.TestTools;
+import org.apache.drill.exec.exception.VersionMismatchException;
+import org.apache.drill.exec.expr.fn.FunctionImplementationRegistry;
+import org.apache.drill.exec.expr.fn.registry.LocalFunctionRegistry;
+import org.apache.drill.exec.expr.fn.registry.RemoteFunctionRegistry;
+import org.apache.drill.exec.proto.UserBitShared.Jar;
+import org.apache.drill.exec.proto.UserBitShared.Registry;
+import org.apache.drill.exec.server.DrillbitContext;
+import org.apache.drill.exec.store.sys.store.DataChangeVersion;
+import org.apache.drill.exec.util.JarUtil;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestDynamicUDFSupport extends BaseTestQuery {
+
+  private static final File jars = new File(TestTools.getWorkingPath() + 
"/src/test/resources/jars");
+  private static final String default_binary_name = "DrillUDF-1.0.jar";
+  private static final String default_source_name = 
JarUtil.getSourceName(default_binary_name);
+
+  @Rule
+  public final TemporaryFolder base = new TemporaryFolder();
+
+  @Before
+  public void setup() {
+    Properties overrideProps = new Properties();
+    overrideProps.setProperty("drill.exec.udf.directory.root", 
base.getRoot().getPath());
+    updateTestCluster(1, DrillConfig.create(overrideProps));
+  }
+
+  @Test
+  public void testSyntax() throws Exception {
+    test("create function using jar 'jar_name.jar'");
+    test("drop function using jar 'jar_name.jar'");
+  }
+
+  @Test
+  public void testEnableDynamicSupport() throws Exception {
+    try {
+      test("alter system set `exec.udf.enable_dynamic_support` = true");
+      test("create function using jar 'jar_name.jar'");
+      test("drop function using jar 'jar_name.jar'");
+    } finally {
+      test("alter system reset `exec.udf.enable_dynamic_support`");
+    }
+  }
+
+  @Test
+  public void testDisableDynamicSupport() throws Exception {
+    try {
+      test("alter system set `exec.udf.enable_dynamic_support` = false");
+      String[] actions = new String[] {"create", "drop"};
+      String query = "%s function using jar 'jar_name.jar'";
+      for (String action : actions) {
+        try {
+          test(query, action);
+        } catch (UserRemoteException e) {
+          assertThat(e.getMessage(), containsString("Dynamic UDFs support is 
disabled."));
+        }
+      }
+    } finally {
+      test("alter system reset `exec.udf.enable_dynamic_support`");
+    }
+  }
+
+  @Test
+  public void testAbsentBinaryInStaging() throws Exception {
+    Path staging = 
getDrillbitContext().getRemoteFunctionRegistry().getStagingArea();
+
+    String summary = String.format("File %s does not exist", new Path(staging, 
default_binary_name).toUri().getPath());
+
+    testBuilder()
+        .sqlQuery("create function using jar '%s'", default_binary_name)
+        .unOrdered()
+        .baselineColumns("ok", "summary")
+        .baselineValues(false, summary)
+        .go();
+  }
+
+  @Test
+  public void testAbsentSourceInStaging() throws Exception {
+    Path staging = 
getDrillbitContext().getRemoteFunctionRegistry().getStagingArea();
+    copyJar(getDrillbitContext().getRemoteFunctionRegistry().getFs(), new 
Path(jars.toURI()), staging, default_binary_name);
+
+    String summary = String.format("File %s does not exist", new Path(staging, 
default_source_name).toUri().getPath());
+
+    testBuilder()
+        .sqlQuery("create function using jar '%s'", default_binary_name)
+        .unOrdered()
+        .baselineColumns("ok", "summary")
+        .baselineValues(false, summary)
+        .go();
+  }
+
+  @Test
+  public void testJarWithoutMarkerFile() throws Exception {
+    String jarWithNoMarkerFile = "DrillUDF_NoMarkerFile-1.0.jar";
+    copyJarsToStagingArea(jarWithNoMarkerFile, 
JarUtil.getSourceName(jarWithNoMarkerFile));
+
+    String summary = "Marker file %s is missing in %s";
+
+    testBuilder()
+        .sqlQuery("create function using jar '%s'", jarWithNoMarkerFile)
+        .unOrdered()
+        .baselineColumns("ok", "summary")
+        .baselineValues(false, String.format(summary, 
CommonConstants.DRILL_JAR_MARKER_FILE_RESOURCE_PATHNAME, jarWithNoMarkerFile))
+        .go();
+  }
+
+  @Test
+  public void testJarWithoutFunctions() throws Exception {
+    String jarWithNoFunctions = "DrillUDF_Empty-1.0.jar";
+    copyJarsToStagingArea(jarWithNoFunctions, 
JarUtil.getSourceName(jarWithNoFunctions));
+
+    String summary = "Jar %s does not contain functions";
+
+    testBuilder()
+        .sqlQuery("create function using jar '%s'", jarWithNoFunctions)
+        .unOrdered()
+        .baselineColumns("ok", "summary")
+        .baselineValues(false, String.format(summary, jarWithNoFunctions))
+        .go();
+  }
+
+  @Test
+  public void testSuccessfulCreate() throws Exception {
+    copyDefaultJarsToStagingArea();
+
+    String summary = "The following UDFs in jar %s have been registered:\n" +
+        "[custom_lower(VARCHAR-REQUIRED)]";
+
+    testBuilder()
+        .sqlQuery("create function using jar '%s'", default_binary_name)
+        .unOrdered()
+        .baselineColumns("ok", "summary")
+        .baselineValues(true, String.format(summary, default_binary_name))
+        .go();
+
+    RemoteFunctionRegistry remoteFunctionRegistry = 
getDrillbitContext().getRemoteFunctionRegistry();
+    FileSystem fs = remoteFunctionRegistry.getFs();
+
+    assertFalse("Staging area should be empty", 
fs.listFiles(remoteFunctionRegistry.getStagingArea(), false).hasNext());
+    assertFalse("Temporary area should be empty", 
fs.listFiles(remoteFunctionRegistry.getTmpArea(), false).hasNext());
+
+    assertTrue("Binary should be present in registry area",
+        fs.exists(new Path(remoteFunctionRegistry.getRegistryArea(), 
default_binary_name)));
+    assertTrue("Source should be present in registry area",
+        fs.exists(new Path(remoteFunctionRegistry.getRegistryArea(), 
default_source_name)));
+
+    Registry registry = remoteFunctionRegistry.getRegistry();
+    assertEquals("Registry should contain one jar", 
registry.getJarList().size(), 1);
+    assertEquals(registry.getJar(0).getName(), default_binary_name);
+  }
+
+  @Test
+  public void testDuplicatedJarInRemoteRegistry() throws Exception {
+    copyDefaultJarsToStagingArea();
+    test("create function using jar '%s'", default_binary_name);
+    copyDefaultJarsToStagingArea();
+
+    String summary = "Jar with %s name has been already registered";
+
+    testBuilder()
+        .sqlQuery("create function using jar '%s'", default_binary_name)
+        .unOrdered()
+        .baselineColumns("ok", "summary")
+        .baselineValues(false, String.format(summary, default_binary_name))
+        .go();
+  }
+
+  @Test
+  public void testDuplicatedJarInLocalRegistry() throws Exception {
+    copyDefaultJarsToStagingArea();
+    test("create function using jar '%s'", default_binary_name);
+    test("select custom_lower('A') from (values(1))");
+    copyDefaultJarsToStagingArea();
+
+    String summary = "Jar with %s name has been already registered";
+
+    testBuilder()
+        .sqlQuery("create function using jar '%s'", default_binary_name)
+        .unOrdered()
+        .baselineColumns("ok", "summary")
+        .baselineValues(false, String.format(summary, default_binary_name))
+        .go();
+  }
+
+  @Test
+  public void testDuplicatedFunctionsInRemoteRegistry() throws Exception {
+    String jarWithDuplicate = "DrillUDF_Copy-1.0.jar";
+    copyDefaultJarsToStagingArea();
+    test("create function using jar '%s'", default_binary_name);
+    copyJarsToStagingArea(jarWithDuplicate, 
JarUtil.getSourceName(jarWithDuplicate));
+
+    String summary = "Found duplicated function in %s: 
custom_lower(VARCHAR-REQUIRED)";
+
+    testBuilder()
+        .sqlQuery("create function using jar '%s'", jarWithDuplicate)
+        .unOrdered()
+        .baselineColumns("ok", "summary")
+        .baselineValues(false, String.format(summary, default_binary_name))
+        .go();
+  }
+
+  @Test
+  public void testDuplicatedFunctionsInLocalRegistry() throws Exception {
+    String jarWithDuplicate = "DrillUDF_DupFunc-1.0.jar";
+    copyJarsToStagingArea(jarWithDuplicate, 
JarUtil.getSourceName(jarWithDuplicate));
+
+    String summary = "Found duplicated function in %s: 
lower(VARCHAR-REQUIRED)";
+
+    testBuilder()
+        .sqlQuery("create function using jar '%s'", jarWithDuplicate)
+        .unOrdered()
+        .baselineColumns("ok", "summary")
+        .baselineValues(false, String.format(summary, 
LocalFunctionRegistry.BUILT_IN))
+        .go();
+  }
+
+  @Test
+  public void testExceedRetryAttemptsDuringRegistration() throws Exception {
+    RemoteFunctionRegistry remoteFunctionRegistry = 
spyRemoteFunctionRegistry();
+    copyDefaultJarsToStagingArea();
+
+    doThrow(new VersionMismatchException("Version mismatch detected", 1))
+        .when(remoteFunctionRegistry).updateRegistry(any(Registry.class), 
any(DataChangeVersion.class));
+
+    String summary = "Failed to update remote function registry. Exceeded 
retry attempts limit.";
+
+    testBuilder()
+        .sqlQuery("create function using jar '%s'", default_binary_name)
+        .unOrdered()
+        .baselineColumns("ok", "summary")
+        .baselineValues(false, summary)
+        .go();
+
+    verify(remoteFunctionRegistry, 
times(remoteFunctionRegistry.getRetryAttempts() + 1))
+        .updateRegistry(any(Registry.class), any(DataChangeVersion.class));
+  }
+
+  @Test
+  public void testExceedRetryAttemptsDuringUnregistration() throws Exception {
+    RemoteFunctionRegistry remoteFunctionRegistry = 
spyRemoteFunctionRegistry();
+    copyDefaultJarsToStagingArea();
+    test("create function using jar '%s'", default_binary_name);
+
+    reset(remoteFunctionRegistry);
+    doThrow(new VersionMismatchException("Version mismatch detected", 1))
+        .when(remoteFunctionRegistry).updateRegistry(any(Registry.class), 
any(DataChangeVersion.class));
+
+    String summary = "Failed to update remote function registry. Exceeded 
retry attempts limit.";
+
+    testBuilder()
+        .sqlQuery("drop function using jar '%s'", default_binary_name)
+        .unOrdered()
+        .baselineColumns("ok", "summary")
+        .baselineValues(false, summary)
+        .go();
+
+    verify(remoteFunctionRegistry, 
times(remoteFunctionRegistry.getRetryAttempts() + 1))
+        .updateRegistry(any(Registry.class), any(DataChangeVersion.class));
+  }
+
+  @Test
+  public void testLazyInit() throws Exception {
+    try {
+      test("select custom_lower('A') from (values(1))");
+    } catch (UserRemoteException e){
+      assertThat(e.getMessage(), containsString("No match found for function 
signature custom_lower(<CHARACTER>)"));
+    }
+
+    copyDefaultJarsToStagingArea();
+    test("create function using jar '%s'", default_binary_name);
+    testBuilder()
+        .sqlQuery("select custom_lower('A') as res from (values(1))")
+        .unOrdered()
+        .baselineColumns("res")
+        .baselineValues("a")
+        .go();
+
+    Path localUdfDirPath = 
Deencapsulation.getField(getDrillbitContext().getFunctionImplementationRegistry(),
 "localUdfDir");
+    File localUdfDir = new File(localUdfDirPath.toUri().getPath());
+
+    assertTrue("Binary should exist in local udf directory", new 
File(localUdfDir, default_binary_name).exists());
+    assertTrue("Source should exist in local udf directory", new 
File(localUdfDir, default_source_name).exists());
+  }
+
+  @Test
+  public void testDropFunction() throws Exception {
+    copyDefaultJarsToStagingArea();
+    test("create function using jar '%s'", default_binary_name);
+    test("select custom_lower('A') from (values(1))");
+
+    Path localUdfDirPath = 
Deencapsulation.getField(getDrillbitContext().getFunctionImplementationRegistry(),
 "localUdfDir");
+    File localUdfDir = new File(localUdfDirPath.toUri().getPath());
+
+    assertTrue("Binary should exist in local udf directory", new 
File(localUdfDir, default_binary_name).exists());
+    assertTrue("Source should exist in local udf directory", new 
File(localUdfDir, default_source_name).exists());
+
+    String summary = "The following UDFs in jar %s have been unregistered:\n" +
+        "[custom_lower(VARCHAR-REQUIRED)]";
+
+    testBuilder()
+        .sqlQuery("drop function using jar '%s'", default_binary_name)
+        .unOrdered()
+        .baselineColumns("ok", "summary")
+        .baselineValues(true, String.format(summary, default_binary_name))
+        .go();
+
+    try {
+      test("select custom_lower('A') from (values(1))");
+    } catch (UserRemoteException e){
+      assertThat(e.getMessage(), containsString("No match found for function 
signature custom_lower(<CHARACTER>)"));
+    }
+
+    RemoteFunctionRegistry remoteFunctionRegistry = 
getDrillbitContext().getRemoteFunctionRegistry();
+    assertEquals("Remote registry should be empty", 
remoteFunctionRegistry.getRegistry().getJarList().size(), 0);
+
+    FileSystem fs = remoteFunctionRegistry.getFs();
+    assertFalse("Binary should not be present in registry area",
+        fs.exists(new Path(remoteFunctionRegistry.getRegistryArea(), 
default_binary_name)));
+    assertFalse("Source should not be present in registry area",
+        fs.exists(new Path(remoteFunctionRegistry.getRegistryArea(), 
default_source_name)));
+
+    assertFalse("Binary should not be present in local udf directory",
+        new File(localUdfDir, default_binary_name).exists());
+    assertFalse("Source should not be present in local udf directory",
+        new File(localUdfDir, default_source_name).exists());
+  }
+
+  @Test
+  public void testReRegisterTheSameJarWithDifferentContent() throws Exception {
+    copyDefaultJarsToStagingArea();
+    test("create function using jar '%s'", default_binary_name);
+    testBuilder()
+        .sqlQuery("select custom_lower('A') as res from (values(1))")
+        .unOrdered()
+        .baselineColumns("res")
+        .baselineValues("a")
+        .go();
+    test("drop function using jar '%s'", default_binary_name);
+
+    Thread.sleep(1000);
+
+    Path src = new Path(jars.toURI().getPath(), "v2");
+    copyJarsToStagingArea(src, default_binary_name, default_source_name);
+    test("create function using jar '%s'", default_binary_name);
+    testBuilder()
+        .sqlQuery("select custom_lower('A') as res from (values(1))")
+        .unOrdered()
+        .baselineColumns("res")
+        .baselineValues("a_v2")
+        .go();
+  }
+
+  @Test
+  public void testDropAbsentJar() throws Exception {
+    String summary = "Jar %s is not registered in remote registry";
+
+    testBuilder()
+        .sqlQuery("drop function using jar '%s'", default_binary_name)
+        .unOrdered()
+        .baselineColumns("ok", "summary")
+        .baselineValues(false, String.format(summary, default_binary_name))
+        .go();
+  }
+
+  @Test
+  public void testRegistryAreaCleanUpOnFail() throws Exception {
+    final RemoteFunctionRegistry remoteFunctionRegistry = 
spyRemoteFunctionRegistry();
+    final FileSystem fs = remoteFunctionRegistry.getFs();
+    final String errorMessage = "Failure during remote registry update.";
+    doAnswer(new Answer<Void>() {
+      @Override
+      public Void answer(InvocationOnMock invocation) throws Throwable {
+        assertTrue("Binary should be present in registry area",
+            fs.exists(new Path(remoteFunctionRegistry.getRegistryArea(), 
default_binary_name)));
+        assertTrue("Source should be present in registry area",
+            fs.exists(new Path(remoteFunctionRegistry.getRegistryArea(), 
default_source_name)));
+        throw new RuntimeException(errorMessage);
+      }
+    }).when(remoteFunctionRegistry).updateRegistry(any(Registry.class), 
any(DataChangeVersion.class));
+
+    copyDefaultJarsToStagingArea();
+
+    testBuilder()
+        .sqlQuery("create function using jar '%s'", default_binary_name)
+        .unOrdered()
+        .baselineColumns("ok", "summary")
+        .baselineValues(false, errorMessage)
+        .go();
+
+    assertFalse("Registry area should be empty", 
fs.listFiles(remoteFunctionRegistry.getRegistryArea(), false).hasNext());
+    assertFalse("Temporary area should be empty", 
fs.listFiles(remoteFunctionRegistry.getTmpArea(), false).hasNext());
+
+    assertTrue("Binary should be present in staging area",
+        fs.exists(new Path(remoteFunctionRegistry.getStagingArea(), 
default_binary_name)));
+    assertTrue("Source should be present in staging area",
+        fs.exists(new Path(remoteFunctionRegistry.getStagingArea(), 
default_source_name)));
+  }
+
+  @Test
+  public void testConcurrentRegistrationOfTheSameJar() throws Exception {
+    RemoteFunctionRegistry remoteFunctionRegistry = 
spyRemoteFunctionRegistry();
+
+    final CountDownLatch latch1 = new CountDownLatch(1);
+    final CountDownLatch latch2 = new CountDownLatch(1);
+
+    doAnswer(new Answer<String>() {
+      @Override
+      public String answer(InvocationOnMock invocation) throws Throwable {
+        String result = (String) invocation.callRealMethod();
+        latch2.countDown();
+        latch1.await();
+        return result;
+      }
+    })
+        .doCallRealMethod()
+        .doCallRealMethod()
+        .when(remoteFunctionRegistry).addToJars(anyString(), 
any(RemoteFunctionRegistry.Action.class));
+
+
+    final String query = String.format("create function using jar '%s'", 
default_binary_name);
+
+    Thread thread = new Thread(new SimpleQueryRunner(query));
+    thread.start();
+    latch2.await();
+
+    try {
+      String summary = "Jar with %s name is used. Action: REGISTRATION";
+
+      testBuilder()
+          .sqlQuery(query)
+          .unOrdered()
+          .baselineColumns("ok", "summary")
+          .baselineValues(false, String.format(summary, default_binary_name))
+          .go();
+
+      testBuilder()
+          .sqlQuery("drop function using jar '%s'", default_binary_name)
+          .unOrdered()
+          .baselineColumns("ok", "summary")
+          .baselineValues(false, String.format(summary, default_binary_name))
+          .go();
+
+    } finally {
+      latch1.countDown();
+      thread.join();
+    }
+  }
+
+  @Test
+  public void testConcurrentRemoteRegistryUpdateWithDuplicates() throws 
Exception {
+    RemoteFunctionRegistry remoteFunctionRegistry = 
spyRemoteFunctionRegistry();
+
+    final CountDownLatch latch1 = new CountDownLatch(1);
+    final CountDownLatch latch2 = new CountDownLatch(1);
+    final CountDownLatch latch3 = new CountDownLatch(1);
+
+    doAnswer(new Answer<Void>() {
+      @Override
+      public Void answer(InvocationOnMock invocation) throws Throwable {
+        latch3.countDown();
+        latch1.await();
+        invocation.callRealMethod();
+        latch2.countDown();
+        return null;
+      }
+    }).doAnswer(new Answer<Void>() {
+      @Override
+      public Void answer(InvocationOnMock invocation) throws Throwable {
+        latch1.countDown();
+        latch2.await();
+        invocation.callRealMethod();
+        return null;
+      }
+    })
+        .when(remoteFunctionRegistry).updateRegistry(any(Registry.class), 
any(DataChangeVersion.class));
+
+
+    final String jarName1 = default_binary_name;
+    final String jarName2 = "DrillUDF_Copy-1.0.jar";
+    final String query = "create function using jar '%s'";
+
+    copyDefaultJarsToStagingArea();
+    copyJarsToStagingArea(jarName2, JarUtil.getSourceName(jarName2));
+
+    Thread thread1 = new Thread(new TestBuilderRunner(
+        testBuilder()
+        .sqlQuery(query, jarName1)
+        .unOrdered()
+        .baselineColumns("ok", "summary")
+        .baselineValues(true,
+            String.format("The following UDFs in jar %s have been 
registered:\n" +
+            "[custom_lower(VARCHAR-REQUIRED)]", jarName1))
+    ));
+
+    Thread thread2 = new Thread(new TestBuilderRunner(
+        testBuilder()
+            .sqlQuery(query, jarName2)
+            .unOrdered()
+            .baselineColumns("ok", "summary")
+            .baselineValues(false,
+                String.format("Found duplicated function in %s: 
custom_lower(VARCHAR-REQUIRED)", jarName1))
+    ));
+
+    thread1.start();
+    latch3.await();
+    thread2.start();
+
+    thread1.join();
+    thread2.join();
+
+    DataChangeVersion version = new DataChangeVersion();
+    Registry registry = remoteFunctionRegistry.getRegistry(version);
+    assertEquals("Remote registry version should match", 2, 
version.getVersion());
+    List<Jar> jarList = registry.getJarList();
+    assertEquals("Only one jar should be registered", 1, jarList.size());
+    assertEquals("Jar name should match", jarName1, jarList.get(0).getName());
+
+    verify(remoteFunctionRegistry, 
times(2)).updateRegistry(any(Registry.class), any(DataChangeVersion.class));
+  }
+
+  @Test
+  public void testConcurrentRemoteRegistryUpdateForDifferentJars() throws 
Exception {
+    RemoteFunctionRegistry remoteFunctionRegistry = 
spyRemoteFunctionRegistry();
+    final CountDownLatch latch1 = new CountDownLatch(1);
+    final CountDownLatch latch2 = new CountDownLatch(2);
+
+    doAnswer(new Answer<Void>() {
+      @Override
+      public Void answer(InvocationOnMock invocation) throws Throwable {
+        latch2.countDown();
+        latch1.await();
+        invocation.callRealMethod();
+        return null;
+      }
+    })
+        .when(remoteFunctionRegistry).updateRegistry(any(Registry.class), 
any(DataChangeVersion.class));
+
+    final String jarName1 = default_binary_name;
+    final String jarName2 = "DrillUDF-2.0.jar";
+    final String query = "create function using jar '%s'";
+
+    copyDefaultJarsToStagingArea();
+    copyJarsToStagingArea(jarName2, JarUtil.getSourceName(jarName2));
+
+
+    Thread thread1 = new Thread(new TestBuilderRunner(
+        testBuilder()
+            .sqlQuery(query, jarName1)
+            .unOrdered()
+            .baselineColumns("ok", "summary")
+            .baselineValues(true,
+                String.format("The following UDFs in jar %s have been 
registered:\n" +
+                    "[custom_lower(VARCHAR-REQUIRED)]", jarName1))
+    ));
+
+
+    Thread thread2 = new Thread(new TestBuilderRunner(
+        testBuilder()
+            .sqlQuery(query, jarName2)
+            .unOrdered()
+            .baselineColumns("ok", "summary")
+            .baselineValues(true, String.format("The following UDFs in jar %s 
have been registered:\n" +
+                "[custom_upper(VARCHAR-REQUIRED)]", jarName2))
+    ));
+
+    thread1.start();
+    thread2.start();
+
+    latch2.await();
+    latch1.countDown();
+
+    thread1.join();
+    thread2.join();
+
+    DataChangeVersion version = new DataChangeVersion();
+    Registry registry = remoteFunctionRegistry.getRegistry(version);
+    assertEquals("Remote registry version should match", 3, 
version.getVersion());
+
+    List<Jar> actualJars = registry.getJarList();
+    List<String> expectedJars = Lists.newArrayList(jarName1, jarName2);
+
+    assertEquals("Only one jar should be registered", 2, actualJars.size());
+    for (Jar jar : actualJars) {
+      assertTrue("Jar should be present in remote function registry", 
expectedJars.contains(jar.getName()));
+    }
+
+    verify(remoteFunctionRegistry, 
times(3)).updateRegistry(any(Registry.class), any(DataChangeVersion.class));
+  }
+
+  @Test
+  public void testLazyInitConcurrent() throws Exception {
+    FunctionImplementationRegistry functionImplementationRegistry = 
spyFunctionImplementationRegistry();
+    copyDefaultJarsToStagingArea();
+    test("create function using jar '%s'", default_binary_name);
+
+    final CountDownLatch latch1 = new CountDownLatch(1);
+    final CountDownLatch latch2 = new CountDownLatch(1);
+
+    final String query = "select custom_lower('A') from (values(1))";
+
+    doAnswer(new Answer<Boolean>() {
+      @Override
+      public Boolean answer(InvocationOnMock invocation) throws Throwable {
+        latch1.await();
+        boolean result = (boolean) invocation.callRealMethod();
+        assertTrue("loadRemoteFunctions() should return true", result);
+        latch2.countDown();
+        return true;
+      }
+    })
+        .doAnswer(new Answer() {
+          @Override
+          public Boolean answer(InvocationOnMock invocation) throws Throwable {
+            latch1.countDown();
+            latch2.await();
+            boolean result = (boolean) invocation.callRealMethod();
+            assertTrue("loadRemoteFunctions() should return true", result);
+            return true;
+          }
+        })
+        .when(functionImplementationRegistry).loadRemoteFunctions(anyLong());
+
+    SimpleQueryRunner simpleQueryRunner = new SimpleQueryRunner(query);
+    Thread thread1 = new Thread(simpleQueryRunner);
+    Thread thread2 = new Thread(simpleQueryRunner);
+
+    thread1.start();
+    thread2.start();
+
+    thread1.join();
+    thread2.join();
+
+    verify(functionImplementationRegistry, 
times(2)).loadRemoteFunctions(anyLong());
+    LocalFunctionRegistry localFunctionRegistry = 
Deencapsulation.getField(functionImplementationRegistry, 
"localFunctionRegistry");
+    assertEquals("Local functionRegistry version should match", 2L, 
localFunctionRegistry.getVersion());
+  }
+
+  @Test
+  public void testLazyInitNoReload() throws Exception {
+    FunctionImplementationRegistry functionImplementationRegistry = 
spyFunctionImplementationRegistry();
+    copyDefaultJarsToStagingArea();
+    test("create function using jar '%s'", default_binary_name);
+
+    doAnswer(new Answer<Boolean>() {
+      @Override
+      public Boolean answer(InvocationOnMock invocation) throws Throwable {
+        boolean result = (boolean) invocation.callRealMethod();
+        assertTrue("loadRemoteFunctions() should return true", result);
+        return true;
+      }
+    })
+        .doAnswer(new Answer() {
+          @Override
+          public Boolean answer(InvocationOnMock invocation) throws Throwable {
+            boolean result = (boolean) invocation.callRealMethod();
+            assertFalse("loadRemoteFunctions() should return false", result);
+            return false;
+          }
+        })
+        .when(functionImplementationRegistry).loadRemoteFunctions(anyLong());
+
+    test("select custom_lower('A') from (values(1))");
+
+    try {
+      test("select unknown_lower('A') from (values(1))");
+    } catch (UserRemoteException e){
+      assertThat(e.getMessage(), containsString("No match found for function 
signature unknown_lower(<CHARACTER>)"));
+    }
+
+    verify(functionImplementationRegistry, 
times(2)).loadRemoteFunctions(anyLong());
+    LocalFunctionRegistry localFunctionRegistry = 
Deencapsulation.getField(functionImplementationRegistry, 
"localFunctionRegistry");
+    assertEquals("Local functionRegistry version should match", 2L, 
localFunctionRegistry.getVersion());
+  }
+
+  private void copyDefaultJarsToStagingArea() throws IOException {
+    copyJarsToStagingArea(new Path(jars.toURI()), default_binary_name, 
default_source_name);
+  }
+
+  private void copyJarsToStagingArea(String binaryName, String sourceName) 
throws IOException  {
+    copyJarsToStagingArea(new Path(jars.toURI()), binaryName, sourceName);
+  }
+
+  private void copyJarsToStagingArea(Path src, String binaryName, String 
sourceName) throws IOException {
+    RemoteFunctionRegistry remoteFunctionRegistry = 
getDrillbitContext().getRemoteFunctionRegistry();
+    copyJar(remoteFunctionRegistry.getFs(), src, 
remoteFunctionRegistry.getStagingArea(), binaryName);
+    copyJar(remoteFunctionRegistry.getFs(), src, 
remoteFunctionRegistry.getStagingArea(), sourceName);
+  }
+
+  private void copyJar(FileSystem fs, Path src, Path dest, String name) throws 
IOException {
+    Path jarPath = new Path(src, name);
+    fs.copyFromLocalFile(jarPath, dest);
+  }
+
+  private RemoteFunctionRegistry spyRemoteFunctionRegistry() {
+    FunctionImplementationRegistry functionImplementationRegistry = 
getDrillbitContext().getFunctionImplementationRegistry();
+    RemoteFunctionRegistry remoteFunctionRegistry = 
functionImplementationRegistry.getRemoteFunctionRegistry();
+    RemoteFunctionRegistry spy = spy(remoteFunctionRegistry);
+    Deencapsulation.setField(functionImplementationRegistry, 
"remoteFunctionRegistry", spy);
+    return spy;
+  }
+
+  private FunctionImplementationRegistry spyFunctionImplementationRegistry() {
+    DrillbitContext drillbitContext = getDrillbitContext();
+    FunctionImplementationRegistry spy = 
spy(drillbitContext.getFunctionImplementationRegistry());
+    Deencapsulation.setField(drillbitContext, "functionRegistry", spy);
+    return spy;
+  }
+
+  private class SimpleQueryRunner implements Runnable {
+
+    private final String query;
+
+    SimpleQueryRunner(String query) {
+      this.query = query;
+    }
+
+    @Override
+    public void run() {
+      try {
+        test(query);
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+
+  private class TestBuilderRunner implements Runnable {
+
+    private final TestBuilder testBuilder;
+
+    TestBuilderRunner(TestBuilder testBuilder) {
+      this.testBuilder = testBuilder;
+    }
+
+    @Override
+    public void run() {
+      try {
+        testBuilder.go();
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/java/org/apache/drill/exec/ExecTest.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/ExecTest.java 
b/exec/java-exec/src/test/java/org/apache/drill/exec/ExecTest.java
index 2f26914..05f0a8d 100644
--- a/exec/java-exec/src/test/java/org/apache/drill/exec/ExecTest.java
+++ b/exec/java-exec/src/test/java/org/apache/drill/exec/ExecTest.java
@@ -18,7 +18,9 @@
 package org.apache.drill.exec;
 
 import com.codahale.metrics.MetricRegistry;
+import com.google.common.io.Files;
 import mockit.NonStrictExpectations;
+import org.apache.commons.io.FileUtils;
 import org.apache.drill.common.config.DrillConfig;
 import org.apache.drill.common.scanner.ClassPathScanner;
 import org.apache.drill.exec.compile.CodeCompilerTestFactory;
@@ -33,6 +35,8 @@ import org.apache.drill.test.DrillTest;
 import org.junit.After;
 import org.junit.BeforeClass;
 
+import java.io.File;
+
 
 public class ExecTest extends DrillTest {
 
@@ -46,7 +50,6 @@ public class ExecTest extends DrillTest {
     DrillMetrics.resetMetrics();
   }
 
-
   @BeforeClass
   public static void setupOptionManager() throws Exception{
     final LocalPersistentStoreProvider provider = new 
LocalPersistentStoreProvider(c);
@@ -55,6 +58,23 @@ public class ExecTest extends DrillTest {
     optionManager.init();
   }
 
+  /**
+   * Create a temp directory to store the given <i>dirName</i>.
+   * Directory will be deleted on exit.
+   * @param dirName directory name
+   * @return Full path including temp parent directory and given directory 
name.
+   */
+  public static String getTempDir(final String dirName) {
+    final File dir = Files.createTempDir();
+    Runtime.getRuntime().addShutdownHook(new Thread() {
+      @Override
+      public void run() {
+        FileUtils.deleteQuietly(dir);
+      }
+    });
+    return dir.getAbsolutePath() + File.separator + dirName;
+  }
+
   protected void mockDrillbitContext(final DrillbitContext bitContext) throws 
Exception {
     new NonStrictExpectations() {{
       bitContext.getMetrics(); result = new MetricRegistry();

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/java/org/apache/drill/exec/coord/zk/TestZookeeperClient.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/exec/coord/zk/TestZookeeperClient.java
 
b/exec/java-exec/src/test/java/org/apache/drill/exec/coord/zk/TestZookeeperClient.java
index 3007566..ab886c4 100644
--- 
a/exec/java-exec/src/test/java/org/apache/drill/exec/coord/zk/TestZookeeperClient.java
+++ 
b/exec/java-exec/src/test/java/org/apache/drill/exec/coord/zk/TestZookeeperClient.java
@@ -37,6 +37,8 @@ import org.apache.curator.test.TestingServer;
 import org.apache.curator.utils.EnsurePath;
 import org.apache.drill.common.collections.ImmutableEntry;
 import org.apache.drill.common.exceptions.DrillRuntimeException;
+import org.apache.drill.exec.exception.VersionMismatchException;
+import org.apache.drill.exec.store.sys.store.DataChangeVersion;
 import org.apache.zookeeper.CreateMode;
 import org.junit.After;
 import org.junit.Assert;
@@ -44,6 +46,9 @@ import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mockito;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
 public class TestZookeeperClient {
   private final static String root = "/test";
   private final static String path = "test-key";
@@ -139,13 +144,13 @@ public class TestZookeeperClient {
         .when(client.getCache().getCurrentData(abspath))
         .thenReturn(null);
 
-    Assert.assertEquals("get should return null", null, client.get(path));
+    assertEquals("get should return null", null, client.get(path));
 
     Mockito
         .when(client.getCache().getCurrentData(abspath))
         .thenReturn(new ChildData(abspath, null, data));
 
-    Assert.assertEquals("get should return data", data, client.get(path, 
false));
+    assertEquals("get should return data", data, client.get(path, false));
   }
 
   @Test
@@ -194,8 +199,46 @@ public class TestZookeeperClient {
     // returned entry must contain the given relative path
     final Map.Entry<String, byte[]> expected = new ImmutableEntry<>(path, 
data);
 
-    Assert.assertEquals("entries do not match", expected, entries.next());
+    assertEquals("entries do not match", expected, entries.next());
+  }
+
+  @Test
+  public void testGetWithVersion() {
+    client.put(path, data);
+    DataChangeVersion version0 = new DataChangeVersion();
+    client.get(path, version0);
+    assertEquals("Versions should match", 0, version0.getVersion());
+    client.put(path, data);
+    DataChangeVersion version1 = new DataChangeVersion();
+    client.get(path, version1);
+    assertEquals("Versions should match", 1, version1.getVersion());
+  }
+
+  @Test
+  public void testPutWithMatchingVersion() {
+    client.put(path, data);
+    DataChangeVersion version = new DataChangeVersion();
+    client.get(path, version);
+    client.put(path, data, version);
+  }
+
+  @Test (expected = VersionMismatchException.class)
+  public void testPutWithNonMatchingVersion() {
+    client.put(path, data);
+    DataChangeVersion version = new DataChangeVersion();
+    version.setVersion(123);
+    client.put(path, data, version);
   }
 
+  @Test
+  public void testPutIfAbsentWhenAbsent() {
+    assertNull(client.putIfAbsent(path, data));
+  }
+
+  @Test
+  public void testPutIfAbsentWhenPresent() {
+    client.putIfAbsent(path, data);
+    assertEquals("Data should match", new String(data), new 
String(client.putIfAbsent(path, "new_data".getBytes())));
+  }
 
 }

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/java/org/apache/drill/exec/expr/fn/registry/FunctionRegistryHolderTest.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/exec/expr/fn/registry/FunctionRegistryHolderTest.java
 
b/exec/java-exec/src/test/java/org/apache/drill/exec/expr/fn/registry/FunctionRegistryHolderTest.java
new file mode 100644
index 0000000..61fa4e5
--- /dev/null
+++ 
b/exec/java-exec/src/test/java/org/apache/drill/exec/expr/fn/registry/FunctionRegistryHolderTest.java
@@ -0,0 +1,279 @@
+/**
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.drill.exec.expr.fn.registry;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.drill.exec.expr.fn.DrillFuncHolder;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+public class FunctionRegistryHolderTest {
+
+  private static final String built_in = "built-in";
+  private static final String udf_jar = "DrillUDF-1.0.jar";
+
+  private static Map<String, List<FunctionHolder>> newJars;
+  private FunctionRegistryHolder registryHolder;
+
+  @BeforeClass
+  public static void init() {
+    newJars = Maps.newHashMap();
+    FunctionHolder lower = new FunctionHolder("lower", 
"lower(VARCHAR-REQUIRED)", mock(DrillFuncHolder.class));
+    FunctionHolder upper = new FunctionHolder("upper", 
"upper(VARCHAR-REQUIRED)", mock(DrillFuncHolder.class));
+    newJars.put(built_in, Lists.newArrayList(lower, upper));
+    FunctionHolder custom_lower = new FunctionHolder("custom_lower", 
"lower(VARCHAR-REQUIRED)", mock(DrillFuncHolder.class));
+    FunctionHolder custom_upper = new FunctionHolder("custom_upper", 
"custom_upper(VARCHAR-REQUIRED)", mock(DrillFuncHolder.class));
+    newJars.put(udf_jar, Lists.newArrayList(custom_lower, custom_upper));
+  }
+
+  @Before
+  public void setup() {
+    resetRegistry();
+    fillInRegistry();
+  }
+
+  @Test
+  public void testVersion() {
+    resetRegistry();
+    assertEquals("Initial version should be 0", 0, 
registryHolder.getVersion());
+    registryHolder.addJars(Maps.<String, List<FunctionHolder>>newHashMap());
+    assertEquals("Version should not change if no jars were added.", 0, 
registryHolder.getVersion());
+    registryHolder.removeJar("unknown.jar");
+    assertEquals("Version should not change if no jars were removed.", 0, 
registryHolder.getVersion());
+    fillInRegistry();
+    assertEquals("Version should have incremented by 1", 1, 
registryHolder.getVersion());
+    registryHolder.removeJar(built_in);
+    assertEquals("Version should have incremented by 1", 2, 
registryHolder.getVersion());
+    fillInRegistry();
+    assertEquals("Version should have incremented by 1", 3, 
registryHolder.getVersion());
+    fillInRegistry();
+    assertEquals("Version should have incremented by 1", 4, 
registryHolder.getVersion());
+  }
+
+  @Test
+  public void testAddJars() {
+    resetRegistry();
+    int functionsSize = 0;
+    List<String> jars = Lists.newArrayList();
+    ListMultimap<String, DrillFuncHolder> functionsWithHolders = 
ArrayListMultimap.create();
+    ListMultimap<String, String> functionsWithSignatures = 
ArrayListMultimap.create();
+    for (Map.Entry<String, List<FunctionHolder>> jar : newJars.entrySet()) {
+      jars.add(jar.getKey());
+      for (FunctionHolder functionHolder : jar.getValue()) {
+        functionsWithHolders.put(functionHolder.getName(), 
functionHolder.getHolder());
+        functionsWithSignatures.put(functionHolder.getName(), 
functionHolder.getSignature());
+        functionsSize++;
+      }
+    }
+
+    registryHolder.addJars(newJars);
+    assertEquals("Version number should match", 1, 
registryHolder.getVersion());
+    compareTwoLists(jars, registryHolder.getAllJarNames());
+    assertEquals(functionsSize, registryHolder.functionsSize());
+    compareListMultimaps(functionsWithHolders, 
registryHolder.getAllFunctionsWithHolders());
+    compareListMultimaps(functionsWithSignatures, 
registryHolder.getAllFunctionsWithSignatures());
+  }
+
+  @Test
+  public void testAddTheSameJars() {
+    resetRegistry();
+    int functionsSize = 0;
+    List<String> jars = Lists.newArrayList();
+    ListMultimap<String, DrillFuncHolder> functionsWithHolders = 
ArrayListMultimap.create();
+    ListMultimap<String, String> functionsWithSignatures = 
ArrayListMultimap.create();
+    for (Map.Entry<String, List<FunctionHolder>> jar : newJars.entrySet()) {
+      jars.add(jar.getKey());
+      for (FunctionHolder functionHolder : jar.getValue()) {
+        functionsWithHolders.put(functionHolder.getName(), 
functionHolder.getHolder());
+        functionsWithSignatures.put(functionHolder.getName(), 
functionHolder.getSignature());
+        functionsSize++;
+      }
+    }
+    registryHolder.addJars(newJars);
+    assertEquals("Version number should match", 1, 
registryHolder.getVersion());
+    compareTwoLists(jars, registryHolder.getAllJarNames());
+    assertEquals(functionsSize, registryHolder.functionsSize());
+    compareListMultimaps(functionsWithHolders, 
registryHolder.getAllFunctionsWithHolders());
+    compareListMultimaps(functionsWithSignatures, 
registryHolder.getAllFunctionsWithSignatures());
+
+    // adding the same jars should not cause adding duplicates, should 
override existing jars only
+    registryHolder.addJars(newJars);
+    assertEquals("Version number should match", 2, 
registryHolder.getVersion());
+    compareTwoLists(jars, registryHolder.getAllJarNames());
+    assertEquals(functionsSize, registryHolder.functionsSize());
+    compareListMultimaps(functionsWithHolders, 
registryHolder.getAllFunctionsWithHolders());
+    compareListMultimaps(functionsWithSignatures, 
registryHolder.getAllFunctionsWithSignatures());
+  }
+
+  @Test
+  public void testRemoveJar() {
+    registryHolder.removeJar(built_in);
+    assertFalse("Jar should be absent", registryHolder.containsJar(built_in));
+    assertTrue("Jar should be present", registryHolder.containsJar(udf_jar));
+    assertEquals("Functions size should match", newJars.get(udf_jar).size(), 
registryHolder.functionsSize());
+  }
+
+  @Test
+  public void testGetAllJarNames() {
+    ArrayList<String> expectedResult = Lists.newArrayList(newJars.keySet());
+    compareTwoLists(expectedResult, registryHolder.getAllJarNames());
+  }
+
+  @Test
+  public void testGetFunctionNamesByJar() {
+    ArrayList<String> expectedResult = Lists.newArrayList();
+    for (FunctionHolder functionHolder : newJars.get(built_in)) {
+      expectedResult.add(functionHolder.getName());
+    }
+    compareTwoLists(expectedResult, 
registryHolder.getFunctionNamesByJar(built_in));
+  }
+
+  @Test
+  public void testGetAllFunctionsWithHoldersWithVersion() {
+    ListMultimap<String, DrillFuncHolder> expectedResult = 
ArrayListMultimap.create();
+    for (List<FunctionHolder> functionHolders : newJars.values()) {
+      for(FunctionHolder functionHolder : functionHolders) {
+        expectedResult.put(functionHolder.getName(), 
functionHolder.getHolder());
+      }
+    }
+    AtomicLong version = new AtomicLong();
+    compareListMultimaps(expectedResult, 
registryHolder.getAllFunctionsWithHolders(version));
+    assertEquals("Version number should match", version.get(), 
registryHolder.getVersion());
+  }
+
+  @Test
+  public void testGetAllFunctionsWithHolders() {
+    ListMultimap<String, DrillFuncHolder> expectedResult = 
ArrayListMultimap.create();
+    for (List<FunctionHolder> functionHolders : newJars.values()) {
+      for(FunctionHolder functionHolder : functionHolders) {
+        expectedResult.put(functionHolder.getName(), 
functionHolder.getHolder());
+      }
+    }
+    compareListMultimaps(expectedResult, 
registryHolder.getAllFunctionsWithHolders());
+  }
+
+  @Test
+  public void testGetAllFunctionsWithSignatures() {
+    ListMultimap<String, String> expectedResult = ArrayListMultimap.create();
+    for (List<FunctionHolder> functionHolders : newJars.values()) {
+      for(FunctionHolder functionHolder : functionHolders) {
+        expectedResult.put(functionHolder.getName(), 
functionHolder.getSignature());
+      }
+    }
+    compareListMultimaps(expectedResult, 
registryHolder.getAllFunctionsWithSignatures());
+  }
+
+  @Test
+  public void testGetHoldersByFunctionNameWithVersion() {
+    List<DrillFuncHolder> expectedResult = Lists.newArrayList();
+    for (List<FunctionHolder> functionHolders : newJars.values()) {
+      for (FunctionHolder functionHolder : functionHolders) {
+        if ("lower".equals(functionHolder.getName())) {
+          expectedResult.add(functionHolder.getHolder()) ;
+        }
+      }
+    }
+    assertFalse(expectedResult.isEmpty());
+    AtomicLong version = new AtomicLong();
+    compareTwoLists(expectedResult, 
registryHolder.getHoldersByFunctionName("lower", version));
+    assertEquals("Version number should match", version.get(), 
registryHolder.getVersion());
+  }
+
+  @Test
+  public void testGetHoldersByFunctionName() {
+    List<DrillFuncHolder> expectedResult = Lists.newArrayList();
+    for (List<FunctionHolder> functionHolders : newJars.values()) {
+      for (FunctionHolder functionHolder : functionHolders) {
+        if ("lower".equals(functionHolder.getName())) {
+          expectedResult.add(functionHolder.getHolder()) ;
+        }
+      }
+    }
+    assertFalse(expectedResult.isEmpty());
+    compareTwoLists(expectedResult, 
registryHolder.getHoldersByFunctionName("lower"));
+  }
+
+  @Test
+  public void testContainsJar() {
+    assertTrue("Jar should be present in registry holder", 
registryHolder.containsJar(built_in));
+    assertFalse("Jar should be absent in registry holder", 
registryHolder.containsJar("unknown.jar"));
+  }
+
+  @Test
+  public void testFunctionsSize() {
+    int count = 0;
+    for (List<FunctionHolder> functionHolders : newJars.values()) {
+      count += functionHolders.size();
+    }
+    assertEquals("Functions size should match", count, 
registryHolder.functionsSize());
+  }
+
+  @Test
+  public void testJarNameByFunctionSignature() {
+    FunctionHolder functionHolder = newJars.get(built_in).get(0);
+    assertEquals("Jar name should match",
+        built_in, 
registryHolder.getJarNameByFunctionSignature(functionHolder.getName(), 
functionHolder.getSignature()));
+    assertNull("Jar name should be null",
+        registryHolder.getJarNameByFunctionSignature("unknown_function", 
"unknown_function(unknown-input)"));
+  }
+
+  private void resetRegistry() {
+    registryHolder = new FunctionRegistryHolder();
+  }
+
+  private void fillInRegistry() {
+    registryHolder.addJars(newJars);
+  }
+
+  private <T> void compareListMultimaps(ListMultimap<String, T> lm1, 
ListMultimap<String, T> lm2) {
+    Map<String, Collection<T>> m1 = lm1.asMap();
+    Map<String, Collection<T>> m2 = lm2.asMap();
+    assertEquals("Multimaps size should match", m1.size(), m2.size());
+    for (Map.Entry<String, Collection<T>> entry : m1.entrySet()) {
+      try {
+        compareTwoLists(Lists.newArrayList(entry.getValue()), 
Lists.newArrayList(m2.get(entry.getKey())));
+      } catch (AssertionError e) {
+        throw new AssertionError("Multimaps values should match", e);
+      }
+    }
+  }
+
+  private <T> void compareTwoLists(List<T> l1, List<T> l2) {
+    assertEquals("Lists size should match", l1.size(), l2.size());
+    for (T item : l1) {
+      assertTrue("Two lists should have the same values", l2.contains(item));
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestSimpleFunctions.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestSimpleFunctions.java
 
b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestSimpleFunctions.java
index 64495db..ede30e6 100644
--- 
a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestSimpleFunctions.java
+++ 
b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestSimpleFunctions.java
@@ -65,17 +65,17 @@ public class TestSimpleFunctions extends ExecTest {
   private final DrillConfig c = DrillConfig.create();
 
   @Test
-  public void testHashFunctionResolution(@Injectable DrillConfig config) 
throws JClassAlreadyExistsException, IOException {
-    final FunctionImplementationRegistry registry = new 
FunctionImplementationRegistry(config);
+  public void testHashFunctionResolution() throws 
JClassAlreadyExistsException, IOException {
+    final FunctionImplementationRegistry registry = new 
FunctionImplementationRegistry(c);
     // test required vs nullable Int input
-    resolveHash(config,
+    resolveHash(c,
         new TypedNullConstant(Types.optional(TypeProtos.MinorType.INT)),
         Types.optional(TypeProtos.MinorType.INT),
         Types.required(TypeProtos.MinorType.INT),
         TypeProtos.DataMode.OPTIONAL,
         registry);
 
-    resolveHash(config,
+    resolveHash(c,
         new ValueExpressions.IntExpression(1, ExpressionPosition.UNKNOWN),
         Types.required(TypeProtos.MinorType.INT),
         Types.required(TypeProtos.MinorType.INT),
@@ -83,14 +83,14 @@ public class TestSimpleFunctions extends ExecTest {
         registry);
 
     // test required vs nullable float input
-    resolveHash(config,
+    resolveHash(c,
         new TypedNullConstant(Types.optional(TypeProtos.MinorType.FLOAT4)),
         Types.optional(TypeProtos.MinorType.FLOAT4),
         Types.required(TypeProtos.MinorType.FLOAT4),
         TypeProtos.DataMode.OPTIONAL,
         registry);
 
-    resolveHash(config,
+    resolveHash(c,
         new ValueExpressions.FloatExpression(5.0f, ExpressionPosition.UNKNOWN),
         Types.required(TypeProtos.MinorType.FLOAT4),
         Types.required(TypeProtos.MinorType.FLOAT4),
@@ -98,14 +98,14 @@ public class TestSimpleFunctions extends ExecTest {
         registry);
 
     // test required vs nullable long input
-    resolveHash(config,
+    resolveHash(c,
         new TypedNullConstant(Types.optional(TypeProtos.MinorType.BIGINT)),
         Types.optional(TypeProtos.MinorType.BIGINT),
         Types.required(TypeProtos.MinorType.BIGINT),
         TypeProtos.DataMode.OPTIONAL,
         registry);
 
-    resolveHash(config,
+    resolveHash(c,
         new ValueExpressions.LongExpression(100L, ExpressionPosition.UNKNOWN),
         Types.required(TypeProtos.MinorType.BIGINT),
         Types.required(TypeProtos.MinorType.BIGINT),
@@ -113,14 +113,14 @@ public class TestSimpleFunctions extends ExecTest {
         registry);
 
     // test required vs nullable double input
-    resolveHash(config,
+    resolveHash(c,
         new TypedNullConstant(Types.optional(TypeProtos.MinorType.FLOAT8)),
         Types.optional(TypeProtos.MinorType.FLOAT8),
         Types.required(TypeProtos.MinorType.FLOAT8),
         TypeProtos.DataMode.OPTIONAL,
         registry);
 
-    resolveHash(config,
+    resolveHash(c,
         new ValueExpressions.DoubleExpression(100.0, 
ExpressionPosition.UNKNOWN),
         Types.required(TypeProtos.MinorType.FLOAT8),
         Types.required(TypeProtos.MinorType.FLOAT8),

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/resources/jars/DrillUDF-1.0-sources.jar
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/resources/jars/DrillUDF-1.0-sources.jar 
b/exec/java-exec/src/test/resources/jars/DrillUDF-1.0-sources.jar
new file mode 100644
index 0000000..b5965c9
Binary files /dev/null and 
b/exec/java-exec/src/test/resources/jars/DrillUDF-1.0-sources.jar differ

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/resources/jars/DrillUDF-1.0.jar
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/resources/jars/DrillUDF-1.0.jar 
b/exec/java-exec/src/test/resources/jars/DrillUDF-1.0.jar
new file mode 100644
index 0000000..7cd2eeb
Binary files /dev/null and 
b/exec/java-exec/src/test/resources/jars/DrillUDF-1.0.jar differ

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/resources/jars/DrillUDF-2.0-sources.jar
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/resources/jars/DrillUDF-2.0-sources.jar 
b/exec/java-exec/src/test/resources/jars/DrillUDF-2.0-sources.jar
new file mode 100644
index 0000000..1c8308c
Binary files /dev/null and 
b/exec/java-exec/src/test/resources/jars/DrillUDF-2.0-sources.jar differ

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/resources/jars/DrillUDF-2.0.jar
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/resources/jars/DrillUDF-2.0.jar 
b/exec/java-exec/src/test/resources/jars/DrillUDF-2.0.jar
new file mode 100644
index 0000000..3522c1e
Binary files /dev/null and 
b/exec/java-exec/src/test/resources/jars/DrillUDF-2.0.jar differ

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/resources/jars/DrillUDF_Copy-1.0-sources.jar
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/test/resources/jars/DrillUDF_Copy-1.0-sources.jar 
b/exec/java-exec/src/test/resources/jars/DrillUDF_Copy-1.0-sources.jar
new file mode 100644
index 0000000..fa449e2
Binary files /dev/null and 
b/exec/java-exec/src/test/resources/jars/DrillUDF_Copy-1.0-sources.jar differ

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/resources/jars/DrillUDF_Copy-1.0.jar
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/resources/jars/DrillUDF_Copy-1.0.jar 
b/exec/java-exec/src/test/resources/jars/DrillUDF_Copy-1.0.jar
new file mode 100644
index 0000000..8945fe7
Binary files /dev/null and 
b/exec/java-exec/src/test/resources/jars/DrillUDF_Copy-1.0.jar differ

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/resources/jars/DrillUDF_DupFunc-1.0-sources.jar
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/test/resources/jars/DrillUDF_DupFunc-1.0-sources.jar 
b/exec/java-exec/src/test/resources/jars/DrillUDF_DupFunc-1.0-sources.jar
new file mode 100644
index 0000000..b19ade6
Binary files /dev/null and 
b/exec/java-exec/src/test/resources/jars/DrillUDF_DupFunc-1.0-sources.jar differ

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/resources/jars/DrillUDF_DupFunc-1.0.jar
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/resources/jars/DrillUDF_DupFunc-1.0.jar 
b/exec/java-exec/src/test/resources/jars/DrillUDF_DupFunc-1.0.jar
new file mode 100644
index 0000000..56a649c
Binary files /dev/null and 
b/exec/java-exec/src/test/resources/jars/DrillUDF_DupFunc-1.0.jar differ

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/resources/jars/DrillUDF_Empty-1.0-sources.jar
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/test/resources/jars/DrillUDF_Empty-1.0-sources.jar 
b/exec/java-exec/src/test/resources/jars/DrillUDF_Empty-1.0-sources.jar
new file mode 100644
index 0000000..2a82dc9
Binary files /dev/null and 
b/exec/java-exec/src/test/resources/jars/DrillUDF_Empty-1.0-sources.jar differ

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/resources/jars/DrillUDF_Empty-1.0.jar
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/resources/jars/DrillUDF_Empty-1.0.jar 
b/exec/java-exec/src/test/resources/jars/DrillUDF_Empty-1.0.jar
new file mode 100644
index 0000000..11ed28b
Binary files /dev/null and 
b/exec/java-exec/src/test/resources/jars/DrillUDF_Empty-1.0.jar differ

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/resources/jars/DrillUDF_NoMarkerFile-1.0-sources.jar
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/test/resources/jars/DrillUDF_NoMarkerFile-1.0-sources.jar 
b/exec/java-exec/src/test/resources/jars/DrillUDF_NoMarkerFile-1.0-sources.jar
new file mode 100644
index 0000000..dbc97dd
Binary files /dev/null and 
b/exec/java-exec/src/test/resources/jars/DrillUDF_NoMarkerFile-1.0-sources.jar 
differ

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/resources/jars/DrillUDF_NoMarkerFile-1.0.jar
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/test/resources/jars/DrillUDF_NoMarkerFile-1.0.jar 
b/exec/java-exec/src/test/resources/jars/DrillUDF_NoMarkerFile-1.0.jar
new file mode 100644
index 0000000..cba65da
Binary files /dev/null and 
b/exec/java-exec/src/test/resources/jars/DrillUDF_NoMarkerFile-1.0.jar differ

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/resources/jars/v2/DrillUDF-1.0-sources.jar
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/resources/jars/v2/DrillUDF-1.0-sources.jar 
b/exec/java-exec/src/test/resources/jars/v2/DrillUDF-1.0-sources.jar
new file mode 100644
index 0000000..583b1c4
Binary files /dev/null and 
b/exec/java-exec/src/test/resources/jars/v2/DrillUDF-1.0-sources.jar differ

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/java-exec/src/test/resources/jars/v2/DrillUDF-1.0.jar
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/resources/jars/v2/DrillUDF-1.0.jar 
b/exec/java-exec/src/test/resources/jars/v2/DrillUDF-1.0.jar
new file mode 100644
index 0000000..42df4a4
Binary files /dev/null and 
b/exec/java-exec/src/test/resources/jars/v2/DrillUDF-1.0.jar differ

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/exec/jdbc-all/src/test/java/org/apache/drill/jdbc/ITTestShadedJar.java
----------------------------------------------------------------------
diff --git 
a/exec/jdbc-all/src/test/java/org/apache/drill/jdbc/ITTestShadedJar.java 
b/exec/jdbc-all/src/test/java/org/apache/drill/jdbc/ITTestShadedJar.java
index 7258661..f64b5b7 100644
--- a/exec/jdbc-all/src/test/java/org/apache/drill/jdbc/ITTestShadedJar.java
+++ b/exec/jdbc-all/src/test/java/org/apache/drill/jdbc/ITTestShadedJar.java
@@ -17,6 +17,7 @@
  */
 package org.apache.drill.jdbc;
 
+import java.io.File;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.net.MalformedURLException;
@@ -27,8 +28,10 @@ import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.util.UUID;
 import java.util.Vector;
 
+import org.apache.commons.io.FileUtils;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -47,6 +50,22 @@ public class ITTestShadedJar {
 
   }
 
+  static {
+    String dirConfDir = "DRILL_CONF_DIR";
+    if (System.getProperty(dirConfDir) == null) {
+      final File condDir = new File(System.getProperty("java.io.tmpdir"), 
UUID.randomUUID().toString());
+      condDir.mkdirs();
+      condDir.deleteOnExit();
+      Runtime.getRuntime().addShutdownHook(new Thread() {
+        @Override
+        public void run() {
+          FileUtils.deleteQuietly(condDir);
+        }
+      });
+      System.setProperty(dirConfDir, condDir.getAbsolutePath());
+    }
+  }
+
   @Test
   public void executeJdbcAllQuery() throws Exception {
 

http://git-wip-us.apache.org/repos/asf/drill/blob/89f2633f/protocol/src/main/java/org/apache/drill/exec/proto/SchemaUserBitShared.java
----------------------------------------------------------------------
diff --git 
a/protocol/src/main/java/org/apache/drill/exec/proto/SchemaUserBitShared.java 
b/protocol/src/main/java/org/apache/drill/exec/proto/SchemaUserBitShared.java
index 58efae3..037484d 100644
--- 
a/protocol/src/main/java/org/apache/drill/exec/proto/SchemaUserBitShared.java
+++ 
b/protocol/src/main/java/org/apache/drill/exec/proto/SchemaUserBitShared.java
@@ -2678,4 +2678,235 @@ public final class SchemaUserBitShared
         }
     }
 
+    public static final class Registry
+    {
+        public static final 
org.apache.drill.exec.proto.SchemaUserBitShared.Registry.MessageSchema WRITE =
+            new 
org.apache.drill.exec.proto.SchemaUserBitShared.Registry.MessageSchema();
+        public static final 
org.apache.drill.exec.proto.SchemaUserBitShared.Registry.BuilderSchema MERGE =
+            new 
org.apache.drill.exec.proto.SchemaUserBitShared.Registry.BuilderSchema();
+        
+        public static class MessageSchema implements 
com.dyuproject.protostuff.Schema<org.apache.drill.exec.proto.UserBitShared.Registry>
+        {
+            public void writeTo(com.dyuproject.protostuff.Output output, 
org.apache.drill.exec.proto.UserBitShared.Registry message) throws 
java.io.IOException
+            {
+                for(org.apache.drill.exec.proto.UserBitShared.Jar jar : 
message.getJarList())
+                    output.writeObject(1, jar, 
org.apache.drill.exec.proto.SchemaUserBitShared.Jar.WRITE, true);
+
+            }
+            public boolean 
isInitialized(org.apache.drill.exec.proto.UserBitShared.Registry message)
+            {
+                return message.isInitialized();
+            }
+            public java.lang.String getFieldName(int number)
+            {
+                return 
org.apache.drill.exec.proto.SchemaUserBitShared.Registry.getFieldName(number);
+            }
+            public int getFieldNumber(java.lang.String name)
+            {
+                return 
org.apache.drill.exec.proto.SchemaUserBitShared.Registry.getFieldNumber(name);
+            }
+            public 
java.lang.Class<org.apache.drill.exec.proto.UserBitShared.Registry> typeClass()
+            {
+                return 
org.apache.drill.exec.proto.UserBitShared.Registry.class;
+            }
+            public java.lang.String messageName()
+            {
+                return 
org.apache.drill.exec.proto.UserBitShared.Registry.class.getSimpleName();
+            }
+            public java.lang.String messageFullName()
+            {
+                return 
org.apache.drill.exec.proto.UserBitShared.Registry.class.getName();
+            }
+            //unused
+            public void mergeFrom(com.dyuproject.protostuff.Input input, 
org.apache.drill.exec.proto.UserBitShared.Registry message) throws 
java.io.IOException {}
+            public org.apache.drill.exec.proto.UserBitShared.Registry 
newMessage() { return null; }
+        }
+        public static class BuilderSchema implements 
com.dyuproject.protostuff.Schema<org.apache.drill.exec.proto.UserBitShared.Registry.Builder>
+        {
+            public void mergeFrom(com.dyuproject.protostuff.Input input, 
org.apache.drill.exec.proto.UserBitShared.Registry.Builder builder) throws 
java.io.IOException
+            {
+                for(int number = input.readFieldNumber(this);; number = 
input.readFieldNumber(this))
+                {
+                    switch(number)
+                    {
+                        case 0:
+                            return;
+                        case 1:
+                            
builder.addJar(input.mergeObject(org.apache.drill.exec.proto.UserBitShared.Jar.newBuilder(),
 org.apache.drill.exec.proto.SchemaUserBitShared.Jar.MERGE));
+
+                            break;
+                        default:
+                            input.handleUnknownField(number, this);
+                    }
+                }
+            }
+            public boolean 
isInitialized(org.apache.drill.exec.proto.UserBitShared.Registry.Builder 
builder)
+            {
+                return builder.isInitialized();
+            }
+            public org.apache.drill.exec.proto.UserBitShared.Registry.Builder 
newMessage()
+            {
+                return 
org.apache.drill.exec.proto.UserBitShared.Registry.newBuilder();
+            }
+            public java.lang.String getFieldName(int number)
+            {
+                return 
org.apache.drill.exec.proto.SchemaUserBitShared.Registry.getFieldName(number);
+            }
+            public int getFieldNumber(java.lang.String name)
+            {
+                return 
org.apache.drill.exec.proto.SchemaUserBitShared.Registry.getFieldNumber(name);
+            }
+            public 
java.lang.Class<org.apache.drill.exec.proto.UserBitShared.Registry.Builder> 
typeClass()
+            {
+                return 
org.apache.drill.exec.proto.UserBitShared.Registry.Builder.class;
+            }
+            public java.lang.String messageName()
+            {
+                return 
org.apache.drill.exec.proto.UserBitShared.Registry.class.getSimpleName();
+            }
+            public java.lang.String messageFullName()
+            {
+                return 
org.apache.drill.exec.proto.UserBitShared.Registry.class.getName();
+            }
+            //unused
+            public void writeTo(com.dyuproject.protostuff.Output output, 
org.apache.drill.exec.proto.UserBitShared.Registry.Builder builder) throws 
java.io.IOException {}
+        }
+        public static java.lang.String getFieldName(int number)
+        {
+            switch(number)
+            {
+                case 1: return "jar";
+                default: return null;
+            }
+        }
+        public static int getFieldNumber(java.lang.String name)
+        {
+            java.lang.Integer number = fieldMap.get(name);
+            return number == null ? 0 : number.intValue();
+        }
+        private static final 
java.util.HashMap<java.lang.String,java.lang.Integer> fieldMap = new 
java.util.HashMap<java.lang.String,java.lang.Integer>();
+        static
+        {
+            fieldMap.put("jar", 1);
+        }
+    }
+
+    public static final class Jar
+    {
+        public static final 
org.apache.drill.exec.proto.SchemaUserBitShared.Jar.MessageSchema WRITE =
+            new 
org.apache.drill.exec.proto.SchemaUserBitShared.Jar.MessageSchema();
+        public static final 
org.apache.drill.exec.proto.SchemaUserBitShared.Jar.BuilderSchema MERGE =
+            new 
org.apache.drill.exec.proto.SchemaUserBitShared.Jar.BuilderSchema();
+        
+        public static class MessageSchema implements 
com.dyuproject.protostuff.Schema<org.apache.drill.exec.proto.UserBitShared.Jar>
+        {
+            public void writeTo(com.dyuproject.protostuff.Output output, 
org.apache.drill.exec.proto.UserBitShared.Jar message) throws 
java.io.IOException
+            {
+                if(message.hasName())
+                    output.writeString(1, message.getName(), false);
+                for(String functionSignature : 
message.getFunctionSignatureList())
+                    output.writeString(2, functionSignature, true);
+            }
+            public boolean 
isInitialized(org.apache.drill.exec.proto.UserBitShared.Jar message)
+            {
+                return message.isInitialized();
+            }
+            public java.lang.String getFieldName(int number)
+            {
+                return 
org.apache.drill.exec.proto.SchemaUserBitShared.Jar.getFieldName(number);
+            }
+            public int getFieldNumber(java.lang.String name)
+            {
+                return 
org.apache.drill.exec.proto.SchemaUserBitShared.Jar.getFieldNumber(name);
+            }
+            public 
java.lang.Class<org.apache.drill.exec.proto.UserBitShared.Jar> typeClass()
+            {
+                return org.apache.drill.exec.proto.UserBitShared.Jar.class;
+            }
+            public java.lang.String messageName()
+            {
+                return 
org.apache.drill.exec.proto.UserBitShared.Jar.class.getSimpleName();
+            }
+            public java.lang.String messageFullName()
+            {
+                return 
org.apache.drill.exec.proto.UserBitShared.Jar.class.getName();
+            }
+            //unused
+            public void mergeFrom(com.dyuproject.protostuff.Input input, 
org.apache.drill.exec.proto.UserBitShared.Jar message) throws 
java.io.IOException {}
+            public org.apache.drill.exec.proto.UserBitShared.Jar newMessage() 
{ return null; }
+        }
+        public static class BuilderSchema implements 
com.dyuproject.protostuff.Schema<org.apache.drill.exec.proto.UserBitShared.Jar.Builder>
+        {
+            public void mergeFrom(com.dyuproject.protostuff.Input input, 
org.apache.drill.exec.proto.UserBitShared.Jar.Builder builder) throws 
java.io.IOException
+            {
+                for(int number = input.readFieldNumber(this);; number = 
input.readFieldNumber(this))
+                {
+                    switch(number)
+                    {
+                        case 0:
+                            return;
+                        case 1:
+                            builder.setName(input.readString());
+                            break;
+                        case 2:
+                            builder.addFunctionSignature(input.readString());
+                            break;
+                        default:
+                            input.handleUnknownField(number, this);
+                    }
+                }
+            }
+            public boolean 
isInitialized(org.apache.drill.exec.proto.UserBitShared.Jar.Builder builder)
+            {
+                return builder.isInitialized();
+            }
+            public org.apache.drill.exec.proto.UserBitShared.Jar.Builder 
newMessage()
+            {
+                return 
org.apache.drill.exec.proto.UserBitShared.Jar.newBuilder();
+            }
+            public java.lang.String getFieldName(int number)
+            {
+                return 
org.apache.drill.exec.proto.SchemaUserBitShared.Jar.getFieldName(number);
+            }
+            public int getFieldNumber(java.lang.String name)
+            {
+                return 
org.apache.drill.exec.proto.SchemaUserBitShared.Jar.getFieldNumber(name);
+            }
+            public 
java.lang.Class<org.apache.drill.exec.proto.UserBitShared.Jar.Builder> 
typeClass()
+            {
+                return 
org.apache.drill.exec.proto.UserBitShared.Jar.Builder.class;
+            }
+            public java.lang.String messageName()
+            {
+                return 
org.apache.drill.exec.proto.UserBitShared.Jar.class.getSimpleName();
+            }
+            public java.lang.String messageFullName()
+            {
+                return 
org.apache.drill.exec.proto.UserBitShared.Jar.class.getName();
+            }
+            //unused
+            public void writeTo(com.dyuproject.protostuff.Output output, 
org.apache.drill.exec.proto.UserBitShared.Jar.Builder builder) throws 
java.io.IOException {}
+        }
+        public static java.lang.String getFieldName(int number)
+        {
+            switch(number)
+            {
+                case 1: return "name";
+                case 2: return "functionSignature";
+                default: return null;
+            }
+        }
+        public static int getFieldNumber(java.lang.String name)
+        {
+            java.lang.Integer number = fieldMap.get(name);
+            return number == null ? 0 : number.intValue();
+        }
+        private static final 
java.util.HashMap<java.lang.String,java.lang.Integer> fieldMap = new 
java.util.HashMap<java.lang.String,java.lang.Integer>();
+        static
+        {
+            fieldMap.put("name", 1);
+            fieldMap.put("functionSignature", 2);
+        }
+    }
+
 }

Reply via email to