You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by ra...@apache.org on 2015/04/27 10:33:47 UTC

[3/7] phoenix git commit: PHOENIX-538 Support UDFs(Rajeshbabu Chintaguntla)

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
index 9c38348..cf72384 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
@@ -23,12 +23,15 @@ import java.util.Map;
 
 import org.apache.phoenix.hbase.index.util.IndexManagementUtil;
 import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
+import org.apache.phoenix.query.QueryServices;
 import org.apache.phoenix.schema.AmbiguousColumnException;
 import org.apache.phoenix.schema.AmbiguousTableException;
 import org.apache.phoenix.schema.ColumnAlreadyExistsException;
 import org.apache.phoenix.schema.ColumnFamilyNotFoundException;
 import org.apache.phoenix.schema.ColumnNotFoundException;
 import org.apache.phoenix.schema.ConcurrentTableMutationException;
+import org.apache.phoenix.schema.FunctionAlreadyExistsException;
+import org.apache.phoenix.schema.FunctionNotFoundException;
 import org.apache.phoenix.schema.ReadOnlyTableException;
 import org.apache.phoenix.schema.SequenceAlreadyExistsException;
 import org.apache.phoenix.schema.SequenceNotFoundException;
@@ -320,7 +323,22 @@ public enum SQLExceptionCode {
             return new SQLTimeoutException(OPERATION_TIMED_OUT.getMessage(),
                     OPERATION_TIMED_OUT.getSQLState(), OPERATION_TIMED_OUT.getErrorCode());
         }
-    }), 
+    }),
+    FUNCTION_UNDEFINED(6001, "42F01", "Function undefined.", new Factory() {
+        @Override
+        public SQLException newException(SQLExceptionInfo info) {
+            return new FunctionNotFoundException(info.getFunctionName());
+        }
+    }),
+    FUNCTION_ALREADY_EXIST(6002, "42F02", "Function already exists.", new Factory() {
+        @Override
+        public SQLException newException(SQLExceptionInfo info) {
+            return new FunctionAlreadyExistsException(info.getSchemaName(), info.getTableName());
+        }
+    }),
+    UNALLOWED_USER_DEFINED_FUNCTIONS(6003, "42F03",
+            "User defined functions are configured to not be allowed. To allow configure "
+                    + QueryServices.ALLOW_USER_DEFINED_FUNCTIONS_ATTRIB + " to true."),
     ;
 
     private final int errorCode;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionInfo.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionInfo.java b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionInfo.java
index fa31190..50dffde 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionInfo.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionInfo.java
@@ -37,6 +37,7 @@ public class SQLExceptionInfo {
     public static final String TABLE_NAME = "tableName";
     public static final String FAMILY_NAME = "familyName";
     public static final String COLUMN_NAME = "columnName";
+    public static final String FUNCTION_NAME = "functionName";
 
     private final Throwable rootCause;
     private final SQLExceptionCode code; // Should always have one.
@@ -45,6 +46,7 @@ public class SQLExceptionInfo {
     private final String tableName;
     private final String familyName;
     private final String columnName;
+    private final String functionName;
 
     public static class Builder {
 
@@ -55,6 +57,7 @@ public class SQLExceptionInfo {
         private String tableName;
         private String familyName;
         private String columnName;
+        private String functionName;
 
         public Builder(SQLExceptionCode code) {
             this.code = code;
@@ -90,6 +93,10 @@ public class SQLExceptionInfo {
             return this;
         }
 
+        public Builder setFunctionName(String functionName) {
+            this.functionName = functionName;
+            return this;
+        }
         public SQLExceptionInfo build() {
             return new SQLExceptionInfo(this);
         }
@@ -108,6 +115,7 @@ public class SQLExceptionInfo {
         tableName = builder.tableName;
         familyName = builder.familyName;
         columnName = builder.columnName;
+        functionName = builder.functionName;
     }
 
     @Override
@@ -116,6 +124,10 @@ public class SQLExceptionInfo {
         if (message != null) {
             builder.append(" ").append(message);
         }
+        if (functionName != null) {
+            builder.append(" ").append(FUNCTION_NAME).append("=").append(functionName);
+            return builder.toString();
+        }
         String columnDisplayName = SchemaUtil.getMetaDataEntityName(schemaName, tableName, familyName, columnName);
         if (columnName != null) {
             builder.append(" ").append(COLUMN_NAME).append("=").append(columnDisplayName);
@@ -153,6 +165,10 @@ public class SQLExceptionInfo {
         return columnName;
     }
 
+    public String getFunctionName() {
+        return functionName;
+    }
+    
     public SQLExceptionCode getCode() {
         return code;
     }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java
index 5f598b9..69a2049 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java
@@ -95,6 +95,7 @@ import org.apache.phoenix.expression.function.ToTimeFunction;
 import org.apache.phoenix.expression.function.ToTimestampFunction;
 import org.apache.phoenix.expression.function.TrimFunction;
 import org.apache.phoenix.expression.function.TruncFunction;
+import org.apache.phoenix.expression.function.UDFExpression;
 import org.apache.phoenix.expression.function.UpperFunction;
 import org.apache.phoenix.expression.function.WeekFunction;
 import org.apache.phoenix.expression.function.YearFunction;
@@ -227,7 +228,8 @@ public enum ExpressionType {
     InstrFunction(InstrFunction.class),
     MinuteFunction(MinuteFunction.class),
     DayOfMonthFunction(DayOfMonthFunction.class),
-    ArrayAppendFunction(ArrayAppendFunction.class)
+    ArrayAppendFunction(ArrayAppendFunction.class),
+    UDFExpression(UDFExpression.class)
     ;
 
     ExpressionType(Class<? extends Expression> clazz) {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/expression/function/ScalarFunction.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/ScalarFunction.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/ScalarFunction.java
index e694680..014bda4 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/ScalarFunction.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/ScalarFunction.java
@@ -53,7 +53,7 @@ public abstract class ScalarFunction extends FunctionExpression {
     }
     
     @Override
-    public final <T> T accept(ExpressionVisitor<T> visitor) {
+    public <T> T accept(ExpressionVisitor<T> visitor) {
         List<T> l = acceptChildren(visitor, visitor.visitEnter(this));
         T t = visitor.visitLeave(this, l);
         if (t == null) {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/expression/function/UDFExpression.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/UDFExpression.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/UDFExpression.java
new file mode 100644
index 0000000..4e12b17
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/UDFExpression.java
@@ -0,0 +1,220 @@
+/*
+ * 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.phoenix.expression.function;
+
+import static org.apache.phoenix.query.QueryServices.DYNAMIC_JARS_DIR_KEY;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.hadoop.hbase.util.DynamicClassLoader;
+import org.apache.hadoop.io.WritableUtils;
+import org.apache.phoenix.compile.KeyPart;
+import org.apache.phoenix.expression.Expression;
+import org.apache.phoenix.expression.visitor.ExpressionVisitor;
+import org.apache.phoenix.parse.PFunction;
+import org.apache.phoenix.schema.PName;
+import org.apache.phoenix.schema.PNameFactory;
+import org.apache.phoenix.schema.tuple.Tuple;
+import org.apache.phoenix.schema.types.PDataType;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.MapMaker;
+
+public class UDFExpression extends ScalarFunction {
+    
+    private static Configuration config = HBaseConfiguration.create();
+
+    private static final ConcurrentMap<PName, DynamicClassLoader> tenantIdSpecificCls =
+            new MapMaker().concurrencyLevel(3).weakValues().makeMap();
+
+    private static final ConcurrentMap<String, DynamicClassLoader> pathSpecificCls =
+            new MapMaker().concurrencyLevel(3).weakValues().makeMap();
+
+    private PName tenantId;
+    private String functionClassName;
+    private String jarPath;
+    private ScalarFunction udfFunction;
+    
+    public UDFExpression() {
+    }
+
+    public UDFExpression(List<Expression> children,PFunction functionInfo) {
+        super(children);
+        this.tenantId =
+                functionInfo.getTenantId() == null ? PName.EMPTY_NAME : functionInfo.getTenantId();
+        this.functionClassName = functionInfo.getClassName();
+        this.jarPath = functionInfo.getJarPath();
+        constructUDFFunction();
+    }
+
+    public UDFExpression(List<Expression> children, PName tenantId, String functionClassName,
+            String jarPath, ScalarFunction udfFunction) {
+        super(children);
+        this.tenantId = tenantId;
+        this.functionClassName = functionClassName;
+        this.jarPath = jarPath;
+        if(udfFunction != null) {
+            this.udfFunction = udfFunction;
+        } else {
+            constructUDFFunction();
+        }
+    }
+
+    @Override
+    public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) {
+        return udfFunction.evaluate(tuple, ptr);
+    }
+
+    @Override
+    public <T> T accept(ExpressionVisitor<T> visitor) {
+        return udfFunction.accept(visitor);
+    }
+
+    @Override
+    public PDataType getDataType() {
+        return udfFunction.getDataType();
+    }
+
+    @Override
+    public String getName() {
+        return udfFunction.getName();
+    }
+
+    @Override
+    public OrderPreserving preservesOrder() {
+        return udfFunction.preservesOrder();
+    }
+
+    @Override
+    public KeyPart newKeyPart(KeyPart childPart) {
+        return udfFunction.newKeyPart(childPart);
+    }
+
+    @Override
+    public int getKeyFormationTraversalIndex() {
+        return udfFunction.getKeyFormationTraversalIndex();
+    }
+
+    public PName getTenantId() {
+        return tenantId;
+    }
+
+    public String getFunctionClassName() {
+        return functionClassName;
+    }
+
+    public String getJarPath() {
+        return jarPath;
+    }
+
+    public ScalarFunction getUdfFunction() {
+        return udfFunction;
+    }
+
+    @Override
+    public void write(DataOutput output) throws IOException {
+        super.write(output);
+        WritableUtils.writeString(output, tenantId.getString());
+        WritableUtils.writeString(output, this.functionClassName);
+        if(this.jarPath == null) {
+            WritableUtils.writeString(output, "");
+        } else {
+            WritableUtils.writeString(output, this.jarPath);
+        }
+    }
+    
+    @Override
+    public void readFields(DataInput input) throws IOException {
+        super.readFields(input);
+        this.tenantId = PNameFactory.newName(WritableUtils.readString(input));
+        this.functionClassName = WritableUtils.readString(input);
+        String str = WritableUtils.readString(input);
+        this.jarPath = str.length() == 0 ? null: str;
+        constructUDFFunction();
+    }
+
+    private void constructUDFFunction() {
+        try {
+            DynamicClassLoader classLoader = getClassLoader(this.tenantId, this.jarPath);
+            Class<?> clazz = classLoader.loadClass(this.functionClassName);
+            Constructor<?> constructor = clazz.getConstructor(List.class);
+            udfFunction = (ScalarFunction)constructor.newInstance(this.children);
+        } catch (ClassNotFoundException | NoSuchMethodException | SecurityException
+                | InstantiationException | IllegalAccessException | IllegalArgumentException
+                | InvocationTargetException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static DynamicClassLoader getClassLoader(final PName tenantId, final String jarPath) {
+        DynamicClassLoader cl = tenantIdSpecificCls.get(tenantId);
+        String parent = null;
+        if (cl != null) return cl;
+        if(jarPath != null && !jarPath.isEmpty()) {
+            cl = pathSpecificCls.get(jarPath);
+            if (cl != null) return cl;
+            Path path = new Path(jarPath);
+            if(jarPath.endsWith(".jar")) {
+                parent = path.getParent().toString();
+            } else {
+                parent = path.toString();
+            }
+        }
+        if (jarPath == null || jarPath.isEmpty() || config.get(DYNAMIC_JARS_DIR_KEY) != null
+                && (parent != null && parent.equals(config.get(DYNAMIC_JARS_DIR_KEY)))) {
+            cl = tenantIdSpecificCls.get(tenantId);
+            if (cl == null) {
+                cl = new DynamicClassLoader(config, UDFExpression.class.getClassLoader());
+            }
+            // Cache class loader as a weak value, will be GC'ed when no reference left
+            DynamicClassLoader prev = tenantIdSpecificCls.putIfAbsent(tenantId, cl);
+            if (prev != null) {
+                cl = prev;
+            }
+            return cl;
+        } else {
+            cl = pathSpecificCls.get(jarPath);
+            if (cl == null) {
+                Configuration conf = HBaseConfiguration.create(config);
+                conf.set(DYNAMIC_JARS_DIR_KEY, parent);
+                cl = new DynamicClassLoader(conf, UDFExpression.class.getClassLoader());
+            }
+            // Cache class loader as a weak value, will be GC'ed when no reference left
+            DynamicClassLoader prev = pathSpecificCls.putIfAbsent(jarPath, cl);
+            if (prev != null) {
+                cl = prev;
+            }
+            return cl;
+        }
+    }
+    
+    @VisibleForTesting
+    public static void setConfig(Configuration conf) {
+        config = conf;
+    }
+}

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/expression/visitor/CloneExpressionVisitor.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/visitor/CloneExpressionVisitor.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/visitor/CloneExpressionVisitor.java
index e6ede7c..b252358 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/expression/visitor/CloneExpressionVisitor.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/visitor/CloneExpressionVisitor.java
@@ -47,6 +47,7 @@ import org.apache.phoenix.expression.function.ArrayAnyComparisonExpression;
 import org.apache.phoenix.expression.function.ArrayElemRefExpression;
 import org.apache.phoenix.expression.function.ScalarFunction;
 import org.apache.phoenix.expression.function.SingleAggregateFunction;
+import org.apache.phoenix.expression.function.UDFExpression;
 
 public class CloneExpressionVisitor extends TraverseAllExpressionVisitor<Expression> {
 
@@ -100,6 +101,11 @@ public class CloneExpressionVisitor extends TraverseAllExpressionVisitor<Express
         return Determinism.PER_INVOCATION.compareTo(node.getDeterminism()) > 0 ? node :  node.clone(l);
     }
 
+    public Expression visitLeave(UDFExpression node, List<Expression> l) {
+        return new UDFExpression(l, node.getTenantId(), node.getFunctionClassName(),
+                node.getJarPath(), node.getUdfFunction());
+    }
+
     @Override
     public Expression visitLeave(ComparisonExpression node, List<Expression> l) {
         return Determinism.PER_INVOCATION.compareTo(node.getDeterminism()) > 0 ? node :  node.clone(l);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java b/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java
index 4f785eb..4565f39 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java
@@ -24,9 +24,11 @@ import java.io.DataOutput;
 import java.io.DataOutputStream;
 import java.io.IOException;
 import java.sql.SQLException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -57,8 +59,15 @@ import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
 import org.apache.phoenix.hbase.index.util.KeyValueBuilder;
 import org.apache.phoenix.jdbc.PhoenixConnection;
 import org.apache.phoenix.jdbc.PhoenixStatement;
+import org.apache.phoenix.parse.AndParseNode;
+import org.apache.phoenix.parse.BaseParseNodeVisitor;
+import org.apache.phoenix.parse.BooleanParseNodeVisitor;
+import org.apache.phoenix.parse.FunctionParseNode;
 import org.apache.phoenix.parse.ParseNode;
 import org.apache.phoenix.parse.SQLParser;
+import org.apache.phoenix.parse.StatelessTraverseAllParseNodeVisitor;
+import org.apache.phoenix.parse.TraverseAllParseNodeVisitor;
+import org.apache.phoenix.parse.UDFParseNode;
 import org.apache.phoenix.query.QueryConstants;
 import org.apache.phoenix.schema.ColumnNotFoundException;
 import org.apache.phoenix.schema.PColumn;
@@ -328,8 +337,21 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
         this.immutableRows = dataTable.isImmutableRows();
         int indexColByteSize = 0;
         ColumnResolver resolver = null;
+        List<ParseNode> parseNodes = new ArrayList<ParseNode>(1);
+        UDFParseNodeVisitor visitor = new UDFParseNodeVisitor();
+        for (int i = indexPosOffset; i < index.getPKColumns().size(); i++) {
+            PColumn indexColumn = index.getPKColumns().get(i);
+            String expressionStr = IndexUtil.getIndexColumnExpressionStr(indexColumn);
+            try {
+                ParseNode parseNode  = SQLParser.parseCondition(expressionStr);
+                parseNode.accept(visitor);
+                parseNodes.add(parseNode);
+            } catch (SQLException e) {
+                throw new RuntimeException(e);
+            }
+        }
         try {
-            resolver = FromCompiler.getResolver(new TableRef(dataTable));
+            resolver = FromCompiler.getResolver(connection, new TableRef(dataTable), visitor.getUdfParseNodes());
         } catch (SQLException e) {
             throw new RuntimeException(e); // Impossible
         }
@@ -341,9 +363,7 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
             Expression expression = null;
             try {
                 expressionIndexCompiler.reset();
-                String expressionStr = IndexUtil.getIndexColumnExpressionStr(indexColumn);
-                ParseNode parseNode  = SQLParser.parseCondition(expressionStr);
-                expression = parseNode.accept(expressionIndexCompiler);
+                expression = parseNodes.get(indexPos).accept(expressionIndexCompiler);
             } catch (SQLException e) {
                 throw new RuntimeException(e); // Impossible
             }
@@ -861,7 +881,7 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
             }
         }
         return delete;
-  }
+    }
 
     public byte[] getIndexTableName() {
         return indexTableName;
@@ -1337,4 +1357,24 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
     public Set<ColumnReference> getIndexedColumns() {
         return indexedColumns;
     }
+
+    public static class UDFParseNodeVisitor extends StatelessTraverseAllParseNodeVisitor {
+
+        private Map<String, UDFParseNode> udfParseNodes;
+        public UDFParseNodeVisitor() {
+            udfParseNodes = new HashMap<String, UDFParseNode>(1);
+        }
+
+        @Override
+        public boolean visitEnter(FunctionParseNode node) throws SQLException {
+            if(node instanceof UDFParseNode) {
+                udfParseNodes.put(node.getName(), (UDFParseNode)node);
+            }
+            return super.visitEnter(node);
+        }
+        
+        public Map<String, UDFParseNode> getUdfParseNodes() {
+            return udfParseNodes;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixConnection.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixConnection.java b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixConnection.java
index b4a5c53..c22a7fa 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixConnection.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixConnection.java
@@ -64,6 +64,7 @@ import org.apache.phoenix.execute.MutationState;
 import org.apache.phoenix.expression.function.FunctionArgumentType;
 import org.apache.phoenix.hbase.index.util.KeyValueBuilder;
 import org.apache.phoenix.jdbc.PhoenixStatement.PhoenixStatementParser;
+import org.apache.phoenix.parse.PFunction;
 import org.apache.phoenix.query.ConnectionQueryServices;
 import org.apache.phoenix.query.DelegateConnectionQueryServices;
 import org.apache.phoenix.query.MetaDataMutated;
@@ -118,7 +119,7 @@ import com.google.common.collect.Maps;
  * 
  * @since 0.1
  */
-public class PhoenixConnection implements Connection, org.apache.phoenix.jdbc.Jdbc7Shim.Connection, MetaDataMutated  {
+public class PhoenixConnection implements Connection, org.apache.phoenix.jdbc.Jdbc7Shim.Connection, MetaDataMutated{
     private final String url;
     private final ConnectionQueryServices services;
     private final Properties info;
@@ -233,7 +234,7 @@ public class PhoenixConnection implements Connection, org.apache.phoenix.jdbc.Jd
         formatters.put(PDecimal.INSTANCE, FunctionArgumentType.NUMERIC.getFormatter(numberPattern));
         // We do not limit the metaData on a connection less than the global one,
         // as there's not much that will be cached here.
-        this.metaData = metaData.pruneTables(new Pruner() {
+        Pruner pruner = new Pruner() {
 
             @Override
             public boolean prune(PTable table) {
@@ -243,8 +244,16 @@ public class PhoenixConnection implements Connection, org.apache.phoenix.jdbc.Jd
                          ! Objects.equal(tenantId, table.getTenantId())) );
             }
             
-        });
+            @Override
+            public boolean prune(PFunction function) {
+                long maxTimestamp = scn == null ? HConstants.LATEST_TIMESTAMP : scn;
+                return ( function.getTimeStamp() >= maxTimestamp ||
+                         ! Objects.equal(tenantId, function.getTenantId()));
+            }
+        };
         this.mutationState = newMutationState(maxSize);
+        this.metaData = metaData.pruneTables(pruner);
+        this.metaData = metaData.pruneFunctions(pruner);
         this.services.addConnection(this);
 
         // setup tracing, if its enabled
@@ -777,6 +786,18 @@ public class PhoenixConnection implements Connection, org.apache.phoenix.jdbc.Jd
         getQueryServices().addTable(table);
         return metaData;
     }
+    
+    @Override
+    public PMetaData addFunction(PFunction function) throws SQLException {
+        // TODO: since a connection is only used by one thread at a time,
+        // we could modify this metadata in place since it's not shared.
+        if (scn == null || scn > function.getTimeStamp()) {
+            metaData = metaData.addFunction(function);
+        }
+        //Cascade through to connectionQueryServices too
+        getQueryServices().addFunction(function);
+        return metaData;
+    }
 
     @Override
     public PMetaData addColumn(PName tenantId, String tableName, List<PColumn> columns, long tableTimeStamp, long tableSeqNum, boolean isImmutableRows, boolean isWalDisabled, boolean isMultitenant, boolean storeNulls)
@@ -796,6 +817,14 @@ public class PhoenixConnection implements Connection, org.apache.phoenix.jdbc.Jd
     }
 
     @Override
+    public PMetaData removeFunction(PName tenantId, String functionName, long tableTimeStamp) throws SQLException {
+        metaData = metaData.removeFunction(tenantId, functionName, tableTimeStamp);
+        //Cascade through to connectionQueryServices too
+        getQueryServices().removeFunction(tenantId, functionName, tableTimeStamp);
+        return metaData;
+    }
+
+    @Override
     public PMetaData removeColumn(PName tenantId, String tableName, List<PColumn> columnsToRemove, long tableTimeStamp,
             long tableSeqNum) throws SQLException {
         metaData = metaData.removeColumn(tenantId, tableName, columnsToRemove, tableTimeStamp, tableSeqNum);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixDatabaseMetaData.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixDatabaseMetaData.java b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixDatabaseMetaData.java
index 1b8b57d..3a0b03b 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixDatabaseMetaData.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixDatabaseMetaData.java
@@ -106,6 +106,11 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData, org.apache.pho
     public static final int SCHEMA_NAME_INDEX = 1;
     public static final int TENANT_ID_INDEX = 0;
 
+
+    public static final int TYPE_INDEX = 2;
+    public static final int FUNTION_NAME_INDEX = 1;
+    
+
     public static final String SYSTEM_CATALOG_SCHEMA = QueryConstants.SYSTEM_SCHEMA_NAME;
     public static final byte[] SYSTEM_CATALOG_SCHEMA_BYTES = QueryConstants.SYSTEM_SCHEMA_NAME_BYTES;
     public static final String SYSTEM_CATALOG_TABLE = "CATALOG";
@@ -209,6 +214,31 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData, org.apache.pho
     public static final byte[] TABLE_FAMILY_BYTES = QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES;
 
     public static final String TYPE_SEQUENCE = "SEQUENCE";
+    public static final String SYSTEM_FUNCTION_TABLE = "FUNCTION";
+    public static final String SYSTEM_FUNCTION_NAME = SchemaUtil.getTableName(SYSTEM_CATALOG_SCHEMA, SYSTEM_FUNCTION_TABLE);
+    public static final byte[] SYSTEM_FUNCTION_NAME_BYTES = Bytes.toBytes(SYSTEM_FUNCTION_NAME);
+
+    public static final String FUNCTION_NAME = "FUNCTION_NAME";
+    public static final byte[] FUNCTION_NAME_BYTES = Bytes.toBytes(FUNCTION_NAME);
+    public static final String CLASS_NAME = "CLASS_NAME";
+    public static final byte[] CLASS_NAME_BYTES = Bytes.toBytes(CLASS_NAME);
+    public static final String JAR_PATH = "JAR_PATH";
+    public static final byte[] JAR_PATH_BYTES = Bytes.toBytes(JAR_PATH);
+    public static final String TYPE = "TYPE";
+    public static final byte[] TYPE_BYTES = Bytes.toBytes(TYPE);
+    public static final String ARG_POSITION = "ARG_POSITION";
+    public static final byte[] ARG_POSITION_TYPE = Bytes.toBytes(ARG_POSITION);
+    public static final String RETURN_TYPE = "RETURN_TYPE";
+    public static final byte[] RETURN_TYPE_BYTES = Bytes.toBytes(RETURN_TYPE);
+    public static final String IS_ARRAY = "IS_ARRAY";
+    public static final byte[] IS_ARRAY_BYTES = Bytes.toBytes(IS_ARRAY);
+    public static final String IS_CONSTANT = "IS_CONSTANT";
+    public static final byte[] IS_CONSTANT_BYTES = Bytes.toBytes(IS_CONSTANT);
+    public static final String DEFAULT_VALUE = "DEFAULT_VALUE";
+    public static final byte[] DEFAULT_VALUE_BYTES = Bytes.toBytes(DEFAULT_VALUE);
+    public static final String NUM_ARGS = "NUM_ARGS";
+    public static final byte[] NUM_ARGS_BYTES = Bytes.toBytes(NUM_ARGS);
+    
     public static final byte[] SEQUENCE_FAMILY_BYTES = QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES;
     public static final String SEQUENCE_SCHEMA_NAME = SYSTEM_CATALOG_SCHEMA;
     public static final byte[] SEQUENCE_SCHEMA_NAME_BYTES = Bytes.toBytes(SEQUENCE_SCHEMA_NAME);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
index a70a36e..7c94d62 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
@@ -42,6 +42,7 @@ import org.apache.hadoop.hbase.util.Pair;
 import org.apache.phoenix.call.CallRunner;
 import org.apache.phoenix.compile.ColumnProjector;
 import org.apache.phoenix.compile.ColumnResolver;
+import org.apache.phoenix.compile.CreateFunctionCompiler;
 import org.apache.phoenix.compile.CreateIndexCompiler;
 import org.apache.phoenix.compile.CreateSequenceCompiler;
 import org.apache.phoenix.compile.CreateTableCompiler;
@@ -78,11 +79,13 @@ import org.apache.phoenix.parse.AlterSessionStatement;
 import org.apache.phoenix.parse.BindableStatement;
 import org.apache.phoenix.parse.ColumnDef;
 import org.apache.phoenix.parse.ColumnName;
+import org.apache.phoenix.parse.CreateFunctionStatement;
 import org.apache.phoenix.parse.CreateIndexStatement;
 import org.apache.phoenix.parse.CreateSequenceStatement;
 import org.apache.phoenix.parse.CreateTableStatement;
 import org.apache.phoenix.parse.DeleteStatement;
 import org.apache.phoenix.parse.DropColumnStatement;
+import org.apache.phoenix.parse.DropFunctionStatement;
 import org.apache.phoenix.parse.DropIndexStatement;
 import org.apache.phoenix.parse.DropSequenceStatement;
 import org.apache.phoenix.parse.DropTableStatement;
@@ -94,6 +97,7 @@ import org.apache.phoenix.parse.LimitNode;
 import org.apache.phoenix.parse.NamedNode;
 import org.apache.phoenix.parse.NamedTableNode;
 import org.apache.phoenix.parse.OrderByNode;
+import org.apache.phoenix.parse.PFunction;
 import org.apache.phoenix.parse.ParseNode;
 import org.apache.phoenix.parse.ParseNodeFactory;
 import org.apache.phoenix.parse.PrimaryKeyConstraint;
@@ -102,6 +106,7 @@ import org.apache.phoenix.parse.SelectStatement;
 import org.apache.phoenix.parse.TableName;
 import org.apache.phoenix.parse.TableNode;
 import org.apache.phoenix.parse.TraceStatement;
+import org.apache.phoenix.parse.UDFParseNode;
 import org.apache.phoenix.parse.UpdateStatisticsStatement;
 import org.apache.phoenix.parse.UpsertStatement;
 import org.apache.phoenix.query.KeyRange;
@@ -332,19 +337,22 @@ public class PhoenixStatement implements Statement, SQLCloseable, org.apache.pho
     
     private static class ExecutableSelectStatement extends SelectStatement implements CompilableStatement {
         private ExecutableSelectStatement(TableNode from, HintNode hint, boolean isDistinct, List<AliasedNode> select, ParseNode where,
-                List<ParseNode> groupBy, ParseNode having, List<OrderByNode> orderBy, LimitNode limit, int bindCount, boolean isAggregate, boolean hasSequence) {
-            this(from, hint, isDistinct, select, where, groupBy, having, orderBy, limit, bindCount, isAggregate, hasSequence, Collections.<SelectStatement>emptyList());
+                List<ParseNode> groupBy, ParseNode having, List<OrderByNode> orderBy, LimitNode limit, int bindCount, boolean isAggregate, boolean hasSequence, Map<String, UDFParseNode> udfParseNodes) {
+            this(from, hint, isDistinct, select, where, groupBy, having, orderBy, limit, bindCount, isAggregate, hasSequence, Collections.<SelectStatement>emptyList(), udfParseNodes);
         }
 
         private ExecutableSelectStatement(TableNode from, HintNode hint, boolean isDistinct, List<AliasedNode> select, ParseNode where,
                 List<ParseNode> groupBy, ParseNode having, List<OrderByNode> orderBy, LimitNode limit, int bindCount, boolean isAggregate,
-                boolean hasSequence, List<SelectStatement> selects) {
-            super(from, hint, isDistinct, select, where, groupBy, having, orderBy, limit, bindCount, isAggregate, hasSequence, selects);
+                boolean hasSequence, List<SelectStatement> selects, Map<String, UDFParseNode> udfParseNodes) {
+            super(from, hint, isDistinct, select, where, groupBy, having, orderBy, limit, bindCount, isAggregate, hasSequence, selects, udfParseNodes);
         }
         
         @SuppressWarnings("unchecked")
         @Override
         public QueryPlan compilePlan(PhoenixStatement stmt, Sequence.ValueOp seqAction) throws SQLException {
+            if(!getUdfParseNodes().isEmpty()) {
+                stmt.throwIfUnallowedUserDefinedFunctions();
+            }
             SelectStatement select = SubselectRewriter.flatten(this, stmt.getConnection());
             ColumnResolver resolver = FromCompiler.getResolverForQuery(select, stmt.getConnection());
             select = StatementNormalizer.normalize(select, resolver);
@@ -357,6 +365,7 @@ public class PhoenixStatement implements Statement, SQLCloseable, org.apache.pho
             plan.getContext().getSequenceManager().validateSequences(seqAction);
             return plan;
         }
+
     }
     
     private static final byte[] EXPLAIN_PLAN_FAMILY = QueryConstants.SINGLE_COLUMN_FAMILY;
@@ -505,13 +514,16 @@ public class PhoenixStatement implements Statement, SQLCloseable, org.apache.pho
     }
 
     private static class ExecutableUpsertStatement extends UpsertStatement implements CompilableStatement {
-        private ExecutableUpsertStatement(NamedTableNode table, HintNode hintNode, List<ColumnName> columns, List<ParseNode> values, SelectStatement select, int bindCount) {
-            super(table, hintNode, columns, values, select, bindCount);
+        private ExecutableUpsertStatement(NamedTableNode table, HintNode hintNode, List<ColumnName> columns, List<ParseNode> values, SelectStatement select, int bindCount, Map<String, UDFParseNode> udfParseNodes) {
+            super(table, hintNode, columns, values, select, bindCount, udfParseNodes);
         }
 
         @SuppressWarnings("unchecked")
         @Override
         public MutationPlan compilePlan(PhoenixStatement stmt, Sequence.ValueOp seqAction) throws SQLException {
+            if(!getUdfParseNodes().isEmpty()) {
+                stmt.throwIfUnallowedUserDefinedFunctions();
+            }
             UpsertCompiler compiler = new UpsertCompiler(stmt);
             MutationPlan plan = compiler.compile(this);
             plan.getContext().getSequenceManager().validateSequences(seqAction);
@@ -520,13 +532,16 @@ public class PhoenixStatement implements Statement, SQLCloseable, org.apache.pho
     }
     
     private static class ExecutableDeleteStatement extends DeleteStatement implements CompilableStatement {
-        private ExecutableDeleteStatement(NamedTableNode table, HintNode hint, ParseNode whereNode, List<OrderByNode> orderBy, LimitNode limit, int bindCount) {
-            super(table, hint, whereNode, orderBy, limit, bindCount);
+        private ExecutableDeleteStatement(NamedTableNode table, HintNode hint, ParseNode whereNode, List<OrderByNode> orderBy, LimitNode limit, int bindCount, Map<String, UDFParseNode> udfParseNodes) {
+            super(table, hint, whereNode, orderBy, limit, bindCount, udfParseNodes);
         }
 
         @SuppressWarnings("unchecked")
         @Override
         public MutationPlan compilePlan(PhoenixStatement stmt, Sequence.ValueOp seqAction) throws SQLException {
+            if(!getUdfParseNodes().isEmpty()) {
+                stmt.throwIfUnallowedUserDefinedFunctions();
+            }
             DeleteCompiler compiler = new DeleteCompiler(stmt);
             MutationPlan plan = compiler.compile(this);
             plan.getContext().getSequenceManager().validateSequences(seqAction);
@@ -549,16 +564,76 @@ public class PhoenixStatement implements Statement, SQLCloseable, org.apache.pho
         }
     }
 
+    private static class ExecutableCreateFunctionStatement extends CreateFunctionStatement implements CompilableStatement {
+
+        public ExecutableCreateFunctionStatement(PFunction functionInfo, boolean temporary) {
+            super(functionInfo, temporary);
+        }
+
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public MutationPlan compilePlan(final PhoenixStatement stmt, Sequence.ValueOp seqAction) throws SQLException {
+            stmt.throwIfUnallowedUserDefinedFunctions();
+            CreateFunctionCompiler compiler = new CreateFunctionCompiler(stmt);
+            return compiler.compile(this);
+        }
+    }
+
+    private static class ExecutableDropFunctionStatement extends DropFunctionStatement implements CompilableStatement {
+
+        public ExecutableDropFunctionStatement(String functionName, boolean ifNotExists) {
+            super(functionName, ifNotExists);
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public MutationPlan compilePlan(final PhoenixStatement stmt, Sequence.ValueOp seqAction) throws SQLException {
+                final StatementContext context = new StatementContext(stmt);
+                return new MutationPlan() {
+
+                    @Override
+                    public StatementContext getContext() {
+                        return context;
+                    }
+
+                    @Override
+                    public ParameterMetaData getParameterMetaData() {
+                        return PhoenixParameterMetaData.EMPTY_PARAMETER_META_DATA;
+                    }
+
+                    @Override
+                    public ExplainPlan getExplainPlan() throws SQLException {
+                        return new ExplainPlan(Collections.singletonList("DROP TABLE"));
+                    }
+
+                    @Override
+                    public PhoenixConnection getConnection() {
+                        return stmt.getConnection();
+                    }
+
+                    @Override
+                    public MutationState execute() throws SQLException {
+                        MetaDataClient client = new MetaDataClient(getConnection());
+                        return client.dropFunction(ExecutableDropFunctionStatement.this);
+                    }
+                };
+        }
+    }
+    
     private static class ExecutableCreateIndexStatement extends CreateIndexStatement implements CompilableStatement {
 
         public ExecutableCreateIndexStatement(NamedNode indexName, NamedTableNode dataTable, IndexKeyConstraint ikConstraint, List<ColumnName> includeColumns, List<ParseNode> splits,
-                ListMultimap<String,Pair<String,Object>> props, boolean ifNotExists, IndexType indexType, boolean async, int bindCount) {
-            super(indexName, dataTable, ikConstraint, includeColumns, splits, props, ifNotExists, indexType, async , bindCount);
+                ListMultimap<String,Pair<String,Object>> props, boolean ifNotExists, IndexType indexType, boolean async, int bindCount, Map<String, UDFParseNode> udfParseNodes) {
+            super(indexName, dataTable, ikConstraint, includeColumns, splits, props, ifNotExists, indexType, async , bindCount, udfParseNodes);
         }
 
         @SuppressWarnings("unchecked")
         @Override
         public MutationPlan compilePlan(PhoenixStatement stmt, Sequence.ValueOp seqAction) throws SQLException {
+            if(!getUdfParseNodes().isEmpty()) {
+                stmt.throwIfUnallowedUserDefinedFunctions();
+            }
             CreateIndexCompiler compiler = new CreateIndexCompiler(stmt);
             return compiler.compile(this);
         }
@@ -908,19 +983,19 @@ public class PhoenixStatement implements Statement, SQLCloseable, org.apache.pho
         @Override
         public ExecutableSelectStatement select(TableNode from, HintNode hint, boolean isDistinct, List<AliasedNode> select, ParseNode where,
                 List<ParseNode> groupBy, ParseNode having, List<OrderByNode> orderBy, LimitNode limit, int bindCount, boolean isAggregate,
-                boolean hasSequence, List<SelectStatement> selects) {
+                boolean hasSequence, List<SelectStatement> selects, Map<String, UDFParseNode> udfParseNodes) {
             return new ExecutableSelectStatement(from, hint, isDistinct, select, where, groupBy == null ? Collections.<ParseNode>emptyList() : groupBy,
-                    having, orderBy == null ? Collections.<OrderByNode>emptyList() : orderBy, limit, bindCount, isAggregate, hasSequence, selects == null ? Collections.<SelectStatement>emptyList() : selects);
+                    having, orderBy == null ? Collections.<OrderByNode>emptyList() : orderBy, limit, bindCount, isAggregate, hasSequence, selects == null ? Collections.<SelectStatement>emptyList() : selects, udfParseNodes);
         }
 
         @Override
-        public ExecutableUpsertStatement upsert(NamedTableNode table, HintNode hintNode, List<ColumnName> columns, List<ParseNode> values, SelectStatement select, int bindCount) {
-            return new ExecutableUpsertStatement(table, hintNode, columns, values, select, bindCount);
+        public ExecutableUpsertStatement upsert(NamedTableNode table, HintNode hintNode, List<ColumnName> columns, List<ParseNode> values, SelectStatement select, int bindCount, Map<String, UDFParseNode> udfParseNodes) {
+            return new ExecutableUpsertStatement(table, hintNode, columns, values, select, bindCount, udfParseNodes);
         }
         
         @Override
-        public ExecutableDeleteStatement delete(NamedTableNode table, HintNode hint, ParseNode whereNode, List<OrderByNode> orderBy, LimitNode limit, int bindCount) {
-            return new ExecutableDeleteStatement(table, hint, whereNode, orderBy, limit, bindCount);
+        public ExecutableDeleteStatement delete(NamedTableNode table, HintNode hint, ParseNode whereNode, List<OrderByNode> orderBy, LimitNode limit, int bindCount, Map<String, UDFParseNode> udfParseNodes) {
+            return new ExecutableDeleteStatement(table, hint, whereNode, orderBy, limit, bindCount, udfParseNodes);
         }
         
         @Override
@@ -938,14 +1013,18 @@ public class PhoenixStatement implements Statement, SQLCloseable, org.apache.pho
         }
         
         @Override
+        public CreateFunctionStatement createFunction(PFunction functionInfo, boolean temporary) {
+            return new ExecutableCreateFunctionStatement(functionInfo, temporary);
+        }
+        @Override
         public DropSequenceStatement dropSequence(TableName tableName, boolean ifExists, int bindCount){
             return new ExecutableDropSequenceStatement(tableName, ifExists, bindCount);
         }
         
         @Override
         public CreateIndexStatement createIndex(NamedNode indexName, NamedTableNode dataTable, IndexKeyConstraint ikConstraint, List<ColumnName> includeColumns, List<ParseNode> splits,
-                ListMultimap<String,Pair<String,Object>> props, boolean ifNotExists, IndexType indexType, boolean async, int bindCount) {
-            return new ExecutableCreateIndexStatement(indexName, dataTable, ikConstraint, includeColumns, splits, props, ifNotExists, indexType, async, bindCount);
+                ListMultimap<String,Pair<String,Object>> props, boolean ifNotExists, IndexType indexType, boolean async, int bindCount, Map<String, UDFParseNode> udfParseNodes) {
+            return new ExecutableCreateIndexStatement(indexName, dataTable, ikConstraint, includeColumns, splits, props, ifNotExists, indexType, async, bindCount, udfParseNodes);
         }
         
         @Override
@@ -962,6 +1041,11 @@ public class PhoenixStatement implements Statement, SQLCloseable, org.apache.pho
         public DropTableStatement dropTable(TableName tableName, PTableType tableType, boolean ifExists, boolean cascade) {
             return new ExecutableDropTableStatement(tableName, tableType, ifExists, cascade);
         }
+
+        @Override
+        public DropFunctionStatement dropFunction(String functionName, boolean ifExists) {
+            return new ExecutableDropFunctionStatement(functionName, ifExists);
+        }
         
         @Override
         public DropIndexStatement dropIndex(NamedNode indexName, TableName tableName, boolean ifExists) {
@@ -1410,4 +1494,16 @@ public class PhoenixStatement implements Statement, SQLCloseable, org.apache.pho
     private void setLastQueryPlan(QueryPlan lastQueryPlan) {
         this.lastQueryPlan = lastQueryPlan;
     }
+    
+    private void throwIfUnallowedUserDefinedFunctions() throws SQLException {
+        if (!connection
+                .getQueryServices()
+                .getProps()
+                .getBoolean(QueryServices.ALLOW_USER_DEFINED_FUNCTIONS_ATTRIB,
+                    QueryServicesOptions.DEFAULT_ALLOW_USER_DEFINED_FUNCTIONS)) {
+            throw new SQLExceptionInfo.Builder(SQLExceptionCode.UNALLOWED_USER_DEFINED_FUNCTIONS)
+                    .build().buildException();
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java b/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
index 7b3a63a..99ca46e 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
@@ -235,7 +235,7 @@ public class QueryOptimizer {
         if (PIndexState.ACTIVE.equals(resolver.getTables().get(0).getTable().getIndexState())) {
             try {
             	// translate nodes that match expressions that are indexed to the associated column parse node
-                indexSelect = ParseNodeRewriter.rewrite(indexSelect, new  IndexExpressionParseNodeRewriter(index, statement.getConnection()));
+                indexSelect = ParseNodeRewriter.rewrite(indexSelect, new  IndexExpressionParseNodeRewriter(index, statement.getConnection(), indexSelect.getUdfParseNodes()));
                 QueryCompiler compiler = new QueryCompiler(statement, indexSelect, resolver, targetColumns, parallelIteratorFactory, dataPlan.getContext().getSequenceManager(), isProjected);
                 
                 QueryPlan plan = compiler.compile();
@@ -290,7 +290,7 @@ public class QueryOptimizer {
                             aliasedNodes.add(FACTORY.aliasedNode(null, indexColNode));
                             nodes.add(new ColumnParseNode(null, '"' + column.getName().getString() + '"'));
                         }
-                        SelectStatement innerSelect = FACTORY.select(indexSelect.getFrom(), indexSelect.getHint(), false, aliasedNodes, where, null, null, null, indexSelect.getLimit(), indexSelect.getBindCount(), false, indexSelect.hasSequence(), Collections.<SelectStatement>emptyList());
+                        SelectStatement innerSelect = FACTORY.select(indexSelect.getFrom(), indexSelect.getHint(), false, aliasedNodes, where, null, null, null, indexSelect.getLimit(), indexSelect.getBindCount(), false, indexSelect.hasSequence(), Collections.<SelectStatement>emptyList(), indexSelect.getUdfParseNodes());
                         ParseNode outerWhere = FACTORY.in(nodes.size() == 1 ? nodes.get(0) : FACTORY.rowValueConstructor(nodes), FACTORY.subquery(innerSelect, false), false, true);
                         ParseNode extractedCondition = whereRewriter.getExtractedCondition();
                         if (extractedCondition != null) {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/parse/CreateFunctionStatement.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/CreateFunctionStatement.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/CreateFunctionStatement.java
new file mode 100644
index 0000000..741e4df
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/CreateFunctionStatement.java
@@ -0,0 +1,42 @@
+/*
+ * 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.phoenix.parse;
+
+public class CreateFunctionStatement extends MutableStatement {
+    private final PFunction functionInfo;
+    private final boolean temporary;
+
+    public CreateFunctionStatement(PFunction functionInfo, boolean temporary) {
+        this.functionInfo = functionInfo;
+        this.temporary = temporary;
+    }
+
+    @Override
+    public int getBindCount() {
+        return 0;
+    }
+
+    public PFunction getFunctionInfo() {
+        return functionInfo;
+    }
+    
+    public boolean isTemporary() {
+        return temporary;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/parse/CreateIndexStatement.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/CreateIndexStatement.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/CreateIndexStatement.java
index 5f52f59..6da0ff3 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/CreateIndexStatement.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/CreateIndexStatement.java
@@ -19,6 +19,7 @@ package org.apache.phoenix.parse;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.hadoop.hbase.util.Pair;
 import org.apache.phoenix.schema.PTable.IndexType;
@@ -36,10 +37,11 @@ public class CreateIndexStatement extends SingleTableStatement {
     private final boolean ifNotExists;
     private final IndexType indexType;
     private final boolean async;
+    private final Map<String, UDFParseNode> udfParseNodes;
 
     public CreateIndexStatement(NamedNode indexTableName, NamedTableNode dataTable, 
             IndexKeyConstraint indexKeyConstraint, List<ColumnName> includeColumns, List<ParseNode> splits,
-            ListMultimap<String,Pair<String,Object>> props, boolean ifNotExists, IndexType indexType, boolean async, int bindCount) {
+            ListMultimap<String,Pair<String,Object>> props, boolean ifNotExists, IndexType indexType, boolean async, int bindCount, Map<String, UDFParseNode> udfParseNodes) {
         super(dataTable, bindCount);
         this.indexTableName =TableName.create(dataTable.getName().getSchemaName(),indexTableName.getName());
         this.indexKeyConstraint = indexKeyConstraint == null ? IndexKeyConstraint.EMPTY : indexKeyConstraint;
@@ -49,6 +51,7 @@ public class CreateIndexStatement extends SingleTableStatement {
         this.ifNotExists = ifNotExists;
         this.indexType = indexType;
         this.async = async;
+        this.udfParseNodes = udfParseNodes;
     }
 
     public IndexKeyConstraint getIndexConstraint() {
@@ -84,4 +87,7 @@ public class CreateIndexStatement extends SingleTableStatement {
         return async;
     }
 
+    public Map<String, UDFParseNode> getUdfParseNodes() {
+        return udfParseNodes;
+    }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/parse/DMLStatement.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/DMLStatement.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/DMLStatement.java
index 46bd907..3b9bd97 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/DMLStatement.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/DMLStatement.java
@@ -17,11 +17,18 @@
  */
 package org.apache.phoenix.parse;
 
+import java.util.Map;
+
 public class DMLStatement extends SingleTableStatement {
 
-    public DMLStatement(NamedTableNode table, int bindCount) {
+    private final Map<String, UDFParseNode> udfParseNodes;
+    
+    public DMLStatement(NamedTableNode table, int bindCount, Map<String, UDFParseNode> udfParseNodes) {
         super(table, bindCount);
+        this.udfParseNodes = udfParseNodes;
     }
     
-
+    public Map<String, UDFParseNode> getUdfParseNodes() {
+        return udfParseNodes;
+    }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/parse/DeleteStatement.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/DeleteStatement.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/DeleteStatement.java
index d295e85..276e6aa 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/DeleteStatement.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/DeleteStatement.java
@@ -19,6 +19,7 @@ package org.apache.phoenix.parse;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.phoenix.jdbc.PhoenixStatement.Operation;
 
@@ -28,8 +29,8 @@ public class DeleteStatement extends DMLStatement implements FilterableStatement
     private final LimitNode limit;
     private final HintNode hint;
     
-    public DeleteStatement(NamedTableNode table, HintNode hint, ParseNode whereNode, List<OrderByNode> orderBy, LimitNode limit, int bindCount) {
-        super(table, bindCount);
+    public DeleteStatement(NamedTableNode table, HintNode hint, ParseNode whereNode, List<OrderByNode> orderBy, LimitNode limit, int bindCount, Map<String, UDFParseNode> udfParseNodes) {
+        super(table, bindCount, udfParseNodes);
         this.whereNode = whereNode;
         this.orderBy = orderBy == null ? Collections.<OrderByNode>emptyList() : orderBy;
         this.limit = limit;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/parse/DropFunctionStatement.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/DropFunctionStatement.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/DropFunctionStatement.java
new file mode 100644
index 0000000..a959eb7
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/DropFunctionStatement.java
@@ -0,0 +1,41 @@
+/*
+ * 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.phoenix.parse;
+
+public class DropFunctionStatement extends MutableStatement {
+
+    private final String functionName;
+    private final boolean ifExists;
+    public DropFunctionStatement(String functionName, boolean ifExists) {
+        this.functionName = functionName;
+        this.ifExists = ifExists;
+    }
+
+    @Override
+    public int getBindCount() {
+        return 0;
+    }
+
+    public String getFunctionName() {
+        return functionName;
+    }
+
+    public boolean ifExists() {
+        return ifExists;
+    }
+}

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/parse/FunctionParseNode.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/FunctionParseNode.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/FunctionParseNode.java
index 9764f52..bd13fb5 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/FunctionParseNode.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/FunctionParseNode.java
@@ -38,6 +38,9 @@ import org.apache.phoenix.expression.Expression;
 import org.apache.phoenix.expression.LiteralExpression;
 import org.apache.phoenix.expression.function.AggregateFunction;
 import org.apache.phoenix.expression.function.FunctionExpression;
+import org.apache.phoenix.expression.function.UDFExpression;
+import org.apache.phoenix.jdbc.PhoenixStatement;
+import org.apache.phoenix.parse.PFunction.FunctionArgument;
 import org.apache.phoenix.schema.ArgumentTypeMismatchException;
 import org.apache.phoenix.schema.ValueRangeExcpetion;
 import org.apache.phoenix.schema.types.PDataType;
@@ -58,7 +61,7 @@ import com.google.common.collect.ImmutableSet;
  */
 public class FunctionParseNode extends CompoundParseNode {
     private final String name;
-    private final BuiltInFunctionInfo info;
+    private BuiltInFunctionInfo info;
 
     FunctionParseNode(String name, List<ParseNode> children, BuiltInFunctionInfo info) {
         super(children);
@@ -84,6 +87,7 @@ public class FunctionParseNode extends CompoundParseNode {
     }
 
     public boolean isAggregate() {
+        if(getInfo()==null) return false;
         return getInfo().isAggregate();
     }
 
@@ -113,9 +117,17 @@ public class FunctionParseNode extends CompoundParseNode {
     }
 
     private static Constructor<? extends FunctionExpression> getExpressionCtor(Class<? extends FunctionExpression> clazz) {
+        return getExpressionCtor(clazz, null);
+    }
+
+    private static Constructor<? extends FunctionExpression> getExpressionCtor(Class<? extends FunctionExpression> clazz, PFunction function) {
         Constructor<? extends FunctionExpression> ctor;
         try {
-            ctor = clazz.getDeclaredConstructor(List.class);
+            if(function == null) {
+                ctor = clazz.getDeclaredConstructor(List.class);
+            } else {
+                ctor = clazz.getDeclaredConstructor(List.class, PFunction.class);
+            }
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -223,8 +235,24 @@ public class FunctionParseNode extends CompoundParseNode {
      * @throws SQLException
      */
     public Expression create(List<Expression> children, StatementContext context) throws SQLException {
+        return create(children, null, context);
+    }
+
+    /**
+     * Entry point for parser to instantiate compiled representation of built-in function
+     * @param children Compiled expressions for child nodes
+     * @param function
+     * @param context Query context for accessing state shared across the processing of multiple clauses
+     * @return compiled representation of built-in function
+     * @throws SQLException
+     */
+    public Expression create(List<Expression> children, PFunction function, StatementContext context) throws SQLException {
         try {
-            return info.getFuncCtor().newInstance(children);
+            if(function == null) {
+                return info.getFuncCtor().newInstance(children);
+            } else {
+                return info.getFuncCtor().newInstance(children, function);
+            }
         } catch (InstantiationException e) {
             throw new SQLException(e);
         } catch (IllegalAccessException e) {
@@ -275,9 +303,9 @@ public class FunctionParseNode extends CompoundParseNode {
         private final boolean isAggregate;
         private final int requiredArgCount;
 
-        BuiltInFunctionInfo(Class<? extends FunctionExpression> f, BuiltInFunction d) {
+        public BuiltInFunctionInfo(Class<? extends FunctionExpression> f, BuiltInFunction d) {
             this.name = SchemaUtil.normalizeIdentifier(d.name());
-            this.funcCtor = d.nodeClass() == FunctionParseNode.class ? getExpressionCtor(f) : null;
+            this.funcCtor = d.nodeClass() == FunctionParseNode.class ? getExpressionCtor(f, null) : null;
             this.nodeCtor = d.nodeClass() == FunctionParseNode.class ? null : getParseNodeCtor(d.nodeClass());
             this.args = new BuiltInFunctionArgInfo[d.args().length];
             int requiredArgCount = 0;
@@ -291,6 +319,22 @@ public class FunctionParseNode extends CompoundParseNode {
             this.isAggregate = AggregateFunction.class.isAssignableFrom(f);
         }
 
+        public BuiltInFunctionInfo(PFunction function) {
+            this.name = SchemaUtil.normalizeIdentifier(function.getFunctionName());
+            this.funcCtor = getExpressionCtor(UDFExpression.class, function);
+            this.nodeCtor = getParseNodeCtor(UDFParseNode.class);
+            this.args = new BuiltInFunctionArgInfo[function.getFunctionArguments().size()];
+            int requiredArgCount = 0;
+            for (int i = 0; i < args.length; i++) {
+                this.args[i] = new BuiltInFunctionArgInfo(function.getFunctionArguments().get(i));
+                if (this.args[i].getDefaultValue() == null) {
+                    requiredArgCount = i + 1;
+                }
+            }
+            this.requiredArgCount = requiredArgCount;
+            this.isAggregate = AggregateFunction.class.isAssignableFrom(UDFExpression.class);
+        }
+
         public int getRequiredArgCount() {
             return requiredArgCount;
         }
@@ -365,6 +409,27 @@ public class FunctionParseNode extends CompoundParseNode {
             }
         }
 
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        BuiltInFunctionArgInfo(FunctionArgument arg) {
+            PDataType dataType =
+                    arg.isArrayType() ? PDataType.fromTypeId(PDataType.sqlArrayType(SchemaUtil
+                            .normalizeIdentifier(SchemaUtil.normalizeIdentifier(arg
+                                    .getArgumentType())))) : PDataType.fromSqlTypeName(SchemaUtil
+                            .normalizeIdentifier(arg.getArgumentType()));
+            this.allowedValues = Collections.emptySet();
+            this.allowedTypes = new Class[] { dataType.getClass() };
+            this.isConstant = arg.isConstant();
+            this.defaultValue =
+                    arg.getDefaultValue() == null ? null : getExpFromConstant((String) arg
+                            .getDefaultValue().getValue());
+            this.minValue =
+                    arg.getMinValue() == null ? null : getExpFromConstant((String) arg
+                            .getMinValue().getValue());
+            this.maxValue =
+                    arg.getMaxValue() == null ? null : getExpFromConstant((String) arg
+                            .getMaxValue().getValue());
+        }
+        
         private LiteralExpression getExpFromConstant(String strValue) {
             LiteralExpression exp = null;
             if (strValue.length() > 0) {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/parse/IndexExpressionParseNodeRewriter.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/IndexExpressionParseNodeRewriter.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/IndexExpressionParseNodeRewriter.java
index 0273041..9f7b2bf 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/IndexExpressionParseNodeRewriter.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/IndexExpressionParseNodeRewriter.java
@@ -37,12 +37,12 @@ public class IndexExpressionParseNodeRewriter extends ParseNodeRewriter {
 
     private final Map<ParseNode, ParseNode> indexedParseNodeToColumnParseNodeMap;
     
-    public IndexExpressionParseNodeRewriter(PTable index, PhoenixConnection connection) throws SQLException {
+    public IndexExpressionParseNodeRewriter(PTable index, PhoenixConnection connection, Map<String, UDFParseNode> udfParseNodes) throws SQLException {
         indexedParseNodeToColumnParseNodeMap = Maps.newHashMapWithExpectedSize(index.getColumns().size());
         NamedTableNode tableNode = NamedTableNode.create(null,
                 TableName.create(index.getParentSchemaName().getString(), index.getParentTableName().getString()),
                 Collections.<ColumnDef> emptyList());
-        ColumnResolver dataResolver = FromCompiler.getResolver(tableNode, connection);
+        ColumnResolver dataResolver = FromCompiler.getResolver(tableNode, connection, udfParseNodes);
         StatementContext context = new StatementContext(new PhoenixStatement(connection), dataResolver);
         IndexStatementRewriter rewriter = new IndexStatementRewriter(dataResolver, null);
         ExpressionCompiler expressionCompiler = new ExpressionCompiler(context);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/parse/NamedNode.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/NamedNode.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/NamedNode.java
index 3f1becc..1957f0e 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/NamedNode.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/NamedNode.java
@@ -27,7 +27,7 @@ public class NamedNode {
     public static NamedNode caseSensitiveNamedNode(String name) {
         return new NamedNode(name,true);
     }
-    
+
     NamedNode(String name, boolean isCaseSensitive) {
         this.name = name;
         this.isCaseSensitive = isCaseSensitive;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/66bd3e35/phoenix-core/src/main/java/org/apache/phoenix/parse/PFunction.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/PFunction.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/PFunction.java
new file mode 100644
index 0000000..351bec7
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/PFunction.java
@@ -0,0 +1,255 @@
+/*
+ * 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.phoenix.parse;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.phoenix.coprocessor.generated.PFunctionProtos;
+import org.apache.phoenix.coprocessor.generated.PFunctionProtos.PFunctionArg;
+import org.apache.phoenix.expression.LiteralExpression;
+import org.apache.phoenix.schema.PMetaDataEntity;
+import org.apache.phoenix.schema.PName;
+import org.apache.phoenix.schema.PNameFactory;
+import org.apache.phoenix.schema.PTableKey;
+import org.apache.phoenix.util.SizedUtil;
+
+import com.google.protobuf.HBaseZeroCopyByteString;
+
+public class PFunction implements PMetaDataEntity {
+
+    private PName tenantId = null;
+    private final PName functionName;
+    private List<FunctionArgument> args;
+    private PName className;
+    private PName jarPath;
+    private PName returnType;
+    private PTableKey functionKey;
+    private long timeStamp;
+    private int estimatedSize;
+    private boolean temporary;
+
+    public PFunction(long timeStamp) { // For index delete marker
+        this.timeStamp = timeStamp;
+        this.args = Collections.emptyList();
+        this.functionName = null;
+    }
+
+    public PFunction(String functionName, List<FunctionArgument> args, String returnType,
+            String className, String jarPath) {
+        this(functionName,args,returnType,className, jarPath, HConstants.LATEST_TIMESTAMP);
+    }
+
+    public PFunction(String functionName, List<FunctionArgument> args, String returnType,
+            String className, String jarPath, long timeStamp) {
+        this(null, functionName, args, returnType, className, jarPath, timeStamp);
+    }    
+
+    public PFunction(PName tenantId, String functionName, List<FunctionArgument> args, String returnType,
+            String className, String jarPath, long timeStamp) {
+        this(tenantId, functionName, args, returnType, className, jarPath, timeStamp, false);
+    }
+    
+    public PFunction(PFunction function, boolean temporary) {
+        this(function.getTenantId(), function.getFunctionName(), function.getFunctionArguments(),
+                function.getReturnType(), function.getClassName(), function.getJarPath(), function
+                        .getTimeStamp(), temporary);
+    }
+
+    public PFunction(PName tenantId, String functionName, List<FunctionArgument> args, String returnType,
+            String className, String jarPath, long timeStamp, boolean temporary) {
+        this.tenantId = tenantId;
+        this.functionName = PNameFactory.newName(functionName);
+        if (args == null){ 
+            this.args = new ArrayList<FunctionArgument>();
+        } else {
+            this.args = args;
+        }
+        this.className = PNameFactory.newName(className);
+        this.jarPath = jarPath == null ? null : PNameFactory.newName(jarPath);
+        this.returnType = PNameFactory.newName(returnType);
+        this.functionKey = new PTableKey(this.tenantId, this.functionName.getString());
+        this.timeStamp = timeStamp;
+        int estimatedSize = SizedUtil.OBJECT_SIZE * 2 + 23 * SizedUtil.POINTER_SIZE + 4 * SizedUtil.INT_SIZE + 2 * SizedUtil.LONG_SIZE + 2 * SizedUtil.INT_OBJECT_SIZE +
+                PNameFactory.getEstimatedSize(tenantId) +
+                PNameFactory.getEstimatedSize(this.functionName) +
+                PNameFactory.getEstimatedSize(this.className) +
+                 (jarPath==null?0:PNameFactory.getEstimatedSize(this.jarPath));
+        this.temporary = temporary;
+    }
+
+    public PFunction(PFunction function) {
+        this(function.getTenantId(), function.getFunctionName(), function.getFunctionArguments(),
+                function.getReturnType(), function.getClassName(), function.getJarPath(), function
+                        .getTimeStamp());
+    }
+
+    public String getFunctionName() {
+        return functionName == null ? null : functionName.getString();
+    }
+
+    public List<FunctionArgument> getFunctionArguments() {
+        return args;
+    }
+
+    public String getClassName() {
+        return className.getString();
+    }
+
+    public String getJarPath() {
+        return jarPath == null ? null : jarPath.getString();
+    }
+
+    public String getReturnType() {
+        return returnType.getString();
+    }
+    
+    public PTableKey getKey() {
+        return this.functionKey;
+    }
+    
+    public long getTimeStamp() {
+        return this.timeStamp;
+    }
+    
+    public PName getTenantId() {
+        return this.tenantId;
+    }
+    
+    public boolean isTemporaryFunction() {
+        return temporary;
+    }
+    
+    public static class FunctionArgument {
+        private final PName argumentType;
+        private final boolean isArrayType;
+        private final boolean isConstant;
+        private final LiteralExpression defaultValue;
+        private final LiteralExpression minValue;
+        private final LiteralExpression maxValue;
+        private short argPosition;
+        
+        public FunctionArgument(String argumentType, boolean isArrayType, boolean isConstant, LiteralExpression defaultValue,
+                LiteralExpression minValue, LiteralExpression maxValue) {
+            this.argumentType = PNameFactory.newName(argumentType);
+            this.isArrayType = isArrayType;
+            this.isConstant = isConstant;
+            this.defaultValue = defaultValue;
+            this.minValue = minValue;
+            this.maxValue = maxValue;
+        }
+        public FunctionArgument(String argumentType, boolean isArrayType, boolean isConstant, LiteralExpression defaultValue,
+                LiteralExpression minValue, LiteralExpression maxValue, short argPosition) {
+            this(argumentType, isArrayType, isConstant, defaultValue, minValue, maxValue);
+            this.argPosition = argPosition;
+        }
+
+        public String getArgumentType() {
+            return argumentType.getString();
+        }
+
+        public boolean isConstant() {
+            return isConstant;
+        }
+
+        public boolean isArrayType() {
+            return isArrayType;
+        }
+
+        public LiteralExpression getDefaultValue() {
+            return defaultValue;
+        }
+
+        public LiteralExpression getMinValue() {
+            return minValue;
+        }
+
+        public LiteralExpression getMaxValue() {
+            return maxValue;
+        }
+        
+        public short getArgPosition() {
+            return argPosition;
+        }
+    }
+    
+    public static PFunctionProtos.PFunction toProto(PFunction function) {
+        PFunctionProtos.PFunction.Builder builder = PFunctionProtos.PFunction.newBuilder();
+        if(function.getTenantId() != null){
+          builder.setTenantId(HBaseZeroCopyByteString.wrap(function.getTenantId().getBytes()));
+        }
+        builder.setFunctionName(function.getFunctionName());
+        builder.setClassname(function.getClassName());
+        if (function.getJarPath() != null) {
+            builder.setJarPath(function.getJarPath());
+        }
+        builder.setReturnType(function.getReturnType());
+        builder.setTimeStamp(function.getTimeStamp());
+        for(FunctionArgument arg: function.getFunctionArguments()) {
+            PFunctionProtos.PFunctionArg.Builder argBuilder = PFunctionProtos.PFunctionArg.newBuilder();
+            argBuilder.setArgumentType(arg.getArgumentType());
+            argBuilder.setIsArrayType(arg.isArrayType);
+            argBuilder.setIsConstant(arg.isConstant);
+            if(arg.getDefaultValue() != null) {
+                argBuilder.setDefaultValue((String)arg.getDefaultValue().getValue());
+            }
+            if(arg.getMinValue() != null) {
+                argBuilder.setMinValue((String)arg.getMinValue().getValue());
+            }
+            if(arg.getMaxValue() != null) {
+                argBuilder.setMaxValue((String)arg.getMaxValue().getValue());
+            }
+            builder.addArguments(argBuilder.build());
+        }
+        return builder.build();
+      }
+
+    public static PFunction createFromProto(
+            org.apache.phoenix.coprocessor.generated.PFunctionProtos.PFunction function) {
+        PName tenantId = null;
+        if(function.hasTenantId()){
+          tenantId = PNameFactory.newName(function.getTenantId().toByteArray());
+        }
+        String functionName = function.getFunctionName();
+        long timeStamp = function.getTimeStamp();
+        String className = function.getClassname();
+        String jarPath = function.getJarPath();
+        String returnType = function.getReturnType();
+        List<FunctionArgument> args = new ArrayList<FunctionArgument>(function.getArgumentsCount());
+        for(PFunctionArg arg: function.getArgumentsList()) {
+            String argType = arg.getArgumentType();
+            boolean isArrayType = arg.hasIsArrayType()?arg.getIsArrayType():false;
+            boolean isConstant = arg.hasIsConstant()?arg.getIsConstant():false;
+            String defaultValue = arg.hasDefaultValue()?arg.getDefaultValue():null;
+            String minValue = arg.hasMinValue()?arg.getMinValue():null;
+            String maxValue = arg.hasMaxValue()?arg.getMaxValue():null;
+            args.add(new FunctionArgument(argType, isArrayType, isConstant,
+                    defaultValue == null ? null : LiteralExpression.newConstant(defaultValue),
+                    minValue == null ? null : LiteralExpression.newConstant(minValue),
+                    maxValue == null ? null : LiteralExpression.newConstant(maxValue)));
+        }
+        return new PFunction(tenantId,functionName, args, returnType, className, jarPath, timeStamp);
+    }
+
+    public int getEstimatedSize() {
+        return estimatedSize;
+    }
+}
+