[NO ISSUE][COMP][RT] External function improvements

- user model changes: no
- storage format changes: no
- interface changes: no

Details:
- Support multipart external identifiers in CREATE FUNCTION
- Add preliminary code for Python UDFs
- Some refactoring in external function framework
- Add method to get IServiceContext from IEvaluatorContext

Change-Id: I7ec91f5be2efa8409cda3a3c13f5e8b4de3e75e8
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/5244
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Ian Maxon <imaxon@uci.edu>
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/jobgen/QueryLogicalExpressionJobGen.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/jobgen/QueryLogicalExpressionJobGen.java
index 3a847a5..74656e4 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/jobgen/QueryLogicalExpressionJobGen.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/jobgen/QueryLogicalExpressionJobGen.java
@@ -22,7 +22,6 @@
 
 import org.apache.asterix.common.api.IApplicationContext;
 import org.apache.asterix.common.config.CompilerProperties;
-import org.apache.asterix.common.dataflow.ICcApplicationContext;
 import org.apache.asterix.common.functions.FunctionDescriptorTag;
 import org.apache.asterix.external.library.ExternalFunctionDescriptorProvider;
 import org.apache.asterix.metadata.declared.MetadataProvider;
@@ -141,8 +140,8 @@
         IScalarEvaluatorFactory[] args = codegenArguments(expr, env, inputSchemas, context);
         IFunctionDescriptor fd = null;
         if (expr.getFunctionInfo() instanceof IExternalFunctionInfo) {
-            fd = ExternalFunctionDescriptorProvider.getExternalFunctionDescriptor(
-                    (IExternalFunctionInfo) expr.getFunctionInfo(), (ICcApplicationContext) context.getAppContext());
+            fd = ExternalFunctionDescriptorProvider
+                    .getExternalFunctionDescriptor((IExternalFunctionInfo) expr.getFunctionInfo());
             CompilerProperties props = ((IApplicationContext) context.getAppContext()).getCompilerProperties();
             FunctionTypeInferers.SET_ARGUMENTS_TYPE.infer(expr, fd, env, props);
         } else {
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/ConstantFoldingRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/ConstantFoldingRule.java
index d3093e7..a34a991 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/ConstantFoldingRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/ConstantFoldingRule.java
@@ -43,11 +43,11 @@
 import org.apache.asterix.formats.nontagged.TypeTraitProvider;
 import org.apache.asterix.jobgen.QueryLogicalExpressionJobGen;
 import org.apache.asterix.metadata.declared.MetadataProvider;
-import org.apache.asterix.metadata.entities.Function;
 import org.apache.asterix.om.base.ADouble;
 import org.apache.asterix.om.base.IAObject;
 import org.apache.asterix.om.constants.AsterixConstantValue;
 import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.asterix.om.functions.ExternalFunctionLanguage;
 import org.apache.asterix.om.functions.IExternalFunctionInfo;
 import org.apache.asterix.om.typecomputer.impl.TypeComputeUtils;
 import org.apache.asterix.om.types.ARecordType;
@@ -87,7 +87,8 @@
 import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
 import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
 import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
-import org.apache.hyracks.algebricks.runtime.evaluators.EvaluatorContext;
+import org.apache.hyracks.api.application.IServiceContext;
+import org.apache.hyracks.api.context.IHyracksTaskContext;
 import org.apache.hyracks.api.dataflow.value.ISerializerDeserializer;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
 import org.apache.hyracks.api.exceptions.IWarningCollector;
@@ -167,17 +168,19 @@
     }
 
     private class ConstantFoldingVisitor implements ILogicalExpressionVisitor<Pair<Boolean, ILogicalExpression>, Void>,
-            ILogicalExpressionReferenceTransform {
+            ILogicalExpressionReferenceTransform, IEvaluatorContext {
 
         private final IPointable p = VoidPointable.FACTORY.createPointable();
         private final ByteBufferInputStream bbis = new ByteBufferInputStream();
         private final DataInputStream dis = new DataInputStream(bbis);
         private final WarningCollector warningCollector = new WarningCollector();
-        private final IEvaluatorContext evalContext = new EvaluatorContext(warningCollector);
         private IOptimizationContext optContext;
+        private IServiceContext serviceContext;
 
         private void reset(IOptimizationContext context) {
             optContext = context;
+            serviceContext =
+                    ((MetadataProvider) context.getMetadataProvider()).getApplicationContext().getServiceContext();
         }
 
         @Override
@@ -230,7 +233,7 @@
                         _emptyTypeEnv, _emptySchemas, jobGenCtx);
 
                 warningCollector.clear();
-                IScalarEvaluator eval = fact.createScalarEvaluator(evalContext);
+                IScalarEvaluator eval = fact.createScalarEvaluator(this);
                 eval.evaluate(null, p);
                 IAType returnType = (IAType) _emptyTypeEnv.getType(expr);
                 ATypeTag runtimeType = PointableHelper.getTypeTag(p);
@@ -362,7 +365,7 @@
             // skip external functions that are not implemented in Java
             IFunctionInfo fi = function.getFunctionInfo();
             if (fi instanceof IExternalFunctionInfo
-                    && !Function.FunctionLanguage.JAVA.name().equals(((IExternalFunctionInfo) fi).getLanguage())) {
+                    && !ExternalFunctionLanguage.JAVA.equals(((IExternalFunctionInfo) fi).getLanguage())) {
                 return false;
             }
             // skip all functions that would produce records/arrays/multisets (derived types) in their open format
@@ -398,5 +401,22 @@
             }
             return true;
         }
+
+        // IEvaluatorContext
+
+        @Override
+        public IServiceContext getServiceContext() {
+            return serviceContext;
+        }
+
+        @Override
+        public IHyracksTaskContext getTaskContext() {
+            return null;
+        }
+
+        @Override
+        public IWarningCollector getWarningCollector() {
+            return warningCollector;
+        }
     }
 }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/AqlExpressionToPlanTranslator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/AqlExpressionToPlanTranslator.java
index b93335b..c0d6f82 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/AqlExpressionToPlanTranslator.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/AqlExpressionToPlanTranslator.java
@@ -35,7 +35,6 @@
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.statement.Query;
 import org.apache.asterix.metadata.declared.MetadataProvider;
-import org.apache.asterix.metadata.entities.Function;
 import org.apache.asterix.om.types.BuiltinType;
 import org.apache.commons.lang3.mutable.Mutable;
 import org.apache.commons.lang3.mutable.MutableObject;
@@ -78,11 +77,6 @@
     }
 
     @Override
-    protected Function.FunctionLanguage getFunctionLanguage() {
-        return Function.FunctionLanguage.AQL;
-    }
-
-    @Override
     public Pair<ILogicalOperator, LogicalVariable> visit(ForClause fc, Mutable<ILogicalOperator> tupSource)
             throws CompilationException {
         LogicalVariable v = context.newVarFromExpression(fc.getVarExpr());
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/LangExpressionToPlanTranslator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/LangExpressionToPlanTranslator.java
index f3480d5..6550bb4 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/LangExpressionToPlanTranslator.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/LangExpressionToPlanTranslator.java
@@ -884,8 +884,6 @@
         return f;
     }
 
-    protected abstract Function.FunctionLanguage getFunctionLanguage();
-
     private AbstractFunctionCallExpression lookupUserDefinedFunction(FunctionSignature signature,
             List<Mutable<ILogicalExpression>> args, SourceLocation sourceLoc) throws CompilationException {
         try {
@@ -895,9 +893,10 @@
                 return null;
             }
             IFunctionInfo finfo =
-                    getFunctionLanguage().equals(function.getLanguage()) ? FunctionUtil.getFunctionInfo(signature)
-                            : ExternalFunctionCompilerUtil
-                                    .getExternalFunctionInfo(metadataProvider.getMetadataTxnContext(), function);
+                    function.isExternal()
+                            ? ExternalFunctionCompilerUtil
+                                    .getExternalFunctionInfo(metadataProvider.getMetadataTxnContext(), function)
+                            : FunctionUtil.getFunctionInfo(signature);
             AbstractFunctionCallExpression f = new ScalarFunctionCallExpression(finfo, args);
             f.setSourceLocation(sourceLoc);
             return f;
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SqlppExpressionToPlanTranslator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SqlppExpressionToPlanTranslator.java
index 3023305..82dc344 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SqlppExpressionToPlanTranslator.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SqlppExpressionToPlanTranslator.java
@@ -82,7 +82,6 @@
 import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
 import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
 import org.apache.asterix.metadata.declared.MetadataProvider;
-import org.apache.asterix.metadata.entities.Function;
 import org.apache.asterix.om.base.ABoolean;
 import org.apache.asterix.om.base.AInt32;
 import org.apache.asterix.om.base.AString;
@@ -162,11 +161,6 @@
     }
 
     @Override
-    protected Function.FunctionLanguage getFunctionLanguage() {
-        return Function.FunctionLanguage.SQLPP;
-    }
-
-    @Override
     public Pair<ILogicalOperator, LogicalVariable> visit(Query q, Mutable<ILogicalOperator> tupSource)
             throws CompilationException {
         Expression queryBody = q.getBody();
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
index dcf2f2d..16ca37f 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
@@ -158,6 +158,7 @@
 import org.apache.asterix.metadata.entities.NodeGroup;
 import org.apache.asterix.metadata.entities.Synonym;
 import org.apache.asterix.metadata.feeds.FeedMetadataUtil;
+import org.apache.asterix.metadata.functions.ExternalFunctionCompilerUtil;
 import org.apache.asterix.metadata.lock.ExternalDatasetsRegistry;
 import org.apache.asterix.metadata.utils.DatasetUtil;
 import org.apache.asterix.metadata.utils.ExternalIndexingOperations;
@@ -166,6 +167,7 @@
 import org.apache.asterix.metadata.utils.MetadataConstants;
 import org.apache.asterix.metadata.utils.MetadataUtil;
 import org.apache.asterix.om.base.IAObject;
+import org.apache.asterix.om.functions.ExternalFunctionLanguage;
 import org.apache.asterix.om.types.ARecordType;
 import org.apache.asterix.om.types.ATypeTag;
 import org.apache.asterix.om.types.AUnionType;
@@ -1843,26 +1845,24 @@
                 if (lang == null) {
                     throw new CompilationException(ErrorCode.COMPILATION_INCOMPATIBLE_FUNCTION_LANGUAGE, sourceLoc, "");
                 }
-                Function.FunctionLanguage functionLang;
+                ExternalFunctionLanguage functionLang;
                 try {
-                    functionLang = Function.FunctionLanguage.valueOf(lang.toUpperCase(Locale.ROOT));
+                    functionLang = ExternalFunctionLanguage.valueOf(lang.toUpperCase(Locale.ROOT));
                 } catch (IllegalArgumentException e) {
                     throw new CompilationException(ErrorCode.COMPILATION_INCOMPATIBLE_FUNCTION_LANGUAGE, sourceLoc,
                             lang);
                 }
-                if (functionLang.equals(getFunctionLanguage())) {
-                    throw new CompilationException(ErrorCode.COMPILATION_INCOMPATIBLE_FUNCTION_LANGUAGE, sourceLoc,
-                            lang);
-                }
                 Library libraryInMetadata = MetadataManager.INSTANCE.getLibrary(mdTxnCtx, dataverseName, libraryName);
                 if (libraryInMetadata == null) {
                     throw new CompilationException(ErrorCode.UNKNOWN_LIBRARY, sourceLoc, libraryName);
                 }
                 // Add functions
+                String body = ExternalFunctionCompilerUtil.encodeExternalIdentifier(signature, functionLang,
+                        cfs.getExternalIdentifier());
                 List<List<Triple<DataverseName, String, String>>> dependencies =
                         FunctionUtil.getExternalFunctionDependencies(dependentTypes);
-                Function f = new Function(signature, argNames, argTypes, returnType, cfs.getExternalIdentifier(),
-                        FunctionKind.SCALAR.toString(), functionLang, libraryName, cfs.getNullCall(),
+                Function f = new Function(signature, argNames, argTypes, returnType, body,
+                        FunctionKind.SCALAR.toString(), functionLang.name(), libraryName, cfs.getNullCall(),
                         cfs.getDeterministic(), cfs.getResources(), dependencies);
                 MetadataManager.INSTANCE.addFunction(mdTxnCtx, f);
                 if (LOGGER.isInfoEnabled()) {
@@ -1882,7 +1882,8 @@
                         FunctionUtil.getFunctionDependencies(rewriterFactory.createQueryRewriter(),
                                 cfs.getFunctionBodyExpression(), metadataProvider, dependentTypes);
                 Function function = new Function(signature, argNames, argTypes, returnType, cfs.getFunctionBody(),
-                        FunctionKind.SCALAR.toString(), getFunctionLanguage(), null, null, null, null, dependencies);
+                        FunctionKind.SCALAR.toString(), compilationProvider.getParserFactory().getLanguage(), null,
+                        null, null, null, dependencies);
                 MetadataManager.INSTANCE.addFunction(mdTxnCtx, function);
                 if (LOGGER.isInfoEnabled()) {
                     LOGGER.info("Installed function: " + signature);
@@ -1949,17 +1950,6 @@
         }
     }
 
-    private Function.FunctionLanguage getFunctionLanguage() {
-        switch (compilationProvider.getLanguage()) {
-            case SQLPP:
-                return Function.FunctionLanguage.SQLPP;
-            case AQL:
-                return Function.FunctionLanguage.AQL;
-            default:
-                throw new IllegalStateException(String.valueOf(compilationProvider.getLanguage()));
-        }
-    }
-
     protected boolean isFunctionUsed(MetadataTransactionContext ctx, FunctionSignature signature,
             DataverseName currentDataverse) throws AlgebricksException {
         List<Dataverse> allDataverses = MetadataManager.INSTANCE.getDataverses(ctx);
diff --git a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalFunction.java b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalFunction.java
deleted file mode 100755
index 55d6f5c..0000000
--- a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalFunction.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.asterix.external.library;
-
-import java.io.IOException;
-
-import org.apache.asterix.common.api.IApplicationContext;
-import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.common.exceptions.RuntimeDataException;
-import org.apache.asterix.common.functions.FunctionSignature;
-import org.apache.asterix.common.library.ILibraryManager;
-import org.apache.asterix.common.metadata.DataverseName;
-import org.apache.asterix.external.api.IExternalFunction;
-import org.apache.asterix.external.api.IFunctionFactory;
-import org.apache.asterix.external.api.IFunctionHelper;
-import org.apache.asterix.om.functions.IExternalFunctionInfo;
-import org.apache.asterix.om.types.IAType;
-import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
-import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
-import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
-import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
-import org.apache.hyracks.api.exceptions.HyracksDataException;
-import org.apache.hyracks.data.std.api.IPointable;
-import org.apache.hyracks.data.std.primitive.VoidPointable;
-import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
-import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
-
-public abstract class ExternalFunction implements IExternalFunction {
-
-    protected final IExternalFunctionInfo finfo;
-    protected final IFunctionFactory externalFunctionFactory;
-    protected final IExternalFunction externalFunctionInstance;
-    protected final IScalarEvaluatorFactory[] evaluatorFactories;
-    protected final IPointable inputVal = new VoidPointable();
-    protected final ArrayBackedValueStorage resultBuffer = new ArrayBackedValueStorage();
-    protected final IScalarEvaluator[] argumentEvaluators;
-    protected final JavaFunctionHelper functionHelper;
-    protected final IAType[] argTypes;
-
-    public ExternalFunction(IExternalFunctionInfo finfo, IScalarEvaluatorFactory args[], IAType[] argTypes,
-            IEvaluatorContext context, IApplicationContext appCtx) throws HyracksDataException {
-        this.finfo = finfo;
-        this.evaluatorFactories = args;
-        this.argTypes = argTypes;
-        argumentEvaluators = new IScalarEvaluator[args.length];
-        for (int i = 0; i < args.length; i++) {
-            argumentEvaluators[i] = args[i].createScalarEvaluator(context);
-        }
-
-        ILibraryManager libraryManager = appCtx.getLibraryManager();
-        String functionLibary = finfo.getLibrary();
-        DataverseName dataverse = FunctionSignature.getDataverseName(finfo.getFunctionIdentifier());
-
-        functionHelper = new JavaFunctionHelper(finfo, argTypes, resultBuffer);
-        ClassLoader libraryClassLoader = libraryManager.getLibraryClassLoader(dataverse, functionLibary);
-        String classname = finfo.getFunctionBody().trim();
-        Class<?> clazz;
-        try {
-            clazz = Class.forName(classname, true, libraryClassLoader);
-            externalFunctionFactory = (IFunctionFactory) clazz.newInstance();
-            externalFunctionInstance = externalFunctionFactory.getExternalFunction();
-        } catch (Exception e) {
-            throw new RuntimeDataException(ErrorCode.LIBRARY_EXTERNAL_FUNCTION_UNABLE_TO_LOAD_CLASS, e, classname);
-        }
-    }
-
-    public void setArguments(IFrameTupleReference tuple) throws AlgebricksException, IOException {
-        for (int i = 0; i < evaluatorFactories.length; i++) {
-            argumentEvaluators[i].evaluate(tuple, inputVal);
-            functionHelper.setArgument(i, inputVal);
-        }
-    }
-
-    @Override
-    public void deinitialize() {
-        externalFunctionInstance.deinitialize();
-    }
-
-    @Override
-    public void initialize(IFunctionHelper functionHelper) throws Exception {
-        externalFunctionInstance.initialize(functionHelper);
-    }
-
-}
diff --git a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalFunctionDescriptorProvider.java b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalFunctionDescriptorProvider.java
index 952b620..8ac507f 100755
--- a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalFunctionDescriptorProvider.java
+++ b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalFunctionDescriptorProvider.java
@@ -18,64 +18,24 @@
  */
 package org.apache.asterix.external.library;
 
-import org.apache.asterix.common.api.IApplicationContext;
+import org.apache.asterix.common.exceptions.AsterixException;
+import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.om.functions.IExternalFunctionInfo;
 import org.apache.asterix.om.functions.IFunctionDescriptor;
-import org.apache.asterix.om.types.IAType;
-import org.apache.asterix.runtime.evaluators.base.AbstractScalarFunctionDynamicDescriptor;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
-import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
-import org.apache.hyracks.algebricks.core.algebra.functions.IFunctionInfo;
-import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
 
 public class ExternalFunctionDescriptorProvider {
 
-    public static IFunctionDescriptor getExternalFunctionDescriptor(IExternalFunctionInfo finfo,
-            IApplicationContext appCtx) throws AlgebricksException {
+    public static IFunctionDescriptor getExternalFunctionDescriptor(IExternalFunctionInfo finfo)
+            throws AlgebricksException {
         switch (finfo.getKind()) {
             case SCALAR:
-                return new ExternalScalarFunctionDescriptor(finfo, appCtx);
+                return new ExternalScalarFunctionDescriptor(finfo);
             case AGGREGATE:
             case UNNEST:
-                throw new AlgebricksException("Unsupported function kind :" + finfo.getKind());
+                throw new AsterixException(ErrorCode.LIBRARY_EXTERNAL_FUNCTION_UNSUPPORTED_KIND, finfo.getKind());
             default:
-                break;
-        }
-        return null;
-    }
-
-}
-
-class ExternalScalarFunctionDescriptor extends AbstractScalarFunctionDynamicDescriptor implements IFunctionDescriptor {
-    private static final long serialVersionUID = 1L;
-    private final IFunctionInfo finfo;
-    private IScalarEvaluatorFactory evaluatorFactory;
-    private final transient IApplicationContext appCtx;
-    private IAType[] argTypes;
-
-    public ExternalScalarFunctionDescriptor(IFunctionInfo finfo, IApplicationContext appCtx) {
-        this.finfo = finfo;
-        this.appCtx = appCtx;
-    }
-
-    @Override
-    public void setImmutableStates(Object... states) {
-        argTypes = new IAType[states.length];
-        for (int i = 0; i < states.length; i++) {
-            argTypes[i] = (IAType) states[i];
+                throw new AsterixException(ErrorCode.LIBRARY_EXTERNAL_FUNCTION_UNKNOWN_KIND, finfo.getKind());
         }
     }
-
-    @Override
-    public IScalarEvaluatorFactory createEvaluatorFactory(IScalarEvaluatorFactory[] args) throws AlgebricksException {
-        evaluatorFactory =
-                new ExternalScalarFunctionEvaluatorFactory((IExternalFunctionInfo) finfo, args, argTypes, appCtx);
-        return evaluatorFactory;
-    }
-
-    @Override
-    public FunctionIdentifier getIdentifier() {
-        return finfo.getFunctionIdentifier();
-    }
-
-}
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalFunctionProvider.java b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalFunctionProvider.java
deleted file mode 100755
index cfc5895..0000000
--- a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalFunctionProvider.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.asterix.external.library;
-
-import org.apache.asterix.common.api.IApplicationContext;
-import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.common.exceptions.RuntimeDataException;
-import org.apache.asterix.external.api.IExternalFunction;
-import org.apache.asterix.external.api.IExternalScalarFunction;
-import org.apache.asterix.external.api.IFunctionHelper;
-import org.apache.asterix.om.functions.IExternalFunctionInfo;
-import org.apache.asterix.om.types.IAType;
-import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
-import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
-import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
-import org.apache.hyracks.api.exceptions.HyracksDataException;
-import org.apache.hyracks.data.std.api.IPointable;
-import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
-
-public class ExternalFunctionProvider {
-
-    public static IExternalFunction getExternalFunctionEvaluator(IExternalFunctionInfo finfo,
-            IScalarEvaluatorFactory[] args, IAType[] argTypes, IEvaluatorContext context, IApplicationContext appCtx)
-            throws HyracksDataException {
-        switch (finfo.getKind()) {
-            case SCALAR:
-                return new ExternalScalarFunction(finfo, args, argTypes, context, appCtx);
-            case AGGREGATE:
-            case UNNEST:
-                throw new RuntimeDataException(ErrorCode.LIBRARY_EXTERNAL_FUNCTION_UNSUPPORTED_KIND, finfo.getKind());
-            default:
-                throw new RuntimeDataException(ErrorCode.LIBRARY_EXTERNAL_FUNCTION_UNKNOWN_KIND, finfo.getKind());
-        }
-    }
-}
-
-class ExternalScalarFunction extends ExternalFunction implements IExternalScalarFunction, IScalarEvaluator {
-
-    public ExternalScalarFunction(IExternalFunctionInfo finfo, IScalarEvaluatorFactory[] args, IAType[] argTypes,
-            IEvaluatorContext context, IApplicationContext appCtx) throws HyracksDataException {
-        super(finfo, args, argTypes, context, appCtx);
-        try {
-            initialize(functionHelper);
-        } catch (Exception e) {
-            throw HyracksDataException.create(e);
-        }
-    }
-
-    @Override
-    public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
-        try {
-            setArguments(tuple);
-            evaluate(functionHelper);
-            result.set(resultBuffer.getByteArray(), resultBuffer.getStartOffset(), resultBuffer.getLength());
-            functionHelper.reset();
-        } catch (Exception e) {
-            throw HyracksDataException.create(e);
-        }
-    }
-
-    @Override
-    public void evaluate(IFunctionHelper argumentProvider) throws HyracksDataException {
-        try {
-            resultBuffer.reset();
-            ((IExternalScalarFunction) externalFunctionInstance).evaluate(argumentProvider);
-            if (!argumentProvider.isValidResult()) {
-                throw new RuntimeDataException(ErrorCode.EXTERNAL_UDF_RESULT_TYPE_ERROR);
-            }
-        } catch (Exception e) {
-            throw HyracksDataException.create(e);
-        }
-    }
-
-}
diff --git a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalScalarFunctionDescriptor.java b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalScalarFunctionDescriptor.java
new file mode 100644
index 0000000..c033cea
--- /dev/null
+++ b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalScalarFunctionDescriptor.java
@@ -0,0 +1,57 @@
+/*
+ * 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.asterix.external.library;
+
+import org.apache.asterix.om.functions.IExternalFunctionInfo;
+import org.apache.asterix.om.functions.IFunctionDescriptor;
+import org.apache.asterix.om.types.IAType;
+import org.apache.asterix.runtime.evaluators.base.AbstractScalarFunctionDynamicDescriptor;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+
+public class ExternalScalarFunctionDescriptor extends AbstractScalarFunctionDynamicDescriptor
+        implements IFunctionDescriptor {
+
+    private static final long serialVersionUID = 2L;
+    private final IExternalFunctionInfo finfo;
+    private IAType[] argTypes;
+
+    public ExternalScalarFunctionDescriptor(IExternalFunctionInfo finfo) {
+        this.finfo = finfo;
+    }
+
+    @Override
+    public void setImmutableStates(Object... states) {
+        argTypes = new IAType[states.length];
+        for (int i = 0; i < states.length; i++) {
+            argTypes[i] = (IAType) states[i];
+        }
+    }
+
+    @Override
+    public IScalarEvaluatorFactory createEvaluatorFactory(IScalarEvaluatorFactory[] args) {
+        return new ExternalScalarFunctionEvaluatorFactory(finfo, args, argTypes);
+    }
+
+    @Override
+    public FunctionIdentifier getIdentifier() {
+        return finfo.getFunctionIdentifier();
+    }
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalScalarFunctionEvaluator.java b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalScalarFunctionEvaluator.java
new file mode 100644
index 0000000..ce23cac
--- /dev/null
+++ b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalScalarFunctionEvaluator.java
@@ -0,0 +1,49 @@
+/*
+ * 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.asterix.external.library;
+
+import org.apache.asterix.common.api.IApplicationContext;
+import org.apache.asterix.common.library.ILibraryManager;
+import org.apache.asterix.om.functions.IExternalFunctionInfo;
+import org.apache.asterix.om.types.IAType;
+import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+
+public abstract class ExternalScalarFunctionEvaluator implements IScalarEvaluator {
+
+    protected final IExternalFunctionInfo finfo;
+    protected final IScalarEvaluator[] argEvals;
+    protected final IAType[] argTypes;
+    protected final ILibraryManager libraryManager;
+
+    public ExternalScalarFunctionEvaluator(IExternalFunctionInfo finfo, IScalarEvaluatorFactory[] args,
+            IAType[] argTypes, IEvaluatorContext context) throws HyracksDataException {
+        this.finfo = finfo;
+        this.argTypes = argTypes;
+        argEvals = new IScalarEvaluator[args.length];
+        for (int i = 0; i < args.length; i++) {
+            argEvals[i] = args[i].createScalarEvaluator(context);
+        }
+        libraryManager =
+                ((IApplicationContext) context.getServiceContext().getApplicationContext()).getLibraryManager();
+    }
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalScalarFunctionEvaluatorFactory.java b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalScalarFunctionEvaluatorFactory.java
index 80aee79..ec757a1 100755
--- a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalScalarFunctionEvaluatorFactory.java
+++ b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalScalarFunctionEvaluatorFactory.java
@@ -18,10 +18,9 @@
  */
 package org.apache.asterix.external.library;
 
-import org.apache.asterix.common.api.IApplicationContext;
+import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.om.functions.IExternalFunctionInfo;
 import org.apache.asterix.om.types.IAType;
-import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
 import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
 import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
@@ -32,22 +31,25 @@
     private static final long serialVersionUID = 1L;
     private final IExternalFunctionInfo finfo;
     private final IScalarEvaluatorFactory[] args;
-    private final transient IApplicationContext appCtx;
     private final IAType[] argTypes;
 
     public ExternalScalarFunctionEvaluatorFactory(IExternalFunctionInfo finfo, IScalarEvaluatorFactory[] args,
-            IAType[] argTypes, IApplicationContext appCtx) throws AlgebricksException {
+            IAType[] argTypes) {
         this.finfo = finfo;
         this.args = args;
         this.argTypes = argTypes;
-        this.appCtx = appCtx;
     }
 
     @Override
     public IScalarEvaluator createScalarEvaluator(IEvaluatorContext ctx) throws HyracksDataException {
-        return (ExternalScalarFunction) ExternalFunctionProvider.getExternalFunctionEvaluator(finfo, args, argTypes,
-                ctx, appCtx == null ? (IApplicationContext) ctx.getTaskContext().getJobletContext().getServiceContext()
-                        .getApplicationContext() : appCtx);
+        switch (finfo.getLanguage()) {
+            case JAVA:
+                return new ExternalScalarJavaFunctionEvaluator(finfo, args, argTypes, ctx);
+            case PYTHON:
+                return new ExternalScalarPythonFunctionEvaluator(finfo, args, argTypes, ctx);
+            default:
+                throw new HyracksDataException(ErrorCode.ASTERIX, ErrorCode.LIBRARY_EXTERNAL_FUNCTION_UNSUPPORTED_KIND,
+                        finfo.getLanguage());
+        }
     }
-
 }
diff --git a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalScalarJavaFunctionEvaluator.java b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalScalarJavaFunctionEvaluator.java
new file mode 100755
index 0000000..e406f9f
--- /dev/null
+++ b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalScalarJavaFunctionEvaluator.java
@@ -0,0 +1,95 @@
+/*
+ * 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.asterix.external.library;
+
+import java.io.IOException;
+
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.common.exceptions.RuntimeDataException;
+import org.apache.asterix.common.functions.FunctionSignature;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.external.api.IExternalScalarFunction;
+import org.apache.asterix.external.api.IFunctionFactory;
+import org.apache.asterix.om.functions.IExternalFunctionInfo;
+import org.apache.asterix.om.types.IAType;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.data.std.api.IPointable;
+import org.apache.hyracks.data.std.primitive.VoidPointable;
+import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
+import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
+
+class ExternalScalarJavaFunctionEvaluator extends ExternalScalarFunctionEvaluator {
+
+    protected final IExternalScalarFunction externalFunctionInstance;
+    protected final IPointable inputVal = VoidPointable.FACTORY.createPointable();
+    protected final ArrayBackedValueStorage resultBuffer = new ArrayBackedValueStorage();
+    protected final JavaFunctionHelper functionHelper;
+
+    public ExternalScalarJavaFunctionEvaluator(IExternalFunctionInfo finfo, IScalarEvaluatorFactory[] args,
+            IAType[] argTypes, IEvaluatorContext context) throws HyracksDataException {
+        super(finfo, args, argTypes, context);
+
+        DataverseName functionDataverse = FunctionSignature.getDataverseName(finfo.getFunctionIdentifier());
+        String functionLibrary = finfo.getLibrary();
+
+        functionHelper = new JavaFunctionHelper(finfo, argTypes, resultBuffer);
+        ClassLoader libraryClassLoader = libraryManager.getLibraryClassLoader(functionDataverse, functionLibrary);
+        String classname = finfo.getExternalIdentifier().get(0).trim();
+        try {
+            Class<?> clazz = Class.forName(classname, true, libraryClassLoader);
+            IFunctionFactory externalFunctionFactory = (IFunctionFactory) clazz.newInstance();
+            externalFunctionInstance = (IExternalScalarFunction) externalFunctionFactory.getExternalFunction();
+        } catch (Exception e) {
+            throw new RuntimeDataException(ErrorCode.LIBRARY_EXTERNAL_FUNCTION_UNABLE_TO_LOAD_CLASS, e, classname);
+        }
+
+        try {
+            externalFunctionInstance.initialize(functionHelper);
+        } catch (Exception e) {
+            throw HyracksDataException.create(e);
+        }
+    }
+
+    @Override
+    public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
+        try {
+            setArguments(tuple);
+            resultBuffer.reset();
+            externalFunctionInstance.evaluate(functionHelper);
+            if (!functionHelper.isValidResult()) {
+                throw new RuntimeDataException(ErrorCode.EXTERNAL_UDF_RESULT_TYPE_ERROR);
+            }
+            result.set(resultBuffer.getByteArray(), resultBuffer.getStartOffset(), resultBuffer.getLength());
+            functionHelper.reset();
+        } catch (Exception e) {
+            throw HyracksDataException.create(e);
+        }
+    }
+
+    public void setArguments(IFrameTupleReference tuple) throws AlgebricksException, IOException {
+        for (int i = 0; i < argEvals.length; i++) {
+            argEvals[i].evaluate(tuple, inputVal);
+            functionHelper.setArgument(i, inputVal);
+        }
+    }
+}
diff --git a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalScalarPythonFunctionEvaluator.java b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalScalarPythonFunctionEvaluator.java
new file mode 100644
index 0000000..dc06a72
--- /dev/null
+++ b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/library/ExternalScalarPythonFunctionEvaluator.java
@@ -0,0 +1,114 @@
+/*
+ * 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.asterix.external.library;
+
+import java.util.Objects;
+
+import org.apache.asterix.common.functions.FunctionSignature;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.om.functions.IExternalFunctionInfo;
+import org.apache.asterix.om.types.IAType;
+import org.apache.asterix.runtime.evaluators.functions.PointableHelper;
+import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+import org.apache.hyracks.api.context.IHyracksTaskContext;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.api.job.JobId;
+import org.apache.hyracks.api.resources.IDeallocatable;
+import org.apache.hyracks.data.std.api.IPointable;
+import org.apache.hyracks.data.std.primitive.VoidPointable;
+import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
+import org.apache.hyracks.dataflow.std.base.AbstractStateObject;
+
+class ExternalScalarPythonFunctionEvaluator extends ExternalScalarFunctionEvaluator {
+
+    private final PythonLibraryEvaluator libraryEvaluator;
+
+    private final IPointable[] argValues;
+
+    public ExternalScalarPythonFunctionEvaluator(IExternalFunctionInfo finfo, IScalarEvaluatorFactory[] args,
+            IAType[] argTypes, IEvaluatorContext ctx) throws HyracksDataException {
+        super(finfo, args, argTypes, ctx);
+        DataverseName dataverseName = FunctionSignature.getDataverseName(finfo.getFunctionIdentifier());
+        libraryEvaluator = PythonLibraryEvaluator.getInstance(dataverseName, finfo.getLibrary(), ctx.getTaskContext());
+        argValues = new IPointable[args.length];
+        for (int i = 0; i < argValues.length; i++) {
+            argValues[i] = VoidPointable.FACTORY.createPointable();
+        }
+    }
+
+    @Override
+    public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
+        for (int i = 0, ln = argEvals.length; i < ln; i++) {
+            argEvals[i].evaluate(tuple, argValues[i]);
+        }
+        PointableHelper.setNull(result);
+    }
+
+    private static class PythonLibraryEvaluator extends AbstractStateObject implements IDeallocatable {
+
+        private PythonLibraryEvaluator(JobId jobId, PythonLibraryEvaluatorId evaluatorId) {
+            super(jobId, evaluatorId);
+        }
+
+        @Override
+        public void deallocate() {
+        }
+
+        private static PythonLibraryEvaluator getInstance(DataverseName dataverseName, String libraryName,
+                IHyracksTaskContext ctx) {
+            PythonLibraryEvaluatorId evaluatorId = new PythonLibraryEvaluatorId(dataverseName, libraryName);
+            PythonLibraryEvaluator evaluator = (PythonLibraryEvaluator) ctx.getStateObject(evaluatorId);
+            if (evaluator == null) {
+                evaluator = new PythonLibraryEvaluator(ctx.getJobletContext().getJobId(), evaluatorId);
+                ctx.registerDeallocatable(evaluator);
+                ctx.setStateObject(evaluator);
+            }
+            return evaluator;
+        }
+    }
+
+    private static final class PythonLibraryEvaluatorId {
+
+        private final DataverseName dataverseName;
+
+        private final String libraryName;
+
+        private PythonLibraryEvaluatorId(DataverseName dataverseName, String libraryName) {
+            this.dataverseName = Objects.requireNonNull(dataverseName);
+            this.libraryName = Objects.requireNonNull(libraryName);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o)
+                return true;
+            if (o == null || getClass() != o.getClass())
+                return false;
+            PythonLibraryEvaluatorId that = (PythonLibraryEvaluatorId) o;
+            return dataverseName.equals(that.dataverseName) && libraryName.equals(that.libraryName);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(dataverseName, libraryName);
+        }
+    }
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/parser/AQLParserFactory.java b/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/parser/AQLParserFactory.java
index 41ca142..3aa1217 100644
--- a/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/parser/AQLParserFactory.java
+++ b/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/parser/AQLParserFactory.java
@@ -25,6 +25,9 @@
 
 public class AQLParserFactory implements IParserFactory {
 
+    // WARNING: This value is stored in function metadata. Do not modify.
+    public static final String AQL = "AQL";
+
     @Override
     public IParser createParser(String query) {
         return new AQLParser(query);
@@ -35,4 +38,8 @@
         return new AQLParser(reader);
     }
 
+    @Override
+    public String getLanguage() {
+        return AQL;
+    }
 }
diff --git a/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/rewrites/AqlQueryRewriter.java b/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/rewrites/AqlQueryRewriter.java
index d94500d..142a183 100644
--- a/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/rewrites/AqlQueryRewriter.java
+++ b/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/rewrites/AqlQueryRewriter.java
@@ -53,7 +53,6 @@
 import org.apache.asterix.lang.common.util.FunctionUtil;
 import org.apache.asterix.lang.common.visitor.GatherFunctionCallsVisitor;
 import org.apache.asterix.metadata.declared.MetadataProvider;
-import org.apache.asterix.metadata.entities.Function;
 import org.apache.hyracks.algebricks.common.utils.Pair;
 
 class AqlQueryRewriter implements IQueryRewriter {
@@ -67,7 +66,7 @@
 
     AqlQueryRewriter(IParserFactory parserFactory) {
         this.parserFactory = parserFactory;
-        functionParser = new FunctionParser(Function.FunctionLanguage.AQL, this.parserFactory);
+        functionParser = new FunctionParser(parserFactory);
     }
 
     private void setup(List<FunctionDecl> declaredFunctions, IReturningStatement topStatement,
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/IParserFactory.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/IParserFactory.java
index 3f8fabc..1775fbb 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/IParserFactory.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/IParserFactory.java
@@ -22,8 +22,9 @@
 
 public interface IParserFactory {
 
-    public IParser createParser(String query);
+    IParser createParser(String query);
 
-    public IParser createParser(Reader reader);
+    IParser createParser(Reader reader);
 
+    String getLanguage();
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/parser/FunctionParser.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/parser/FunctionParser.java
index 3a5d488..e37868c 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/parser/FunctionParser.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/parser/FunctionParser.java
@@ -30,22 +30,19 @@
 
 public class FunctionParser {
 
-    private final Function.FunctionLanguage language;
-
     private final IParserFactory parserFactory;
 
-    public FunctionParser(Function.FunctionLanguage language, IParserFactory parserFactory) {
-        this.language = language;
+    public FunctionParser(IParserFactory parserFactory) {
         this.parserFactory = parserFactory;
     }
 
-    public Function.FunctionLanguage getFunctionLanguage() {
-        return language;
+    public String getLanguage() {
+        return parserFactory.getLanguage();
     }
 
     public FunctionDecl getFunctionDecl(Function function) throws CompilationException {
-        if (!function.getLanguage().equals(language)) {
-            throw new CompilationException(ErrorCode.COMPILATION_INCOMPATIBLE_FUNCTION_LANGUAGE, language,
+        if (!function.getLanguage().equals(getLanguage())) {
+            throw new CompilationException(ErrorCode.COMPILATION_INCOMPATIBLE_FUNCTION_LANGUAGE, getLanguage(),
                     function.getLanguage());
         }
         IParser parser = parserFactory.createParser(new StringReader(function.getFunctionBody()));
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/CreateFunctionStatement.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/CreateFunctionStatement.java
index 1c21665..72962fa 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/CreateFunctionStatement.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/CreateFunctionStatement.java
@@ -47,7 +47,7 @@
 
     private final String lang;
     private final String libName;
-    private final String externalIdentifier;
+    private final List<String> externalIdentifier;
     private final Boolean deterministic;
     private final Boolean nullCall;
     private final AdmObjectNode resources;
@@ -71,7 +71,7 @@
 
     public CreateFunctionStatement(FunctionSignature signature,
             List<Pair<VarIdentifier, IndexedTypeExpression>> parameterList, IndexedTypeExpression returnType,
-            boolean deterministic, boolean nullCall, String lang, String libName, String externalIdentifier,
+            boolean deterministic, boolean nullCall, String lang, String libName, List<String> externalIdentifier,
             RecordConstructor resources, boolean ifNotExists) throws CompilationException {
         this.signature = signature;
         this.ifNotExists = ifNotExists;
@@ -120,7 +120,7 @@
         return externalIdentifier != null;
     }
 
-    public String getExternalIdentifier() {
+    public List<String> getExternalIdentifier() {
         return externalIdentifier;
     }
 
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/FunctionUtil.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/FunctionUtil.java
index c9c8abc..0c16f47 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/FunctionUtil.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/FunctionUtil.java
@@ -200,7 +200,7 @@
                         messageBuilder.toString());
             }
 
-            if (functionParser.getFunctionLanguage().equals(function.getLanguage())) {
+            if (functionParser.getLanguage().equals(function.getLanguage())) {
                 FunctionDecl functionDecl = functionParser.getFunctionDecl(function);
                 if (functionDecl != null) {
                     if (functionDecls.contains(functionDecl)) {
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/parser/SqlppParserFactory.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/parser/SqlppParserFactory.java
index 1e68a19..5b724f2 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/parser/SqlppParserFactory.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/parser/SqlppParserFactory.java
@@ -25,6 +25,9 @@
 
 public class SqlppParserFactory implements IParserFactory {
 
+    // WARNING: This value is stored in function metadata. Do not modify.
+    public static final String SQLPP = "SQLPP";
+
     @Override
     public IParser createParser(String query) {
         return new SQLPPParser(query);
@@ -34,4 +37,9 @@
     public IParser createParser(Reader reader) {
         return new SQLPPParser(reader);
     }
+
+    @Override
+    public String getLanguage() {
+        return SQLPP;
+    }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppQueryRewriter.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppQueryRewriter.java
index dc39425..72de614 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppQueryRewriter.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppQueryRewriter.java
@@ -78,7 +78,6 @@
 import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
 import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
 import org.apache.asterix.metadata.declared.MetadataProvider;
-import org.apache.asterix.metadata.entities.Function;
 import org.apache.hyracks.algebricks.common.utils.Pair;
 import org.apache.hyracks.util.LogRedactionUtil;
 import org.apache.logging.log4j.LogManager;
@@ -101,7 +100,7 @@
 
     public SqlppQueryRewriter(IParserFactory parserFactory) {
         this.parserFactory = parserFactory;
-        functionParser = new FunctionParser(Function.FunctionLanguage.SQLPP, parserFactory);
+        functionParser = new FunctionParser(parserFactory);
     }
 
     protected void setup(List<FunctionDecl> declaredFunctions, IReturningStatement topExpr,
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index 3681445..0b9c394 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -1062,6 +1062,7 @@
   String lang = null;
   String libName ="";
   String externalIdent = "";
+  List<String> externalIdentList = null;
   List<Pair<VarIdentifier,IndexedTypeExpression>> args = null;
   RecordConstructor resources = null;
 }
@@ -1119,7 +1120,9 @@
               lang = Identifier()
               ( (<NOT> <IDENTIFIER> {expectToken(DETERMINISTIC); deterministic = false;}) | (<IDENTIFIER> {expectToken(DETERMINISTIC); deterministic = true;}) )?
               (<NULL> <IDENTIFIER> { expectToken(CALL); nullCall = true;})?
-              <AS> libName = ConstantString() <COMMA>  externalIdent = ConstantString()
+              <AS> libName = ConstantString()
+              { externalIdentList = new ArrayList<String>(2); }
+              ( <COMMA>  externalIdent = ConstantString() { externalIdentList.add(externalIdent); } )+
               (<WITH> resources = RecordConstructor())?
               {
                   signature = new FunctionSignature(fctName.dataverse, fctName.function, args.size());
@@ -1128,7 +1131,7 @@
                   try{
                       stmt =
                       new CreateFunctionStatement(signature, args, returnType, deterministic, nullCall,
-                                                   lang, libName, externalIdent, resources,  ifNotExists);
+                                                   lang, libName, externalIdentList, resources,  ifNotExists);
                   } catch (AlgebricksException e) {
                       throw new SqlppParseException(getSourceLocation(startStmtToken), e.getMessage());
                   }
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/Function.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/Function.java
index f9328b8..6884970 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/Function.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/Function.java
@@ -39,7 +39,7 @@
     private final List<IAType> argTypes;
     private final IAType returnType;
     private final String body;
-    private final FunctionLanguage language;
+    private final String language;
     private final String kind;
     private final String library;
     private final Boolean deterministic; // null for SQL++ and AQL functions
@@ -48,7 +48,7 @@
     private final List<List<Triple<DataverseName, String, String>>> dependencies;
 
     public Function(FunctionSignature signature, List<String> argNames, List<IAType> argTypes, IAType returnType,
-            String functionBody, String functionKind, FunctionLanguage language, String library, Boolean nullCall,
+            String functionBody, String functionKind, String language, String library, Boolean nullCall,
             Boolean deterministic, Map<String, String> params,
             List<List<Triple<DataverseName, String, String>>> dependencies) {
         this.signature = signature;
@@ -99,7 +99,7 @@
         return returnType;
     }
 
-    public FunctionLanguage getLanguage() {
+    public String getLanguage() {
         return language;
     }
 
@@ -107,6 +107,10 @@
         return kind;
     }
 
+    public boolean isExternal() {
+        return library != null;
+    }
+
     public String getLibrary() {
         return library;
     }
@@ -136,12 +140,4 @@
     public Function dropFromCache(MetadataCache cache) {
         return cache.dropFunction(this);
     }
-
-    // WARNING: These values are stored in function metadata. Do not rename.
-    public enum FunctionLanguage {
-        AQL,
-        SQLPP,
-        JAVA,
-        PYTHON
-    }
 }
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/FunctionTupleTranslator.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/FunctionTupleTranslator.java
index 65aa94c..14089c2 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/FunctionTupleTranslator.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/FunctionTupleTranslator.java
@@ -41,8 +41,6 @@
 import org.apache.asterix.builders.IARecordBuilder;
 import org.apache.asterix.builders.OrderedListBuilder;
 import org.apache.asterix.builders.RecordBuilder;
-import org.apache.asterix.common.exceptions.AsterixException;
-import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.functions.FunctionSignature;
 import org.apache.asterix.common.metadata.DataverseName;
 import org.apache.asterix.common.transactions.TxnId;
@@ -126,15 +124,8 @@
 
         String definition = ((AString) functionRecord
                 .getValueByPos(MetadataRecordTypes.FUNCTION_ARECORD_FUNCTION_DEFINITION_FIELD_INDEX)).getStringValue();
-        String languageValue = ((AString) functionRecord
+        String language = ((AString) functionRecord
                 .getValueByPos(MetadataRecordTypes.FUNCTION_ARECORD_FUNCTION_LANGUAGE_FIELD_INDEX)).getStringValue();
-
-        Function.FunctionLanguage language;
-        try {
-            language = Function.FunctionLanguage.valueOf(languageValue);
-        } catch (IllegalArgumentException e) {
-            throw new AsterixException(ErrorCode.METADATA_ERROR);
-        }
         String functionKind =
                 ((AString) functionRecord.getValueByPos(MetadataRecordTypes.FUNCTION_ARECORD_FUNCTION_KIND_FIELD_INDEX))
                         .getStringValue();
@@ -316,7 +307,7 @@
 
         // write field 6
         fieldValue.reset();
-        aString.setValue(function.getLanguage().name());
+        aString.setValue(function.getLanguage());
         stringSerde.serialize(aString, fieldValue.getDataOutput());
         recordBuilder.addField(MetadataRecordTypes.FUNCTION_ARECORD_FUNCTION_LANGUAGE_FIELD_INDEX, fieldValue);
 
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/functions/ExternalFunctionCompilerUtil.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/functions/ExternalFunctionCompilerUtil.java
index 12c1c78..537cc24 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/functions/ExternalFunctionCompilerUtil.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/functions/ExternalFunctionCompilerUtil.java
@@ -18,10 +18,16 @@
  */
 package org.apache.asterix.metadata.functions;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
 import org.apache.asterix.common.exceptions.AsterixException;
 import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.common.functions.FunctionSignature;
 import org.apache.asterix.metadata.MetadataTransactionContext;
 import org.apache.asterix.metadata.entities.Function;
+import org.apache.asterix.om.functions.ExternalFunctionLanguage;
 import org.apache.asterix.om.typecomputer.base.IResultTypeComputer;
 import org.apache.asterix.om.types.IAType;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
@@ -54,15 +60,23 @@
     private static IFunctionInfo getScalarFunctionInfo(MetadataTransactionContext txnCtx, Function function)
             throws AlgebricksException {
         if (function.getDeterministic() == null) {
-            throw new AsterixException(ErrorCode.METADATA_ERROR);
+            throw new AsterixException(ErrorCode.METADATA_ERROR, "");
         }
 
         IAType returnType = function.getReturnType();
         IResultTypeComputer typeComputer = new ExternalTypeComputer(returnType, function.getArgTypes());
 
+        ExternalFunctionLanguage lang;
+        try {
+            lang = ExternalFunctionLanguage.valueOf(function.getLanguage());
+        } catch (IllegalArgumentException e) {
+            throw new AsterixException(ErrorCode.METADATA_ERROR, function.getLanguage());
+        }
+        List<String> externalIdentifier = decodeExternalIdentifier(lang, function.getFunctionBody());
+
         return new ExternalScalarFunctionInfo(function.getSignature().createFunctionIdentifier(), returnType,
-                function.getFunctionBody(), function.getLanguage().name(), function.getLibrary(),
-                function.getArgTypes(), function.getParams(), function.getDeterministic(), typeComputer);
+                externalIdentifier, lang, function.getLibrary(), function.getArgTypes(), function.getParams(),
+                function.getDeterministic(), typeComputer);
     }
 
     private static IFunctionInfo getUnnestFunctionInfo(MetadataTransactionContext txnCtx, Function function) {
@@ -77,4 +91,79 @@
         return null;
     }
 
+    public static String encodeExternalIdentifier(FunctionSignature functionSignature,
+            ExternalFunctionLanguage language, List<String> identList) throws AlgebricksException {
+        switch (language) {
+            case JAVA:
+                // input:
+                // [0] = package.class
+                //
+                // output: package.class
+
+                return identList.get(0);
+
+            case PYTHON:
+                // input: either a method or a top-level function
+                // [0] = package.module(:class)?
+                // [1] = (function_or_method)? - if missing then defaults to declared function name
+                //
+                // output:
+                // case 1 (method): package.module:class.method
+                // case 2 (function): package.module:function
+
+                String ident0 = identList.get(0);
+                String ident1 = identList.size() > 1 ? identList.get(1) : functionSignature.getName();
+                boolean classExists = ident0.indexOf(':') > 0;
+                return ident0 + (classExists ? '.' : ':') + ident1;
+
+            default:
+                throw new AsterixException(ErrorCode.COMPILATION_ERROR, language);
+        }
+    }
+
+    public static List<String> decodeExternalIdentifier(ExternalFunctionLanguage language, String encodedValue)
+            throws AlgebricksException {
+        switch (language) {
+            case JAVA:
+                // input: class
+                //
+                // output:
+                // [0] = class
+                return Collections.singletonList(encodedValue);
+
+            case PYTHON:
+                // input:
+                //  case 1 (method): package.module:class.method
+                //  case 2 (function): package.module:function
+                //
+                // output:
+                //  case 1:
+                //    [0] = package.module
+                //    [1] = class
+                //    [2] = method
+                //  case 2:
+                //    [0] = package.module
+                //    [1] = function
+
+                int d1 = encodedValue.indexOf(':');
+                if (d1 <= 0) {
+                    throw new AsterixException(ErrorCode.COMPILATION_ERROR, encodedValue);
+                }
+                String moduleName = encodedValue.substring(0, d1);
+                int d2 = encodedValue.lastIndexOf('.');
+                if (d2 > d1) {
+                    // class.method
+                    String className = encodedValue.substring(d1 + 1, d2);
+                    String methodName = encodedValue.substring(d2 + 1);
+                    return Arrays.asList(moduleName, className, methodName);
+                } else {
+                    // function
+                    String functionName = encodedValue.substring(d1 + 1);
+                    return Arrays.asList(moduleName, functionName);
+                }
+
+            default:
+                throw new AsterixException(ErrorCode.COMPILATION_ERROR, language);
+        }
+    }
 }
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/functions/ExternalScalarFunctionInfo.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/functions/ExternalScalarFunctionInfo.java
index a81bcf6..c0ef0fb 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/functions/ExternalScalarFunctionInfo.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/functions/ExternalScalarFunctionInfo.java
@@ -22,6 +22,7 @@
 import java.util.Map;
 
 import org.apache.asterix.om.functions.ExternalFunctionInfo;
+import org.apache.asterix.om.functions.ExternalFunctionLanguage;
 import org.apache.asterix.om.typecomputer.base.IResultTypeComputer;
 import org.apache.asterix.om.types.IAType;
 import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression.FunctionKind;
@@ -29,18 +30,19 @@
 
 public class ExternalScalarFunctionInfo extends ExternalFunctionInfo {
 
-    private static final long serialVersionUID = 1L;
+    private static final long serialVersionUID = 2L;
 
     public ExternalScalarFunctionInfo(String namespace, String library, String name, int arity, IAType returnType,
-            String body, String language, List<IAType> argumentTypes, Map<String, String> params, boolean deterministic,
-            IResultTypeComputer rtc) {
-        super(namespace, name, arity, FunctionKind.SCALAR, argumentTypes, returnType, rtc, body, language, library,
-                params, deterministic);
+            List<String> externalIdentifier, ExternalFunctionLanguage language, List<IAType> argumentTypes,
+            Map<String, String> params, boolean deterministic, IResultTypeComputer rtc) {
+        super(namespace, name, arity, FunctionKind.SCALAR, argumentTypes, returnType, rtc, language, library,
+                externalIdentifier, params, deterministic);
     }
 
-    public ExternalScalarFunctionInfo(FunctionIdentifier fid, IAType returnType, String body, String language,
-            String library, List<IAType> argumentTypes, Map<String, String> params, boolean deterministic,
-            IResultTypeComputer rtc) {
-        super(fid, FunctionKind.SCALAR, argumentTypes, returnType, rtc, body, language, library, params, deterministic);
+    public ExternalScalarFunctionInfo(FunctionIdentifier fid, IAType returnType, List<String> externalIdentifier,
+            ExternalFunctionLanguage language, String library, List<IAType> argumentTypes, Map<String, String> params,
+            boolean deterministic, IResultTypeComputer rtc) {
+        super(fid, FunctionKind.SCALAR, argumentTypes, returnType, rtc, language, library, externalIdentifier, params,
+                deterministic);
     }
 }
diff --git a/asterixdb/asterix-metadata/src/test/java/org/apache/asterix/metadata/functions/ExternalFunctionCompilerUtilTest.java b/asterixdb/asterix-metadata/src/test/java/org/apache/asterix/metadata/functions/ExternalFunctionCompilerUtilTest.java
index edaabc9..732ae09 100644
--- a/asterixdb/asterix-metadata/src/test/java/org/apache/asterix/metadata/functions/ExternalFunctionCompilerUtilTest.java
+++ b/asterixdb/asterix-metadata/src/test/java/org/apache/asterix/metadata/functions/ExternalFunctionCompilerUtilTest.java
@@ -25,6 +25,7 @@
 import org.apache.asterix.common.transactions.TxnId;
 import org.apache.asterix.metadata.MetadataTransactionContext;
 import org.apache.asterix.metadata.entities.Function;
+import org.apache.asterix.om.functions.ExternalFunctionLanguage;
 import org.apache.asterix.om.types.BuiltinType;
 import org.apache.asterix.om.types.IAType;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
@@ -38,7 +39,7 @@
         MetadataTransactionContext txnCtx = new MetadataTransactionContext(new TxnId(1));
         FunctionSignature signature = new FunctionSignature(DataverseName.createSinglePartName("test"), "test", 0);
         Function function = new Function(signature, new LinkedList<>(), new LinkedList<>(), BuiltinType.ASTRING, "",
-                "SCALAR", Function.FunctionLanguage.JAVA, "", false, false, null, null);
+                "SCALAR", ExternalFunctionLanguage.JAVA.name(), "", false, false, null, null);
 
         // when
         ExternalScalarFunctionInfo info =
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/ExternalFunctionInfo.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/ExternalFunctionInfo.java
index 7220d05..0cf42a6 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/ExternalFunctionInfo.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/ExternalFunctionInfo.java
@@ -28,33 +28,33 @@
 
 public class ExternalFunctionInfo extends FunctionInfo implements IExternalFunctionInfo {
 
-    private static final long serialVersionUID = 1L;
+    private static final long serialVersionUID = 2L;
 
     private final transient IResultTypeComputer rtc;
     private final List<IAType> argumentTypes;
-    private final String body;
-    private final String language;
+    private final ExternalFunctionLanguage language;
     private final FunctionKind kind;
     private final IAType returnType;
     private final String library;
+    private final List<String> externalIdentifier;
     private final Map<String, String> params;
 
     public ExternalFunctionInfo(String namespace, String name, int arity, FunctionKind kind, List<IAType> argumentTypes,
-            IAType returnType, IResultTypeComputer rtc, String body, String language, String library,
-            Map<String, String> params, boolean deterministic) {
-        this(new FunctionIdentifier(namespace, name, arity), kind, argumentTypes, returnType, rtc, body, language,
-                library, params, deterministic);
+            IAType returnType, IResultTypeComputer rtc, ExternalFunctionLanguage language, String library,
+            List<String> externalIdentifier, Map<String, String> params, boolean deterministic) {
+        this(new FunctionIdentifier(namespace, name, arity), kind, argumentTypes, returnType, rtc, language, library,
+                externalIdentifier, params, deterministic);
     }
 
     public ExternalFunctionInfo(FunctionIdentifier fid, FunctionKind kind, List<IAType> argumentTypes,
-            IAType returnType, IResultTypeComputer rtc, String body, String language, String library,
-            Map<String, String> params, boolean deterministic) {
+            IAType returnType, IResultTypeComputer rtc, ExternalFunctionLanguage language, String library,
+            List<String> externalIdentifier, Map<String, String> params, boolean deterministic) {
         super(fid, deterministic);
         this.rtc = rtc;
         this.argumentTypes = argumentTypes;
-        this.body = body;
         this.library = library;
         this.language = language;
+        this.externalIdentifier = externalIdentifier;
         this.kind = kind;
         this.returnType = returnType;
         this.params = params;
@@ -69,17 +69,12 @@
     }
 
     @Override
-    public String getFunctionBody() {
-        return body;
-    }
-
-    @Override
     public List<IAType> getArgumentList() {
         return argumentTypes;
     }
 
     @Override
-    public String getLanguage() {
+    public ExternalFunctionLanguage getLanguage() {
         return language;
     }
 
@@ -99,8 +94,12 @@
     }
 
     @Override
+    public List<String> getExternalIdentifier() {
+        return externalIdentifier;
+    }
+
+    @Override
     public Map<String, String> getParams() {
         return params;
     }
-
 }
diff --git a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/api/ExternalLanguage.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/ExternalFunctionLanguage.java
similarity index 83%
rename from asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/api/ExternalLanguage.java
rename to asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/ExternalFunctionLanguage.java
index dfdf2c7..d4788f8 100644
--- a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/api/ExternalLanguage.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/ExternalFunctionLanguage.java
@@ -1,4 +1,3 @@
-package org.apache.asterix.external.api;
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -18,7 +17,10 @@
  * under the License.
  */
 
-public enum ExternalLanguage {
+package org.apache.asterix.om.functions;
+
+// WARNING: These values are stored in function metadata. Do not rename.
+public enum ExternalFunctionLanguage {
     JAVA,
     PYTHON
-}
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/IExternalFunctionInfo.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/IExternalFunctionInfo.java
index 45744e8..8bd00fb 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/IExternalFunctionInfo.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/IExternalFunctionInfo.java
@@ -32,11 +32,11 @@
 
     public IAType getReturnType();
 
-    public String getFunctionBody();
+    public List<String> getExternalIdentifier();
 
     public List<IAType> getArgumentList();
 
-    public String getLanguage();
+    public ExternalFunctionLanguage getLanguage();
 
     public FunctionKind getKind();
 
diff --git a/hyracks-fullstack/algebricks/algebricks-runtime/src/main/java/org/apache/hyracks/algebricks/runtime/base/IEvaluatorContext.java b/hyracks-fullstack/algebricks/algebricks-runtime/src/main/java/org/apache/hyracks/algebricks/runtime/base/IEvaluatorContext.java
index 1c14c1d..09655da 100644
--- a/hyracks-fullstack/algebricks/algebricks-runtime/src/main/java/org/apache/hyracks/algebricks/runtime/base/IEvaluatorContext.java
+++ b/hyracks-fullstack/algebricks/algebricks-runtime/src/main/java/org/apache/hyracks/algebricks/runtime/base/IEvaluatorContext.java
@@ -19,6 +19,7 @@
 
 package org.apache.hyracks.algebricks.runtime.base;
 
+import org.apache.hyracks.api.application.IServiceContext;
 import org.apache.hyracks.api.context.IHyracksTaskContext;
 import org.apache.hyracks.api.exceptions.IWarningCollector;
 
@@ -27,6 +28,12 @@
  */
 public interface IEvaluatorContext {
     /**
+     * Returns service context. Available at compile time
+     * (CC context) and at run time (NC context).
+     */
+    IServiceContext getServiceContext();
+
+    /**
      * Returns current task's context, or {@code null} if this evaluator
      * is being executed by the constant folding rule at compile time.
      */
diff --git a/hyracks-fullstack/algebricks/algebricks-runtime/src/main/java/org/apache/hyracks/algebricks/runtime/evaluators/EvaluatorContext.java b/hyracks-fullstack/algebricks/algebricks-runtime/src/main/java/org/apache/hyracks/algebricks/runtime/evaluators/EvaluatorContext.java
index beecee9..8d31f6b 100644
--- a/hyracks-fullstack/algebricks/algebricks-runtime/src/main/java/org/apache/hyracks/algebricks/runtime/evaluators/EvaluatorContext.java
+++ b/hyracks-fullstack/algebricks/algebricks-runtime/src/main/java/org/apache/hyracks/algebricks/runtime/evaluators/EvaluatorContext.java
@@ -22,24 +22,33 @@
 import java.util.Objects;
 
 import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
+import org.apache.hyracks.api.application.IServiceContext;
 import org.apache.hyracks.api.context.IHyracksTaskContext;
 import org.apache.hyracks.api.exceptions.IWarningCollector;
 
 public final class EvaluatorContext implements IEvaluatorContext {
 
+    private final IServiceContext serviceContext;
+
     private final IHyracksTaskContext taskContext;
 
     private final IWarningCollector warningCollector;
 
     public EvaluatorContext(IHyracksTaskContext taskContext) {
-        this.taskContext = taskContext;
-        this.warningCollector = taskContext.getWarningCollector();
+        this.taskContext = Objects.requireNonNull(taskContext);
+        this.serviceContext = Objects.requireNonNull(taskContext.getJobletContext().getServiceContext());
+        this.warningCollector = Objects.requireNonNull(taskContext.getWarningCollector());
     }
 
-    public EvaluatorContext(IWarningCollector warningCollector) {
-        Objects.requireNonNull(warningCollector);
+    public EvaluatorContext(IServiceContext serviceContext, IWarningCollector warningCollector) {
         this.taskContext = null;
-        this.warningCollector = warningCollector;
+        this.serviceContext = Objects.requireNonNull(serviceContext);
+        this.warningCollector = Objects.requireNonNull(warningCollector);
+    }
+
+    @Override
+    public IServiceContext getServiceContext() {
+        return serviceContext;
     }
 
     @Override