[NO ISSUE][API] Add Warnings to Query Service API

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

Details:
- Add warnings to the query service API output.

Change-Id: I4f43b5a020c1b4a7b75f956ce5b31a2eadb3c044
Reviewed-on: https://asterix-gerrit.ics.uci.edu/2817
Sonar-Qube: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Contrib: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Till Westmann <tillw@apache.org>
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/AbstractQueryApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/AbstractQueryApiServlet.java
index bd096dd..c5420ba 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/AbstractQueryApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/AbstractQueryApiServlet.java
@@ -52,7 +52,8 @@
         HANDLE("handle"),
         ERRORS("errors"),
         METRICS("metrics"),
-        PLANS("plans");
+        PLANS("plans"),
+        WARNINGS("warnings");
 
         private final String str;
 
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ExecutionWarning.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ExecutionWarning.java
new file mode 100644
index 0000000..baaa5bd
--- /dev/null
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ExecutionWarning.java
@@ -0,0 +1,38 @@
+/*
+ * 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.api.http.server;
+
+public class ExecutionWarning {
+
+    private final int code;
+    private final String message;
+
+    public ExecutionWarning(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
index 316646d..0c5d216 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
@@ -19,14 +19,15 @@
 package org.apache.asterix.api.http.server;
 
 import static org.apache.asterix.common.exceptions.ErrorCode.ASTERIX;
-import static org.apache.asterix.common.exceptions.ErrorCode.REQUEST_TIMEOUT;
 import static org.apache.asterix.common.exceptions.ErrorCode.REJECT_BAD_CLUSTER_STATE;
 import static org.apache.asterix.common.exceptions.ErrorCode.REJECT_NODE_UNREGISTERED;
+import static org.apache.asterix.common.exceptions.ErrorCode.REQUEST_TIMEOUT;
 
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -53,9 +54,9 @@
 import org.apache.asterix.translator.ExecutionPlansJsonPrintUtil;
 import org.apache.asterix.translator.IRequestParameters;
 import org.apache.asterix.translator.IStatementExecutor;
-import org.apache.asterix.translator.IStatementExecutorContext;
 import org.apache.asterix.translator.IStatementExecutor.ResultDelivery;
 import org.apache.asterix.translator.IStatementExecutor.Stats;
+import org.apache.asterix.translator.IStatementExecutorContext;
 import org.apache.asterix.translator.IStatementExecutorFactory;
 import org.apache.asterix.translator.ResultProperties;
 import org.apache.asterix.translator.SessionConfig;
@@ -185,7 +186,8 @@
         RESULT_COUNT("resultCount"),
         RESULT_SIZE("resultSize"),
         ERROR_COUNT("errorCount"),
-        PROCESSED_OBJECTS_COUNT("processedObjects");
+        PROCESSED_OBJECTS_COUNT("processedObjects"),
+        WARNING_COUNT("warningCount");
 
         private final String str;
 
@@ -334,8 +336,9 @@
     }
 
     private static void printMetrics(PrintWriter pw, long elapsedTime, long executionTime, long resultCount,
-            long resultSize, long processedObjects, long errorCount) {
+            long resultSize, long processedObjects, long errorCount, long warnCount) {
         boolean hasErrors = errorCount != 0;
+        boolean hasWarnings = warnCount != 0;
         pw.print("\t\"");
         pw.print(ResultFields.METRICS.str());
         pw.print("\": {\n");
@@ -348,7 +351,11 @@
         pw.print("\t");
         ResultUtil.printField(pw, Metrics.RESULT_SIZE.str(), resultSize, true);
         pw.print("\t");
-        ResultUtil.printField(pw, Metrics.PROCESSED_OBJECTS_COUNT.str(), processedObjects, hasErrors);
+        ResultUtil.printField(pw, Metrics.PROCESSED_OBJECTS_COUNT.str(), processedObjects, hasWarnings || hasErrors);
+        if (hasWarnings) {
+            pw.print("\t");
+            ResultUtil.printField(pw, Metrics.WARNING_COUNT.str(), warnCount, hasErrors);
+        }
         if (hasErrors) {
             pw.print("\t");
             ResultUtil.printField(pw, Metrics.ERROR_COUNT.str(), errorCount, false);
@@ -522,6 +529,7 @@
         printSignature(sessionOutput.out(), param);
         printType(sessionOutput.out(), sessionConfig);
         long errorCount = 1; // so far we just return 1 error
+        List<ExecutionWarning> warnings = Collections.emptyList(); // we don't have any warnings yet
         try {
             if (param.getStatement() == null || param.getStatement().isEmpty()) {
                 throw new AsterixException("Empty request, no statement provided");
@@ -543,6 +551,9 @@
             if (ResultDelivery.IMMEDIATE == delivery || ResultDelivery.DEFERRED == delivery) {
                 ResultUtil.printStatus(sessionOutput, execution.getResultStatus());
             }
+            if (!warnings.isEmpty()) {
+                printWarnings(sessionOutput.out(), warnings);
+            }
             errorCount = 0;
         } catch (Exception | TokenMgrError | org.apache.asterix.aqlplus.parser.TokenMgrError e) {
             handleExecuteStatementException(e, execution, param);
@@ -555,7 +566,7 @@
             execution.finish();
         }
         printMetrics(sessionOutput.out(), System.nanoTime() - elapsedStart, execution.duration(), stats.getCount(),
-                stats.getSize(), stats.getProcessedObjects(), errorCount);
+                stats.getSize(), stats.getProcessedObjects(), errorCount, warnings.size());
         sessionOutput.out().print("}\n");
         sessionOutput.out().flush();
         if (sessionOutput.out().checkError()) {
@@ -626,6 +637,10 @@
         ResultUtil.printError(sessionOut, throwable);
     }
 
+    protected void printWarnings(PrintWriter pw, List<ExecutionWarning> warnings) {
+        ResultUtil.printWarnings(pw, warnings);
+    }
+
     protected void printExecutionPlans(SessionOutput output, ExecutionPlans executionPlans) {
         final PrintWriter pw = output.out();
         pw.print("\t\"");
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ResultUtil.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ResultUtil.java
index ec128c2..8824f6a 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ResultUtil.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ResultUtil.java
@@ -138,6 +138,25 @@
         pw.print(comma ? "\t}],\n" : "\t}]\n");
     }
 
+    public static void printWarnings(PrintWriter pw, List<ExecutionWarning> warnings) {
+        pw.print("\t\"");
+        pw.print(AbstractQueryApiServlet.ResultFields.WARNINGS.str());
+        pw.print("\": [");
+        for (int i = 0; i < warnings.size(); i++) {
+            final ExecutionWarning warning = warnings.get(i);
+            pw.print("{ \n\t");
+            printField(pw, QueryServiceServlet.ErrorField.CODE.str(), warning.getCode());
+            pw.print("\t");
+            printField(pw, QueryServiceServlet.ErrorField.MSG.str(), JSONUtil.escape(warning.getMessage()), false);
+            pw.print("\t} \n\t");
+            boolean lastWarning = i == warnings.size() - 1;
+            if (!lastWarning) {
+                pw.print(",");
+            }
+        }
+        pw.print("],\n");
+    }
+
     public static void printField(PrintWriter pw, String name, String value) {
         printField(pw, name, value, true);
     }
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/ResultExtractor.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/ResultExtractor.java
index 43833a2..412cf03 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/ResultExtractor.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/ResultExtractor.java
@@ -54,7 +54,8 @@
         STATUS("status"),
         TYPE("type"),
         ERRORS("errors"),
-        PLANS("plans");
+        PLANS("plans"),
+        WARNINGS("warnings");
 
         private static final Map<String, ResultField> fields = new HashMap<>();
 
@@ -164,6 +165,7 @@
                 case STATUS:
                 case TYPE:
                 case PLANS:
+                case WARNINGS:
                     resultBuilder.append(OBJECT_MAPPER.writeValueAsString(fieldValue));
                     break;
                 default: