Add function signature check to Connect Feed

1. Revise the exception info when apply an unknown function to feed.
2. Fix the possible NPE in connect feed statement.
3. Add test case for applying undefined function.

Change-Id: I1462b394d84ea7e1eae5a03f98fe8cd39213eb8e
Reviewed-on: https://asterix-gerrit.ics.uci.edu/1674
Sonar-Qube: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
BAD: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: abdullah alamoudi <bamousaa@gmail.com>
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 b7aae59..6a2b4e0 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
@@ -2138,6 +2138,12 @@
             ARecordType outputType = FeedMetadataUtil.getOutputType(feed, feed.getAdapterConfiguration(),
                     ExternalDataConstants.KEY_TYPE_NAME);
             List<FunctionSignature> appliedFunctions = cfs.getAppliedFunctions();
+            for (FunctionSignature func : appliedFunctions) {
+                if (MetadataManager.INSTANCE.getFunction(mdTxnCtx, func) == null) {
+                    throw new CompilationException(ErrorCode.FEED_CONNECT_FEED_APPLIED_INVALID_FUNCTION,
+                            func.getName());
+                }
+            }
             fc = MetadataManager.INSTANCE.getFeedConnection(metadataProvider.getMetadataTxnContext(), dataverseName,
                     feedName, datasetName);
             if (fc != null) {
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries/feeds/feed-with-undefined-function/feed-with-undefined-function.1.ddl.aql b/asterixdb/asterix-app/src/test/resources/runtimets/queries/feeds/feed-with-undefined-function/feed-with-undefined-function.1.ddl.aql
new file mode 100644
index 0000000..d294772
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries/feeds/feed-with-undefined-function/feed-with-undefined-function.1.ddl.aql
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+drop dataverse externallibtest if exists;
+create dataverse externallibtest;
+use dataverse externallibtest;
+
+create type TweetInputType as closed {
+  id: string,
+  username : string,
+  location : string,
+  text : string,
+  timestamp : string
+}
+
+create feed TweetFeed
+using localfs
+(("type-name"="TweetInputType"),
+("path"="asterix_nc1://data/twitter/obamatweets.adm"),
+("format"="adm"));
+
+create dataset TweetsFeedIngest(TweetInputType)
+primary key id;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries/feeds/feed-with-undefined-function/feed-with-undefined-function.2.update.aql b/asterixdb/asterix-app/src/test/resources/runtimets/queries/feeds/feed-with-undefined-function/feed-with-undefined-function.2.update.aql
new file mode 100644
index 0000000..a2a00ba
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries/feeds/feed-with-undefined-function/feed-with-undefined-function.2.update.aql
@@ -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.
+ */
+use dataverse externallibtest;
+
+set wait-for-completion-feed "true";
+
+connect feed TweetFeed to dataset TweetsFeedIngest
+apply function function_undefined;
+
+start feed TweetFeed;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/feeds/feed-with-undefined-function/feed-with-external-function.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/feeds/feed-with-undefined-function/feed-with-external-function.1.adm
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/feeds/feed-with-undefined-function/feed-with-external-function.1.adm
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite.xml b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite.xml
index a69d313..83d5581 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite.xml
@@ -273,6 +273,12 @@
         <output-dir compare="Text">record-reader-with-malformed-input-stream</output-dir>
       </compilation-unit>
     </test-case>
+    <test-case FilePath="feeds">
+      <compilation-unit name="feed-with-undefined-function">
+        <output-dir compare="Text">feed-with-undefined-function</output-dir>
+        <expected-error>Cannot find function</expected-error>
+      </compilation-unit>
+    </test-case>
   </test-group>
   <test-group name="upsert">
     <test-case FilePath="upsert">
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 8897dbf..23d6727 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
@@ -179,6 +179,7 @@
     public static final int FEED_METADATA_UTIL_UNEXPECTED_FEED_DATATYPE = 3080;
     public static final int FEED_METADATA_SOCKET_ADAPTOR_SOCKET_NOT_PROPERLY_CONFIGURED = 3081;
     public static final int FEED_METADATA_SOCKET_ADAPTOR_SOCKET_INVALID_HOST_NC = 3082;
+    public static final int FEED_CONNECT_FEED_APPLIED_INVALID_FUNCTION = 3087;
 
     private ErrorCode() {
     }
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 1c93efe..e1a54cf 100644
--- a/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
+++ b/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
@@ -167,4 +167,5 @@
 3079 = Cannot register runtime, active manager has been shutdown
 3080 = Unexpected feed datatype '%1$s'
 3081 = socket is not properly configured
-3082 = "Invalid %1$s %2$s as it is not part of the AsterixDB cluster. Valid choices are %3$s"
\ No newline at end of file
+3082 = "Invalid %1$s %2$s as it is not part of the AsterixDB cluster. Valid choices are %3$s"
+3087 = Cannot find function %1$s
\ No newline at end of file
diff --git a/asterixdb/asterix-lang-aql/src/main/javacc/AQL.jj b/asterixdb/asterix-lang-aql/src/main/javacc/AQL.jj
index b2909c4..b92805b 100644
--- a/asterixdb/asterix-lang-aql/src/main/javacc/AQL.jj
+++ b/asterixdb/asterix-lang-aql/src/main/javacc/AQL.jj
@@ -818,11 +818,10 @@
     }
 }
 
-List<FunctionSignature> ApplyFunction() throws ParseException:
+void ApplyFunction(List<FunctionSignature> funcSigs) throws ParseException:
 {
   FunctionName functioName = null;
   String fqFunctionName = null;
-  List<FunctionSignature> funcSigs = new ArrayList<FunctionSignature>();
 }
 {
   <APPLY> <FUNCTION> functioName = FunctionName()
@@ -837,9 +836,6 @@
       funcSigs.add(new FunctionSignature(functioName.dataverse, fqFunctionName, 1));
     }
   )*
-    {
-        return funcSigs;
-    }
 }
 
 String GetPolicy() throws ParseException:
@@ -1167,14 +1163,14 @@
   Pair<Identifier,Identifier> datasetNameComponents = null;
 
   Map<String,String> configuration = null;
-  List<FunctionSignature> appliedFunctions = null;
+  List<FunctionSignature> appliedFunctions = new ArrayList<FunctionSignature>();
   Statement stmt = null;
   String policy = null;
 }
 {
   (
     <CONNECT> <FEED> feedNameComponents = QualifiedName() <TO> <DATASET> datasetNameComponents = QualifiedName()
-    (appliedFunctions = ApplyFunction())? (policy = GetPolicy())?
+    (ApplyFunction(appliedFunctions))? (policy = GetPolicy())?
       {
         stmt = new ConnectFeedStatement(feedNameComponents, datasetNameComponents, appliedFunctions, policy, getVarCounter());
       }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index 67c742a..69bfbc5 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -840,11 +840,10 @@
     }
 }
 
-List<FunctionSignature> ApplyFunction() throws ParseException:
+void ApplyFunction(List<FunctionSignature> funcSigs) throws ParseException:
 {
   FunctionName functioName = null;
   String fqFunctionName = null;
-  List<FunctionSignature> funcSigs = new ArrayList<FunctionSignature>();
 }
 {
   <APPLY> <FUNCTION> functioName = FunctionName()
@@ -859,9 +858,6 @@
         funcSigs.add(new FunctionSignature(functioName.dataverse, fqFunctionName, 1));
       }
   )*
-    {
-        return funcSigs;
-    }
 }
 
 String GetPolicy() throws ParseException:
@@ -1254,14 +1250,14 @@
   Pair<Identifier,Identifier> datasetNameComponents = null;
 
   Map<String,String> configuration = null;
-  List<FunctionSignature> appliedFunctions = null;
+  List<FunctionSignature> appliedFunctions = new ArrayList<FunctionSignature>();
   Statement stmt = null;
   String policy = null;
 }
 {
   (
     <FEED> feedNameComponents = QualifiedName() <TO> Dataset() datasetNameComponents = QualifiedName()
-    (appliedFunctions = ApplyFunction())?  (policy = GetPolicy())?
+    (ApplyFunction(appliedFunctions))?  (policy = GetPolicy())?
       {
         stmt = new ConnectFeedStatement(feedNameComponents, datasetNameComponents, appliedFunctions,
          policy, getVarCounter());