You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asterixdb.apache.org by dl...@apache.org on 2021/04/13 00:06:52 UTC

[asterixdb] 03/07: [ASTERIXDB-2871][COMP] Improve UDF recursion detection

This is an automated email from the ASF dual-hosted git repository.

dlych pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb.git

commit 9ba748792633afce52a18cd2c7f3ddbde5cc304f
Author: Dmitry Lychagin <dm...@couchbase.com>
AuthorDate: Mon Apr 5 14:38:20 2021 -0700

    [ASTERIXDB-2871][COMP] Improve UDF recursion detection
    
    - user model changes: no
    - storage format changes: no
    - interface changes: no
    
    Details:
    - Improve UDF recursion detection
    - Rewrite UDF body expression only once
    - Add testcases
    
    Change-Id: I7886404bfa47d1d89674df1200f30ad6ad0e0fc6
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/10905
    Integration-Tests: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Tested-by: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Reviewed-by: Ali Alsuliman <al...@gmail.com>
---
 .../apache/asterix/api/common/APIFramework.java    |  10 +-
 .../asterix/app/translator/QueryTranslator.java    |  34 +-
 .../asterix/test/sqlpp/ParserTestExecutor.java     |  63 ++--
 .../parserts/results_parser_sqlpp/IfInFLOWGR.ast   |  22 +-
 .../parserts/results_parser_sqlpp/IfThenElse.ast   |  18 +-
 .../results_parser_sqlpp/nestedFLWOGR1.ast         |  18 +-
 .../results_parser_sqlpp/nestedFLWOGR2.ast         |  22 +-
 .../udf37_recursion/udf37_recursion.1.ddl.sqlpp    |  23 ++
 .../udf37_recursion/udf37_recursion.2.ddl.sqlpp    |  24 ++
 .../udf37_recursion/udf37_recursion.3.ddl.sqlpp    |  25 ++
 .../udf37_recursion/udf37_recursion.4.ddl.sqlpp    |  26 ++
 .../udf37_recursion/udf37_recursion.5.query.sqlpp  |  27 ++
 .../udf37_recursion/udf37_recursion.6.query.sqlpp  |  28 ++
 .../udf37_recursion/udf37_recursion.7.query.sqlpp  |  29 ++
 .../udf37_recursion/udf37_recursion.8.ddl.sqlpp    |  30 ++
 .../udf37_recursion/udf37_recursion.9.query.sqlpp  |  31 ++
 .../udf38_no_recursion.1.ddl.sqlpp                 |  31 ++
 .../udf38_no_recursion.2.query.sqlpp               |  28 ++
 .../udf38_no_recursion.3.ddl.sqlpp                 |  36 ++
 .../udf38_no_recursion.4.query.sqlpp               |  28 ++
 .../udf38_no_recursion.5.ddl.sqlpp                 |  37 +++
 .../udf38_no_recursion.6.query.sqlpp               |  28 ++
 .../udf39_illegal_call.1.ddl.sqlpp                 |  32 ++
 .../udf39_illegal_call.2.ddl.sqlpp                 |  29 ++
 .../udf38_no_recursion/udf38_no_recursion.2.adm    |   2 +
 .../udf38_no_recursion/udf38_no_recursion.4.adm    |   2 +
 .../udf38_no_recursion/udf38_no_recursion.6.adm    |   2 +
 .../grouping-sets-2/grouping-sets-2.17.ast         |  92 +++---
 .../group-by/grouping-sets-2/grouping-sets-2.8.ast |  78 ++---
 .../misc/ifthenelse_01/ifthenelse_01.3.ast         |  18 +-
 .../string/like_01/like_01.3.ast                   |  12 +-
 .../string/like_null/like_null.3.ast               |   6 +-
 .../q08_national_market_share.3.ast                |  36 +-
 .../tpch-sql-like/q12_shipping/q12_shipping.3.ast  |  86 +----
 .../q13_customer_distribution.3.ast                |  13 +-
 .../q14_promotion_effect.3.ast                     |  39 +--
 .../q08_national_market_share.3.ast                |  32 +-
 .../tpch/q12_shipping/q12_shipping.3.ast           |  81 ++---
 .../q13_customer_distribution.3.ast                |  13 +-
 .../q14_promotion_effect.3.ast                     |  39 +--
 .../window/misc_01/misc_01.3.ast                   |  22 +-
 .../test/resources/runtimets/testsuite_sqlpp.xml   |  25 ++
 .../asterix/common/exceptions/ErrorCode.java       |   2 +
 .../src/main/resources/asx_errormsg/en.properties  |   4 +-
 .../apache/asterix/lang/common/base/IParser.java   |   3 +-
 .../asterix/lang/common/base/IQueryRewriter.java   |  21 +-
 .../asterix/lang/common/parser/FunctionParser.java |  62 ----
 .../lang/common/rewrites/LangRewritingContext.java |  26 +-
 .../lang/common/statement/FunctionDecl.java        |  23 +-
 .../asterix/lang/common/util/FunctionUtil.java     | 219 ++++++------
 .../common/visitor/AbstractInlineUdfsVisitor.java  | 335 ++++++++-----------
 .../CloneAndSubstituteVariablesVisitor.java        |   2 +-
 .../common/visitor/GatherFunctionCallsVisitor.java |  15 +-
 .../sqlpp/rewrites/SqlppFunctionBodyRewriter.java  |  20 +-
 .../lang/sqlpp/rewrites/SqlppQueryRewriter.java    | 368 +++++++--------------
 .../visitor/SqlppFunctionCallResolverVisitor.java  |  22 +-
 .../visitor/SqlppGatherFunctionCallsVisitor.java   | 238 +++++++++++++
 .../rewrites/visitor/SqlppInlineUdfsVisitor.java   | 132 ++++----
 .../lang/sqlpp/visitor/DeepCopyVisitor.java        |   4 +-
 .../asterix-lang-sqlpp/src/main/javacc/SQLPP.jj    |   6 +-
 60 files changed, 1608 insertions(+), 1171 deletions(-)

diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
index 919a24e..b3c7f54 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
@@ -185,8 +185,9 @@ public class APIFramework {
     }
 
     public Pair<IReturningStatement, Integer> reWriteQuery(List<FunctionDecl> declaredFunctions,
-            MetadataProvider metadataProvider, IReturningStatement q, SessionOutput output, boolean inlineUdfs,
-            Collection<VarIdentifier> externalVars, IWarningCollector warningCollector) throws CompilationException {
+            MetadataProvider metadataProvider, IReturningStatement q, SessionOutput output,
+            boolean allowNonStoredUdfCalls, boolean inlineUdfs, Collection<VarIdentifier> externalVars,
+            IWarningCollector warningCollector) throws CompilationException {
         if (q == null) {
             return null;
         }
@@ -195,8 +196,9 @@ public class APIFramework {
             generateExpressionTree(q);
         }
         IQueryRewriter rw = rewriterFactory.createQueryRewriter();
-        rw.rewrite(new ArrayList<>(declaredFunctions), q, metadataProvider,
-                new LangRewritingContext(q.getVarCounter(), warningCollector), inlineUdfs, externalVars);
+        LangRewritingContext rwCtx =
+                new LangRewritingContext(metadataProvider, declaredFunctions, warningCollector, q.getVarCounter());
+        rw.rewrite(rwCtx, q, allowNonStoredUdfCalls, inlineUdfs, externalVars);
         return new Pair<>(q, q.getVarCounter());
     }
 
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 d8698f9..60a96d4 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
@@ -95,13 +95,17 @@ import org.apache.asterix.external.indexing.IndexingConstants;
 import org.apache.asterix.external.operators.FeedIntakeOperatorNodePushable;
 import org.apache.asterix.external.util.ExternalDataConstants;
 import org.apache.asterix.external.util.ExternalDataUtils;
+import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.IReturningStatement;
 import org.apache.asterix.lang.common.base.IRewriterFactory;
 import org.apache.asterix.lang.common.base.IStatementRewriter;
 import org.apache.asterix.lang.common.base.Statement;
+import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.expression.IndexedTypeExpression;
+import org.apache.asterix.lang.common.expression.LiteralExpr;
 import org.apache.asterix.lang.common.expression.TypeExpression;
 import org.apache.asterix.lang.common.expression.TypeReferenceExpression;
+import org.apache.asterix.lang.common.literal.MissingLiteral;
 import org.apache.asterix.lang.common.statement.AdapterDropStatement;
 import org.apache.asterix.lang.common.statement.CompactStatement;
 import org.apache.asterix.lang.common.statement.ConnectFeedStatement;
@@ -2158,16 +2162,30 @@ public class QueryTranslator extends AbstractLangTranslator implements IStatemen
                     }
                 }
 
-                //Check whether the function is use-able
+                // Check whether the function is usable:
+                // create a function declaration for this function,
+                // and a query body calls this function with each argument set to 'missing'
+                FunctionDecl fd = new FunctionDecl(functionSignature, paramVars, cfs.getFunctionBodyExpression(), true);
+                fd.setSourceLocation(sourceLoc);
+                CallExpr fcall = new CallExpr(functionSignature,
+                        Collections.nCopies(paramVars.size(), new LiteralExpr(MissingLiteral.INSTANCE)));
+                fcall.setSourceLocation(sourceLoc);
                 metadataProvider.setDefaultDataverse(dv);
                 Query wrappedQuery = new Query(false);
                 wrappedQuery.setSourceLocation(sourceLoc);
-                wrappedQuery.setBody(cfs.getFunctionBodyExpression());
+                wrappedQuery.setBody(fcall);
                 wrappedQuery.setTopLevel(false);
-                apiFramework.reWriteQuery(declaredFunctions, metadataProvider, wrappedQuery, sessionOutput, false,
-                        paramVars, warningCollector);
-                List<List<Triple<DataverseName, String, String>>> dependencies = FunctionUtil.getFunctionDependencies(
-                        rewriterFactory.createQueryRewriter(), cfs.getFunctionBodyExpression());
+                List<FunctionDecl> fdList = new ArrayList<>(declaredFunctions);
+                fdList.add(fd);
+                apiFramework.reWriteQuery(fdList, metadataProvider, wrappedQuery, sessionOutput, false, false,
+                        Collections.emptyList(), warningCollector);
+                Expression fdNormBody = fd.getNormalizedFuncBody();
+                if (fdNormBody == null) {
+                    throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, sourceLoc,
+                            functionSignature.toString());
+                }
+                List<List<Triple<DataverseName, String, String>>> dependencies =
+                        FunctionUtil.getFunctionDependencies(rewriterFactory.createQueryRewriter(), fdNormBody);
 
                 newInlineTypes = Collections.emptyMap();
                 function = new Function(functionSignature, paramNames, null, null, cfs.getFunctionBody(),
@@ -2864,7 +2882,7 @@ public class QueryTranslator extends AbstractLangTranslator implements IStatemen
 
         // Query Rewriting (happens under the same ongoing metadata transaction)
         Pair<IReturningStatement, Integer> rewrittenResult = apiFramework.reWriteQuery(declaredFunctions,
-                metadataProvider, query, sessionOutput, true, externalVars.keySet(), warningCollector);
+                metadataProvider, query, sessionOutput, true, true, externalVars.keySet(), warningCollector);
 
         // Query Compilation (happens under the same ongoing metadata transaction)
         return apiFramework.compileQuery(clusterInfoCollector, metadataProvider, (Query) rewrittenResult.first,
@@ -2881,7 +2899,7 @@ public class QueryTranslator extends AbstractLangTranslator implements IStatemen
 
         // Insert/upsert statement rewriting (happens under the same ongoing metadata transaction)
         Pair<IReturningStatement, Integer> rewrittenResult = apiFramework.reWriteQuery(declaredFunctions,
-                metadataProvider, insertUpsert, sessionOutput, true, externalVars.keySet(), warningCollector);
+                metadataProvider, insertUpsert, sessionOutput, true, true, externalVars.keySet(), warningCollector);
 
         InsertStatement rewrittenInsertUpsert = (InsertStatement) rewrittenResult.first;
         DataverseName dataverseName = getActiveDataverseName(rewrittenInsertUpsert.getDataverseName());
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/sqlpp/ParserTestExecutor.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/sqlpp/ParserTestExecutor.java
index 8b6f05f..98f328d 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/sqlpp/ParserTestExecutor.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/sqlpp/ParserTestExecutor.java
@@ -27,15 +27,17 @@ import static org.mockito.Mockito.when;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.PrintWriter;
+import java.lang.reflect.Method;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import org.apache.asterix.common.config.GlobalConfig;
-import org.apache.asterix.common.exceptions.AsterixException;
 import org.apache.asterix.common.functions.FunctionSignature;
 import org.apache.asterix.common.metadata.DataverseName;
 import org.apache.asterix.lang.common.base.IParser;
@@ -187,8 +189,9 @@ public class ParserTestExecutor extends TestExecutor {
                 if (st.getKind() == Statement.Kind.QUERY) {
                     Query query = (Query) st;
                     IQueryRewriter rewriter = sqlppRewriterFactory.createQueryRewriter();
-                    rewrite(rewriter, functions, query, metadataProvider,
-                            new LangRewritingContext(query.getVarCounter(), TestUtils.NOOP_WARNING_COLLECTOR));
+                    LangRewritingContext rwContext = new LangRewritingContext(metadataProvider, functions,
+                            TestUtils.NOOP_WARNING_COLLECTOR, query.getVarCounter());
+                    rewrite(rewriter, query, rwContext);
 
                     // Tests deep copy and deep equality.
                     Query copiedQuery = (Query) SqlppRewriteUtil.deepCopy(query);
@@ -254,28 +257,38 @@ public class ParserTestExecutor extends TestExecutor {
     }
 
     // Rewrite queries.
-    // Note: we do not do inline function rewriting here because this needs real
-    // metadata access.
-    private void rewrite(IQueryRewriter rewriter, List<FunctionDecl> declaredFunctions, Query topExpr,
-            MetadataProvider metadataProvider, LangRewritingContext context) throws AsterixException {
-        PA.invokeMethod(rewriter,
-                "setup(java.util.List, org.apache.asterix.lang.common.base.IReturningStatement, "
-                        + "org.apache.asterix.metadata.declared.MetadataProvider, "
-                        + "org.apache.asterix.lang.common.rewrites.LangRewritingContext, " + "java.util.Collection)",
-                declaredFunctions, topExpr, metadataProvider, context, null);
-        PA.invokeMethod(rewriter, "resolveFunctionCalls()");
-        PA.invokeMethod(rewriter, "generateColumnNames()");
-        PA.invokeMethod(rewriter, "substituteGroupbyKeyExpression()");
-        PA.invokeMethod(rewriter, "rewriteGroupBys()");
-        PA.invokeMethod(rewriter, "rewriteSetOperations()");
-        PA.invokeMethod(rewriter, "inlineColumnAlias()");
-        PA.invokeMethod(rewriter, "rewriteWindowExpressions()");
-        PA.invokeMethod(rewriter, "rewriteGroupingSets()");
-        PA.invokeMethod(rewriter, "variableCheckAndRewrite()");
-        PA.invokeMethod(rewriter, "extractAggregatesFromCaseExpressions()");
-        PA.invokeMethod(rewriter, "rewriteGroupByAggregationSugar()");
-        PA.invokeMethod(rewriter, "rewriteWindowAggregationSugar()");
-        PA.invokeMethod(rewriter, "rewriteSpecialFunctionNames()");
+    // Note: we do not do inline function rewriting here because this needs real metadata access.
+    private void rewrite(IQueryRewriter rewriter, Query topExpr, LangRewritingContext context) throws Exception {
+        invokeMethod(rewriter, "setup", context, topExpr, null, true, false);
+        invokeMethod(rewriter, "resolveFunctionCalls");
+        invokeMethod(rewriter, "generateColumnNames");
+        invokeMethod(rewriter, "substituteGroupbyKeyExpression");
+        invokeMethod(rewriter, "rewriteGroupBys");
+        invokeMethod(rewriter, "rewriteSetOperations");
+        invokeMethod(rewriter, "inlineColumnAlias");
+        invokeMethod(rewriter, "rewriteWindowExpressions");
+        invokeMethod(rewriter, "rewriteGroupingSets");
+        invokeMethod(rewriter, "variableCheckAndRewrite");
+        invokeMethod(rewriter, "extractAggregatesFromCaseExpressions");
+        invokeMethod(rewriter, "rewriteGroupByAggregationSugar");
+        invokeMethod(rewriter, "rewriteWindowAggregationSugar");
+        invokeMethod(rewriter, "rewriteSpecialFunctionNames");
+        invokeMethod(rewriter, "rewriteOperatorExpression");
+        invokeMethod(rewriter, "rewriteCaseExpressions");
+        invokeMethod(rewriter, "rewriteListInputFunctions");
+        invokeMethod(rewriter, "rewriteRightJoins");
     }
 
+    private static void invokeMethod(Object instance, String methodName, Object... args) throws Exception {
+        PA.invokeMethod(instance, getMethodSignature(instance.getClass(), methodName), args);
+    }
+
+    private static String getMethodSignature(Class<?> cls, String methodName) throws Exception {
+        Method[] methods = cls.getDeclaredMethods();
+        Method method = Arrays.stream(methods).filter(m -> m.getName().equals(methodName)).findFirst()
+                .orElseThrow(NoSuchMethodException::new);
+        String parameterTypes =
+                Arrays.stream(method.getParameterTypes()).map(Class::getName).collect(Collectors.joining(","));
+        return String.format("%s(%s)", method.getName(), parameterTypes);
+    }
 }
diff --git a/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/IfInFLOWGR.ast b/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/IfInFLOWGR.ast
index 58fe98b..2e1d5a8 100644
--- a/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/IfInFLOWGR.ast
+++ b/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/IfInFLOWGR.ast
@@ -1,17 +1,15 @@
 Query:
 SELECT ELEMENT [
-CASE    LiteralExpr [TRUE]
-
-WHEN     OperatorExpr [
-      Variable [ Name=$i ]
-      >
-      Variable [ Name=$j ]
-    ]
-THEN     Variable [ Name=$i ]
-
-ELSE     Variable [ Name=$j ]
-
-END
+FunctionCall asterix.switch-case[
+  LiteralExpr [TRUE]
+  OperatorExpr [
+    Variable [ Name=$i ]
+    >
+    Variable [ Name=$j ]
+  ]
+  Variable [ Name=$i ]
+  Variable [ Name=$j ]
+]
 ]
 FROM [  OrderedListConstructor [
     LiteralExpr [LONG] [1]
diff --git a/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/IfThenElse.ast b/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/IfThenElse.ast
index cf95780..eeed914 100644
--- a/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/IfThenElse.ast
+++ b/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/IfThenElse.ast
@@ -1,15 +1,9 @@
 Query:
 SELECT ELEMENT [
-CASE    OperatorExpr [
-      LiteralExpr [LONG] [2]
-      >
-      LiteralExpr [LONG] [1]
-    ]
-
-WHEN     LiteralExpr [TRUE]
-THEN     LiteralExpr [LONG] [20]
-
-ELSE     LiteralExpr [LONG] [10]
-
-END
+FunctionCall asterix.switch-case[
+  LiteralExpr [TRUE]
+  LiteralExpr [TRUE]
+  LiteralExpr [LONG] [20]
+  LiteralExpr [LONG] [10]
+]
 ]
diff --git a/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/nestedFLWOGR1.ast b/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/nestedFLWOGR1.ast
index 42831c4..5fb51ac 100644
--- a/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/nestedFLWOGR1.ast
+++ b/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/nestedFLWOGR1.ast
@@ -4,18 +4,12 @@ SELECT ELEMENT [
   SELECT ELEMENT [
   Variable [ Name=$k ]
   ]
-  FROM [    CASE        OperatorExpr [
-          Variable [ Name=$i ]
-          >
-          Variable [ Name=$j ]
-        ]
-
-    WHEN         LiteralExpr [TRUE]
-    THEN         Variable [ Name=$i ]
-
-    ELSE         Variable [ Name=$j ]
-
-    END
+  FROM [    FunctionCall asterix.switch-case[
+      LiteralExpr [TRUE]
+      LiteralExpr [TRUE]
+      Variable [ Name=$i ]
+      Variable [ Name=$j ]
+    ]
     AS Variable [ Name=$k ]
   ]
   Where
diff --git a/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/nestedFLWOGR2.ast b/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/nestedFLWOGR2.ast
index 3434965..d40210f 100644
--- a/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/nestedFLWOGR2.ast
+++ b/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/nestedFLWOGR2.ast
@@ -4,18 +4,16 @@ SELECT ELEMENT [
   SELECT ELEMENT [
   Variable [ Name=$k ]
   ]
-  FROM [    CASE        LiteralExpr [TRUE]
-
-    WHEN         OperatorExpr [
-          Variable [ Name=$i ]
-          >
-          Variable [ Name=$j ]
-        ]
-    THEN         Variable [ Name=$i ]
-
-    ELSE         Variable [ Name=$j ]
-
-    END
+  FROM [    FunctionCall asterix.switch-case[
+      LiteralExpr [TRUE]
+      OperatorExpr [
+        Variable [ Name=$i ]
+        >
+        Variable [ Name=$j ]
+      ]
+      Variable [ Name=$i ]
+      Variable [ Name=$j ]
+    ]
     AS Variable [ Name=$k ]
   ]
   Where
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.1.ddl.sqlpp
new file mode 100644
index 0000000..cf1603e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.1.ddl.sqlpp
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+/*
+ * Description  : Test illegal recursive function calls
+ */
+drop dataverse test if exists;
+create dataverse test;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.2.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.2.ddl.sqlpp
new file mode 100644
index 0000000..ee465fa
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.2.ddl.sqlpp
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+/*
+ * Description  : Recursion in CREATE FUNCTION
+ * Expected Res : Failure
+ */
+use test;
+create function f2a(a) { - f2a(a) };
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.3.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.3.ddl.sqlpp
new file mode 100644
index 0000000..79fc6bb
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.3.ddl.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Description  : Recursion in CREATE FUNCTION
+ * Expected Res : Failure
+ */
+use test;
+create function f3a(a) { -a };
+create or replace function f3a(a) { f3a(a) };
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.4.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.4.ddl.sqlpp
new file mode 100644
index 0000000..24af0d4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.4.ddl.sqlpp
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+/*
+ * Description  : Recursion in CREATE FUNCTION
+ * Expected Res : Failure
+ */
+use test;
+create function f4a(a) { -a };
+create function f4b(b) { f4a(b) };
+create or replace function f4a(a) { f4b(a) };
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.5.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.5.query.sqlpp
new file mode 100644
index 0000000..c6ded74
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.5.query.sqlpp
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+/*
+ * Description  : Recursion in DECLARE FUNCTION
+ * Expected Res : Failure
+ */
+use test;
+
+declare function f5a(a) { - f5a(a) };
+
+f5a(1);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.6.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.6.query.sqlpp
new file mode 100644
index 0000000..f47b781
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.6.query.sqlpp
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+/*
+ * Description  : Recursion in DECLARE FUNCTION
+ * Expected Res : Failure
+ */
+use test;
+
+declare function f6a(a) { f6b(a) + f6b(-a) };
+declare function f6b(b) { f6a(-b) + f6a(b) };
+
+f6a(1);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.7.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.7.query.sqlpp
new file mode 100644
index 0000000..0c89f14
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.7.query.sqlpp
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+/*
+ * Description  : Recursion in DECLARE FUNCTION
+ * Expected Res : Failure
+ */
+use test;
+
+declare function f7a(a) { f7b(a) + f7b(-a) };
+declare function f7b(b) { f7a(-b) + f7a(b) };
+declare function f7c(c) { f7b(c) };
+
+f7c(1);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.8.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.8.ddl.sqlpp
new file mode 100644
index 0000000..bf5ca06
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.8.ddl.sqlpp
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+/*
+ * Description  : Recursion in CREATE FUNCTION (varargs)
+ * Expected Res : Failure
+ */
+
+use test;
+
+create function f8a(...) { - args[0] - args[1] };
+
+create function f8b(...) { f8a(args[0], args[1]) + f8a(args[1], args[0]) };
+
+create or replace function f8a(...) { f8b(args[0], args[1]) };
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.9.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.9.query.sqlpp
new file mode 100644
index 0000000..e5d4e98
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf37_recursion/udf37_recursion.9.query.sqlpp
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+/*
+ * Description  : Recursion in DECLARE FUNCTION (varargs)
+ * Expected Res : Failure
+ */
+use test;
+
+declare function f9a(...) { f9b(args[0]) + f9b(-args[1]) };
+
+declare function f9b(...) { f9a(-args[0]) + f9a(args[1]) };
+
+declare function f9c(...) { f9b(args[0], args[1]) };
+
+f9c(1, 2);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.1.ddl.sqlpp
new file mode 100644
index 0000000..f237df5
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.1.ddl.sqlpp
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+/*
+ * Description  : No recursion
+ */
+
+drop dataverse test if exists;
+create dataverse test;
+
+use test;
+
+create function f1a(a) { -a };
+
+create function f1b(b) { f1a(b) + f1a(b) };
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.2.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.2.query.sqlpp
new file mode 100644
index 0000000..2c4236b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.2.query.sqlpp
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+/*
+ * Description  : No recursion
+ */
+
+use test;
+
+select r, f1a(r) f1a, f1b(r) f1b
+from range(1,2) r
+order by r;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.3.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.3.ddl.sqlpp
new file mode 100644
index 0000000..0ab206d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.3.ddl.sqlpp
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+/*
+ * Description  : No recursion
+ */
+
+use test;
+
+create function f3a(a) { -a };
+
+create function f3b(b) { f3a(b) };
+
+create function f3c(c) { f3a(c) + f3b(c) };
+
+create function f3d(d) { f3a(d) + f3b(d) + f3c(d) };
+
+create function f3e(e) { f3a(e) + f3b(e) + f3c(e) + f3d(e) };
+
+create function f3f(f) { f3a(f) + f3b(f) + f3c(f) + f3d(f) + f3e(f) };
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.4.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.4.query.sqlpp
new file mode 100644
index 0000000..2ad1f20
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.4.query.sqlpp
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+/*
+ * Description  : No recursion
+ */
+
+use test;
+
+select r, f3a(r) f3a, f3b(r) f3b, f3c(r) f3c, f3d(r) f3d, f3e(r) f3e, f3f(r) f3f
+from range(1,2) r
+order by r;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.5.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.5.ddl.sqlpp
new file mode 100644
index 0000000..e4ce3f7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.5.ddl.sqlpp
@@ -0,0 +1,37 @@
+
+/*
+ * 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.
+ */
+
+/*
+ * Description  : No recursion (vararg functions)
+ */
+
+use test;
+
+create function f5a(...) { - args[0] - args[1] };
+
+create function f5b(...) { f5a(args[0], args[1]) };
+
+create function f5c(...) { f5a(args[0], args[1]) + f5b(args[0], args[1]) };
+
+create function f5d(...) { f5a(args[0], args[1]) + f5b(args[0], args[1]) + f5c(args[0], args[1]) };
+
+create function f5e(...) { f5a(args[0], args[1]) + f5b(args[0], args[1]) + f5c(args[0], args[1]) + f5d(args[0], args[1]) };
+
+create function f5f(...) { f5a(args[0], args[1]) + f5b(args[0], args[1]) + f5c(args[0], args[1]) + f5d(args[0], args[1]) + f5e(args[0], args[1]) };
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.6.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.6.query.sqlpp
new file mode 100644
index 0000000..5e8087b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf38_no_recursion/udf38_no_recursion.6.query.sqlpp
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+/*
+ * Description  : No recursion
+ */
+
+use test;
+
+select r, f5a(r, r+1) f5a, f5b(r, r+1) f5b, f5c(r, r+1) f5c, f5d(r, r+1) f5d, f5e(r, r+1) f5e, f5f(r, r+1) f5f
+from range(1,2) r
+order by r;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf39_illegal_call/udf39_illegal_call.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf39_illegal_call/udf39_illegal_call.1.ddl.sqlpp
new file mode 100644
index 0000000..b7e7b81
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf39_illegal_call/udf39_illegal_call.1.ddl.sqlpp
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+/*
+ * Description  : Cannot call a declared function from
+ *                a stored function
+ */
+
+drop dataverse test if exists;
+create dataverse test;
+
+use test;
+
+declare function f1a(a) { -a };
+
+create function f1b(b) { f1a(b) + f1a(b) };
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf39_illegal_call/udf39_illegal_call.2.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf39_illegal_call/udf39_illegal_call.2.ddl.sqlpp
new file mode 100644
index 0000000..0ebb577
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/user-defined-functions/udf39_illegal_call/udf39_illegal_call.2.ddl.sqlpp
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+/*
+ * Description  : Cannot call a declared function from
+ *                a stored function (varargs)
+ */
+
+use test;
+
+declare function f2a(...) { - args[0] - args[1] };
+
+create function f2b(...) { f2a(args[0], args[1]) + f2a(args[1], args[0]) };
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/user-defined-functions/udf38_no_recursion/udf38_no_recursion.2.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/user-defined-functions/udf38_no_recursion/udf38_no_recursion.2.adm
new file mode 100644
index 0000000..c82ed63
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/user-defined-functions/udf38_no_recursion/udf38_no_recursion.2.adm
@@ -0,0 +1,2 @@
+{ "r": 1, "f1a": -1, "f1b": -2 }
+{ "r": 2, "f1a": -2, "f1b": -4 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/user-defined-functions/udf38_no_recursion/udf38_no_recursion.4.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/user-defined-functions/udf38_no_recursion/udf38_no_recursion.4.adm
new file mode 100644
index 0000000..5c663b8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/user-defined-functions/udf38_no_recursion/udf38_no_recursion.4.adm
@@ -0,0 +1,2 @@
+{ "r": 1, "f3a": -1, "f3b": -1, "f3c": -2, "f3d": -4, "f3e": -8, "f3f": -16 }
+{ "r": 2, "f3a": -2, "f3b": -2, "f3c": -4, "f3d": -8, "f3e": -16, "f3f": -32 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/user-defined-functions/udf38_no_recursion/udf38_no_recursion.6.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/user-defined-functions/udf38_no_recursion/udf38_no_recursion.6.adm
new file mode 100644
index 0000000..6389c0d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/user-defined-functions/udf38_no_recursion/udf38_no_recursion.6.adm
@@ -0,0 +1,2 @@
+{ "r": 1, "f5a": -3, "f5b": -3, "f5c": -6, "f5d": -12, "f5e": -24, "f5f": -48 }
+{ "r": 2, "f5a": -5, "f5b": -5, "f5c": -10, "f5d": -20, "f5e": -40, "f5f": -80 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.17.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.17.ast
index f57186c..3523c8e 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.17.ast
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.17.ast
@@ -8,32 +8,32 @@ FunctionCall asterix.field-access-by-name@2[
 ]
 FROM [  (
     SELECT [
-    OperatorExpr [
-      FunctionCall asterix.to-string@1[
-        FunctionCall asterix.if-null[
-          Variable [ Name=$four ]
-          LiteralExpr [STRING] [null]
+    FunctionCall asterix.string-concat@1[
+      OrderedListConstructor [
+        FunctionCall asterix.to-string@1[
+          FunctionCall asterix.if-null[
+            Variable [ Name=$four ]
+            LiteralExpr [STRING] [null]
+          ]
         ]
-      ]
-      ||
-      LiteralExpr [STRING] [,]
-      ||
-      FunctionCall asterix.to-string@1[
-        FunctionCall asterix.sql-sum@1[
-          (
-            SELECT ELEMENT [
-            FieldAccessor [
+        LiteralExpr [STRING] [,]
+        FunctionCall asterix.to-string@1[
+          FunctionCall asterix.sql-sum@1[
+            (
+              SELECT ELEMENT [
               FieldAccessor [
-                Variable [ Name=#5 ]
-                Field=tenk
+                FieldAccessor [
+                  Variable [ Name=#5 ]
+                  Field=tenk
+                ]
+                Field=two
               ]
-              Field=two
-            ]
-            ]
-            FROM [              Variable [ Name=#1 ]
-              AS Variable [ Name=#5 ]
-            ]
-          )
+              ]
+              FROM [                Variable [ Name=#1 ]
+                AS Variable [ Name=#5 ]
+              ]
+            )
+          ]
         ]
       ]
     ]
@@ -60,32 +60,32 @@ FROM [  (
 
     UNION
       SELECT [
-      OperatorExpr [
-        FunctionCall asterix.to-string@1[
-          FunctionCall asterix.if-null[
-            Variable [ Name=$four ]
-            LiteralExpr [STRING] [null]
+      FunctionCall asterix.string-concat@1[
+        OrderedListConstructor [
+          FunctionCall asterix.to-string@1[
+            FunctionCall asterix.if-null[
+              Variable [ Name=$four ]
+              LiteralExpr [STRING] [null]
+            ]
           ]
-        ]
-        ||
-        LiteralExpr [STRING] [,]
-        ||
-        FunctionCall asterix.to-string@1[
-          FunctionCall asterix.sql-sum@1[
-            (
-              SELECT ELEMENT [
-              FieldAccessor [
+          LiteralExpr [STRING] [,]
+          FunctionCall asterix.to-string@1[
+            FunctionCall asterix.sql-sum@1[
+              (
+                SELECT ELEMENT [
                 FieldAccessor [
-                  Variable [ Name=#6 ]
-                  Field=tenk
+                  FieldAccessor [
+                    Variable [ Name=#6 ]
+                    Field=tenk
+                  ]
+                  Field=two
                 ]
-                Field=two
-              ]
-              ]
-              FROM [                Variable [ Name=#1 ]
-                AS Variable [ Name=#6 ]
-              ]
-            )
+                ]
+                FROM [                  Variable [ Name=#1 ]
+                  AS Variable [ Name=#6 ]
+                ]
+              )
+            ]
           ]
         ]
       ]
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.8.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.8.ast
index a7b455a..c3b4dec 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.8.ast
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.8.ast
@@ -8,20 +8,20 @@ FunctionCall asterix.field-access-by-name@2[
 ]
 FROM [  (
     SELECT [
-    OperatorExpr [
-      FunctionCall asterix.to-string@1[
-        FunctionCall asterix.if-null[
-          Variable [ Name=$two ]
-          LiteralExpr [STRING] [null]
+    FunctionCall asterix.string-concat@1[
+      OrderedListConstructor [
+        FunctionCall asterix.to-string@1[
+          FunctionCall asterix.if-null[
+            Variable [ Name=$two ]
+            LiteralExpr [STRING] [null]
+          ]
         ]
-      ]
-      ||
-      LiteralExpr [STRING] [,]
-      ||
-      FunctionCall asterix.to-string@1[
-        FunctionCall asterix.if-null[
-          Variable [ Name=$four ]
-          LiteralExpr [STRING] [null]
+        LiteralExpr [STRING] [,]
+        FunctionCall asterix.to-string@1[
+          FunctionCall asterix.if-null[
+            Variable [ Name=$four ]
+            LiteralExpr [STRING] [null]
+          ]
         ]
       ]
     ]
@@ -72,20 +72,20 @@ FROM [  (
       ]
     UNION
       SELECT [
-      OperatorExpr [
-        FunctionCall asterix.to-string@1[
-          FunctionCall asterix.if-null[
-            Variable [ Name=$two ]
-            LiteralExpr [STRING] [null]
+      FunctionCall asterix.string-concat@1[
+        OrderedListConstructor [
+          FunctionCall asterix.to-string@1[
+            FunctionCall asterix.if-null[
+              Variable [ Name=$two ]
+              LiteralExpr [STRING] [null]
+            ]
           ]
-        ]
-        ||
-        LiteralExpr [STRING] [,]
-        ||
-        FunctionCall asterix.to-string@1[
-          FunctionCall asterix.if-null[
-            Variable [ Name=$four ]
-            LiteralExpr [STRING] [null]
+          LiteralExpr [STRING] [,]
+          FunctionCall asterix.to-string@1[
+            FunctionCall asterix.if-null[
+              Variable [ Name=$four ]
+              LiteralExpr [STRING] [null]
+            ]
           ]
         ]
       ]
@@ -134,20 +134,20 @@ FROM [  (
         ]
     UNION
       SELECT [
-      OperatorExpr [
-        FunctionCall asterix.to-string@1[
-          FunctionCall asterix.if-null[
-            Variable [ Name=$two ]
-            LiteralExpr [STRING] [null]
+      FunctionCall asterix.string-concat@1[
+        OrderedListConstructor [
+          FunctionCall asterix.to-string@1[
+            FunctionCall asterix.if-null[
+              Variable [ Name=$two ]
+              LiteralExpr [STRING] [null]
+            ]
           ]
-        ]
-        ||
-        LiteralExpr [STRING] [,]
-        ||
-        FunctionCall asterix.to-string@1[
-          FunctionCall asterix.if-null[
-            Variable [ Name=$four ]
-            LiteralExpr [STRING] [null]
+          LiteralExpr [STRING] [,]
+          FunctionCall asterix.to-string@1[
+            FunctionCall asterix.if-null[
+              Variable [ Name=$four ]
+              LiteralExpr [STRING] [null]
+            ]
           ]
         ]
       ]
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/misc/ifthenelse_01/ifthenelse_01.3.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/misc/ifthenelse_01/ifthenelse_01.3.ast
index f16c720..981cc39 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/misc/ifthenelse_01/ifthenelse_01.3.ast
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/misc/ifthenelse_01/ifthenelse_01.3.ast
@@ -1,16 +1,10 @@
 DataverseUse test
 Query:
 SELECT ELEMENT [
-CASE    OperatorExpr [
-      LiteralExpr [LONG] [2]
-      >
-      LiteralExpr [LONG] [1]
-    ]
-
-WHEN     LiteralExpr [TRUE]
-THEN     LiteralExpr [LONG] [20]
-
-ELSE     LiteralExpr [LONG] [10]
-
-END
+FunctionCall asterix.switch-case[
+  LiteralExpr [TRUE]
+  LiteralExpr [TRUE]
+  LiteralExpr [LONG] [20]
+  LiteralExpr [LONG] [10]
+]
 ]
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/string/like_01/like_01.3.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/string/like_01/like_01.3.ast
index ac04a07..d5c1517 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/string/like_01/like_01.3.ast
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/string/like_01/like_01.3.ast
@@ -2,24 +2,20 @@ DataverseUse test
 Query:
 SELECT ELEMENT [
 OrderedListConstructor [
-  OperatorExpr [
+  FunctionCall asterix.like@2[
     LiteralExpr [STRING] [A6BBB]
-    like
     LiteralExpr [STRING] [_6%]
   ]
-  OperatorExpr [
+  FunctionCall asterix.like@2[
     LiteralExpr [STRING] [A8BBB]
-    like
     LiteralExpr [STRING] [_6%]
   ]
-  OperatorExpr [
+  FunctionCall asterix.like@2[
     LiteralExpr [STRING] [+0300]
-    like
     LiteralExpr [STRING] [+03%]
   ]
-  OperatorExpr [
+  FunctionCall asterix.like@2[
     LiteralExpr [STRING] [?0300]
-    like
     LiteralExpr [STRING] [?03%]
   ]
 ]
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/string/like_null/like_null.3.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/string/like_null/like_null.3.ast
index a829f6b..649e47d 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/string/like_null/like_null.3.ast
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/string/like_null/like_null.3.ast
@@ -4,18 +4,16 @@ RecordConstructor [
   (
     LiteralExpr [STRING] [field1]
     :
-    OperatorExpr [
+    FunctionCall asterix.like@2[
       LiteralExpr [STRING] [A8BBB]
-      like
       LiteralExpr [NULL]
     ]
   )
   (
     LiteralExpr [STRING] [field2]
     :
-    OperatorExpr [
+    FunctionCall asterix.like@2[
       LiteralExpr [NULL]
-      like
       LiteralExpr [STRING] [_6%]
     ]
   )
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch-sql-like/q08_national_market_share/q08_national_market_share.3.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch-sql-like/q08_national_market_share/q08_national_market_share.3.ast
index 30243af..05c1b37 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch-sql-like/q08_national_market_share/q08_national_market_share.3.ast
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch-sql-like/q08_national_market_share/q08_national_market_share.3.ast
@@ -14,30 +14,28 @@ RecordConstructor [
       FunctionCall asterix.sum@1[
         (
           SELECT ELEMENT [
-          CASE              LiteralExpr [TRUE]
-
-          WHEN               OperatorExpr [
-                FieldAccessor [
-                  FieldAccessor [
-                    Variable [ Name=$i ]
-                    Field=t
-                  ]
-                  Field=s_name
-                ]
-                =
-                LiteralExpr [STRING] [BRAZIL]
-              ]
-          THEN               FieldAccessor [
+          FunctionCall asterix.switch-case[
+            LiteralExpr [TRUE]
+            OperatorExpr [
+              FieldAccessor [
                 FieldAccessor [
                   Variable [ Name=$i ]
                   Field=t
                 ]
-                Field=revenue
+                Field=s_name
               ]
-
-          ELSE               LiteralExpr [DOUBLE] [0.0]
-
-          END
+              =
+              LiteralExpr [STRING] [BRAZIL]
+            ]
+            FieldAccessor [
+              FieldAccessor [
+                Variable [ Name=$i ]
+                Field=t
+              ]
+              Field=revenue
+            ]
+            LiteralExpr [DOUBLE] [0.0]
+          ]
           ]
           FROM [            Variable [ Name=$g ]
             AS Variable [ Name=$i ]
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch-sql-like/q12_shipping/q12_shipping.3.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch-sql-like/q12_shipping/q12_shipping.3.ast
index f6b7ea4..d18bf81 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch-sql-like/q12_shipping/q12_shipping.3.ast
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch-sql-like/q12_shipping/q12_shipping.3.ast
@@ -13,41 +13,14 @@ RecordConstructor [
     FunctionCall asterix.sum@1[
       (
         SELECT ELEMENT [
-        CASE            OperatorExpr [
-              OperatorExpr [
-                FieldAccessor [
-                  FieldAccessor [
-                    Variable [ Name=$i ]
-                    Field=o
-                  ]
-                  Field=o_orderpriority
-                ]
-                =
-                LiteralExpr [STRING] [1-URGENT]
-              ]
-              or
-              OperatorExpr [
-                FieldAccessor [
-                  FieldAccessor [
-                    Variable [ Name=$i ]
-                    Field=o
-                  ]
-                  Field=o_orderpriority
-                ]
-                =
-                LiteralExpr [STRING] [2-HIGH]
-              ]
-            ]
-
-        WHEN             LiteralExpr [TRUE]
-        THEN             LiteralExpr [LONG] [1]
-
-        WHEN             LiteralExpr [FALSE]
-        THEN             LiteralExpr [LONG] [0]
-
-        ELSE             LiteralExpr [NULL]
-
-        END
+        FunctionCall asterix.switch-case[
+          LiteralExpr [TRUE]
+          LiteralExpr [TRUE]
+          LiteralExpr [LONG] [1]
+          LiteralExpr [FALSE]
+          LiteralExpr [LONG] [0]
+          LiteralExpr [NULL]
+        ]
         ]
         FROM [          Variable [ Name=$g ]
           AS Variable [ Name=$i ]
@@ -61,41 +34,14 @@ RecordConstructor [
     FunctionCall asterix.sum@1[
       (
         SELECT ELEMENT [
-        CASE            OperatorExpr [
-              OperatorExpr [
-                FieldAccessor [
-                  FieldAccessor [
-                    Variable [ Name=$i ]
-                    Field=o
-                  ]
-                  Field=o_orderpriority
-                ]
-                =
-                LiteralExpr [STRING] [1-URGENT]
-              ]
-              or
-              OperatorExpr [
-                FieldAccessor [
-                  FieldAccessor [
-                    Variable [ Name=$i ]
-                    Field=o
-                  ]
-                  Field=o_orderpriority
-                ]
-                =
-                LiteralExpr [STRING] [2-HIGH]
-              ]
-            ]
-
-        WHEN             LiteralExpr [TRUE]
-        THEN             LiteralExpr [LONG] [0]
-
-        WHEN             LiteralExpr [FALSE]
-        THEN             LiteralExpr [LONG] [1]
-
-        ELSE             LiteralExpr [NULL]
-
-        END
+        FunctionCall asterix.switch-case[
+          LiteralExpr [TRUE]
+          LiteralExpr [TRUE]
+          LiteralExpr [LONG] [0]
+          LiteralExpr [FALSE]
+          LiteralExpr [LONG] [1]
+          LiteralExpr [NULL]
+        ]
         ]
         FROM [          Variable [ Name=$g ]
           AS Variable [ Name=$i ]
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch-sql-like/q13_customer_distribution/q13_customer_distribution.3.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch-sql-like/q13_customer_distribution/q13_customer_distribution.3.ast
index d6c8ace..c3cbcfd 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch-sql-like/q13_customer_distribution/q13_customer_distribution.3.ast
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch-sql-like/q13_customer_distribution/q13_customer_distribution.3.ast
@@ -93,13 +93,14 @@ FROM [  (
                       ]
                     ]
                     and
-                    OperatorExpr [
-                      FieldAccessor [
-                        Variable [ Name=$o ]
-                        Field=o_comment
+                    FunctionCall algebricks.not@1[
+                      FunctionCall asterix.like@2[
+                        FieldAccessor [
+                          Variable [ Name=$o ]
+                          Field=o_comment
+                        ]
+                        LiteralExpr [STRING] [%special%requests%]
                       ]
-                      not_like
-                      LiteralExpr [STRING] [%special%requests%]
                     ]
                   ]
               )
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch-sql-like/q14_promotion_effect/q14_promotion_effect.3.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch-sql-like/q14_promotion_effect/q14_promotion_effect.3.ast
index 8aaceb4..76cc0a1 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch-sql-like/q14_promotion_effect/q14_promotion_effect.3.ast
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch-sql-like/q14_promotion_effect/q14_promotion_effect.3.ast
@@ -7,35 +7,26 @@ OperatorExpr [
   FunctionCall asterix.sum@1[
     (
       SELECT ELEMENT [
-      CASE          OperatorExpr [
-            FieldAccessor [
-              Variable [ Name=$i ]
-              Field=p_type
-            ]
-            like
-            LiteralExpr [STRING] [PROMO%]
+      FunctionCall asterix.switch-case[
+        LiteralExpr [TRUE]
+        LiteralExpr [TRUE]
+        OperatorExpr [
+          FieldAccessor [
+            Variable [ Name=$i ]
+            Field=l_extendedprice
           ]
-
-      WHEN           LiteralExpr [TRUE]
-      THEN           OperatorExpr [
+          *
+          OperatorExpr [
+            Variable [ Name=$t ]
+            -
             FieldAccessor [
               Variable [ Name=$i ]
-              Field=l_extendedprice
-            ]
-            *
-            OperatorExpr [
-              Variable [ Name=$t ]
-              -
-              FieldAccessor [
-                Variable [ Name=$i ]
-                Field=l_discount
-              ]
+              Field=l_discount
             ]
           ]
-
-      ELSE           LiteralExpr [DOUBLE] [0.0]
-
-      END
+        ]
+        LiteralExpr [DOUBLE] [0.0]
+      ]
       ]
       FROM [        (
           SELECT ELEMENT [
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch/q08_national_market_share/q08_national_market_share.3.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch/q08_national_market_share/q08_national_market_share.3.ast
index 26016cc..8aae309 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch/q08_national_market_share/q08_national_market_share.3.ast
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch/q08_national_market_share/q08_national_market_share.3.ast
@@ -14,27 +14,17 @@ RecordConstructor [
       FunctionCall asterix.sum@1[
         (
           SELECT ELEMENT [
-          CASE              OperatorExpr [
-                FieldAccessor [
-                  Variable [ Name=$i ]
-                  Field=s_name
-                ]
-                =
-                LiteralExpr [STRING] [BRAZIL]
-              ]
-
-          WHEN               LiteralExpr [TRUE]
-          THEN               FieldAccessor [
-                Variable [ Name=$i ]
-                Field=revenue
-              ]
-
-          WHEN               LiteralExpr [FALSE]
-          THEN               LiteralExpr [DOUBLE] [0.0]
-
-          ELSE               LiteralExpr [NULL]
-
-          END
+          FunctionCall asterix.switch-case[
+            LiteralExpr [TRUE]
+            LiteralExpr [TRUE]
+            FieldAccessor [
+              Variable [ Name=$i ]
+              Field=revenue
+            ]
+            LiteralExpr [FALSE]
+            LiteralExpr [DOUBLE] [0.0]
+            LiteralExpr [NULL]
+          ]
           ]
           FROM [            (
               SELECT ELEMENT [
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch/q12_shipping/q12_shipping.3.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch/q12_shipping/q12_shipping.3.ast
index 1590d2d..c5ac9cc 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch/q12_shipping/q12_shipping.3.ast
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch/q12_shipping/q12_shipping.3.ast
@@ -13,35 +13,14 @@ RecordConstructor [
     FunctionCall asterix.sum@1[
       (
         SELECT ELEMENT [
-        CASE            OperatorExpr [
-              OperatorExpr [
-                FieldAccessor [
-                  Variable [ Name=$i ]
-                  Field=o_orderpriority
-                ]
-                =
-                LiteralExpr [STRING] [1-URGENT]
-              ]
-              or
-              OperatorExpr [
-                FieldAccessor [
-                  Variable [ Name=$i ]
-                  Field=o_orderpriority
-                ]
-                =
-                LiteralExpr [STRING] [2-HIGH]
-              ]
-            ]
-
-        WHEN             LiteralExpr [TRUE]
-        THEN             LiteralExpr [LONG] [1]
-
-        WHEN             LiteralExpr [FALSE]
-        THEN             LiteralExpr [LONG] [0]
-
-        ELSE             LiteralExpr [NULL]
-
-        END
+        FunctionCall asterix.switch-case[
+          LiteralExpr [TRUE]
+          LiteralExpr [TRUE]
+          LiteralExpr [LONG] [1]
+          LiteralExpr [FALSE]
+          LiteralExpr [LONG] [0]
+          LiteralExpr [NULL]
+        ]
         ]
         FROM [          (
             SELECT ELEMENT [
@@ -65,32 +44,30 @@ RecordConstructor [
     FunctionCall asterix.sum@1[
       (
         SELECT ELEMENT [
-        CASE            LiteralExpr [TRUE]
-
-        WHEN             OperatorExpr [
-              OperatorExpr [
-                FieldAccessor [
-                  Variable [ Name=$i ]
-                  Field=o_orderpriority
-                ]
-                =
-                LiteralExpr [STRING] [1-URGENT]
+        FunctionCall asterix.switch-case[
+          LiteralExpr [TRUE]
+          OperatorExpr [
+            OperatorExpr [
+              FieldAccessor [
+                Variable [ Name=$i ]
+                Field=o_orderpriority
               ]
-              or
-              OperatorExpr [
-                FieldAccessor [
-                  Variable [ Name=$i ]
-                  Field=o_orderpriority
-                ]
-                =
-                LiteralExpr [STRING] [2-HIGH]
+              =
+              LiteralExpr [STRING] [1-URGENT]
+            ]
+            or
+            OperatorExpr [
+              FieldAccessor [
+                Variable [ Name=$i ]
+                Field=o_orderpriority
               ]
+              =
+              LiteralExpr [STRING] [2-HIGH]
             ]
-        THEN             LiteralExpr [LONG] [0]
-
-        ELSE             LiteralExpr [LONG] [1]
-
-        END
+          ]
+          LiteralExpr [LONG] [0]
+          LiteralExpr [LONG] [1]
+        ]
         ]
         FROM [          (
             SELECT ELEMENT [
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch/q13_customer_distribution/q13_customer_distribution.3.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch/q13_customer_distribution/q13_customer_distribution.3.ast
index 21dcbf3..9c7dc06 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch/q13_customer_distribution/q13_customer_distribution.3.ast
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch/q13_customer_distribution/q13_customer_distribution.3.ast
@@ -93,13 +93,14 @@ FROM [  (
                       ]
                     ]
                     and
-                    OperatorExpr [
-                      FieldAccessor [
-                        Variable [ Name=$o ]
-                        Field=o_comment
+                    FunctionCall algebricks.not@1[
+                      FunctionCall asterix.like@2[
+                        FieldAccessor [
+                          Variable [ Name=$o ]
+                          Field=o_comment
+                        ]
+                        LiteralExpr [STRING] [%special%requests%]
                       ]
-                      not_like
-                      LiteralExpr [STRING] [%special%requests%]
                     ]
                   ]
               )
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch/q14_promotion_effect/q14_promotion_effect.3.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch/q14_promotion_effect/q14_promotion_effect.3.ast
index 8aaceb4..76cc0a1 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch/q14_promotion_effect/q14_promotion_effect.3.ast
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/tpch/q14_promotion_effect/q14_promotion_effect.3.ast
@@ -7,35 +7,26 @@ OperatorExpr [
   FunctionCall asterix.sum@1[
     (
       SELECT ELEMENT [
-      CASE          OperatorExpr [
-            FieldAccessor [
-              Variable [ Name=$i ]
-              Field=p_type
-            ]
-            like
-            LiteralExpr [STRING] [PROMO%]
+      FunctionCall asterix.switch-case[
+        LiteralExpr [TRUE]
+        LiteralExpr [TRUE]
+        OperatorExpr [
+          FieldAccessor [
+            Variable [ Name=$i ]
+            Field=l_extendedprice
           ]
-
-      WHEN           LiteralExpr [TRUE]
-      THEN           OperatorExpr [
+          *
+          OperatorExpr [
+            Variable [ Name=$t ]
+            -
             FieldAccessor [
               Variable [ Name=$i ]
-              Field=l_extendedprice
-            ]
-            *
-            OperatorExpr [
-              Variable [ Name=$t ]
-              -
-              FieldAccessor [
-                Variable [ Name=$i ]
-                Field=l_discount
-              ]
+              Field=l_discount
             ]
           ]
-
-      ELSE           LiteralExpr [DOUBLE] [0.0]
-
-      END
+        ]
+        LiteralExpr [DOUBLE] [0.0]
+      ]
       ]
       FROM [        (
           SELECT ELEMENT [
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/window/misc_01/misc_01.3.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/window/misc_01/misc_01.3.ast
index 86cd5b1..e44fae6 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/window/misc_01/misc_01.3.ast
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/window/misc_01/misc_01.3.ast
@@ -198,18 +198,16 @@ Let Variable [ Name=$percent_rank_result_delta_raw ]
   ]
 Let Variable [ Name=$percent_rank_result_delta ]
   :=
-  CASE      LiteralExpr [TRUE]
-
-  WHEN       OperatorExpr [
-        Variable [ Name=$percent_rank_result_delta_raw ]
-        <
-        LiteralExpr [DOUBLE] [0.001]
-      ]
-  THEN       LiteralExpr [LONG] [0]
-
-  ELSE       Variable [ Name=$percent_rank_result_delta_raw ]
-
-  END
+  FunctionCall asterix.switch-case[
+    LiteralExpr [TRUE]
+    OperatorExpr [
+      Variable [ Name=$percent_rank_result_delta_raw ]
+      <
+      LiteralExpr [DOUBLE] [0.001]
+    ]
+    LiteralExpr [LONG] [0]
+    Variable [ Name=$percent_rank_result_delta_raw ]
+  ]
 Group All
   GROUP AS Variable [ Name=#1 ]
   (
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
index 19e0965..04a274a 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -12341,6 +12341,31 @@
       </compilation-unit>
     </test-case>
     <test-case FilePath="user-defined-functions">
+      <compilation-unit name="udf37_recursion">
+        <output-dir compare="Text">none</output-dir>
+        <expected-error>ASX1149: Illegal function recursion (in line 24, at column 1)</expected-error>
+        <expected-error>ASX1149: Illegal function recursion (in line 25, at column 1)</expected-error>
+        <expected-error>ASX1149: Illegal function recursion (in line 26, at column 1)</expected-error>
+        <expected-error>ASX1149: Illegal function recursion (in line 27, at column 1)</expected-error>
+        <expected-error>ASX1149: Illegal function recursion (in line 28, at column 1)</expected-error>
+        <expected-error>ASX1149: Illegal function recursion (in line 29, at column 1)</expected-error>
+        <expected-error>ASX1149: Illegal function recursion (in line 30, at column 1)</expected-error>
+        <expected-error>ASX1149: Illegal function recursion (in line 31, at column 1)</expected-error>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="user-defined-functions">
+      <compilation-unit name="udf38_no_recursion">
+        <output-dir compare="Text">udf38_no_recursion</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="user-defined-functions">
+      <compilation-unit name="udf39_illegal_call">
+        <output-dir compare="Text">none</output-dir>
+        <expected-error>ASX1150: Illegal use of function test.f1a(1) (in line 32, at column 26)</expected-error>
+        <expected-error>ASX1150: Illegal use of function test.f2a(...) (in line 29, at column 28)</expected-error>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="user-defined-functions">
       <compilation-unit name="f01">
         <output-dir compare="Text">f01</output-dir>
         <expected-error>ASX1081: Cannot find function with signature test.tinyint()</expected-error>
diff --git a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
index 47a388d..210ce0f 100644
--- a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
+++ b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
@@ -233,6 +233,8 @@ public enum ErrorCode implements IError {
     UNKNOWN_FEED_POLICY(1146),
     CANNOT_DROP_DATAVERSE_DEPENDENT_EXISTS(1147),
     CANNOT_DROP_OBJECT_DEPENDENT_EXISTS(1148),
+    ILLEGAL_FUNCTION_RECURSION(1149),
+    ILLEGAL_FUNCTION_USE(1150),
 
     // Feed errors
     DATAFLOW_ILLEGAL_STATE(3001),
diff --git a/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties b/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
index 8954766..93fa828 100644
--- a/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
+++ b/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
@@ -142,7 +142,7 @@
 1052 = Cannot create index with the same field \"%1$s\" specified more than once.
 1053 = Cannot create primary index on external dataset.
 1054 = Compilation failed due to some problem in the query plan.
-1055 = Incompatible function language: %1$s.
+1055 = Incompatible implementation language %1$s for function %2$s. Expected language %3$s.
 1056 = Too many options were specified for %1$s
 1057 = Expression of type %1$s is not supported in constant record
 1058 = Literal of type %1$s is not supported in constant record
@@ -235,6 +235,8 @@
 1146 = Cannot find feed policy with name %1$s
 1147 = Cannot drop dataverse: %1$s %2$s being used by %3$s %4$s
 1148 = Cannot drop %1$s %2$s being used by %3$s %4$s
+1149 = Illegal function recursion
+1150 = Illegal use of function %1$s
 
 # Feed Errors
 3001 = Illegal state.
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/IParser.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/IParser.java
index 0fe58ff..3df490c 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/IParser.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/IParser.java
@@ -35,7 +35,8 @@ public interface IParser {
 
     List<String> parseMultipartIdentifier() throws CompilationException;
 
-    FunctionDecl parseFunctionBody(FunctionSignature signature, List<String> paramNames) throws CompilationException;
+    FunctionDecl parseFunctionBody(FunctionSignature signature, List<String> paramNames, boolean isStored)
+            throws CompilationException;
 
     /**
      * Gets the warnings generated during parsing
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/IQueryRewriter.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/IQueryRewriter.java
index 3e1851c..f28de55 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/IQueryRewriter.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/IQueryRewriter.java
@@ -19,40 +19,37 @@
 package org.apache.asterix.lang.common.base;
 
 import java.util.Collection;
-import java.util.List;
 import java.util.Set;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.lang.common.expression.AbstractCallExpression;
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
-import org.apache.asterix.lang.common.statement.FunctionDecl;
 import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.metadata.declared.MetadataProvider;
 
 public interface IQueryRewriter {
 
     /**
      * Rewrite a query at the AST level.
-     * @param declaredFunctions,
-     *          a list of declared functions associated with the query.
      * @param topExpr,
      *          the query to be rewritten.
-     * @param metadataProvider,
-     *          providing the definition of created (i.e., stored) user-defined functions.
      * @param context
      *          rewriting context
+     * @param allowNonStoredUdfCalls
+     *          whether calls to non-stored user-defined functions should be resolved
+     * @param inlineUdfs
+     *          whether user defined functions should be inlines
      * @param externalVars
-     *          external variables
+     *          statement parameters (external variables)
      */
-    void rewrite(List<FunctionDecl> declaredFunctions, IReturningStatement topExpr, MetadataProvider metadataProvider,
-            LangRewritingContext context, boolean inlineUdfs, Collection<VarIdentifier> externalVars)
-            throws CompilationException;
+    void rewrite(LangRewritingContext context, IReturningStatement topExpr, boolean allowNonStoredUdfCalls,
+            boolean inlineUdfs, Collection<VarIdentifier> externalVars) throws CompilationException;
 
     /**
      * Find the function calls used by a given expression
      */
-    Set<AbstractCallExpression> getFunctionCalls(Expression expression) throws CompilationException;
+    void getFunctionCalls(Expression expression, Collection<? super AbstractCallExpression> outCalls)
+            throws CompilationException;
 
     /**
      * Find all external variables (positional and named variables) in given expression
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
deleted file mode 100644
index d18aa86..0000000
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/parser/FunctionParser.java
+++ /dev/null
@@ -1,62 +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.lang.common.parser;
-
-import java.io.StringReader;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.lang.common.base.IParser;
-import org.apache.asterix.lang.common.base.IParserFactory;
-import org.apache.asterix.lang.common.statement.FunctionDecl;
-import org.apache.asterix.metadata.entities.Function;
-import org.apache.hyracks.api.exceptions.IWarningCollector;
-
-public class FunctionParser {
-
-    private final IParserFactory parserFactory;
-
-    public FunctionParser(IParserFactory parserFactory) {
-        this.parserFactory = parserFactory;
-    }
-
-    public String getLanguage() {
-        return parserFactory.getLanguage();
-    }
-
-    public FunctionDecl getFunctionDecl(Function function, IWarningCollector warningCollector)
-            throws CompilationException {
-        if (!function.getLanguage().equals(getLanguage())) {
-            throw new CompilationException(ErrorCode.COMPILATION_INCOMPATIBLE_FUNCTION_LANGUAGE, getLanguage(),
-                    function.getLanguage());
-        }
-        IParser parser = parserFactory.createParser(new StringReader(function.getFunctionBody()));
-        try {
-            FunctionDecl functionDecl = parser.parseFunctionBody(function.getSignature(), function.getParameterNames());
-            if (warningCollector != null) {
-                parser.getWarnings(warningCollector);
-            }
-            return functionDecl;
-        } catch (CompilationException e) {
-            throw new CompilationException(ErrorCode.COMPILATION_BAD_FUNCTION_DEFINITION, e, function.getSignature(),
-                    e.getMessage());
-        }
-    }
-}
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/rewrites/LangRewritingContext.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/rewrites/LangRewritingContext.java
index 563e07e..675d0d3 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/rewrites/LangRewritingContext.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/rewrites/LangRewritingContext.java
@@ -19,21 +19,31 @@
 package org.apache.asterix.lang.common.rewrites;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
+import org.apache.asterix.common.functions.FunctionSignature;
+import org.apache.asterix.lang.common.statement.FunctionDecl;
 import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.common.util.FunctionUtil;
+import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.hyracks.algebricks.core.algebra.base.Counter;
 import org.apache.hyracks.api.exceptions.IWarningCollector;
 
 public final class LangRewritingContext {
+    private final MetadataProvider metadataProvider;
     private final IWarningCollector warningCollector;
-    private Counter varCounter;
+    private final Map<FunctionSignature, FunctionDecl> declaredFunctions;
+    private final Counter varCounter;
     private int systemVarCounter = 1;
-    private Map<Integer, VarIdentifier> oldVarIdToNewVarId = new HashMap<>();
+    private final Map<Integer, VarIdentifier> oldVarIdToNewVarId = new HashMap<>();
 
-    public LangRewritingContext(int varCounter, IWarningCollector warningCollector) {
-        this.varCounter = new Counter(varCounter);
+    public LangRewritingContext(MetadataProvider metadataProvider, List<FunctionDecl> declaredFunctions,
+            IWarningCollector warningCollector, int varCounter) {
+        this.metadataProvider = metadataProvider;
         this.warningCollector = warningCollector;
+        this.declaredFunctions = FunctionUtil.getFunctionMap(declaredFunctions);
+        this.varCounter = new Counter(varCounter);
     }
 
     public Counter getVarCounter() {
@@ -75,4 +85,12 @@ public final class LangRewritingContext {
     public IWarningCollector getWarningCollector() {
         return warningCollector;
     }
+
+    public MetadataProvider getMetadataProvider() {
+        return metadataProvider;
+    }
+
+    public Map<FunctionSignature, FunctionDecl> getDeclaredFunctions() {
+        return declaredFunctions;
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/FunctionDecl.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/FunctionDecl.java
index 4161824..2ef11ad 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/FunctionDecl.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/FunctionDecl.java
@@ -29,14 +29,18 @@ import org.apache.asterix.lang.common.struct.VarIdentifier;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 
 public class FunctionDecl extends AbstractStatement {
-    private FunctionSignature signature;
-    private List<VarIdentifier> paramList;
+    private final FunctionSignature signature;
+    private final List<VarIdentifier> paramList;
     private Expression funcBody;
+    private Expression funcBodyNormalized;
+    private final boolean isStored;
 
-    public FunctionDecl(FunctionSignature signature, List<VarIdentifier> paramList, Expression funcBody) {
+    public FunctionDecl(FunctionSignature signature, List<VarIdentifier> paramList, Expression funcBody,
+            boolean isStored) {
         this.signature = signature;
         this.paramList = paramList;
         this.funcBody = funcBody;
+        this.isStored = isStored;
     }
 
     public FunctionSignature getSignature() {
@@ -53,14 +57,19 @@ public class FunctionDecl extends AbstractStatement {
 
     public void setFuncBody(Expression funcBody) {
         this.funcBody = funcBody;
+        this.funcBodyNormalized = null;
     }
 
-    public void setSignature(FunctionSignature signature) {
-        this.signature = signature;
+    public Expression getNormalizedFuncBody() {
+        return funcBodyNormalized;
     }
 
-    public void setParamList(List<VarIdentifier> paramList) {
-        this.paramList = paramList;
+    public void setNormalizedFuncBody(Expression funcBody) {
+        this.funcBodyNormalized = funcBody;
+    }
+
+    public boolean isStored() {
+        return isStored;
     }
 
     @Override
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 7f4e078..50a4bf2 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
@@ -19,11 +19,14 @@
 
 package org.apache.asterix.lang.common.util;
 
+import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.BiFunction;
@@ -34,6 +37,8 @@ import org.apache.asterix.common.functions.FunctionConstants;
 import org.apache.asterix.common.functions.FunctionSignature;
 import org.apache.asterix.common.metadata.DataverseName;
 import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.IParser;
+import org.apache.asterix.lang.common.base.IParserFactory;
 import org.apache.asterix.lang.common.base.IQueryRewriter;
 import org.apache.asterix.lang.common.expression.AbstractCallExpression;
 import org.apache.asterix.lang.common.expression.CallExpr;
@@ -41,8 +46,8 @@ import org.apache.asterix.lang.common.expression.OrderedListTypeDefinition;
 import org.apache.asterix.lang.common.expression.TypeExpression;
 import org.apache.asterix.lang.common.expression.TypeReferenceExpression;
 import org.apache.asterix.lang.common.expression.UnorderedListTypeDefinition;
-import org.apache.asterix.lang.common.parser.FunctionParser;
 import org.apache.asterix.lang.common.statement.FunctionDecl;
+import org.apache.asterix.lang.common.visitor.GatherFunctionCallsVisitor;
 import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.asterix.metadata.entities.BuiltinTypeMap;
 import org.apache.asterix.metadata.entities.Dataverse;
@@ -62,6 +67,10 @@ import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 import org.apache.hyracks.api.exceptions.IWarningCollector;
 import org.apache.hyracks.api.exceptions.SourceLocation;
 
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.Graphs;
+import com.google.common.graph.MutableGraph;
+
 public class FunctionUtil {
 
     public static final String IMPORT_PRIVATE_FUNCTIONS = "import-private-functions";
@@ -104,54 +113,50 @@ public class FunctionUtil {
         }
     }
 
-    @FunctionalInterface
-    public interface IFunctionCollector {
-        Set<AbstractCallExpression> getFunctionCalls(Expression expression) throws CompilationException;
-    }
-
     public static FunctionSignature resolveFunctionCall(FunctionSignature fs, SourceLocation sourceLoc,
-            MetadataProvider metadataProvider, Set<FunctionSignature> declaredFunctions,
-            BiFunction<String, Integer, FunctionSignature> builtinFunctionResolver) throws CompilationException {
-        int arity = fs.getArity();
+            MetadataProvider metadataProvider, BiFunction<String, Integer, FunctionSignature> builtinFunctionResolver,
+            boolean searchUdfs, Map<FunctionSignature, FunctionDecl> declaredFunctionMap,
+            boolean allowNonStoredUdfCalls) throws CompilationException {
         DataverseName dataverse = fs.getDataverseName();
         if (dataverse == null) {
             dataverse = metadataProvider.getDefaultDataverseName();
         }
-        boolean isBuiltinFuncDataverse =
-                dataverse.equals(FunctionConstants.ASTERIX_DV) || dataverse.equals(FunctionConstants.ALGEBRICKS_DV);
-
-        if (!isBuiltinFuncDataverse) {
+        if (searchUdfs && !isBuiltinFunctionDataverse(dataverse)) {
             // attempt to resolve to a user-defined function
             FunctionSignature fsWithDv =
-                    fs.getDataverseName() == null ? new FunctionSignature(dataverse, fs.getName(), arity) : fs;
-            if (declaredFunctions.contains(fsWithDv)) {
-                return fsWithDv;
-            }
+                    fs.getDataverseName() == null ? new FunctionSignature(dataverse, fs.getName(), fs.getArity()) : fs;
             FunctionSignature fsWithDvVarargs =
                     new FunctionSignature(fsWithDv.getDataverseName(), fsWithDv.getName(), FunctionIdentifier.VARARGS);
-            if (declaredFunctions.contains(fsWithDvVarargs)) {
-                return fsWithDvVarargs;
+
+            FunctionDecl fd = declaredFunctionMap.get(fsWithDv);
+            if (fd == null) {
+                fd = declaredFunctionMap.get(fsWithDvVarargs);
+            }
+            if (fd != null) {
+                if (!allowNonStoredUdfCalls && !fd.isStored()) {
+                    throw new CompilationException(ErrorCode.ILLEGAL_FUNCTION_USE, sourceLoc,
+                            fd.getSignature().toString());
+                }
+                return fd.getSignature();
             }
             try {
-                Function function = metadataProvider.lookupUserDefinedFunction(fsWithDv);
-                if (function != null) {
-                    return fsWithDv;
+                Function fn = metadataProvider.lookupUserDefinedFunction(fsWithDv);
+                if (fn == null) {
+                    fn = metadataProvider.lookupUserDefinedFunction(fsWithDvVarargs);
                 }
-                function = metadataProvider.lookupUserDefinedFunction(fsWithDvVarargs);
-                if (function != null) {
-                    return fsWithDvVarargs;
+                if (fn != null) {
+                    return fn.getSignature();
                 }
             } catch (AlgebricksException e) {
-                throw new CompilationException(ErrorCode.COMPILATION_ERROR, e, sourceLoc, e.getMessage());
+                throw new CompilationException(ErrorCode.UNKNOWN_FUNCTION, e, sourceLoc, fs.toString());
             }
-
             // fail if the dataverse was specified in the function call but this dataverse does not exist
             if (fs.getDataverseName() != null) {
                 Dataverse dv;
                 try {
                     dv = metadataProvider.findDataverse(dataverse);
                 } catch (AlgebricksException e) {
-                    throw new CompilationException(ErrorCode.COMPILATION_ERROR, e, sourceLoc, e.getMessage());
+                    throw new CompilationException(ErrorCode.UNKNOWN_DATAVERSE, e, sourceLoc, dataverse);
                 }
                 if (dv == null) {
                     throw new CompilationException(ErrorCode.UNKNOWN_DATAVERSE, sourceLoc, dataverse);
@@ -165,13 +170,22 @@ public class FunctionUtil {
         if (mappedName != null) {
             name = mappedName;
         }
-        FunctionSignature fsBuiltin = builtinFunctionResolver.apply(name, arity);
+        FunctionSignature fsBuiltin = builtinFunctionResolver.apply(name, fs.getArity());
         if (fsBuiltin == null) {
             throw new CompilationException(ErrorCode.UNKNOWN_FUNCTION, sourceLoc, fs.toString());
         }
         return fsBuiltin;
     }
 
+    public static boolean isBuiltinFunctionSignature(FunctionSignature fs) {
+        return isBuiltinFunctionDataverse(Objects.requireNonNull(fs.getDataverseName()))
+                || BuiltinFunctions.getBuiltinFunctionInfo(fs.createFunctionIdentifier()) != null;
+    }
+
+    private static boolean isBuiltinFunctionDataverse(DataverseName dataverse) {
+        return FunctionConstants.ASTERIX_DV.equals(dataverse) || FunctionConstants.ALGEBRICKS_DV.equals(dataverse);
+    }
+
     public static BiFunction<String, Integer, FunctionSignature> createBuiltinFunctionResolver(
             MetadataProvider metadataProvider) {
         boolean includePrivateFunctions = getImportPrivateFunctions(metadataProvider);
@@ -193,88 +207,38 @@ public class FunctionUtil {
         };
     }
 
-    /**
-     * Retrieve stored functions (from CREATE FUNCTION statements) that have been
-     * used in an expression.
-     *
-     * @param metadataProvider,
-     *            the metadata provider
-     * @param expression,
-     *            the expression for analysis
-     * @param declaredFunctions,
-     *            a set of declared functions in the query, which can potentially
-     *            override stored functions.
-     * @param functionCollector,
-     *            for collecting function calls in the <code>expression</code>
-     * @param functionParser,
-     *            for parsing stored functions in the string represetnation.
-     * @param warningCollector
-     *            for reporting warnings encountered during parsing
-     * @throws CompilationException
-     */
-    public static List<FunctionDecl> retrieveUsedStoredFunctions(MetadataProvider metadataProvider,
-            Expression expression, List<FunctionSignature> declaredFunctions, List<FunctionDecl> inputFunctionDecls,
-            IFunctionCollector functionCollector, FunctionParser functionParser, IWarningCollector warningCollector)
-            throws CompilationException {
-        if (expression == null) {
-            return Collections.emptyList();
-        }
-        List<FunctionDecl> functionDecls =
-                inputFunctionDecls == null ? new ArrayList<>() : new ArrayList<>(inputFunctionDecls);
-        Set<AbstractCallExpression> functionCalls = functionCollector.getFunctionCalls(expression);
-        Set<FunctionSignature> functionSignatures = new HashSet<>();
-        for (AbstractCallExpression functionCall : functionCalls) {
-            switch (functionCall.getKind()) {
-                case CALL_EXPRESSION:
-                    FunctionSignature fs = functionCall.getFunctionSignature();
-                    if (fs.getDataverseName() == null) {
-                        throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
-                                functionCall.getSourceLocation(), fs);
-                    }
-                    if (!functionSignatures.add(fs)) {
-                        // already seen this signature
-                        continue;
+    public static void checkFunctionRecursion(Map<FunctionSignature, FunctionDecl> functionDeclMap,
+            java.util.function.Function<Collection<AbstractCallExpression>, GatherFunctionCallsVisitor> gfcFactory,
+            SourceLocation sourceLoc) throws CompilationException {
+        List<AbstractCallExpression> callList = new ArrayList<>();
+        GatherFunctionCallsVisitor gfc = gfcFactory.apply(callList);
+        MutableGraph<FunctionDecl> graph = GraphBuilder.directed().allowsSelfLoops(true).build();
+        for (FunctionDecl fdFrom : functionDeclMap.values()) {
+            callList.clear();
+            fdFrom.getNormalizedFuncBody().accept(gfc, null);
+            for (AbstractCallExpression callExpr : callList) {
+                if (callExpr.getKind() == Expression.Kind.CALL_EXPRESSION) {
+                    FunctionSignature callSignature = callExpr.getFunctionSignature();
+                    FunctionDecl fdTo = functionDeclMap.get(callSignature);
+                    if (fdTo != null) {
+                        graph.putEdge(fdFrom, fdTo);
                     }
-                    if (declaredFunctions != null && declaredFunctions.contains(fs)) {
-                        continue;
-                    }
-                    Function function;
-                    try {
-                        function = metadataProvider.lookupUserDefinedFunction(fs);
-                    } catch (AlgebricksException e) {
-                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, e, functionCall.getSourceLocation(),
-                                e.toString());
-                    }
-                    if (function == null || !functionParser.getLanguage().equals(function.getLanguage())) {
-                        // the function is either unknown, builtin, or in a different language.
-                        // either way we ignore it here because it will be handled by the function inlining rule later
-                        continue;
-                    }
-
-                    FunctionDecl functionDecl = functionParser.getFunctionDecl(function, warningCollector);
-                    if (functionDecls.contains(functionDecl)) {
-                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, functionCall.getSourceLocation(),
-                                "Recursive invocation " + functionDecls.get(functionDecls.size() - 1).getSignature()
-                                        + " <==> " + functionDecl.getSignature());
-                    }
-                    functionDecls.add(functionDecl);
-                    functionDecls = retrieveUsedStoredFunctions(metadataProvider, functionDecl.getFuncBody(),
-                            declaredFunctions, functionDecls, functionCollector, functionParser, warningCollector);
-                    break;
-                case WINDOW_EXPRESSION:
-                    // there cannot be used-defined window functions
-                    break;
-                default:
-                    throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, expression.getSourceLocation(),
-                            functionCall.getFunctionSignature().toString(false));
+                }
             }
         }
-        return functionDecls;
+        if (Graphs.hasCycle(graph)) {
+            throw new CompilationException(ErrorCode.ILLEGAL_FUNCTION_RECURSION, sourceLoc);
+        }
     }
 
     public static List<List<Triple<DataverseName, String, String>>> getFunctionDependencies(IQueryRewriter rewriter,
             Expression expression) throws CompilationException {
-        Set<AbstractCallExpression> functionCalls = rewriter.getFunctionCalls(expression);
+        List<AbstractCallExpression> functionCalls = new ArrayList<>();
+        rewriter.getFunctionCalls(expression, functionCalls);
+        // Duplicate elimination
+        Set<FunctionSignature> seenFunctions = new HashSet<>();
+        Set<Pair<DataverseName, String>> seenDatasets = new HashSet<>();
+        Set<Pair<DataverseName, String>> seenSynonyms = new HashSet<>();
         //Get the List of used functions and used datasets
         List<Triple<DataverseName, String, String>> datasetDependencies = new ArrayList<>();
         List<Triple<DataverseName, String, String>> functionDependencies = new ArrayList<>();
@@ -289,17 +253,23 @@ public class FunctionUtil {
                         if (callExpr.getExprList().size() > 2) {
                             // resolved via synonym -> store synonym name as a dependency
                             Pair<DataverseName, String> synonymReference = parseDatasetFunctionArguments(callExpr, 2);
-                            synonymDependencies
-                                    .add(new Triple<>(synonymReference.first, synonymReference.second, null));
+                            if (seenSynonyms.add(synonymReference)) {
+                                synonymDependencies
+                                        .add(new Triple<>(synonymReference.first, synonymReference.second, null));
+                            }
                         } else {
                             // resolved directly -> store dataset name as a dependency
                             Pair<DataverseName, String> datasetReference = parseDatasetFunctionArguments(callExpr, 0);
-                            datasetDependencies
-                                    .add(new Triple<>(datasetReference.first, datasetReference.second, null));
+                            if (seenDatasets.add(datasetReference)) {
+                                datasetDependencies
+                                        .add(new Triple<>(datasetReference.first, datasetReference.second, null));
+                            }
                         }
                     } else if (BuiltinFunctions.getBuiltinFunctionInfo(signature.createFunctionIdentifier()) == null) {
-                        functionDependencies.add(new Triple<>(signature.getDataverseName(), signature.getName(),
-                                Integer.toString(signature.getArity())));
+                        if (seenFunctions.add(signature)) {
+                            functionDependencies.add(new Triple<>(signature.getDataverseName(), signature.getName(),
+                                    Integer.toString(signature.getArity())));
+                        }
                     }
                     break;
                 case WINDOW_EXPRESSION:
@@ -374,14 +344,35 @@ public class FunctionUtil {
         return (value != null) && Boolean.parseBoolean(value.toLowerCase());
     }
 
-    public static Set<FunctionSignature> getFunctionSignatures(List<FunctionDecl> declaredFunctions) {
+    public static Map<FunctionSignature, FunctionDecl> getFunctionMap(List<FunctionDecl> declaredFunctions) {
         if (declaredFunctions == null || declaredFunctions.isEmpty()) {
-            return Collections.emptySet();
+            return Collections.emptyMap();
         }
-        Set<FunctionSignature> result = new HashSet<>();
+        Map<FunctionSignature, FunctionDecl> result = new HashMap<>();
         for (FunctionDecl fd : declaredFunctions) {
-            result.add(fd.getSignature());
+            result.put(fd.getSignature(), fd);
         }
         return result;
     }
+
+    public static FunctionDecl parseStoredFunction(Function function, IParserFactory parserFactory,
+            IWarningCollector warningCollector, SourceLocation sourceLoc) throws CompilationException {
+        if (!function.getLanguage().equals(parserFactory.getLanguage())) {
+            throw new CompilationException(ErrorCode.COMPILATION_INCOMPATIBLE_FUNCTION_LANGUAGE, sourceLoc,
+                    function.getLanguage(), function.getSignature().toString(), parserFactory.getLanguage());
+        }
+        IParser parser = parserFactory.createParser(new StringReader(function.getFunctionBody()));
+        try {
+            FunctionDecl functionDecl =
+                    parser.parseFunctionBody(function.getSignature(), function.getParameterNames(), true);
+            functionDecl.setSourceLocation(sourceLoc);
+            if (warningCollector != null) {
+                parser.getWarnings(warningCollector);
+            }
+            return functionDecl;
+        } catch (CompilationException e) {
+            throw new CompilationException(ErrorCode.COMPILATION_BAD_FUNCTION_DEFINITION, e, sourceLoc,
+                    function.getSignature(), e.getMessage());
+        }
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/AbstractInlineUdfsVisitor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/AbstractInlineUdfsVisitor.java
index f1bcb93..cba6bb5 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/AbstractInlineUdfsVisitor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/AbstractInlineUdfsVisitor.java
@@ -26,12 +26,9 @@ import java.util.Map;
 import org.apache.asterix.common.exceptions.CompilationException;
 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.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.Expression.Kind;
 import org.apache.asterix.lang.common.base.ILangExpression;
-import org.apache.asterix.lang.common.base.IQueryRewriter;
-import org.apache.asterix.lang.common.base.IRewriterFactory;
 import org.apache.asterix.lang.common.clause.GroupbyClause;
 import org.apache.asterix.lang.common.clause.LetClause;
 import org.apache.asterix.lang.common.clause.LimitClause;
@@ -58,31 +55,26 @@ import org.apache.asterix.lang.common.statement.Query;
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.common.struct.QuantifiedPair;
 import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.common.util.FunctionUtil;
 import org.apache.asterix.lang.common.visitor.base.AbstractQueryExpressionVisitor;
-import org.apache.asterix.metadata.declared.MetadataProvider;
-import org.apache.asterix.metadata.entities.Dataverse;
 import org.apache.asterix.om.functions.BuiltinFunctions;
-import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.common.utils.Pair;
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 import org.apache.hyracks.api.exceptions.SourceLocation;
 
-public abstract class AbstractInlineUdfsVisitor extends AbstractQueryExpressionVisitor<Boolean, List<FunctionDecl>> {
+public abstract class AbstractInlineUdfsVisitor extends AbstractQueryExpressionVisitor<Boolean, Void> {
 
     protected final LangRewritingContext context;
+
+    protected final Map<FunctionSignature, FunctionDecl> usedUDFs;
+
     protected final CloneAndSubstituteVariablesVisitor cloneVisitor;
-    private final IRewriterFactory rewriterFactory;
-    private final List<FunctionDecl> declaredFunctions;
-    private final MetadataProvider metadataProvider;
 
-    public AbstractInlineUdfsVisitor(LangRewritingContext context, IRewriterFactory rewriterFactory,
-            List<FunctionDecl> declaredFunctions, MetadataProvider metadataProvider,
+    public AbstractInlineUdfsVisitor(LangRewritingContext context, Map<FunctionSignature, FunctionDecl> usedUDFs,
             CloneAndSubstituteVariablesVisitor cloneVisitor) {
         this.context = context;
+        this.usedUDFs = usedUDFs;
         this.cloneVisitor = cloneVisitor;
-        this.rewriterFactory = rewriterFactory;
-        this.declaredFunctions = declaredFunctions;
-        this.metadataProvider = metadataProvider;
     }
 
     /**
@@ -96,36 +88,33 @@ public abstract class AbstractInlineUdfsVisitor extends AbstractQueryExpressionV
             throws CompilationException;
 
     @Override
-    public Boolean visit(Query q, List<FunctionDecl> arg) throws CompilationException {
-        Pair<Boolean, Expression> p = inlineUdfsInExpr(q.getBody(), arg);
+    public Boolean visit(Query q, Void arg) throws CompilationException {
+        Pair<Boolean, Expression> p = inlineUdfsInExpr(q.getBody());
         q.setBody(p.second);
         return p.first;
     }
 
     @Override
-    public Boolean visit(FunctionDecl fd, List<FunctionDecl> arg) throws CompilationException {
-        // Careful, we should only do this after analyzing the graph of function
-        // calls.
-        Pair<Boolean, Expression> p = inlineUdfsInExpr(fd.getFuncBody(), arg);
-        fd.setFuncBody(p.second);
-        return p.first;
+    public Boolean visit(FunctionDecl fd, Void arg) throws CompilationException {
+        throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, fd.getSourceLocation(),
+                fd.getSignature().toString());
     }
 
     @Override
-    public Boolean visit(ListConstructor lc, List<FunctionDecl> arg) throws CompilationException {
-        Pair<Boolean, List<Expression>> p = inlineUdfsInExprList(lc.getExprList(), arg);
+    public Boolean visit(ListConstructor lc, Void arg) throws CompilationException {
+        Pair<Boolean, List<Expression>> p = inlineUdfsInExprList(lc.getExprList());
         lc.setExprList(p.second);
         return p.first;
     }
 
     @Override
-    public Boolean visit(RecordConstructor rc, List<FunctionDecl> arg) throws CompilationException {
+    public Boolean visit(RecordConstructor rc, Void arg) throws CompilationException {
         boolean changed = false;
         for (FieldBinding b : rc.getFbList()) {
-            Pair<Boolean, Expression> leftExprInlined = inlineUdfsInExpr(b.getLeftExpr(), arg);
+            Pair<Boolean, Expression> leftExprInlined = inlineUdfsInExpr(b.getLeftExpr());
             b.setLeftExpr(leftExprInlined.second);
             changed = changed || leftExprInlined.first;
-            Pair<Boolean, Expression> rightExprInlined = inlineUdfsInExpr(b.getRightExpr(), arg);
+            Pair<Boolean, Expression> rightExprInlined = inlineUdfsInExpr(b.getRightExpr());
             b.setRightExpr(rightExprInlined.second);
             changed = changed || rightExprInlined.first;
         }
@@ -133,12 +122,12 @@ public abstract class AbstractInlineUdfsVisitor extends AbstractQueryExpressionV
     }
 
     @Override
-    public Boolean visit(CallExpr callExpr, List<FunctionDecl> arg) throws CompilationException {
-        Pair<Boolean, List<Expression>> p = inlineUdfsInExprList(callExpr.getExprList(), arg);
+    public Boolean visit(CallExpr callExpr, Void arg) throws CompilationException {
+        Pair<Boolean, List<Expression>> p = inlineUdfsInExprList(callExpr.getExprList());
         callExpr.setExprList(p.second);
         boolean changed = p.first;
         if (callExpr.hasAggregateFilterExpr()) {
-            Pair<Boolean, Expression> be = inlineUdfsInExpr(callExpr.getAggregateFilterExpr(), arg);
+            Pair<Boolean, Expression> be = inlineUdfsInExpr(callExpr.getAggregateFilterExpr());
             callExpr.setAggregateFilterExpr(be.second);
             changed |= be.first;
         }
@@ -146,96 +135,96 @@ public abstract class AbstractInlineUdfsVisitor extends AbstractQueryExpressionV
     }
 
     @Override
-    public Boolean visit(OperatorExpr ifbo, List<FunctionDecl> arg) throws CompilationException {
-        Pair<Boolean, List<Expression>> p = inlineUdfsInExprList(ifbo.getExprList(), arg);
+    public Boolean visit(OperatorExpr ifbo, Void arg) throws CompilationException {
+        Pair<Boolean, List<Expression>> p = inlineUdfsInExprList(ifbo.getExprList());
         ifbo.setExprList(p.second);
         return p.first;
     }
 
     @Override
-    public Boolean visit(FieldAccessor fa, List<FunctionDecl> arg) throws CompilationException {
-        Pair<Boolean, Expression> p = inlineUdfsInExpr(fa.getExpr(), arg);
+    public Boolean visit(FieldAccessor fa, Void arg) throws CompilationException {
+        Pair<Boolean, Expression> p = inlineUdfsInExpr(fa.getExpr());
         fa.setExpr(p.second);
         return p.first;
     }
 
     @Override
-    public Boolean visit(IndexAccessor fa, List<FunctionDecl> arg) throws CompilationException {
-        Pair<Boolean, Expression> p = inlineUdfsInExpr(fa.getExpr(), arg);
+    public Boolean visit(IndexAccessor fa, Void arg) throws CompilationException {
+        Pair<Boolean, Expression> p = inlineUdfsInExpr(fa.getExpr());
         fa.setExpr(p.second);
         return p.first;
     }
 
     @Override
-    public Boolean visit(IfExpr ifexpr, List<FunctionDecl> arg) throws CompilationException {
-        Pair<Boolean, Expression> p1 = inlineUdfsInExpr(ifexpr.getCondExpr(), arg);
+    public Boolean visit(IfExpr ifexpr, Void arg) throws CompilationException {
+        Pair<Boolean, Expression> p1 = inlineUdfsInExpr(ifexpr.getCondExpr());
         ifexpr.setCondExpr(p1.second);
-        Pair<Boolean, Expression> p2 = inlineUdfsInExpr(ifexpr.getThenExpr(), arg);
+        Pair<Boolean, Expression> p2 = inlineUdfsInExpr(ifexpr.getThenExpr());
         ifexpr.setThenExpr(p2.second);
-        Pair<Boolean, Expression> p3 = inlineUdfsInExpr(ifexpr.getElseExpr(), arg);
+        Pair<Boolean, Expression> p3 = inlineUdfsInExpr(ifexpr.getElseExpr());
         ifexpr.setElseExpr(p3.second);
         return p1.first || p2.first || p3.first;
     }
 
     @Override
-    public Boolean visit(QuantifiedExpression qe, List<FunctionDecl> arg) throws CompilationException {
+    public Boolean visit(QuantifiedExpression qe, Void arg) throws CompilationException {
         boolean changed = false;
         for (QuantifiedPair t : qe.getQuantifiedList()) {
-            Pair<Boolean, Expression> p = inlineUdfsInExpr(t.getExpr(), arg);
+            Pair<Boolean, Expression> p = inlineUdfsInExpr(t.getExpr());
             t.setExpr(p.second);
             if (p.first) {
                 changed = true;
             }
         }
-        Pair<Boolean, Expression> p2 = inlineUdfsInExpr(qe.getSatisfiesExpr(), arg);
+        Pair<Boolean, Expression> p2 = inlineUdfsInExpr(qe.getSatisfiesExpr());
         qe.setSatisfiesExpr(p2.second);
         return changed || p2.first;
     }
 
     @Override
-    public Boolean visit(LetClause lc, List<FunctionDecl> arg) throws CompilationException {
-        Pair<Boolean, Expression> p = inlineUdfsInExpr(lc.getBindingExpr(), arg);
+    public Boolean visit(LetClause lc, Void arg) throws CompilationException {
+        Pair<Boolean, Expression> p = inlineUdfsInExpr(lc.getBindingExpr());
         lc.setBindingExpr(p.second);
         return p.first;
     }
 
     @Override
-    public Boolean visit(WhereClause wc, List<FunctionDecl> arg) throws CompilationException {
-        Pair<Boolean, Expression> p = inlineUdfsInExpr(wc.getWhereExpr(), arg);
+    public Boolean visit(WhereClause wc, Void arg) throws CompilationException {
+        Pair<Boolean, Expression> p = inlineUdfsInExpr(wc.getWhereExpr());
         wc.setWhereExpr(p.second);
         return p.first;
     }
 
     @Override
-    public Boolean visit(OrderbyClause oc, List<FunctionDecl> arg) throws CompilationException {
-        Pair<Boolean, List<Expression>> p = inlineUdfsInExprList(oc.getOrderbyList(), arg);
+    public Boolean visit(OrderbyClause oc, Void arg) throws CompilationException {
+        Pair<Boolean, List<Expression>> p = inlineUdfsInExprList(oc.getOrderbyList());
         oc.setOrderbyList(p.second);
         return p.first;
     }
 
     @Override
-    public Boolean visit(GroupbyClause gc, List<FunctionDecl> arg) throws CompilationException {
+    public Boolean visit(GroupbyClause gc, Void arg) throws CompilationException {
         boolean changed = false;
         List<List<GbyVariableExpressionPair>> gbyList = gc.getGbyPairList();
         List<List<GbyVariableExpressionPair>> newGbyList = new ArrayList<>(gbyList.size());
         for (List<GbyVariableExpressionPair> gbyPairList : gbyList) {
-            Pair<Boolean, List<GbyVariableExpressionPair>> p1 = inlineUdfsInGbyPairList(gbyPairList, arg);
+            Pair<Boolean, List<GbyVariableExpressionPair>> p1 = inlineUdfsInGbyPairList(gbyPairList);
             newGbyList.add(p1.second);
             changed |= p1.first;
         }
         gc.setGbyPairList(newGbyList);
         if (gc.hasDecorList()) {
-            Pair<Boolean, List<GbyVariableExpressionPair>> p2 = inlineUdfsInGbyPairList(gc.getDecorPairList(), arg);
+            Pair<Boolean, List<GbyVariableExpressionPair>> p2 = inlineUdfsInGbyPairList(gc.getDecorPairList());
             gc.setDecorPairList(p2.second);
             changed |= p2.first;
         }
         if (gc.hasGroupFieldList()) {
-            Pair<Boolean, List<Pair<Expression, Identifier>>> p3 = inlineUdfsInFieldList(gc.getGroupFieldList(), arg);
+            Pair<Boolean, List<Pair<Expression, Identifier>>> p3 = inlineUdfsInFieldList(gc.getGroupFieldList());
             gc.setGroupFieldList(p3.second);
             changed |= p3.first;
         }
         if (gc.hasWithMap()) {
-            Pair<Boolean, Map<Expression, VariableExpr>> p4 = inlineUdfsInVarMap(gc.getWithVarMap(), arg);
+            Pair<Boolean, Map<Expression, VariableExpr>> p4 = inlineUdfsInVarMap(gc.getWithVarMap());
             gc.setWithVarMap(p4.second);
             changed |= p4.first;
         }
@@ -243,15 +232,15 @@ public abstract class AbstractInlineUdfsVisitor extends AbstractQueryExpressionV
     }
 
     @Override
-    public Boolean visit(LimitClause lc, List<FunctionDecl> arg) throws CompilationException {
+    public Boolean visit(LimitClause lc, Void arg) throws CompilationException {
         boolean changed = false;
         if (lc.hasLimitExpr()) {
-            Pair<Boolean, Expression> p1 = inlineUdfsInExpr(lc.getLimitExpr(), arg);
+            Pair<Boolean, Expression> p1 = inlineUdfsInExpr(lc.getLimitExpr());
             lc.setLimitExpr(p1.second);
             changed = p1.first;
         }
         if (lc.hasOffset()) {
-            Pair<Boolean, Expression> p2 = inlineUdfsInExpr(lc.getOffset(), arg);
+            Pair<Boolean, Expression> p2 = inlineUdfsInExpr(lc.getOffset());
             lc.setOffset(p2.second);
             changed |= p2.first;
         }
@@ -259,131 +248,132 @@ public abstract class AbstractInlineUdfsVisitor extends AbstractQueryExpressionV
     }
 
     @Override
-    public Boolean visit(UnaryExpr u, List<FunctionDecl> arg) throws CompilationException {
+    public Boolean visit(UnaryExpr u, Void arg) throws CompilationException {
         return u.getExpr().accept(this, arg);
     }
 
     @Override
-    public Boolean visit(VariableExpr v, List<FunctionDecl> arg) throws CompilationException {
+    public Boolean visit(VariableExpr v, Void arg) throws CompilationException {
         return false;
     }
 
     @Override
-    public Boolean visit(LiteralExpr l, List<FunctionDecl> arg) throws CompilationException {
+    public Boolean visit(LiteralExpr l, Void arg) throws CompilationException {
         return false;
     }
 
     @Override
-    public Boolean visit(InsertStatement insert, List<FunctionDecl> arg) throws CompilationException {
+    public Boolean visit(InsertStatement insert, Void arg) throws CompilationException {
         boolean changed = false;
         Expression returnExpression = insert.getReturnExpression();
         if (returnExpression != null) {
-            Pair<Boolean, Expression> rewrittenReturnExpr = inlineUdfsInExpr(returnExpression, arg);
+            Pair<Boolean, Expression> rewrittenReturnExpr = inlineUdfsInExpr(returnExpression);
             insert.setReturnExpression(rewrittenReturnExpr.second);
             changed |= rewrittenReturnExpr.first;
         }
-        Pair<Boolean, Expression> rewrittenBodyExpression = inlineUdfsInExpr(insert.getBody(), arg);
+        Pair<Boolean, Expression> rewrittenBodyExpression = inlineUdfsInExpr(insert.getBody());
         insert.setBody(rewrittenBodyExpression.second);
         return changed || rewrittenBodyExpression.first;
     }
 
-    protected Pair<Boolean, Expression> inlineUdfsInExpr(Expression expr, List<FunctionDecl> arg)
-            throws CompilationException {
+    protected Pair<Boolean, Expression> inlineUdfsInExpr(Expression expr) throws CompilationException {
         if (expr.getKind() != Kind.CALL_EXPRESSION) {
-            boolean r = expr.accept(this, arg);
+            boolean r = expr.accept(this, null);
             return new Pair<>(r, expr);
         }
         CallExpr f = (CallExpr) expr;
-        boolean r = expr.accept(this, arg);
-        FunctionDecl implem = findFuncDeclaration(f.getFunctionSignature(), arg);
-        if (implem == null) {
+        boolean r = expr.accept(this, null);
+        FunctionSignature fs = f.getFunctionSignature();
+        if (FunctionUtil.isBuiltinFunctionSignature(fs)) {
             return new Pair<>(r, expr);
-        } else {
-            if (f.hasAggregateFilterExpr()) {
-                throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_USE_OF_FILTER_CLAUSE,
-                        f.getSourceLocation());
-            }
-            // Rewrite the function body itself (without setting unbounded variables to dataset access).
-            // TODO(buyingyi): throw an exception for recursive function definition or limit the stack depth.
-            implem.setFuncBody(rewriteFunctionBody(implem));
-            // it's one of the functions we want to inline
-            List<Expression> argList = f.getExprList();
-            int argCount = argList.size();
-            List<LetClause> clauses = new ArrayList<>(argCount + 1);
-            List<Expression> argVars = new ArrayList<>(argCount);
-            for (Expression e : f.getExprList()) {
-                // Obs: we could do smth about passing also literals, or let
-                // variable inlining to take care of this.
-                VarIdentifier argVar;
-                if (e.getKind() == Kind.VARIABLE_EXPRESSION) {
-                    argVar = ((VariableExpr) e).getVar();
-                } else {
-                    SourceLocation sourceLoc = e.getSourceLocation();
-                    argVar = context.newVariable();
-                    Pair<ILangExpression, VariableSubstitutionEnvironment> p1 =
-                            e.accept(cloneVisitor, new VariableSubstitutionEnvironment());
-                    VariableExpr newVRef1 = new VariableExpr(argVar);
-                    newVRef1.setSourceLocation(sourceLoc);
-                    LetClause c = new LetClause(newVRef1, (Expression) p1.first);
-                    c.setSourceLocation(sourceLoc);
-                    clauses.add(c);
-                }
-
-                VariableExpr argVarExpr = new VariableExpr(argVar);
-                argVarExpr.setSourceLocation(e.getSourceLocation());
-                argVars.add(argVarExpr);
+        }
+        if (f.hasAggregateFilterExpr()) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_USE_OF_FILTER_CLAUSE, f.getSourceLocation());
+        }
+        FunctionDecl implem = usedUDFs.get(fs);
+        if (implem == null) {
+            throw new CompilationException(ErrorCode.UNKNOWN_FUNCTION, f.getSourceLocation(), fs.toString());
+        }
+        // it's one of the functions we want to inline
+        List<Expression> argList = f.getExprList();
+        int argCount = argList.size();
+        List<LetClause> clauses = new ArrayList<>(argCount + 1);
+        List<Expression> argVars = new ArrayList<>(argCount);
+        for (Expression e : f.getExprList()) {
+            // Obs: we could do smth about passing also literals, or let
+            // variable inlining to take care of this.
+            VarIdentifier argVar;
+            if (e.getKind() == Kind.VARIABLE_EXPRESSION) {
+                argVar = ((VariableExpr) e).getVar();
+            } else {
+                SourceLocation sourceLoc = e.getSourceLocation();
+                argVar = context.newVariable();
+                Pair<ILangExpression, VariableSubstitutionEnvironment> p1 =
+                        e.accept(cloneVisitor, new VariableSubstitutionEnvironment());
+                VariableExpr newVRef1 = new VariableExpr(argVar);
+                newVRef1.setSourceLocation(sourceLoc);
+                LetClause c = new LetClause(newVRef1, (Expression) p1.first);
+                c.setSourceLocation(sourceLoc);
+                clauses.add(c);
             }
 
-            VariableSubstitutionEnvironment subst = new VariableSubstitutionEnvironment();
-            List<VarIdentifier> paramList = implem.getParamList();
-            if (implem.getSignature().getArity() == FunctionIdentifier.VARARGS) {
-                if (paramList.size() != 1) {
-                    throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, expr.getSourceLocation(),
-                            paramList.size());
-                }
-                VarIdentifier paramVarargs = paramList.get(0);
-                CallExpr argsListExpr =
-                        new CallExpr(new FunctionSignature(BuiltinFunctions.ORDERED_LIST_CONSTRUCTOR), argVars);
-                argsListExpr.setSourceLocation(expr.getSourceLocation());
-
-                VarIdentifier argsVar = context.newVariable();
-                VariableExpr argsVarRef1 = new VariableExpr(argsVar);
-                argsVarRef1.setSourceLocation(expr.getSourceLocation());
-                LetClause c = new LetClause(argsVarRef1, argsListExpr);
-                c.setSourceLocation(expr.getSourceLocation());
-                clauses.add(c);
+            VariableExpr argVarExpr = new VariableExpr(argVar);
+            argVarExpr.setSourceLocation(e.getSourceLocation());
+            argVars.add(argVarExpr);
+        }
 
-                VariableExpr argsVarRef2 = new VariableExpr(argsVar);
-                argsVarRef2.setSourceLocation(expr.getSourceLocation());
-                subst.addSubstituion(new VariableExpr(paramVarargs), argsVarRef2);
-            } else {
-                if (paramList.size() != argCount) {
-                    throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, expr.getSourceLocation(),
-                            paramList.size());
-                }
-                for (int i = 0; i < argCount; i++) {
-                    subst.addSubstituion(new VariableExpr(paramList.get(i)), argVars.get(i));
-                }
+        VariableSubstitutionEnvironment subst = new VariableSubstitutionEnvironment();
+        List<VarIdentifier> paramList = implem.getParamList();
+        if (implem.getSignature().getArity() == FunctionIdentifier.VARARGS) {
+            if (paramList.size() != 1) {
+                throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, expr.getSourceLocation(),
+                        paramList.size());
             }
-
-            Pair<ILangExpression, VariableSubstitutionEnvironment> p2 =
-                    implem.getFuncBody().accept(cloneVisitor, subst);
-            Expression resExpr;
-            if (clauses.isEmpty()) {
-                resExpr = (Expression) p2.first;
-            } else {
-                resExpr = generateQueryExpression(clauses, (Expression) p2.first);
+            VarIdentifier paramVarargs = paramList.get(0);
+            CallExpr argsListExpr =
+                    new CallExpr(new FunctionSignature(BuiltinFunctions.ORDERED_LIST_CONSTRUCTOR), argVars);
+            argsListExpr.setSourceLocation(expr.getSourceLocation());
+
+            VarIdentifier argsVar = context.newVariable();
+            VariableExpr argsVarRef1 = new VariableExpr(argsVar);
+            argsVarRef1.setSourceLocation(expr.getSourceLocation());
+            LetClause c = new LetClause(argsVarRef1, argsListExpr);
+            c.setSourceLocation(expr.getSourceLocation());
+            clauses.add(c);
+
+            VariableExpr argsVarRef2 = new VariableExpr(argsVar);
+            argsVarRef2.setSourceLocation(expr.getSourceLocation());
+            subst.addSubstituion(new VariableExpr(paramVarargs), argsVarRef2);
+        } else {
+            if (paramList.size() != argCount) {
+                throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, expr.getSourceLocation(),
+                        paramList.size());
             }
-            return new Pair<>(true, resExpr);
+            for (int i = 0; i < argCount; i++) {
+                subst.addSubstituion(new VariableExpr(paramList.get(i)), argVars.get(i));
+            }
+        }
+
+        Expression funcBodyNorm = implem.getNormalizedFuncBody();
+        if (funcBodyNorm == null) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, f.getSourceLocation(), fs.toString());
+        }
+        Pair<ILangExpression, VariableSubstitutionEnvironment> p2 = funcBodyNorm.accept(cloneVisitor, subst);
+        Expression resExpr;
+        if (clauses.isEmpty()) {
+            resExpr = (Expression) p2.first;
+        } else {
+            resExpr = generateQueryExpression(clauses, (Expression) p2.first);
         }
+        return new Pair<>(true, resExpr);
     }
 
-    protected Pair<Boolean, List<Expression>> inlineUdfsInExprList(List<Expression> exprList, List<FunctionDecl> fds)
+    protected Pair<Boolean, List<Expression>> inlineUdfsInExprList(List<Expression> exprList)
             throws CompilationException {
         List<Expression> newList = new ArrayList<>(exprList.size());
         boolean changed = false;
         for (Expression e : exprList) {
-            Pair<Boolean, Expression> be = inlineUdfsInExpr(e, fds);
+            Pair<Boolean, Expression> be = inlineUdfsInExpr(e);
             newList.add(be.second);
             changed |= be.first;
         }
@@ -391,11 +381,11 @@ public abstract class AbstractInlineUdfsVisitor extends AbstractQueryExpressionV
     }
 
     private Pair<Boolean, List<GbyVariableExpressionPair>> inlineUdfsInGbyPairList(
-            List<GbyVariableExpressionPair> gbyPairList, List<FunctionDecl> fds) throws CompilationException {
+            List<GbyVariableExpressionPair> gbyPairList) throws CompilationException {
         List<GbyVariableExpressionPair> newList = new ArrayList<>(gbyPairList.size());
         boolean changed = false;
         for (GbyVariableExpressionPair p : gbyPairList) {
-            Pair<Boolean, Expression> be = inlineUdfsInExpr(p.getExpr(), fds);
+            Pair<Boolean, Expression> be = inlineUdfsInExpr(p.getExpr());
             newList.add(new GbyVariableExpressionPair(p.getVar(), be.second));
             changed |= be.first;
         }
@@ -403,69 +393,26 @@ public abstract class AbstractInlineUdfsVisitor extends AbstractQueryExpressionV
     }
 
     protected Pair<Boolean, List<Pair<Expression, Identifier>>> inlineUdfsInFieldList(
-            List<Pair<Expression, Identifier>> fieldList, List<FunctionDecl> fds) throws CompilationException {
+            List<Pair<Expression, Identifier>> fieldList) throws CompilationException {
         List<Pair<Expression, Identifier>> newList = new ArrayList<>(fieldList.size());
         boolean changed = false;
         for (Pair<Expression, Identifier> p : fieldList) {
-            Pair<Boolean, Expression> be = inlineUdfsInExpr(p.first, fds);
+            Pair<Boolean, Expression> be = inlineUdfsInExpr(p.first);
             newList.add(new Pair<>(be.second, p.second));
             changed |= be.first;
         }
         return new Pair<>(changed, newList);
     }
 
-    private Pair<Boolean, Map<Expression, VariableExpr>> inlineUdfsInVarMap(Map<Expression, VariableExpr> varMap,
-            List<FunctionDecl> fds) throws CompilationException {
+    private Pair<Boolean, Map<Expression, VariableExpr>> inlineUdfsInVarMap(Map<Expression, VariableExpr> varMap)
+            throws CompilationException {
         Map<Expression, VariableExpr> newMap = new HashMap<>();
         boolean changed = false;
         for (Map.Entry<Expression, VariableExpr> me : varMap.entrySet()) {
-            Pair<Boolean, Expression> be = inlineUdfsInExpr(me.getKey(), fds);
+            Pair<Boolean, Expression> be = inlineUdfsInExpr(me.getKey());
             newMap.put(be.second, me.getValue());
             changed |= be.first;
         }
         return new Pair<>(changed, newMap);
     }
-
-    private Expression rewriteFunctionBody(FunctionDecl fnDecl) throws CompilationException {
-        SourceLocation sourceLoc = fnDecl.getSourceLocation();
-
-        DataverseName fnDataverseName = fnDecl.getSignature().getDataverseName();
-        Dataverse defaultDataverse = metadataProvider.getDefaultDataverse();
-        Dataverse fnDataverse;
-        if (fnDataverseName == null || fnDataverseName.equals(defaultDataverse.getDataverseName())) {
-            fnDataverse = defaultDataverse;
-        } else {
-            try {
-                fnDataverse = metadataProvider.findDataverse(fnDataverseName);
-            } catch (AlgebricksException e) {
-                throw new CompilationException(ErrorCode.UNKNOWN_DATAVERSE, e, sourceLoc, fnDataverseName);
-            }
-        }
-
-        metadataProvider.setDefaultDataverse(fnDataverse);
-        try {
-            Query wrappedQuery = new Query(false);
-            wrappedQuery.setSourceLocation(sourceLoc);
-            wrappedQuery.setBody(fnDecl.getFuncBody());
-            wrappedQuery.setTopLevel(false);
-            IQueryRewriter queryRewriter = rewriterFactory.createQueryRewriter();
-            queryRewriter.rewrite(declaredFunctions, wrappedQuery, metadataProvider, context, true,
-                    fnDecl.getParamList());
-            return wrappedQuery.getBody();
-        } catch (CompilationException e) {
-            throw new CompilationException(ErrorCode.COMPILATION_BAD_FUNCTION_DEFINITION, e, fnDecl.getSignature(),
-                    e.getMessage());
-        } finally {
-            metadataProvider.setDefaultDataverse(defaultDataverse);
-        }
-    }
-
-    private static FunctionDecl findFuncDeclaration(FunctionSignature fid, List<FunctionDecl> sequence) {
-        for (FunctionDecl f : sequence) {
-            if (f.getSignature().equals(fid)) {
-                return f;
-            }
-        }
-        return null;
-    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/CloneAndSubstituteVariablesVisitor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/CloneAndSubstituteVariablesVisitor.java
index 1fcf822..4b30c97 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/CloneAndSubstituteVariablesVisitor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/CloneAndSubstituteVariablesVisitor.java
@@ -178,7 +178,7 @@ public class CloneAndSubstituteVariablesVisitor extends
         }
 
         Pair<ILangExpression, VariableSubstitutionEnvironment> p1 = fd.getFuncBody().accept(this, env);
-        FunctionDecl newF = new FunctionDecl(fd.getSignature(), newList, (Expression) p1.first);
+        FunctionDecl newF = new FunctionDecl(fd.getSignature(), newList, (Expression) p1.first, fd.isStored());
         newF.setSourceLocation(fd.getSourceLocation());
         return new Pair<>(newF, env);
     }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/GatherFunctionCallsVisitor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/GatherFunctionCallsVisitor.java
index 3ad0f1b..25f4103 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/GatherFunctionCallsVisitor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/GatherFunctionCallsVisitor.java
@@ -19,10 +19,9 @@
 
 package org.apache.asterix.lang.common.visitor;
 
-import java.util.LinkedHashSet;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.lang.common.base.Expression;
@@ -56,9 +55,13 @@ import org.apache.asterix.lang.common.struct.QuantifiedPair;
 import org.apache.asterix.lang.common.visitor.base.AbstractQueryExpressionVisitor;
 import org.apache.hyracks.algebricks.common.utils.Pair;
 
-public class GatherFunctionCallsVisitor extends AbstractQueryExpressionVisitor<Void, Void> {
+public abstract class GatherFunctionCallsVisitor extends AbstractQueryExpressionVisitor<Void, Void> {
 
-    protected final Set<AbstractCallExpression> calls = new LinkedHashSet<>();
+    protected final Collection<? super AbstractCallExpression> calls;
+
+    protected GatherFunctionCallsVisitor(Collection<? super AbstractCallExpression> calls) {
+        this.calls = calls;
+    }
 
     @Override
     public Void visit(CallExpr callExpr, Void arg) throws CompilationException {
@@ -240,10 +243,6 @@ public class GatherFunctionCallsVisitor extends AbstractQueryExpressionVisitor<V
         return null;
     }
 
-    public Set<AbstractCallExpression> getCalls() {
-        return calls;
-    }
-
     @Override
     public Void visit(FunctionDecl fd, Void arg) throws CompilationException {
         return null;
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppFunctionBodyRewriter.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppFunctionBodyRewriter.java
index 356a683..8dd206f 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppFunctionBodyRewriter.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppFunctionBodyRewriter.java
@@ -19,15 +19,13 @@
 package org.apache.asterix.lang.sqlpp.rewrites;
 
 import java.util.Collection;
-import java.util.List;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.lang.common.base.IParserFactory;
 import org.apache.asterix.lang.common.base.IReturningStatement;
 import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
-import org.apache.asterix.lang.common.statement.FunctionDecl;
 import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.metadata.declared.MetadataProvider;
 
 class SqlppFunctionBodyRewriter extends SqlppQueryRewriter {
 
@@ -36,11 +34,16 @@ class SqlppFunctionBodyRewriter extends SqlppQueryRewriter {
     }
 
     @Override
-    public void rewrite(List<FunctionDecl> declaredFunctions, IReturningStatement topStatement,
-            MetadataProvider metadataProvider, LangRewritingContext context, boolean inlineUdfs,
-            Collection<VarIdentifier> externalVars) throws CompilationException {
+    public void rewrite(LangRewritingContext context, IReturningStatement topStatement, boolean allowNonStoredUdfCalls,
+            boolean inlineUdfs, Collection<VarIdentifier> externalVars) throws CompilationException {
+        if (inlineUdfs) {
+            // When rewriting function body we do not inline UDFs into it.
+            // The main query rewriter will inline everything later, when it processes the query
+            throw new CompilationException(ErrorCode.ILLEGAL_STATE, topStatement.getSourceLocation(), "inlineUdfs");
+        }
+
         // Sets up parameters.
-        setup(declaredFunctions, topStatement, metadataProvider, context, externalVars);
+        setup(context, topStatement, externalVars, allowNonStoredUdfCalls, inlineUdfs);
 
         // Resolves function calls
         resolveFunctionCalls();
@@ -93,8 +96,5 @@ class SqlppFunctionBodyRewriter extends SqlppQueryRewriter {
 
         // Rewrites RIGHT OUTER JOINs into LEFT OUTER JOINs if possible
         rewriteRightJoins();
-
-        // Inlines functions recursively.
-        inlineDeclaredUdfs(inlineUdfs);
     }
 }
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 af1bb41..a3ff1c6 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
@@ -18,48 +18,31 @@
  */
 package org.apache.asterix.lang.sqlpp.rewrites;
 
-import java.util.ArrayList;
+import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Deque;
 import java.util.HashSet;
-import java.util.List;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.functions.FunctionSignature;
-import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.common.metadata.DataverseName;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.IParserFactory;
 import org.apache.asterix.lang.common.base.IQueryRewriter;
 import org.apache.asterix.lang.common.base.IReturningStatement;
-import org.apache.asterix.lang.common.clause.LetClause;
 import org.apache.asterix.lang.common.expression.AbstractCallExpression;
-import org.apache.asterix.lang.common.expression.ListSliceExpression;
 import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.parser.FunctionParser;
 import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
 import org.apache.asterix.lang.common.statement.FunctionDecl;
-import org.apache.asterix.lang.common.struct.Identifier;
+import org.apache.asterix.lang.common.statement.Query;
 import org.apache.asterix.lang.common.struct.VarIdentifier;
 import org.apache.asterix.lang.common.util.FunctionUtil;
-import org.apache.asterix.lang.common.visitor.GatherFunctionCallsVisitor;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
-import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
-import org.apache.asterix.lang.sqlpp.clause.FromClause;
-import org.apache.asterix.lang.sqlpp.clause.FromTerm;
-import org.apache.asterix.lang.sqlpp.clause.HavingClause;
-import org.apache.asterix.lang.sqlpp.clause.JoinClause;
-import org.apache.asterix.lang.sqlpp.clause.NestClause;
-import org.apache.asterix.lang.sqlpp.clause.Projection;
-import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
-import org.apache.asterix.lang.sqlpp.clause.SelectClause;
-import org.apache.asterix.lang.sqlpp.clause.SelectElement;
-import org.apache.asterix.lang.sqlpp.clause.SelectRegular;
-import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
-import org.apache.asterix.lang.sqlpp.clause.UnnestClause;
-import org.apache.asterix.lang.sqlpp.expression.CaseExpression;
-import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
-import org.apache.asterix.lang.sqlpp.expression.WindowExpression;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.GenerateColumnNameVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.InlineColumnAliasVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.InlineWithExpressionVisitor;
@@ -68,6 +51,7 @@ import org.apache.asterix.lang.sqlpp.rewrites.visitor.SetOperationVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppCaseAggregateExtractionVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppCaseExpressionVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppFunctionCallResolverVisitor;
+import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppGatherFunctionCallsVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppGroupByAggregationSugarVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppGroupByVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppGroupingSetsVisitor;
@@ -79,12 +63,12 @@ import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppWindowAggregationSuga
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppWindowRewriteVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SubstituteGroupbyExpressionWithVariableVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.VariableCheckAndRewriteVisitor;
-import org.apache.asterix.lang.sqlpp.struct.SetOperationRight;
 import org.apache.asterix.lang.sqlpp.util.SqlppAstPrintUtil;
 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.hyracks.algebricks.common.utils.Pair;
+import org.apache.asterix.metadata.entities.Dataverse;
+import org.apache.asterix.metadata.entities.Function;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.util.LogRedactionUtil;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -96,41 +80,38 @@ public class SqlppQueryRewriter implements IQueryRewriter {
     public static final String INLINE_WITH_OPTION = "inline_with";
     private static final boolean INLINE_WITH_OPTION_DEFAULT = true;
     private final IParserFactory parserFactory;
-    private final FunctionParser functionParser;
-    private IReturningStatement topExpr;
-    private List<FunctionDecl> declaredFunctions;
+    private SqlppFunctionBodyRewriter functionBodyRewriter;
+    private IReturningStatement topStatement;
     private LangRewritingContext context;
     private MetadataProvider metadataProvider;
     private Collection<VarIdentifier> externalVars;
+    private boolean allowNonStoredUdfCalls;
+    private boolean inlineUdfs;
     private boolean isLogEnabled;
 
     public SqlppQueryRewriter(IParserFactory parserFactory) {
         this.parserFactory = parserFactory;
-        functionParser = new FunctionParser(parserFactory);
     }
 
-    protected void setup(List<FunctionDecl> declaredFunctions, IReturningStatement topExpr,
-            MetadataProvider metadataProvider, LangRewritingContext context, Collection<VarIdentifier> externalVars)
+    protected void setup(LangRewritingContext context, IReturningStatement topStatement,
+            Collection<VarIdentifier> externalVars, boolean allowNonStoredUdfCalls, boolean inlineUdfs)
             throws CompilationException {
-        this.topExpr = topExpr;
         this.context = context;
-        this.declaredFunctions = declaredFunctions;
-        this.metadataProvider = metadataProvider;
+        this.metadataProvider = context.getMetadataProvider();
+        this.topStatement = topStatement;
         this.externalVars = externalVars != null ? externalVars : Collections.emptyList();
+        this.allowNonStoredUdfCalls = allowNonStoredUdfCalls;
+        this.inlineUdfs = inlineUdfs;
         this.isLogEnabled = LOGGER.isTraceEnabled();
         logExpression("Starting AST rewrites on", "");
     }
 
     @Override
-    public void rewrite(List<FunctionDecl> declaredFunctions, IReturningStatement topStatement,
-            MetadataProvider metadataProvider, LangRewritingContext context, boolean inlineUdfs,
-            Collection<VarIdentifier> externalVars) throws CompilationException {
-        if (topStatement == null) {
-            return;
-        }
+    public void rewrite(LangRewritingContext context, IReturningStatement topStatement, boolean allowNonStoredUdfCalls,
+            boolean inlineUdfs, Collection<VarIdentifier> externalVars) throws CompilationException {
 
         // Sets up parameters.
-        setup(declaredFunctions, topStatement, metadataProvider, context, externalVars);
+        setup(context, topStatement, externalVars, allowNonStoredUdfCalls, inlineUdfs);
 
         // Resolves function calls
         resolveFunctionCalls();
@@ -182,7 +163,7 @@ public class SqlppQueryRewriter implements IQueryRewriter {
         rewriteRightJoins();
 
         // Inlines functions.
-        inlineDeclaredUdfs(inlineUdfs);
+        loadAndInlineDeclaredUdfs();
 
         // Rewrites SQL++ core aggregate function names into internal names
         rewriteSpecialFunctionNames();
@@ -214,7 +195,7 @@ public class SqlppQueryRewriter implements IQueryRewriter {
 
     protected void resolveFunctionCalls() throws CompilationException {
         SqlppFunctionCallResolverVisitor visitor =
-                new SqlppFunctionCallResolverVisitor(metadataProvider, declaredFunctions);
+                new SqlppFunctionCallResolverVisitor(context, allowNonStoredUdfCalls);
         rewriteTopExpr(visitor, null);
     }
 
@@ -308,53 +289,40 @@ public class SqlppQueryRewriter implements IQueryRewriter {
         rewriteTopExpr(visitor, null);
     }
 
-    protected void inlineDeclaredUdfs(boolean inlineUdfs) throws CompilationException {
-        List<FunctionSignature> funIds = new ArrayList<FunctionSignature>();
-        for (FunctionDecl fdecl : declaredFunctions) {
-            funIds.add(fdecl.getSignature());
-        }
-
-        List<FunctionDecl> usedStoredFunctionDecls = new ArrayList<>();
-        for (Expression topLevelExpr : topExpr.getDirectlyEnclosedExpressions()) {
-            usedStoredFunctionDecls.addAll(FunctionUtil.retrieveUsedStoredFunctions(metadataProvider, topLevelExpr,
-                    funIds, null, this::getFunctionCalls, functionParser, context.getWarningCollector()));
-        }
-        declaredFunctions.addAll(usedStoredFunctionDecls);
-        if (inlineUdfs && !declaredFunctions.isEmpty()) {
-            SqlppFunctionBodyRewriterFactory functionBodyRewriterFactory =
-                    new SqlppFunctionBodyRewriterFactory(parserFactory);
-            SqlppInlineUdfsVisitor visitor = new SqlppInlineUdfsVisitor(context, functionBodyRewriterFactory,
-                    declaredFunctions, metadataProvider);
-            while (rewriteTopExpr(visitor, declaredFunctions)) {
+    protected void loadAndInlineDeclaredUdfs() throws CompilationException {
+        Map<FunctionSignature, FunctionDecl> udfs = fetchUserDefinedSqlppFunctions(topStatement);
+        FunctionUtil.checkFunctionRecursion(udfs, SqlppGatherFunctionCallsVisitor::new,
+                topStatement.getSourceLocation());
+        if (!udfs.isEmpty() && inlineUdfs) {
+            SqlppInlineUdfsVisitor visitor = new SqlppInlineUdfsVisitor(context, udfs);
+            while (rewriteTopExpr(visitor, null)) {
                 // loop until no more changes
             }
         }
-        declaredFunctions.removeAll(usedStoredFunctionDecls);
     }
 
     private <R, T> R rewriteTopExpr(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
-        R result = topExpr.accept(visitor, arg);
+        R result = topStatement.accept(visitor, arg);
         logExpression(">>>> AST After", visitor.getClass().getSimpleName());
         return result;
     }
 
     private void logExpression(String p0, String p1) throws CompilationException {
         if (isLogEnabled) {
-            LOGGER.trace("{} {}\n{}", p0, p1, LogRedactionUtil.userData(SqlppAstPrintUtil.toString(topExpr)));
+            LOGGER.trace("{} {}\n{}", p0, p1, LogRedactionUtil.userData(SqlppAstPrintUtil.toString(topStatement)));
         }
     }
 
     @Override
-    public Set<AbstractCallExpression> getFunctionCalls(Expression expression) throws CompilationException {
-        GatherFunctionCalls gfc = new GatherFunctionCalls();
+    public void getFunctionCalls(Expression expression, Collection<? super AbstractCallExpression> outCalls)
+            throws CompilationException {
+        SqlppGatherFunctionCallsVisitor gfc = new SqlppGatherFunctionCallsVisitor(outCalls);
         expression.accept(gfc, null);
-        return gfc.getCalls();
     }
 
     @Override
     public Set<VariableExpr> getExternalVariables(Expression expr) throws CompilationException {
         Set<VariableExpr> freeVars = SqlppVariableUtil.getFreeVariables(expr);
-
         Set<VariableExpr> extVars = new HashSet<>();
         for (VariableExpr ve : freeVars) {
             if (SqlppVariableUtil.isExternalVariableReference(ve)) {
@@ -364,189 +332,107 @@ public class SqlppQueryRewriter implements IQueryRewriter {
         return extVars;
     }
 
-    private static class GatherFunctionCalls extends GatherFunctionCallsVisitor implements ISqlppVisitor<Void, Void> {
-
-        public GatherFunctionCalls() {
-        }
-
-        @Override
-        public Void visit(FromClause fromClause, Void arg) throws CompilationException {
-            for (FromTerm fromTerm : fromClause.getFromTerms()) {
-                fromTerm.accept(this, arg);
-            }
-            return null;
-        }
-
-        @Override
-        public Void visit(FromTerm fromTerm, Void arg) throws CompilationException {
-            fromTerm.getLeftExpression().accept(this, arg);
-            for (AbstractBinaryCorrelateClause correlateClause : fromTerm.getCorrelateClauses()) {
-                correlateClause.accept(this, arg);
-            }
-            return null;
-        }
-
-        @Override
-        public Void visit(JoinClause joinClause, Void arg) throws CompilationException {
-            joinClause.getRightExpression().accept(this, arg);
-            joinClause.getConditionExpression().accept(this, arg);
-            return null;
-        }
-
-        @Override
-        public Void visit(NestClause nestClause, Void arg) throws CompilationException {
-            nestClause.getRightExpression().accept(this, arg);
-            nestClause.getConditionExpression().accept(this, arg);
-            return null;
-        }
-
-        @Override
-        public Void visit(Projection projection, Void arg) throws CompilationException {
-            if (!projection.star()) {
-                projection.getExpression().accept(this, arg);
-            }
-            return null;
-        }
-
-        @Override
-        public Void visit(SelectBlock selectBlock, Void arg) throws CompilationException {
-            if (selectBlock.hasFromClause()) {
-                selectBlock.getFromClause().accept(this, arg);
-            }
-            if (selectBlock.hasLetWhereClauses()) {
-                for (AbstractClause letWhereClause : selectBlock.getLetWhereList()) {
-                    letWhereClause.accept(this, arg);
-                }
-            }
-            if (selectBlock.hasGroupbyClause()) {
-                selectBlock.getGroupbyClause().accept(this, arg);
-            }
-            if (selectBlock.hasLetHavingClausesAfterGroupby()) {
-                for (AbstractClause letHavingClause : selectBlock.getLetHavingListAfterGroupby()) {
-                    letHavingClause.accept(this, arg);
-                }
-            }
-            selectBlock.getSelectClause().accept(this, arg);
-            return null;
-        }
-
-        @Override
-        public Void visit(SelectClause selectClause, Void arg) throws CompilationException {
-            if (selectClause.selectElement()) {
-                selectClause.getSelectElement().accept(this, arg);
-            } else {
-                selectClause.getSelectRegular().accept(this, arg);
-            }
-            return null;
-        }
-
-        @Override
-        public Void visit(SelectElement selectElement, Void arg) throws CompilationException {
-            selectElement.getExpression().accept(this, arg);
-            return null;
-        }
-
-        @Override
-        public Void visit(SelectRegular selectRegular, Void arg) throws CompilationException {
-            for (Projection projection : selectRegular.getProjections()) {
-                projection.accept(this, arg);
-            }
-            return null;
-        }
+    private Map<FunctionSignature, FunctionDecl> fetchUserDefinedSqlppFunctions(IReturningStatement topExpr)
+            throws CompilationException {
+        Map<FunctionSignature, FunctionDecl> udfs = new LinkedHashMap<>();
 
-        @Override
-        public Void visit(SelectSetOperation selectSetOperation, Void arg) throws CompilationException {
-            selectSetOperation.getLeftInput().accept(this, arg);
-            for (SetOperationRight setOperationRight : selectSetOperation.getRightInputs()) {
-                setOperationRight.getSetOperationRightInput().accept(this, arg);
-            }
-            return null;
+        Deque<AbstractCallExpression> workQueue = new ArrayDeque<>();
+        SqlppGatherFunctionCallsVisitor gfc = new SqlppGatherFunctionCallsVisitor(workQueue);
+        for (Expression expr : topExpr.getDirectlyEnclosedExpressions()) {
+            expr.accept(gfc, null);
         }
-
-        @Override
-        public Void visit(SelectExpression selectStatement, Void arg) throws CompilationException {
-            if (selectStatement.hasLetClauses()) {
-                for (LetClause letClause : selectStatement.getLetList()) {
-                    letClause.accept(this, arg);
-                }
+        AbstractCallExpression fnCall;
+        while ((fnCall = workQueue.poll()) != null) {
+            switch (fnCall.getKind()) {
+                case CALL_EXPRESSION:
+                    FunctionSignature fs = fnCall.getFunctionSignature();
+                    DataverseName fsDataverse = fs.getDataverseName();
+                    if (fsDataverse == null) {
+                        throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, fnCall.getSourceLocation(),
+                                fs);
+                    }
+                    if (FunctionUtil.isBuiltinFunctionSignature(fs) || udfs.containsKey(fs)) {
+                        continue;
+                    }
+                    FunctionDecl fd = context.getDeclaredFunctions().get(fs);
+                    if (fd == null) {
+                        Function function;
+                        try {
+                            function = metadataProvider.lookupUserDefinedFunction(fs);
+                        } catch (AlgebricksException e) {
+                            throw new CompilationException(ErrorCode.UNKNOWN_FUNCTION, fnCall.getSourceLocation(),
+                                    fs.toString());
+                        }
+                        if (function == null) {
+                            throw new CompilationException(ErrorCode.UNKNOWN_FUNCTION, fnCall.getSourceLocation(),
+                                    fs.toString());
+                        }
+                        if (function.isExternal()) {
+                            continue;
+                        }
+                        fd = FunctionUtil.parseStoredFunction(function, parserFactory, context.getWarningCollector(),
+                                fnCall.getSourceLocation());
+                    }
+                    prepareFunction(fd);
+                    udfs.put(fs, fd);
+                    fd.getNormalizedFuncBody().accept(gfc, null);
+                    break;
+                case WINDOW_EXPRESSION:
+                    // there cannot be used-defined window functions
+                    break;
+                default:
+                    throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, fnCall.getSourceLocation(),
+                            fnCall.getFunctionSignature().toString(false));
             }
-            selectStatement.getSelectSetOperation().accept(this, arg);
-            if (selectStatement.hasOrderby()) {
-                selectStatement.getOrderbyClause().accept(this, arg);
-            }
-            if (selectStatement.hasLimit()) {
-                selectStatement.getLimitClause().accept(this, arg);
-            }
-            return null;
-        }
-
-        @Override
-        public Void visit(UnnestClause unnestClause, Void arg) throws CompilationException {
-            unnestClause.getRightExpression().accept(this, arg);
-            return null;
         }
+        return udfs;
+    }
 
-        @Override
-        public Void visit(HavingClause havingClause, Void arg) throws CompilationException {
-            havingClause.getFilterExpression().accept(this, arg);
-            return null;
+    private void prepareFunction(FunctionDecl fd) throws CompilationException {
+        Expression fnNormBody = fd.getNormalizedFuncBody();
+        if (fnNormBody == null) {
+            fnNormBody = rewriteFunctionBody(fd);
+            fd.setNormalizedFuncBody(fnNormBody);
         }
+    }
 
-        @Override
-        public Void visit(CaseExpression caseExpression, Void arg) throws CompilationException {
-            caseExpression.getConditionExpr().accept(this, arg);
-            for (Expression expr : caseExpression.getWhenExprs()) {
-                expr.accept(this, arg);
-            }
-            for (Expression expr : caseExpression.getThenExprs()) {
-                expr.accept(this, arg);
+    private Expression rewriteFunctionBody(FunctionDecl fnDecl) throws CompilationException {
+        DataverseName fnDataverseName = fnDecl.getSignature().getDataverseName();
+        Dataverse defaultDataverse = metadataProvider.getDefaultDataverse();
+        Dataverse fnDataverse;
+        if (fnDataverseName == null || fnDataverseName.equals(defaultDataverse.getDataverseName())) {
+            fnDataverse = defaultDataverse;
+        } else {
+            try {
+                fnDataverse = metadataProvider.findDataverse(fnDataverseName);
+            } catch (AlgebricksException e) {
+                throw new CompilationException(ErrorCode.UNKNOWN_DATAVERSE, e, fnDecl.getSourceLocation(),
+                        fnDataverseName);
             }
-            caseExpression.getElseExpr().accept(this, arg);
-            return null;
         }
 
-        @Override
-        public Void visit(WindowExpression winExpr, Void arg) throws CompilationException {
-            calls.add(winExpr);
-            if (winExpr.hasPartitionList()) {
-                for (Expression expr : winExpr.getPartitionList()) {
-                    expr.accept(this, arg);
-                }
-            }
-            if (winExpr.hasOrderByList()) {
-                for (Expression expr : winExpr.getOrderbyList()) {
-                    expr.accept(this, arg);
-                }
-            }
-            if (winExpr.hasFrameStartExpr()) {
-                winExpr.getFrameStartExpr().accept(this, arg);
-            }
-            if (winExpr.hasFrameEndExpr()) {
-                winExpr.getFrameEndExpr().accept(this, arg);
-            }
-            if (winExpr.hasWindowFieldList()) {
-                for (Pair<Expression, Identifier> p : winExpr.getWindowFieldList()) {
-                    p.first.accept(this, arg);
-                }
-            }
-            if (winExpr.hasAggregateFilterExpr()) {
-                winExpr.getAggregateFilterExpr().accept(this, arg);
-            }
-            for (Expression expr : winExpr.getExprList()) {
-                expr.accept(this, arg);
-            }
-            return null;
+        metadataProvider.setDefaultDataverse(fnDataverse);
+        try {
+            Query wrappedQuery = new Query(false);
+            wrappedQuery.setSourceLocation(fnDecl.getSourceLocation());
+            wrappedQuery.setBody(fnDecl.getFuncBody());
+            wrappedQuery.setTopLevel(false);
+            boolean allowNonStoredUdfCalls = !fnDecl.isStored();
+            getFunctionBodyRewriter().rewrite(context, wrappedQuery, allowNonStoredUdfCalls, false,
+                    fnDecl.getParamList());
+            return wrappedQuery.getBody();
+        } catch (CompilationException e) {
+            throw new CompilationException(ErrorCode.COMPILATION_BAD_FUNCTION_DEFINITION, e, fnDecl.getSignature(),
+                    e.getMessage());
+        } finally {
+            metadataProvider.setDefaultDataverse(defaultDataverse);
         }
+    }
 
-        @Override
-        public Void visit(ListSliceExpression expression, Void arg) throws CompilationException {
-            expression.getExpr().accept(this, arg);
-            expression.getStartIndexExpression().accept(this, arg);
-
-            if (expression.hasEndExpression()) {
-                expression.getEndIndexExpression().accept(this, arg);
-            }
-            return null;
+    protected SqlppFunctionBodyRewriter getFunctionBodyRewriter() {
+        if (functionBodyRewriter == null) {
+            functionBodyRewriter = new SqlppFunctionBodyRewriter(parserFactory);
         }
+        return functionBodyRewriter;
     }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppFunctionCallResolverVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppFunctionCallResolverVisitor.java
index 7666032..38b66e2 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppFunctionCallResolverVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppFunctionCallResolverVisitor.java
@@ -18,8 +18,6 @@
  */
 package org.apache.asterix.lang.sqlpp.rewrites.visitor;
 
-import java.util.List;
-import java.util.Set;
 import java.util.function.BiFunction;
 
 import org.apache.asterix.common.exceptions.CompilationException;
@@ -28,35 +26,35 @@ import org.apache.asterix.common.functions.FunctionSignature;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
 import org.apache.asterix.lang.common.expression.CallExpr;
-import org.apache.asterix.lang.common.statement.FunctionDecl;
+import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
 import org.apache.asterix.lang.common.util.FunctionUtil;
 import org.apache.asterix.lang.sqlpp.expression.WindowExpression;
 import org.apache.asterix.lang.sqlpp.util.FunctionMapUtil;
 import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppSimpleExpressionVisitor;
-import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.asterix.om.functions.BuiltinFunctions;
 
 public final class SqlppFunctionCallResolverVisitor extends AbstractSqlppSimpleExpressionVisitor {
 
-    private final MetadataProvider metadataProvider;
+    private final LangRewritingContext context;
 
-    private final Set<FunctionSignature> declaredFunctions;
+    private final boolean allowNonStoredUdfCalls;
 
     private final BiFunction<String, Integer, FunctionSignature> builtinFunctionResolver;
 
     private final BiFunction<String, Integer, FunctionSignature> callExprResolver;
 
-    public SqlppFunctionCallResolverVisitor(MetadataProvider metadataProvider, List<FunctionDecl> declaredFunctions) {
-        this.metadataProvider = metadataProvider;
-        this.declaredFunctions = FunctionUtil.getFunctionSignatures(declaredFunctions);
-        this.builtinFunctionResolver = FunctionUtil.createBuiltinFunctionResolver(metadataProvider);
+    public SqlppFunctionCallResolverVisitor(LangRewritingContext context, boolean allowNonStoredUdfCalls) {
+        this.context = context;
+        this.allowNonStoredUdfCalls = allowNonStoredUdfCalls;
+        this.builtinFunctionResolver = FunctionUtil.createBuiltinFunctionResolver(context.getMetadataProvider());
         this.callExprResolver = this::resolveCallExpr;
     }
 
     @Override
     public Expression visit(CallExpr callExpr, ILangExpression arg) throws CompilationException {
         FunctionSignature fs = FunctionUtil.resolveFunctionCall(callExpr.getFunctionSignature(),
-                callExpr.getSourceLocation(), metadataProvider, declaredFunctions, callExprResolver);
+                callExpr.getSourceLocation(), context.getMetadataProvider(), callExprResolver, true,
+                context.getDeclaredFunctions(), allowNonStoredUdfCalls);
         callExpr.setFunctionSignature(fs);
         return super.visit(callExpr, arg);
     }
@@ -64,7 +62,7 @@ public final class SqlppFunctionCallResolverVisitor extends AbstractSqlppSimpleE
     @Override
     public Expression visit(WindowExpression winExpr, ILangExpression arg) throws CompilationException {
         FunctionSignature fs = FunctionUtil.resolveFunctionCall(winExpr.getFunctionSignature(),
-                winExpr.getSourceLocation(), metadataProvider, declaredFunctions, callExprResolver);
+                winExpr.getSourceLocation(), context.getMetadataProvider(), callExprResolver, false, null, false);
         winExpr.setFunctionSignature(fs);
         return super.visit(winExpr, arg);
     }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppGatherFunctionCallsVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppGatherFunctionCallsVisitor.java
new file mode 100644
index 0000000..8559c96
--- /dev/null
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppGatherFunctionCallsVisitor.java
@@ -0,0 +1,238 @@
+/*
+ * 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.lang.sqlpp.rewrites.visitor;
+
+import java.util.Collection;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.expression.AbstractCallExpression;
+import org.apache.asterix.lang.common.expression.ListSliceExpression;
+import org.apache.asterix.lang.common.struct.Identifier;
+import org.apache.asterix.lang.common.visitor.GatherFunctionCallsVisitor;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.FromClause;
+import org.apache.asterix.lang.sqlpp.clause.FromTerm;
+import org.apache.asterix.lang.sqlpp.clause.HavingClause;
+import org.apache.asterix.lang.sqlpp.clause.JoinClause;
+import org.apache.asterix.lang.sqlpp.clause.NestClause;
+import org.apache.asterix.lang.sqlpp.clause.Projection;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.clause.SelectClause;
+import org.apache.asterix.lang.sqlpp.clause.SelectElement;
+import org.apache.asterix.lang.sqlpp.clause.SelectRegular;
+import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
+import org.apache.asterix.lang.sqlpp.clause.UnnestClause;
+import org.apache.asterix.lang.sqlpp.expression.CaseExpression;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.expression.WindowExpression;
+import org.apache.asterix.lang.sqlpp.struct.SetOperationRight;
+import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
+import org.apache.hyracks.algebricks.common.utils.Pair;
+
+public final class SqlppGatherFunctionCallsVisitor extends GatherFunctionCallsVisitor
+        implements ISqlppVisitor<Void, Void> {
+
+    public SqlppGatherFunctionCallsVisitor(Collection<? super AbstractCallExpression> calls) {
+        super(calls);
+    }
+
+    @Override
+    public Void visit(FromClause fromClause, Void arg) throws CompilationException {
+        for (FromTerm fromTerm : fromClause.getFromTerms()) {
+            fromTerm.accept(this, arg);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visit(FromTerm fromTerm, Void arg) throws CompilationException {
+        fromTerm.getLeftExpression().accept(this, arg);
+        for (AbstractBinaryCorrelateClause correlateClause : fromTerm.getCorrelateClauses()) {
+            correlateClause.accept(this, arg);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visit(JoinClause joinClause, Void arg) throws CompilationException {
+        joinClause.getRightExpression().accept(this, arg);
+        joinClause.getConditionExpression().accept(this, arg);
+        return null;
+    }
+
+    @Override
+    public Void visit(NestClause nestClause, Void arg) throws CompilationException {
+        nestClause.getRightExpression().accept(this, arg);
+        nestClause.getConditionExpression().accept(this, arg);
+        return null;
+    }
+
+    @Override
+    public Void visit(Projection projection, Void arg) throws CompilationException {
+        if (!projection.star()) {
+            projection.getExpression().accept(this, arg);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visit(SelectBlock selectBlock, Void arg) throws CompilationException {
+        if (selectBlock.hasFromClause()) {
+            selectBlock.getFromClause().accept(this, arg);
+        }
+        if (selectBlock.hasLetWhereClauses()) {
+            for (AbstractClause letWhereClause : selectBlock.getLetWhereList()) {
+                letWhereClause.accept(this, arg);
+            }
+        }
+        if (selectBlock.hasGroupbyClause()) {
+            selectBlock.getGroupbyClause().accept(this, arg);
+        }
+        if (selectBlock.hasLetHavingClausesAfterGroupby()) {
+            for (AbstractClause letHavingClause : selectBlock.getLetHavingListAfterGroupby()) {
+                letHavingClause.accept(this, arg);
+            }
+        }
+        selectBlock.getSelectClause().accept(this, arg);
+        return null;
+    }
+
+    @Override
+    public Void visit(SelectClause selectClause, Void arg) throws CompilationException {
+        if (selectClause.selectElement()) {
+            selectClause.getSelectElement().accept(this, arg);
+        } else {
+            selectClause.getSelectRegular().accept(this, arg);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visit(SelectElement selectElement, Void arg) throws CompilationException {
+        selectElement.getExpression().accept(this, arg);
+        return null;
+    }
+
+    @Override
+    public Void visit(SelectRegular selectRegular, Void arg) throws CompilationException {
+        for (Projection projection : selectRegular.getProjections()) {
+            projection.accept(this, arg);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visit(SelectSetOperation selectSetOperation, Void arg) throws CompilationException {
+        selectSetOperation.getLeftInput().accept(this, arg);
+        for (SetOperationRight setOperationRight : selectSetOperation.getRightInputs()) {
+            setOperationRight.getSetOperationRightInput().accept(this, arg);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visit(SelectExpression selectStatement, Void arg) throws CompilationException {
+        if (selectStatement.hasLetClauses()) {
+            for (LetClause letClause : selectStatement.getLetList()) {
+                letClause.accept(this, arg);
+            }
+        }
+        selectStatement.getSelectSetOperation().accept(this, arg);
+        if (selectStatement.hasOrderby()) {
+            selectStatement.getOrderbyClause().accept(this, arg);
+        }
+        if (selectStatement.hasLimit()) {
+            selectStatement.getLimitClause().accept(this, arg);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visit(UnnestClause unnestClause, Void arg) throws CompilationException {
+        unnestClause.getRightExpression().accept(this, arg);
+        return null;
+    }
+
+    @Override
+    public Void visit(HavingClause havingClause, Void arg) throws CompilationException {
+        havingClause.getFilterExpression().accept(this, arg);
+        return null;
+    }
+
+    @Override
+    public Void visit(CaseExpression caseExpression, Void arg) throws CompilationException {
+        caseExpression.getConditionExpr().accept(this, arg);
+        for (Expression expr : caseExpression.getWhenExprs()) {
+            expr.accept(this, arg);
+        }
+        for (Expression expr : caseExpression.getThenExprs()) {
+            expr.accept(this, arg);
+        }
+        caseExpression.getElseExpr().accept(this, arg);
+        return null;
+    }
+
+    @Override
+    public Void visit(WindowExpression winExpr, Void arg) throws CompilationException {
+        calls.add(winExpr);
+        if (winExpr.hasPartitionList()) {
+            for (Expression expr : winExpr.getPartitionList()) {
+                expr.accept(this, arg);
+            }
+        }
+        if (winExpr.hasOrderByList()) {
+            for (Expression expr : winExpr.getOrderbyList()) {
+                expr.accept(this, arg);
+            }
+        }
+        if (winExpr.hasFrameStartExpr()) {
+            winExpr.getFrameStartExpr().accept(this, arg);
+        }
+        if (winExpr.hasFrameEndExpr()) {
+            winExpr.getFrameEndExpr().accept(this, arg);
+        }
+        if (winExpr.hasWindowFieldList()) {
+            for (Pair<Expression, Identifier> p : winExpr.getWindowFieldList()) {
+                p.first.accept(this, arg);
+            }
+        }
+        if (winExpr.hasAggregateFilterExpr()) {
+            winExpr.getAggregateFilterExpr().accept(this, arg);
+        }
+        for (Expression expr : winExpr.getExprList()) {
+            expr.accept(this, arg);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visit(ListSliceExpression expression, Void arg) throws CompilationException {
+        expression.getExpr().accept(this, arg);
+        expression.getStartIndexExpression().accept(this, arg);
+
+        if (expression.hasEndExpression()) {
+            expression.getEndIndexExpression().accept(this, arg);
+        }
+        return null;
+    }
+}
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppInlineUdfsVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppInlineUdfsVisitor.java
index 4fb17f0..5200b94 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppInlineUdfsVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppInlineUdfsVisitor.java
@@ -23,9 +23,9 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.functions.FunctionSignature;
 import org.apache.asterix.lang.common.base.AbstractClause;
 import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.IRewriterFactory;
 import org.apache.asterix.lang.common.clause.LetClause;
 import org.apache.asterix.lang.common.expression.ListSliceExpression;
 import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
@@ -52,26 +52,18 @@ import org.apache.asterix.lang.sqlpp.struct.SetOperationRight;
 import org.apache.asterix.lang.sqlpp.util.SqlppRewriteUtil;
 import org.apache.asterix.lang.sqlpp.visitor.SqlppCloneAndSubstituteVariablesVisitor;
 import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
-import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.hyracks.algebricks.common.utils.Pair;
 
-public class SqlppInlineUdfsVisitor extends AbstractInlineUdfsVisitor
-        implements ISqlppVisitor<Boolean, List<FunctionDecl>> {
+public class SqlppInlineUdfsVisitor extends AbstractInlineUdfsVisitor implements ISqlppVisitor<Boolean, Void> {
 
     /**
      * @param context,
      *            manages ids of variables and guarantees uniqueness of variables.
-     * @param rewriterFactory,
-     *            a rewrite factory for rewriting user-defined functions.
-     * @param declaredFunctions,
-     *            a list of declared functions associated with the query.
-     * @param metadataProvider,
-     *            providing the definition of created (i.e., stored) user-defined functions.
+     * @param usedUDFs,
+     *            user defined functions used by this query.
      */
-    public SqlppInlineUdfsVisitor(LangRewritingContext context, IRewriterFactory rewriterFactory,
-            List<FunctionDecl> declaredFunctions, MetadataProvider metadataProvider) {
-        super(context, rewriterFactory, declaredFunctions, metadataProvider,
-                new SqlppCloneAndSubstituteVariablesVisitor(context));
+    public SqlppInlineUdfsVisitor(LangRewritingContext context, Map<FunctionSignature, FunctionDecl> usedUDFs) {
+        super(context, usedUDFs, new SqlppCloneAndSubstituteVariablesVisitor(context));
     }
 
     @Override
@@ -82,220 +74,218 @@ public class SqlppInlineUdfsVisitor extends AbstractInlineUdfsVisitor
     }
 
     @Override
-    public Boolean visit(FromClause fromClause, List<FunctionDecl> func) throws CompilationException {
+    public Boolean visit(FromClause fromClause, Void arg) throws CompilationException {
         boolean changed = false;
         for (FromTerm fromTerm : fromClause.getFromTerms()) {
-            changed |= fromTerm.accept(this, func);
+            changed |= fromTerm.accept(this, arg);
         }
         return changed;
     }
 
     @Override
-    public Boolean visit(FromTerm fromTerm, List<FunctionDecl> func) throws CompilationException {
+    public Boolean visit(FromTerm fromTerm, Void arg) throws CompilationException {
         boolean changed = false;
-        Pair<Boolean, Expression> p = inlineUdfsInExpr(fromTerm.getLeftExpression(), func);
+        Pair<Boolean, Expression> p = inlineUdfsInExpr(fromTerm.getLeftExpression());
         fromTerm.setLeftExpression(p.second);
         changed |= p.first;
         for (AbstractBinaryCorrelateClause correlateClause : fromTerm.getCorrelateClauses()) {
-            changed |= correlateClause.accept(this, func);
+            changed |= correlateClause.accept(this, arg);
         }
         return changed;
     }
 
     @Override
-    public Boolean visit(JoinClause joinClause, List<FunctionDecl> funcs) throws CompilationException {
-        Pair<Boolean, Expression> p1 = inlineUdfsInExpr(joinClause.getRightExpression(), funcs);
+    public Boolean visit(JoinClause joinClause, Void arg) throws CompilationException {
+        Pair<Boolean, Expression> p1 = inlineUdfsInExpr(joinClause.getRightExpression());
         joinClause.setRightExpression(p1.second);
-        Pair<Boolean, Expression> p2 = inlineUdfsInExpr(joinClause.getConditionExpression(), funcs);
+        Pair<Boolean, Expression> p2 = inlineUdfsInExpr(joinClause.getConditionExpression());
         joinClause.setConditionExpression(p2.second);
         return p1.first || p2.first;
     }
 
     @Override
-    public Boolean visit(NestClause nestClause, List<FunctionDecl> funcs) throws CompilationException {
-        Pair<Boolean, Expression> p1 = inlineUdfsInExpr(nestClause.getRightExpression(), funcs);
+    public Boolean visit(NestClause nestClause, Void arg) throws CompilationException {
+        Pair<Boolean, Expression> p1 = inlineUdfsInExpr(nestClause.getRightExpression());
         nestClause.setRightExpression(p1.second);
-        Pair<Boolean, Expression> p2 = inlineUdfsInExpr(nestClause.getConditionExpression(), funcs);
+        Pair<Boolean, Expression> p2 = inlineUdfsInExpr(nestClause.getConditionExpression());
         nestClause.setConditionExpression(p2.second);
         return p1.first || p2.first;
     }
 
     @Override
-    public Boolean visit(Projection projection, List<FunctionDecl> funcs) throws CompilationException {
+    public Boolean visit(Projection projection, Void arg) throws CompilationException {
         if (projection.star()) {
             return false;
         }
-        Pair<Boolean, Expression> p = inlineUdfsInExpr(projection.getExpression(), funcs);
+        Pair<Boolean, Expression> p = inlineUdfsInExpr(projection.getExpression());
         projection.setExpression(p.second);
         return p.first;
     }
 
     @Override
-    public Boolean visit(SelectBlock selectBlock, List<FunctionDecl> funcs) throws CompilationException {
+    public Boolean visit(SelectBlock selectBlock, Void arg) throws CompilationException {
         boolean changed = false;
         if (selectBlock.hasFromClause()) {
-            changed |= selectBlock.getFromClause().accept(this, funcs);
+            changed |= selectBlock.getFromClause().accept(this, arg);
         }
         if (selectBlock.hasLetWhereClauses()) {
             for (AbstractClause letWhereClause : selectBlock.getLetWhereList()) {
-                changed |= letWhereClause.accept(this, funcs);
+                changed |= letWhereClause.accept(this, arg);
             }
         }
         if (selectBlock.hasGroupbyClause()) {
-            changed |= selectBlock.getGroupbyClause().accept(this, funcs);
+            changed |= selectBlock.getGroupbyClause().accept(this, arg);
         }
         if (selectBlock.hasLetHavingClausesAfterGroupby()) {
             for (AbstractClause letHavingClause : selectBlock.getLetHavingListAfterGroupby()) {
-                changed |= letHavingClause.accept(this, funcs);
+                changed |= letHavingClause.accept(this, arg);
             }
         }
-        changed |= selectBlock.getSelectClause().accept(this, funcs);
+        changed |= selectBlock.getSelectClause().accept(this, arg);
         return changed;
     }
 
     @Override
-    public Boolean visit(SelectClause selectClause, List<FunctionDecl> funcs) throws CompilationException {
+    public Boolean visit(SelectClause selectClause, Void arg) throws CompilationException {
         boolean changed = false;
         if (selectClause.selectElement()) {
-            changed |= selectClause.getSelectElement().accept(this, funcs);
+            changed |= selectClause.getSelectElement().accept(this, arg);
         } else {
-            changed |= selectClause.getSelectRegular().accept(this, funcs);
+            changed |= selectClause.getSelectRegular().accept(this, arg);
         }
         return changed;
     }
 
     @Override
-    public Boolean visit(SelectElement selectElement, List<FunctionDecl> funcs) throws CompilationException {
-        Pair<Boolean, Expression> p = inlineUdfsInExpr(selectElement.getExpression(), funcs);
+    public Boolean visit(SelectElement selectElement, Void arg) throws CompilationException {
+        Pair<Boolean, Expression> p = inlineUdfsInExpr(selectElement.getExpression());
         selectElement.setExpression(p.second);
         return p.first;
     }
 
     @Override
-    public Boolean visit(SelectRegular selectRegular, List<FunctionDecl> funcs) throws CompilationException {
+    public Boolean visit(SelectRegular selectRegular, Void arg) throws CompilationException {
         boolean changed = false;
         for (Projection projection : selectRegular.getProjections()) {
-            changed |= projection.accept(this, funcs);
+            changed |= projection.accept(this, arg);
         }
         return changed;
     }
 
     @Override
-    public Boolean visit(SelectSetOperation selectSetOperation, List<FunctionDecl> funcs) throws CompilationException {
+    public Boolean visit(SelectSetOperation selectSetOperation, Void arg) throws CompilationException {
         boolean changed = false;
-        changed |= selectSetOperation.getLeftInput().accept(this, funcs);
+        changed |= selectSetOperation.getLeftInput().accept(this, arg);
         for (SetOperationRight right : selectSetOperation.getRightInputs()) {
-            changed |= right.getSetOperationRightInput().accept(this, funcs);
+            changed |= right.getSetOperationRightInput().accept(this, arg);
         }
         return changed;
     }
 
     @Override
-    public Boolean visit(SelectExpression selectExpression, List<FunctionDecl> funcs) throws CompilationException {
+    public Boolean visit(SelectExpression selectExpression, Void arg) throws CompilationException {
         boolean changed = false;
         if (selectExpression.hasLetClauses()) {
             for (LetClause letClause : selectExpression.getLetList()) {
-                changed |= letClause.accept(this, funcs);
+                changed |= letClause.accept(this, arg);
             }
         }
-        changed |= selectExpression.getSelectSetOperation().accept(this, funcs);
+        changed |= selectExpression.getSelectSetOperation().accept(this, arg);
         if (selectExpression.hasOrderby()) {
-            changed |= selectExpression.getOrderbyClause().accept(this, funcs);
+            changed |= selectExpression.getOrderbyClause().accept(this, arg);
         }
         if (selectExpression.hasLimit()) {
-            changed |= selectExpression.getLimitClause().accept(this, funcs);
+            changed |= selectExpression.getLimitClause().accept(this, arg);
         }
         return changed;
     }
 
     @Override
-    public Boolean visit(UnnestClause unnestClause, List<FunctionDecl> funcs) throws CompilationException {
-        Pair<Boolean, Expression> p = inlineUdfsInExpr(unnestClause.getRightExpression(), funcs);
+    public Boolean visit(UnnestClause unnestClause, Void arg) throws CompilationException {
+        Pair<Boolean, Expression> p = inlineUdfsInExpr(unnestClause.getRightExpression());
         unnestClause.setRightExpression(p.second);
         return p.first;
     }
 
     @Override
-    public Boolean visit(HavingClause havingClause, List<FunctionDecl> funcs) throws CompilationException {
-        Pair<Boolean, Expression> p = inlineUdfsInExpr(havingClause.getFilterExpression(), funcs);
+    public Boolean visit(HavingClause havingClause, Void arg) throws CompilationException {
+        Pair<Boolean, Expression> p = inlineUdfsInExpr(havingClause.getFilterExpression());
         havingClause.setFilterExpression(p.second);
         return p.first;
     }
 
     @Override
-    public Boolean visit(CaseExpression caseExpr, List<FunctionDecl> funcs) throws CompilationException {
-        Pair<Boolean, Expression> result = inlineUdfsInExpr(caseExpr.getConditionExpr(), funcs);
+    public Boolean visit(CaseExpression caseExpr, Void arg) throws CompilationException {
+        Pair<Boolean, Expression> result = inlineUdfsInExpr(caseExpr.getConditionExpr());
         caseExpr.setConditionExpr(result.second);
         boolean inlined = result.first;
 
-        Pair<Boolean, List<Expression>> inlinedList = inlineUdfsInExprList(caseExpr.getWhenExprs(), funcs);
+        Pair<Boolean, List<Expression>> inlinedList = inlineUdfsInExprList(caseExpr.getWhenExprs());
         inlined = inlined || inlinedList.first;
         caseExpr.setWhenExprs(inlinedList.second);
 
-        inlinedList = inlineUdfsInExprList(caseExpr.getThenExprs(), funcs);
+        inlinedList = inlineUdfsInExprList(caseExpr.getThenExprs());
         inlined = inlined || inlinedList.first;
         caseExpr.setThenExprs(inlinedList.second);
 
-        result = inlineUdfsInExpr(caseExpr.getElseExpr(), funcs);
+        result = inlineUdfsInExpr(caseExpr.getElseExpr());
         caseExpr.setElseExpr(result.second);
         return inlined || result.first;
     }
 
     @Override
-    public Boolean visit(WindowExpression winExpr, List<FunctionDecl> funcs) throws CompilationException {
+    public Boolean visit(WindowExpression winExpr, Void arg) throws CompilationException {
         boolean inlined = false;
         if (winExpr.hasPartitionList()) {
-            Pair<Boolean, List<Expression>> inlinedList = inlineUdfsInExprList(winExpr.getPartitionList(), funcs);
+            Pair<Boolean, List<Expression>> inlinedList = inlineUdfsInExprList(winExpr.getPartitionList());
             winExpr.setPartitionList(inlinedList.second);
             inlined = inlinedList.first;
         }
         if (winExpr.hasOrderByList()) {
-            Pair<Boolean, List<Expression>> inlinedList = inlineUdfsInExprList(winExpr.getOrderbyList(), funcs);
+            Pair<Boolean, List<Expression>> inlinedList = inlineUdfsInExprList(winExpr.getOrderbyList());
             winExpr.setOrderbyList(inlinedList.second);
             inlined |= inlinedList.first;
         }
         if (winExpr.hasFrameStartExpr()) {
-            Pair<Boolean, Expression> inlinedExpr = inlineUdfsInExpr(winExpr.getFrameStartExpr(), funcs);
+            Pair<Boolean, Expression> inlinedExpr = inlineUdfsInExpr(winExpr.getFrameStartExpr());
             winExpr.setFrameStartExpr(inlinedExpr.second);
             inlined |= inlinedExpr.first;
         }
         if (winExpr.hasFrameEndExpr()) {
-            Pair<Boolean, Expression> inlinedExpr = inlineUdfsInExpr(winExpr.getFrameEndExpr(), funcs);
+            Pair<Boolean, Expression> inlinedExpr = inlineUdfsInExpr(winExpr.getFrameEndExpr());
             winExpr.setFrameEndExpr(inlinedExpr.second);
             inlined |= inlinedExpr.first;
         }
         if (winExpr.hasWindowFieldList()) {
             Pair<Boolean, List<Pair<Expression, Identifier>>> inlinedList =
-                    inlineUdfsInFieldList(winExpr.getWindowFieldList(), funcs);
+                    inlineUdfsInFieldList(winExpr.getWindowFieldList());
             winExpr.setWindowFieldList(inlinedList.second);
             inlined |= inlinedList.first;
         }
         if (winExpr.hasAggregateFilterExpr()) {
-            Pair<Boolean, Expression> inlinedExpr = inlineUdfsInExpr(winExpr.getAggregateFilterExpr(), funcs);
+            Pair<Boolean, Expression> inlinedExpr = inlineUdfsInExpr(winExpr.getAggregateFilterExpr());
             winExpr.setAggregateFilterExpr(inlinedExpr.second);
             inlined |= inlinedExpr.first;
         }
-        Pair<Boolean, List<Expression>> inlinedList = inlineUdfsInExprList(winExpr.getExprList(), funcs);
+        Pair<Boolean, List<Expression>> inlinedList = inlineUdfsInExprList(winExpr.getExprList());
         winExpr.setExprList(inlinedList.second);
         inlined |= inlinedList.first;
         return inlined;
     }
 
     @Override
-    public Boolean visit(ListSliceExpression expression, List<FunctionDecl> funcs) throws CompilationException {
-        Pair<Boolean, Expression> expressionResult = inlineUdfsInExpr(expression.getExpr(), funcs);
+    public Boolean visit(ListSliceExpression expression, Void arg) throws CompilationException {
+        Pair<Boolean, Expression> expressionResult = inlineUdfsInExpr(expression.getExpr());
         expression.setExpr(expressionResult.second);
         boolean inlined = expressionResult.first;
 
-        Pair<Boolean, Expression> startIndexExpressResult =
-                inlineUdfsInExpr(expression.getStartIndexExpression(), funcs);
+        Pair<Boolean, Expression> startIndexExpressResult = inlineUdfsInExpr(expression.getStartIndexExpression());
         expression.setStartIndexExpression(startIndexExpressResult.second);
         inlined |= startIndexExpressResult.first;
 
         // End index expression can be null (optional)
         if (expression.hasEndExpression()) {
-            Pair<Boolean, Expression> endIndexExpressionResult =
-                    inlineUdfsInExpr(expression.getEndIndexExpression(), funcs);
+            Pair<Boolean, Expression> endIndexExpressionResult = inlineUdfsInExpr(expression.getEndIndexExpression());
             expression.setEndIndexExpression(endIndexExpressionResult.second);
             inlined |= endIndexExpressionResult.first;
         }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/DeepCopyVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/DeepCopyVisitor.java
index b5375d2..998d8e6 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/DeepCopyVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/DeepCopyVisitor.java
@@ -257,8 +257,8 @@ public class DeepCopyVisitor extends AbstractSqlppQueryExpressionVisitor<ILangEx
 
     @Override
     public FunctionDecl visit(FunctionDecl fd, Void arg) throws CompilationException {
-        FunctionDecl copy =
-                new FunctionDecl(fd.getSignature(), fd.getParamList(), (Expression) fd.getFuncBody().accept(this, arg));
+        FunctionDecl copy = new FunctionDecl(fd.getSignature(), fd.getParamList(),
+                (Expression) fd.getFuncBody().accept(this, arg), fd.isStored());
         copy.setSourceLocation(fd.getSourceLocation());
         return copy;
     }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index faee227..b93ab87 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -384,7 +384,7 @@ class SQLPPParser extends ScopeChecker implements IParser {
     }
 
     @Override
-    public FunctionDecl parseFunctionBody(FunctionSignature signature, List<String> paramNames)
+    public FunctionDecl parseFunctionBody(FunctionSignature signature, List<String> paramNames, boolean isStored)
       throws CompilationException {
         return parseImpl(new ParseFunction<FunctionDecl>() {
             @Override
@@ -399,7 +399,7 @@ class SQLPPParser extends ScopeChecker implements IParser {
                 Expression functionBodyExpr = SQLPPParser.this.FunctionBody();
                 removeCurrentScope();
                 defaultDataverse = dataverse;
-                return new FunctionDecl(signature, paramVars, functionBodyExpr);
+                return new FunctionDecl(signature, paramVars, functionBodyExpr, isStored);
             }
         });
     }
@@ -2701,7 +2701,7 @@ FunctionDecl FunctionDeclaration() throws ParseException:
     for (Pair<VarIdentifier,TypeExpression> p: paramList) {
         params.add(p.getFirst());
     }
-    FunctionDecl stmt = new FunctionDecl(signature, params, funcBody);
+    FunctionDecl stmt = new FunctionDecl(signature, params, funcBody, false);
     removeCurrentScope();
     return addSourceLocation(stmt, startToken);
   }