Merge branch 'gerrit/trinity' into 'gerrit/master'

Change-Id: Ibdfd6c191239b4f5743000cd568e1e0ffb089dd8
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/CancelUnnestWithNestedListifyRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/CancelUnnestWithNestedListifyRule.java
index 4974fed..366da5a 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/CancelUnnestWithNestedListifyRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/CancelUnnestWithNestedListifyRule.java
@@ -194,7 +194,7 @@
         AggregateOperator agg = (AggregateOperator) nestedPlanRoot;
         Mutable<ILogicalOperator> aggInputOpRef = agg.getInputs().get(0);
 
-        if (agg.getVariables().size() > 1) {
+        if (agg.getVariables().size() != 1) {
             return false;
         }
 
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/RemoveRedundantListifyRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/RemoveRedundantListifyRule.java
index f968b35..0f490be 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/RemoveRedundantListifyRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/RemoveRedundantListifyRule.java
@@ -200,7 +200,7 @@
             return false;
         }
         AggregateOperator agg = (AggregateOperator) r;
-        if (agg.getVariables().size() > 1) {
+        if (agg.getVariables().size() != 1) {
             return false;
         }
         LogicalVariable aggVar = agg.getVariables().get(0);
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/query-ASTERIXDB-3403/query-ASTERIXDB-3403.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/query-ASTERIXDB-3403/query-ASTERIXDB-3403.1.ddl.sqlpp
new file mode 100644
index 0000000..0b94eb1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/query-ASTERIXDB-3403/query-ASTERIXDB-3403.1.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: This test case is to verify the fix for ASTERIXDB-3403
+ */
+
+drop dataverse test if exists;
+create dataverse test;
+
+use test;
+
+create type dt1 as {id:int};
+create dataset collection1(dt1) primary key id;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/query-ASTERIXDB-3403/query-ASTERIXDB-3403.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/query-ASTERIXDB-3403/query-ASTERIXDB-3403.2.update.sqlpp
new file mode 100644
index 0000000..327d1d6
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/query-ASTERIXDB-3403/query-ASTERIXDB-3403.2.update.sqlpp
@@ -0,0 +1,47 @@
+/*
+ * 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 test;
+
+insert into collection1
+([
+    {
+      "id": 1,
+      "f1": "f1",
+      "x1": [{"date":"01-02-2024", "item": '1234', "cnt":2}]
+
+    },
+    {
+        "id": 2,
+        "f1": "f1",
+        "x2": [{"date":"01-02-2024", "item": '5678', "cnt":2}]
+    },
+    {
+      "id": 3,
+      "f1": "f1",
+      "x3": [{"su": {"x4":2}, "item": "i1", "cnt":2}]
+    },
+    {
+      "id": 4,
+      "f1": "f1",
+      "x3": [{"su": {"x4":5}, "item": 1234, "cnt":2}]
+    }
+]);
+
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/query-ASTERIXDB-3403/query-ASTERIXDB-3403.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/query-ASTERIXDB-3403/query-ASTERIXDB-3403.3.query.sqlpp
new file mode 100644
index 0000000..ca70034
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/query-ASTERIXDB-3403/query-ASTERIXDB-3403.3.query.sqlpp
@@ -0,0 +1,40 @@
+/*
+ * 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: This test case is to verify the fix for ASTERIXDB-3403
+ */
+
+use test;
+
+    SELECT COUNT(id) AS matches
+    FROM collection1 AS d
+    WHERE d.`f1` = 'f1'
+      AND (ARRAY_SUM((
+          SELECT VALUE i.`cnt`
+          FROM d.`x1` AS i
+          WHERE i.`date` BETWEEN "01-01-2024" AND "02-02-2024"
+            AND i.`item` IN ['1234', '5678'] )) >= 1
+        OR ARRAY_SUM((
+          SELECT VALUE i.`cnt`
+          FROM d.`x2` AS i
+          WHERE i.`date` BETWEEN "01-01-2024" AND "02-02-2024"
+            AND i.`item` IN ['1234', '5678'] )) >= 1
+        OR (ANY e IN d.x3 SATISFIES e.item IN ['i1', 'i2', 'i3']
+          AND e.su.`x4` >= 1 END));
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/misc/query-ASTERIXDB-3403/query-ASTERIXDB-3403.3.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/misc/query-ASTERIXDB-3403/query-ASTERIXDB-3403.3.adm
new file mode 100644
index 0000000..433b8ee
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/misc/query-ASTERIXDB-3403/query-ASTERIXDB-3403.3.adm
@@ -0,0 +1 @@
+{ "matches": 3 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml b/asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml
index 7e2259b..16e9da4 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml
@@ -7376,6 +7376,11 @@
         <output-dir compare="Text">query-ASTERIXDB-3334</output-dir>
       </compilation-unit>
     </test-case>
+    <test-case FilePath="misc">
+      <compilation-unit name="query-ASTERIXDB-3403">
+        <output-dir compare="Text">query-ASTERIXDB-3403</output-dir>
+      </compilation-unit>
+    </test-case>
   </test-group>
   <test-group name="multipart-dataverse">
     <test-case FilePath="multipart-dataverse">
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/utils/RequestTracker.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/utils/RequestTracker.java
index 9875651..ef9154d 100644
--- a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/utils/RequestTracker.java
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/utils/RequestTracker.java
@@ -18,6 +18,7 @@
  */
 package org.apache.asterix.runtime.utils;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
@@ -109,7 +110,7 @@
 
     @Override
     public synchronized Collection<IClientRequest> getCompletedRequests() {
-        return Collections.unmodifiableCollection(completedRequests.values());
+        return Collections.unmodifiableCollection(new ArrayList<>(completedRequests.values()));
     }
 
     private void cancel(IClientRequest request) throws HyracksDataException {
diff --git a/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-cc/src/main/java/org/apache/hyracks/control/cc/job/JobManager.java b/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-cc/src/main/java/org/apache/hyracks/control/cc/job/JobManager.java
index 3a954f4..2b03da5 100644
--- a/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-cc/src/main/java/org/apache/hyracks/control/cc/job/JobManager.java
+++ b/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-cc/src/main/java/org/apache/hyracks/control/cc/job/JobManager.java
@@ -36,6 +36,7 @@
 import org.apache.hyracks.api.exceptions.HyracksDataException;
 import org.apache.hyracks.api.exceptions.HyracksException;
 import org.apache.hyracks.api.exceptions.IError;
+import org.apache.hyracks.api.exceptions.IFormattedException;
 import org.apache.hyracks.api.job.ActivityClusterGraph;
 import org.apache.hyracks.api.job.JobId;
 import org.apache.hyracks.api.job.JobSpecification;
@@ -148,7 +149,6 @@
             // trigger JobCleanupWork and JobCleanupNotificationWork which will update the lifecycle of the job.
             // Therefore, we do not remove the job out of activeRunMap here.
             jobRun.getExecutor().cancelJob(callback);
-            incrementCancelledJobs();
             return;
         }
         // Removes a pending job.
@@ -237,11 +237,7 @@
     @Override
     public void finalComplete(JobRun run) throws HyracksException {
         checkJob(run);
-        if (run.getPendingStatus() == JobStatus.FAILURE) {
-            incrementFailedJobs();
-        } else if (run.getPendingStatus() == JobStatus.TERMINATED) {
-            incrementSuccessfulJobs();
-        }
+        boolean successful = run.getPendingStatus() == JobStatus.TERMINATED;
 
         JobId jobId = run.getJobId();
         Throwable caughtException = null;
@@ -256,6 +252,8 @@
         run.setStatus(run.getPendingStatus(), run.getPendingExceptions());
         run.setEndTime(System.currentTimeMillis());
         if (activeRunMap.remove(jobId) != null) {
+            incrementJobCounters(run, successful);
+
             // non-active jobs have zero capacity
             releaseJobCapacity(run);
         }
@@ -283,6 +281,41 @@
         }
     }
 
+    /**
+     * Increments the job counters depending on the status
+     *
+     * @param run job run
+     * @param successful if job is successful
+     */
+    private void incrementJobCounters(JobRun run, boolean successful) {
+        if (successful) {
+            incrementSuccessfulJobs();
+            return;
+        }
+
+        if (run.getExceptions() != null && !run.getExceptions().isEmpty() && isCancelledJob(run)) {
+            incrementCancelledJobs();
+        } else {
+            incrementFailedJobs();
+        }
+    }
+
+    /**
+     * Checks the exceptions for a job run to see if the job is cancelled
+     *
+     * @param run job run
+     * @return true if cancelled job, false otherwise
+     */
+    private boolean isCancelledJob(JobRun run) {
+        List<Exception> exceptions = run.getExceptions();
+        for (Exception e : exceptions) {
+            if (e instanceof IFormattedException f && f.getErrorCode() == ErrorCode.JOB_CANCELED.intValue()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public Collection<JobRun> getRunningJobs() {
         return activeRunMap.values();