[ASTERIXDB-3143] Add SqlppAnalyzedExecutionTest

Details:
This is an extension to SqlppExecutionTest that runs
ANALYZE DATASET on every dataset that is updated via
an *.update.sqlpp file, and then continues to run
the suite. The idea is that no results should change
due to enabling CBO.

A few tests are filtered out that can't work with CBO
on, due to either bugs or known limitations.

Change-Id: Ib5e1b2efefe38332ea1f8f7328c63ace12ac9cab
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/17428
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Vijay Sarathy <vijay.sarathy@couchbase.com>
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/AnalyzingTestExecutor.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/AnalyzingTestExecutor.java
new file mode 100644
index 0000000..ef20c85
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/AnalyzingTestExecutor.java
@@ -0,0 +1,84 @@
+/*
+ * 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.test.common;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.InputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.asterix.testframework.context.TestCaseContext;
+import org.apache.commons.io.IOUtils;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class AnalyzingTestExecutor extends TestExecutor {
+
+    private Pattern loadPattern = Pattern.compile("(load)\\s+(dataset|collection)\\s+([a-zA-z0-9\\.`]+)\\s+using",
+            Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
+    private Pattern upsertPattern = Pattern.compile("^(upsert|insert)\\s+into\\s+([a-zA-z0-9\\.`]+)\\s*(\\(|as)?",
+            Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
+    private Pattern usePattern = Pattern.compile("use\\s+(dataverse\\s+)?([a-zA-z0-9\\.`]+)\\s*;",
+            Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
+
+    public AnalyzingTestExecutor() {
+        super();
+        super.deltaPath = "results_cbo";
+    }
+
+    @Override
+    public ExtractedResult executeSqlppUpdateOrDdl(String statement, TestCaseContext.OutputFormat outputFormat)
+            throws Exception {
+        Matcher dvMatcher = usePattern.matcher(statement);
+        String dv = "";
+        if (dvMatcher.find()) {
+            dv = dvMatcher.group(2) + ".";
+        }
+        Matcher dsMatcher = loadPattern.matcher(statement);
+        Matcher upsertMatcher = upsertPattern.matcher(statement);
+        ExtractedResult res = super.executeUpdateOrDdl(statement, outputFormat, getQueryServiceUri(SQLPP));
+        analyzeFromRegex(dsMatcher, dv, 3);
+        analyzeFromRegex(upsertMatcher, dv, 2);
+        return res;
+    }
+
+    private void analyzeFromRegex(Matcher m, String dv, int pos) throws Exception {
+        while (m.find()) {
+            String ds = m.group(pos);
+            StringBuilder analyzeStmt = new StringBuilder();
+            analyzeStmt.append("ANALYZE DATASET ");
+            if (!ds.contains(".")) {
+                analyzeStmt.append(dv);
+            }
+            analyzeStmt.append(ds);
+            analyzeStmt.append(";");
+            InputStream resultStream = executeQueryService(analyzeStmt.toString(), getQueryServiceUri(SQLPP),
+                    TestCaseContext.OutputFormat.CLEAN_JSON);
+            String resultStr = IOUtils.toString(resultStream, UTF_8);
+            JsonNode result = RESULT_NODE_READER.<ObjectNode> readValue(resultStr).get("status");
+            if (!"success".equals(result.asText())) {
+                JsonNode error = RESULT_NODE_READER.<ObjectNode> readValue(resultStr).get("errors");
+                throw new IllegalStateException("ANALYZE DATASET failed with error: " + error);
+            }
+        }
+    }
+
+}
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java
index 04da930..27a575f 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java
@@ -196,9 +196,9 @@
     private static final ObjectReader JSON_NODE_READER = OM.readerFor(JsonNode.class);
     private static final ObjectReader SINGLE_JSON_NODE_READER = JSON_NODE_READER
             .with(DeserializationFeature.FAIL_ON_TRAILING_TOKENS, DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);
-    private static final ObjectReader RESULT_NODE_READER =
+    protected static final ObjectReader RESULT_NODE_READER =
             JSON_NODE_READER.with(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
-    private static final String SQLPP = "sqlpp";
+    protected static final String SQLPP = "sqlpp";
     private static final String DEFAULT_PLAN_FORMAT = "string";
     // see
     // https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers/417184
@@ -276,6 +276,8 @@
     private double timeoutMultiplier = 1;
     protected int loopIteration;
 
+    protected String deltaPath = null;
+
     public TestExecutor() {
         this(Inet4Address.getLoopbackAddress().getHostAddress(), 19002);
     }
@@ -1827,14 +1829,14 @@
         return executeUpdateOrDdl(statement, outputFormat, getQueryServiceUri(SQLPP), cUnit);
     }
 
-    private ExtractedResult executeUpdateOrDdl(String statement, OutputFormat outputFormat, URI serviceUri)
+    protected ExtractedResult executeUpdateOrDdl(String statement, OutputFormat outputFormat, URI serviceUri)
             throws Exception {
         try (InputStream resultStream = executeQueryService(statement, serviceUri, outputFormat, UTF_8)) {
             return ResultExtractor.extract(resultStream, UTF_8, outputFormat);
         }
     }
 
-    private ExtractedResult executeUpdateOrDdl(String statement, OutputFormat outputFormat, URI serviceUri,
+    protected ExtractedResult executeUpdateOrDdl(String statement, OutputFormat outputFormat, URI serviceUri,
             CompilationUnit cUnit) throws Exception {
         try (InputStream resultStream = executeQueryService(statement, outputFormat, serviceUri, cUnit.getParameter(),
                 cUnit.getPlaceholder(), false, UTF_8)) {
@@ -2157,7 +2159,9 @@
                 Assert.fail("No test files found for test: " + testCaseCtx.getTestCase().getFilePath() + "/"
                         + cUnit.getName());
             }
-            List<TestFileContext> expectedResultFileCtxs = testCaseCtx.getExpectedResultFiles(cUnit);
+            List<TestFileContext> expectedResultFileCtxs =
+                    deltaPath != null ? testCaseCtx.getExpectedResultsAndDelta(cUnit, deltaPath)
+                            : testCaseCtx.getExpectedResultFiles(cUnit);
             int[] savedQueryCounts = new int[numOfFiles + testFileCtxs.size()];
             loopIteration = 0;
             for (ListIterator<TestFileContext> iter = testFileCtxs.listIterator(); iter.hasNext();) {
@@ -2869,7 +2873,7 @@
         return ResultExtractor.extract(inputStream, responseCharset, outputFormat).getResult();
     }
 
-    private URI getQueryServiceUri(String extension) throws URISyntaxException {
+    protected URI getQueryServiceUri(String extension) throws URISyntaxException {
         return getEndpoint(Servlets.QUERY_SERVICE);
     }
 
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppAnalyzedExecutionTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppAnalyzedExecutionTest.java
new file mode 100644
index 0000000..a6fb0d4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppAnalyzedExecutionTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.test.runtime;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.asterix.common.api.INcApplicationContext;
+import org.apache.asterix.test.common.AnalyzingTestExecutor;
+import org.apache.asterix.test.common.TestExecutor;
+import org.apache.asterix.testframework.context.TestCaseContext;
+import org.apache.hyracks.control.nc.NodeControllerService;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Runs the SQL++ runtime tests with samples collected for all datasets.
+ */
+@RunWith(Parameterized.class)
+public class SqlppAnalyzedExecutionTest {
+    protected static final String TEST_CONFIG_FILE_NAME = "src/test/resources/cc-analyze.conf";
+    private final String[] denyList = { "synonym: synonym-01", "ddl: analyze-dataset-1", "misc: dump_index",
+            "array-index: composite-index-queries", "filters: upsert", "column: ", "ddl: analyze-dataset-with-indexes",
+            "warnings: cardinality-hint-warning" };
+
+    @BeforeClass
+    public static void setUp() throws Exception {
+        final TestExecutor testExecutor = new AnalyzingTestExecutor();
+        LangExecutionUtil.setUp(TEST_CONFIG_FILE_NAME, testExecutor);
+        setNcEndpoints(testExecutor);
+    }
+
+    @AfterClass
+    public static void tearDown() throws Exception {
+        LangExecutionUtil.tearDown();
+    }
+
+    @Parameters(name = "SqlppAnalyzedExecutionTest {index}: {0}")
+    public static Collection<Object[]> tests() throws Exception {
+        return LangExecutionUtil.tests("only_sqlpp.xml", "testsuite_sqlpp.xml");
+    }
+
+    protected TestCaseContext tcCtx;
+
+    public SqlppAnalyzedExecutionTest(TestCaseContext tcCtx) {
+        this.tcCtx = tcCtx;
+    }
+
+    @Test
+    public void test() throws Exception {
+        if (!Arrays.stream(denyList).anyMatch(s -> tcCtx.toString().contains(s))) {
+            LangExecutionUtil.test(tcCtx);
+        }
+    }
+
+    private static void setNcEndpoints(TestExecutor testExecutor) {
+        final NodeControllerService[] ncs = ExecutionTestUtil.integrationUtil.ncs;
+        final Map<String, InetSocketAddress> ncEndPoints = new HashMap<>();
+        final String ip = InetAddress.getLoopbackAddress().getHostAddress();
+        for (NodeControllerService nc : ncs) {
+            final String nodeId = nc.getId();
+            final INcApplicationContext appCtx = (INcApplicationContext) nc.getApplicationContext();
+            int apiPort = appCtx.getExternalProperties().getNcApiPort();
+            ncEndPoints.put(nodeId, InetSocketAddress.createUnresolved(ip, apiPort));
+        }
+        testExecutor.setNcEndPoints(ncEndPoints);
+    }
+}
diff --git a/asterixdb/asterix-app/src/test/resources/cc-analyze.conf b/asterixdb/asterix-app/src/test/resources/cc-analyze.conf
new file mode 100644
index 0000000..58bba25
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/cc-analyze.conf
@@ -0,0 +1,56 @@
+; 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.
+
+[nc/asterix_nc1]
+txn.log.dir=target/tmp/asterix_nc1/txnlog
+core.dump.dir=target/tmp/asterix_nc1/coredump
+iodevices=target/tmp/asterix_nc1/iodevice1,
+iodevices=../asterix-server/target/tmp/asterix_nc1/iodevice2
+nc.api.port=19004
+#jvm.args=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5006
+
+[nc/asterix_nc2]
+ncservice.port=9091
+txn.log.dir=target/tmp/asterix_nc2/txnlog
+core.dump.dir=target/tmp/asterix_nc2/coredump
+iodevices=target/tmp/asterix_nc2/iodevice1,../asterix-server/target/tmp/asterix_nc2/iodevice2
+nc.api.port=19005
+#jvm.args=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5007
+
+[nc]
+credential.file=src/test/resources/security/passwd
+python.cmd.autolocate=true
+python.env=FOO=BAR=BAZ,BAR=BAZ
+address=127.0.0.1
+command=asterixnc
+app.class=org.apache.asterix.hyracks.bootstrap.NCApplication
+jvm.args=-Xmx4096m -Dnode.Resolver="org.apache.asterix.external.util.IdentitiyResolverFactory"
+storage.buffercache.pagesize=32KB
+storage.buffercache.size=128MB
+storage.memorycomponent.globalbudget=512MB
+
+[cc]
+address = 127.0.0.1
+app.class=org.apache.asterix.hyracks.bootstrap.CCApplication
+heartbeat.period=2000
+heartbeat.max.misses=25
+credential.file=src/test/resources/security/passwd
+
+[common]
+log.dir = logs/
+log.level = INFO
+compiler.groupmemory=64MB
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/fulltext/stopwords-full-text-filter-1/stopwords-full-text-filter-1.7.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/fulltext/stopwords-full-text-filter-1/stopwords-full-text-filter-1.7.query.sqlpp
index f770fee..392c48f 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/fulltext/stopwords-full-text-filter-1/stopwords-full-text-filter-1.7.query.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/fulltext/stopwords-full-text-filter-1/stopwords-full-text-filter-1.7.query.sqlpp
@@ -17,4 +17,6 @@
  * under the License.
  */
 
-SELECT Value v from Metadata.`Index` v WHERE v.DataverseName = "MyDataVerse" ORDER BY v. IndexName;
\ No newline at end of file
+FROM Metadata.`Index` v WHERE v.DataverseName = "MyDataVerse"
+SELECT v.* EXCLUDE SampleSeed
+ORDER BY v.IndexName;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/aggregate-sql/count_dataset/count_dataset.1.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/aggregate-sql/count_dataset/count_dataset.1.plan
new file mode 100644
index 0000000..f15e0d9
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/aggregate-sql/count_dataset/count_dataset.1.plan
@@ -0,0 +1,30 @@
+distribute result [$$26] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    aggregate [$$26] <- [agg-sql-sum($$29)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- AGGREGATE  |UNPARTITIONED|
+      aggregate [$$29] <- [agg-sql-count(1)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      -- AGGREGATE  |PARTITIONED|
+        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+        -- SORT_MERGE_EXCHANGE [$$27(ASC) ]  |PARTITIONED|
+          order (ASC, $$27) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- STABLE_SORT [$$27(ASC)]  |PARTITIONED|
+            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$27]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                select (and(ge($$25, 1), le($$25, 10))) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- STREAM_SELECT  |PARTITIONED|
+                  project ([$$27, $$25]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    assign [$$25] <- [$$Tweet.getField(1)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- ASSIGN  |PARTITIONED|
+                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        data-scan []<-[$$27, $$Tweet] <- Twitter.Tweet [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- DATASOURCE_SCAN  |PARTITIONED|
+                          exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/aggregate/count_dataset/count_dataset.1.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/aggregate/count_dataset/count_dataset.1.plan
new file mode 100644
index 0000000..b3eb407
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/aggregate/count_dataset/count_dataset.1.plan
@@ -0,0 +1,30 @@
+distribute result [$$26] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    aggregate [$$26] <- [agg-sum($$29)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- AGGREGATE  |UNPARTITIONED|
+      aggregate [$$29] <- [agg-count(1)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      -- AGGREGATE  |PARTITIONED|
+        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+        -- SORT_MERGE_EXCHANGE [$$27(ASC) ]  |PARTITIONED|
+          order (ASC, $$27) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- STABLE_SORT [$$27(ASC)]  |PARTITIONED|
+            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$27]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                select (and(ge($$25, 1), le($$25, 10))) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- STREAM_SELECT  |PARTITIONED|
+                  project ([$$27, $$25]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    assign [$$25] <- [$$Tweet.getField(1)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- ASSIGN  |PARTITIONED|
+                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        data-scan []<-[$$27, $$Tweet] <- Twitter.Tweet [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- DATASOURCE_SCAN  |PARTITIONED|
+                          exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/ddl/index-cast-null/index-cast-null.030.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/ddl/index-cast-null/index-cast-null.030.adm
new file mode 100644
index 0000000..8e7de54
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/ddl/index-cast-null/index-cast-null.030.adm
@@ -0,0 +1,8 @@
+{ "IndexName": "ds3_o_idx_f_d", "Cast": { "Default": null } }
+{ "IndexName": "ds3_o_idx_f_d_fmt", "Cast": { "Default": null, "DataFormat": [ null, "MM/DD/YYYY", null ] } }
+{ "IndexName": "ds3_o_idx_f_dt", "Cast": { "Default": null } }
+{ "IndexName": "ds3_o_idx_f_dt_fmt", "Cast": { "Default": null, "DataFormat": [ "MM/DD/YYYY hh:mm:ss.nnna", null, null ] } }
+{ "IndexName": "ds3_o_idx_f_t", "Cast": { "Default": null } }
+{ "IndexName": "ds3_o_idx_f_t_fmt", "Cast": { "Default": null, "DataFormat": [ null, null, "hh:mm:ss.nnna" ] } }
+{ "IndexName": "ds3_o_idx_invalid_fmt", "Cast": { "Default": null, "DataFormat": [ null, "invalid_format", null ] } }
+{ "IndexName": "sample_idx_1_ds3" }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/ddl/index-cast-null/index-cast-null.031.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/ddl/index-cast-null/index-cast-null.031.adm
new file mode 100644
index 0000000..11fcbf6b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/ddl/index-cast-null/index-cast-null.031.adm
@@ -0,0 +1,8 @@
+{ "IndexName": "ds4_o_idx_f_d", "Cast": { "Default": null } }
+{ "IndexName": "ds4_o_idx_f_d_fmt", "Cast": { "Default": null, "DataFormat": [ null, "MM/DD/YYYY", null ] } }
+{ "IndexName": "ds4_o_idx_f_dt", "Cast": { "Default": null } }
+{ "IndexName": "ds4_o_idx_f_dt_fmt", "Cast": { "Default": null, "DataFormat": [ "MM/DD/YYYY hh:mm:ss.nnna", null, null ] } }
+{ "IndexName": "ds4_o_idx_f_t", "Cast": { "Default": null } }
+{ "IndexName": "ds4_o_idx_f_t_fmt", "Cast": { "Default": null, "DataFormat": [ null, null, "hh:mm:ss.nnna" ] } }
+{ "IndexName": "ds4_o_idx_invalid_fmt", "Cast": { "Default": null, "DataFormat": [ null, "invalid_format", null ] } }
+{ "IndexName": "sample_idx_1_ds4" }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/ddl/index-cast-null/index-cast-null.050.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/ddl/index-cast-null/index-cast-null.050.adm
new file mode 100644
index 0000000..0a328d4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/ddl/index-cast-null/index-cast-null.050.adm
@@ -0,0 +1,19 @@
+{ "IndexName": "idx1", "SearchKey": [ [ "s_f1" ] ] }
+{ "IndexName": "idx10", "SearchKey": [ [ "s_f1" ] ], "SearchKeyType": [ "string" ], "Cast": { "Default": null } }
+{ "IndexName": "idx11", "SearchKey": [ [ "s_f1" ] ], "SearchKeyType": [ "int64" ], "Cast": { "Default": null } }
+{ "IndexName": "idx12", "SearchKey": [ [ "s_f2" ] ] }
+{ "IndexName": "idx13", "SearchKey": [ [ "s_f2" ] ], "SearchKeyType": [ "string" ], "Cast": { "Default": null } }
+{ "IndexName": "idx14", "SearchKey": [ [ "s_f2" ] ], "SearchKeyType": [ "int64" ], "Cast": { "Default": null } }
+{ "IndexName": "idx15", "SearchKey": [ [ "i_f" ] ], "SearchKeyType": [ "int64" ], "Cast": { "Default": null } }
+{ "IndexName": "idx16", "SearchKey": [ [ "i_f" ] ], "SearchKeyType": [ "string" ], "Cast": { "Default": null } }
+{ "IndexName": "idx2", "SearchKey": [ [ "s_f1" ] ], "SearchKeyType": [ "string" ], "Cast": { "Default": null } }
+{ "IndexName": "idx3", "SearchKey": [ [ "s_f1" ] ], "SearchKeyType": [ "int64" ], "Cast": { "Default": null } }
+{ "IndexName": "idx4", "SearchKey": [ [ "s_f2" ] ] }
+{ "IndexName": "idx5", "SearchKey": [ [ "s_f2" ] ], "SearchKeyType": [ "string" ], "Cast": { "Default": null } }
+{ "IndexName": "idx6", "SearchKey": [ [ "s_f2" ] ], "SearchKeyType": [ "int64" ], "Cast": { "Default": null } }
+{ "IndexName": "idx7", "SearchKey": [ [ "i_f" ] ], "SearchKeyType": [ "int64" ], "Cast": { "Default": null } }
+{ "IndexName": "idx8", "SearchKey": [ [ "i_f" ] ], "SearchKeyType": [ "string" ], "Cast": { "Default": null } }
+{ "IndexName": "idx9", "SearchKey": [ [ "s_f1" ] ] }
+{ "IndexName": "idx_exc1", "SearchKey": [ [ "s_f2" ] ], "SearchKeyType": [ "int64" ], "Cast": { "Default": null } }
+{ "IndexName": "idx_exc2", "SearchKey": [ [ "s_f2" ] ], "SearchKeyType": [ "int64" ], "Cast": { "Default": null } }
+{ "IndexName": "sample_idx_1_ds5", "SearchKey": [ [ "id" ] ] }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/dml/index-unknown-key/index-unknown-key.11.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/dml/index-unknown-key/index-unknown-key.11.adm
new file mode 100644
index 0000000..98cc7b9
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/dml/index-unknown-key/index-unknown-key.11.adm
@@ -0,0 +1,8 @@
+{ "DataverseName": "test", "IndexName": "ds1" }
+{ "DataverseName": "test", "IndexName": "exclude_unknown_idx1", "ExcludeUnknownKey": true }
+{ "DataverseName": "test", "IndexName": "exclude_unknown_idx2", "ExcludeUnknownKey": true }
+{ "DataverseName": "test", "IndexName": "idx1", "ExcludeUnknownKey": false }
+{ "DataverseName": "test", "IndexName": "idx2", "ExcludeUnknownKey": false }
+{ "DataverseName": "test", "IndexName": "include_unknown_idx1", "ExcludeUnknownKey": false }
+{ "DataverseName": "test", "IndexName": "include_unknown_idx2", "ExcludeUnknownKey": false }
+{ "DataverseName": "test", "IndexName": "sample_idx_1_ds1" }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/explain/explain_field_access/explain_field_access.1.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/explain/explain_field_access/explain_field_access.1.plan
new file mode 100644
index 0000000..fb83a56
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/explain/explain_field_access/explain_field_access.1.plan
@@ -0,0 +1,42 @@
+distribute result [$$50] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$50]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$50] <- [{"deptId": $#1, "star_cost": $$53}] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          group by ([$#1 := $$58]) decor ([]) {
+                    aggregate [$$53] <- [agg-global-sql-sum($$57)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- AGGREGATE  |LOCAL|
+                      nested tuple source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- NESTED_TUPLE_SOURCE  |LOCAL|
+                 } [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- SORT_GROUP_BY[$$58]  |PARTITIONED|
+            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- HASH_PARTITION_EXCHANGE [$$58]  |PARTITIONED|
+              group by ([$$58 := $$51]) decor ([]) {
+                        aggregate [$$57] <- [agg-local-sql-sum($$48)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- AGGREGATE  |LOCAL|
+                          nested tuple source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- NESTED_TUPLE_SOURCE  |LOCAL|
+                     } [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- SORT_GROUP_BY[$$51]  |PARTITIONED|
+                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  project ([$$48, $$51]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    assign [$$51, $$48] <- [substring($$e.getField("dept").getField("department_id"), 0), $$e.getField("salary")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- ASSIGN  |PARTITIONED|
+                      project ([$$e]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          data-scan []<-[$$52, $$e] <- gby.Employee [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- DATASOURCE_SCAN  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/explain/explain_field_access_closed/explain_field_access_closed.1.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/explain/explain_field_access_closed/explain_field_access_closed.1.plan
new file mode 100644
index 0000000..bd96df9
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/explain/explain_field_access_closed/explain_field_access_closed.1.plan
@@ -0,0 +1,42 @@
+distribute result [$$49] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$49]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$49] <- [{"deptId": $#1, "star_cost": $$52}] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          group by ([$#1 := $$56]) decor ([]) {
+                    aggregate [$$52] <- [agg-global-sql-sum($$55)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- AGGREGATE  |LOCAL|
+                      nested tuple source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- NESTED_TUPLE_SOURCE  |LOCAL|
+                 } [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- SORT_GROUP_BY[$$56]  |PARTITIONED|
+            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- HASH_PARTITION_EXCHANGE [$$56]  |PARTITIONED|
+              group by ([$$56 := $$50]) decor ([]) {
+                        aggregate [$$55] <- [agg-local-sql-sum($$47)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- AGGREGATE  |LOCAL|
+                          nested tuple source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- NESTED_TUPLE_SOURCE  |LOCAL|
+                     } [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- SORT_GROUP_BY[$$50]  |PARTITIONED|
+                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  project ([$$47, $$50]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    assign [$$50, $$47] <- [substring($$e.getField(1), 0), $$e.getField(2)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- ASSIGN  |PARTITIONED|
+                      project ([$$e]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          data-scan []<-[$$51, $$e] <- gby.Employee [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- DATASOURCE_SCAN  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/fulltext/stopwords-full-text-filter-1/stopwords-full-text-filter-1.7.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/fulltext/stopwords-full-text-filter-1/stopwords-full-text-filter-1.7.adm
new file mode 100644
index 0000000..16635ef
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/fulltext/stopwords-full-text-filter-1/stopwords-full-text-filter-1.7.adm
@@ -0,0 +1,5 @@
+{ "DataverseName": "MyDataVerse", "DatasetName": "MyMessageDataset", "IndexName": "MyMessageDataset", "IndexStructure": "BTREE", "SearchKey": [ [ "myMessageId" ] ], "IsPrimary": true, "Timestamp": "Wed Mar 08 15:49:09 PST 2023", "PendingOp": 0 }
+{ "DataverseName": "MyDataVerse", "DatasetName": "MyMessageDataset", "IndexName": "message_ft_index_0", "IndexStructure": "SINGLE_PARTITION_WORD_INVIX", "SearchKey": [ [ "myMessageBody" ] ], "IsPrimary": false, "Timestamp": "Wed Mar 08 15:49:09 PST 2023", "PendingOp": 0 }
+{ "DataverseName": "MyDataVerse", "DatasetName": "MyMessageDataset", "IndexName": "message_ft_index_1", "IndexStructure": "SINGLE_PARTITION_WORD_INVIX", "SearchKey": [ [ "myMessageBody" ] ], "IsPrimary": false, "Timestamp": "Wed Mar 08 15:49:10 PST 2023", "PendingOp": 0, "FullTextConfig": "my_first_stopword_config" }
+{ "DataverseName": "MyDataVerse", "DatasetName": "MyMessageDataset", "IndexName": "message_ft_index_2", "IndexStructure": "SINGLE_PARTITION_WORD_INVIX", "SearchKey": [ [ "myMessageBody" ] ], "IsPrimary": false, "Timestamp": "Wed Mar 08 15:49:10 PST 2023", "PendingOp": 0, "FullTextConfig": "my_second_stopword_config" }
+{ "DataverseName": "MyDataVerse", "DatasetName": "MyMessageDataset", "IndexName": "sample_idx_2_MyMessageDataset", "IndexStructure": "SAMPLE", "SearchKey": [ [ "myMessageId" ] ], "IsPrimary": false, "Timestamp": "Wed Mar 08 15:49:09 PST 2023", "PendingOp": 0, "SampleCardinalityTarget": 1063, "SourceCardinality": 2, "SourceAvgItemSize": 62 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.04.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.04.plan
new file mode 100644
index 0000000..25663a0
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.04.plan
@@ -0,0 +1,66 @@
+distribute result [$$51] [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$51]) [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$51] <- [{"n_nationkey": $$58, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+        -- SORT_MERGE_EXCHANGE [$$58(ASC), $$56(ASC), $$55(ASC) ]  |PARTITIONED|
+          order (ASC, $$58) (ASC, $$56) (ASC, $$55) [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+          -- STABLE_SORT [$$58(ASC), $$56(ASC), $$55(ASC)]  |PARTITIONED|
+            exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$58, $$56, $$55]) [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  join (and(eq($$55, $$58), eq($$56, $$66))) [cardinality: 15.0, op-cost: 175.0, total-cost: 560.0]
+                  -- HYBRID_HASH_JOIN [$$55, $$66][$$58, $$56]  |PARTITIONED|
+                    exchange [cardinality: 150.0, op-cost: 150.0, total-cost: 300.0]
+                    -- HASH_PARTITION_EXCHANGE [$$55, $$66]  |PARTITIONED|
+                      assign [$$66] <- [$$55] [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                      -- ASSIGN  |PARTITIONED|
+                        project ([$$55]) [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          assign [$$55] <- [$$c.getField(3)] [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                          -- ASSIGN  |PARTITIONED|
+                            project ([$$c]) [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              exchange [cardinality: 150.0, op-cost: 150.0, total-cost: 300.0]
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                data-scan []<-[$$60, $$c] <- tpch.Customer [cardinality: 150.0, op-cost: 150.0, total-cost: 150.0]
+                                -- DATASOURCE_SCAN  |PARTITIONED|
+                                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                    exchange [cardinality: 25.0, op-cost: 25.0, total-cost: 85.0]
+                    -- HASH_PARTITION_EXCHANGE [$$58, $$56]  |PARTITIONED|
+                      project ([$$56, $$58]) [cardinality: 25.0, op-cost: 0.0, total-cost: 60.0]
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        exchange [cardinality: 25.0, op-cost: 25.0, total-cost: 85.0]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          unnest-map [$$58, $$n] <- index-search("Nation", 0, "tpch", "Nation", true, true, 1, $$56, 1, $$56, true, true, true) [cardinality: 25.0, op-cost: 10.0, total-cost: 60.0]
+                          -- BTREE_SEARCH  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              order (ASC, $$56) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- STABLE_SORT [$$56(ASC)]  |PARTITIONED|
+                                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- HASH_PARTITION_EXCHANGE [$$56]  |PARTITIONED|
+                                  project ([$$56]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    assign [$$56] <- [$$s.getField(3)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- ASSIGN  |PARTITIONED|
+                                      project ([$$s]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          data-scan []<-[$$59, $$s] <- tpch.Supplier [cardinality: 10.0, op-cost: 10.0, total-cost: 10.0]
+                                          -- DATASOURCE_SCAN  |PARTITIONED|
+                                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.06.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.06.plan
new file mode 100644
index 0000000..486174f
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.06.plan
@@ -0,0 +1,50 @@
+distribute result [$$36] [cardinality: 6005.0, op-cost: 0.0, total-cost: 22515.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 22515.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$36]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 22515.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$36] <- [{"o_orderkey": $$43, "l_orderkey": $$44, "l_suppkey": $$42}] [cardinality: 6005.0, op-cost: 0.0, total-cost: 22515.0]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 22515.0]
+        -- SORT_MERGE_EXCHANGE [$$43(ASC), $$44(ASC), $$42(ASC) ]  |PARTITIONED|
+          order (ASC, $$43) (ASC, $$44) (ASC, $$42) [cardinality: 6005.0, op-cost: 0.0, total-cost: 22515.0]
+          -- STABLE_SORT [$$43(ASC), $$44(ASC), $$42(ASC)]  |PARTITIONED|
+            exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 22515.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$43, $$44, $$42]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 22515.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 22515.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  join (and(eq($$43, $$44), eq($$49, $$42))) [cardinality: 6005.0, op-cost: 7505.0, total-cost: 22515.0]
+                  -- HYBRID_HASH_JOIN [$$44, $$42][$$43, $$49]  |PARTITIONED|
+                    exchange [cardinality: 6005.0, op-cost: 6005.0, total-cost: 12010.0]
+                    -- HASH_PARTITION_EXCHANGE [$$44, $$42]  |PARTITIONED|
+                      project ([$$44, $$42]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        assign [$$42] <- [$$l.getField(2)] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+                        -- ASSIGN  |PARTITIONED|
+                          project ([$$44, $$l]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            exchange [cardinality: 6005.0, op-cost: 6005.0, total-cost: 12010.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              data-scan []<-[$$44, $$45, $$l] <- tpch.LineItem [cardinality: 6005.0, op-cost: 6005.0, total-cost: 6005.0]
+                              -- DATASOURCE_SCAN  |PARTITIONED|
+                                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                    exchange [cardinality: 1500.0, op-cost: 1500.0, total-cost: 3000.0]
+                    -- HASH_PARTITION_EXCHANGE [$$43, $$49]  |PARTITIONED|
+                      assign [$$49] <- [$$43] [cardinality: 1500.0, op-cost: 0.0, total-cost: 1500.0]
+                      -- ASSIGN  |PARTITIONED|
+                        project ([$$43]) [cardinality: 1500.0, op-cost: 0.0, total-cost: 1500.0]
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          exchange [cardinality: 1500.0, op-cost: 1500.0, total-cost: 3000.0]
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            data-scan []<-[$$43, $$o] <- tpch.Orders [cardinality: 1500.0, op-cost: 1500.0, total-cost: 1500.0]
+                            -- DATASOURCE_SCAN  |PARTITIONED|
+                              exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.10.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.10.plan
new file mode 100644
index 0000000..2ace4af
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.10.plan
@@ -0,0 +1,66 @@
+distribute result [$$51] [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$51]) [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$51] <- [{"n_nationkey": $$58, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+        -- SORT_MERGE_EXCHANGE [$$58(ASC), $$56(ASC), $$55(ASC) ]  |PARTITIONED|
+          order (ASC, $$58) (ASC, $$56) (ASC, $$55) [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+          -- STABLE_SORT [$$58(ASC), $$56(ASC), $$55(ASC)]  |PARTITIONED|
+            exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$58, $$56, $$55]) [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  join (and(eq($$55, $$58), eq($$56, $$66))) [cardinality: 15.0, op-cost: 225.0, total-cost: 500.0]
+                  -- HYBRID_HASH_JOIN [$$55, $$66][$$58, $$56]  |PARTITIONED|
+                    exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                    -- RANDOM_PARTITION_EXCHANGE  |PARTITIONED|
+                      assign [$$66] <- [$$55] [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                      -- ASSIGN  |PARTITIONED|
+                        project ([$$55]) [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          assign [$$55] <- [$$c.getField(3)] [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                          -- ASSIGN  |PARTITIONED|
+                            project ([$$c]) [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                data-scan []<-[$$60, $$c] <- tpch.Customer [cardinality: 150.0, op-cost: 150.0, total-cost: 150.0]
+                                -- DATASOURCE_SCAN  |PARTITIONED|
+                                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                    exchange [cardinality: 25.0, op-cost: 75.0, total-cost: 125.0]
+                    -- BROADCAST_EXCHANGE  |PARTITIONED|
+                      project ([$$56, $$58]) [cardinality: 25.0, op-cost: 0.0, total-cost: 50.0]
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        exchange [cardinality: 25.0, op-cost: 75.0, total-cost: 125.0]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          unnest-map [$$58, $$n] <- index-search("Nation", 0, "tpch", "Nation", true, true, 1, $$56, 1, $$56, true, true, true) [cardinality: 25.0, op-cost: 10.0, total-cost: 50.0]
+                          -- BTREE_SEARCH  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              order (ASC, $$56) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- STABLE_SORT [$$56(ASC)]  |PARTITIONED|
+                                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- HASH_PARTITION_EXCHANGE [$$56]  |PARTITIONED|
+                                  project ([$$56]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    assign [$$56] <- [$$s.getField(3)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- ASSIGN  |PARTITIONED|
+                                      project ([$$s]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          data-scan []<-[$$59, $$s] <- tpch.Supplier [cardinality: 10.0, op-cost: 10.0, total-cost: 10.0]
+                                          -- DATASOURCE_SCAN  |PARTITIONED|
+                                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.12.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.12.plan
new file mode 100644
index 0000000..354a94b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.12.plan
@@ -0,0 +1,64 @@
+distribute result [$$51] [cardinality: 15.0, op-cost: 0.0, total-cost: 605.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 605.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$51]) [cardinality: 15.0, op-cost: 0.0, total-cost: 605.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$51] <- [{"n_nationkey": $$58, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 15.0, op-cost: 0.0, total-cost: 605.0]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 605.0]
+        -- SORT_MERGE_EXCHANGE [$$58(ASC), $$56(ASC), $$55(ASC) ]  |PARTITIONED|
+          order (ASC, $$58) (ASC, $$56) (ASC, $$55) [cardinality: 15.0, op-cost: 0.0, total-cost: 605.0]
+          -- STABLE_SORT [$$58(ASC), $$56(ASC), $$55(ASC)]  |PARTITIONED|
+            exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 605.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              join (eq($$55, $$58)) [cardinality: 15.0, op-cost: 175.0, total-cost: 605.0]
+              -- HYBRID_HASH_JOIN [$$55][$$58]  |PARTITIONED|
+                exchange [cardinality: 150.0, op-cost: 150.0, total-cost: 300.0]
+                -- HASH_PARTITION_EXCHANGE [$$55]  |PARTITIONED|
+                  project ([$$55]) [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    assign [$$55] <- [$$c.getField(3)] [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                    -- ASSIGN  |PARTITIONED|
+                      project ([$$c]) [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        exchange [cardinality: 150.0, op-cost: 150.0, total-cost: 300.0]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          data-scan []<-[$$60, $$c] <- tpch.Customer [cardinality: 150.0, op-cost: 150.0, total-cost: 150.0]
+                          -- DATASOURCE_SCAN  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                exchange [cardinality: 25.0, op-cost: 25.0, total-cost: 130.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  join (eq($$56, $$58)) [cardinality: 25.0, op-cost: 35.0, total-cost: 105.0]
+                  -- HYBRID_HASH_JOIN [$$58][$$56]  |PARTITIONED|
+                    exchange [cardinality: 25.0, op-cost: 25.0, total-cost: 50.0]
+                    -- HASH_PARTITION_EXCHANGE [$$58]  |PARTITIONED|
+                      project ([$$58]) [cardinality: 25.0, op-cost: 0.0, total-cost: 25.0]
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        exchange [cardinality: 25.0, op-cost: 25.0, total-cost: 50.0]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          data-scan []<-[$$58, $$n] <- tpch.Nation [cardinality: 25.0, op-cost: 25.0, total-cost: 25.0]
+                          -- DATASOURCE_SCAN  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                    exchange [cardinality: 10.0, op-cost: 10.0, total-cost: 20.0]
+                    -- HASH_PARTITION_EXCHANGE [$$56]  |PARTITIONED|
+                      project ([$$56]) [cardinality: 10.0, op-cost: 0.0, total-cost: 10.0]
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        assign [$$56] <- [$$s.getField(3)] [cardinality: 10.0, op-cost: 0.0, total-cost: 10.0]
+                        -- ASSIGN  |PARTITIONED|
+                          project ([$$s]) [cardinality: 10.0, op-cost: 0.0, total-cost: 10.0]
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            exchange [cardinality: 10.0, op-cost: 10.0, total-cost: 20.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              data-scan []<-[$$59, $$s] <- tpch.Supplier [cardinality: 10.0, op-cost: 10.0, total-cost: 10.0]
+                              -- DATASOURCE_SCAN  |PARTITIONED|
+                                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.14.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.14.plan
new file mode 100644
index 0000000..66124a1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.14.plan
@@ -0,0 +1,66 @@
+distribute result [$$51] [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$51]) [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$51] <- [{"n_nationkey": $$59, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+        -- SORT_MERGE_EXCHANGE [$$59(ASC), $$56(ASC), $$55(ASC) ]  |PARTITIONED|
+          order (ASC, $$59) (ASC, $$56) (ASC, $$55) [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+          -- STABLE_SORT [$$59(ASC), $$56(ASC), $$55(ASC)]  |PARTITIONED|
+            exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$59, $$56, $$55]) [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  join (and(eq($$55, $$59), eq($$56, $$66))) [cardinality: 15.0, op-cost: 175.0, total-cost: 560.0]
+                  -- HYBRID_HASH_JOIN [$$55, $$66][$$59, $$56]  |PARTITIONED|
+                    exchange [cardinality: 150.0, op-cost: 150.0, total-cost: 300.0]
+                    -- HASH_PARTITION_EXCHANGE [$$55, $$66]  |PARTITIONED|
+                      assign [$$66] <- [$$55] [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                      -- ASSIGN  |PARTITIONED|
+                        project ([$$55]) [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          assign [$$55] <- [$$c.getField(3)] [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                          -- ASSIGN  |PARTITIONED|
+                            project ([$$c]) [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              exchange [cardinality: 150.0, op-cost: 150.0, total-cost: 300.0]
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                data-scan []<-[$$60, $$c] <- tpch.Customer [cardinality: 150.0, op-cost: 150.0, total-cost: 150.0]
+                                -- DATASOURCE_SCAN  |PARTITIONED|
+                                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                    exchange [cardinality: 25.0, op-cost: 25.0, total-cost: 85.0]
+                    -- HASH_PARTITION_EXCHANGE [$$59, $$56]  |PARTITIONED|
+                      project ([$$56, $$59]) [cardinality: 25.0, op-cost: 0.0, total-cost: 60.0]
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        exchange [cardinality: 25.0, op-cost: 25.0, total-cost: 85.0]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          unnest-map [$$59, $$n] <- index-search("Nation", 0, "tpch", "Nation", true, true, 1, $$56, 1, $$56, true, true, true) [cardinality: 25.0, op-cost: 10.0, total-cost: 60.0]
+                          -- BTREE_SEARCH  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              order (ASC, $$56) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- STABLE_SORT [$$56(ASC)]  |PARTITIONED|
+                                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- HASH_PARTITION_EXCHANGE [$$56]  |PARTITIONED|
+                                  project ([$$56]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    assign [$$56] <- [$$s.getField(3)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- ASSIGN  |PARTITIONED|
+                                      project ([$$s]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          data-scan []<-[$$58, $$s] <- tpch.Supplier [cardinality: 10.0, op-cost: 10.0, total-cost: 10.0]
+                                          -- DATASOURCE_SCAN  |PARTITIONED|
+                                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.16.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.16.plan
new file mode 100644
index 0000000..0b31760
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.16.plan
@@ -0,0 +1,66 @@
+distribute result [$$51] [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$51]) [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$51] <- [{"n_nationkey": $$59, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+        -- SORT_MERGE_EXCHANGE [$$59(ASC), $$56(ASC), $$55(ASC) ]  |PARTITIONED|
+          order (ASC, $$59) (ASC, $$56) (ASC, $$55) [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+          -- STABLE_SORT [$$59(ASC), $$56(ASC), $$55(ASC)]  |PARTITIONED|
+            exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$59, $$56, $$55]) [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  join (and(eq($$55, $$59), eq($$56, $$66))) [cardinality: 15.0, op-cost: 225.0, total-cost: 500.0]
+                  -- HYBRID_HASH_JOIN [$$55, $$66][$$59, $$56]  |PARTITIONED|
+                    exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                    -- RANDOM_PARTITION_EXCHANGE  |PARTITIONED|
+                      assign [$$66] <- [$$55] [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                      -- ASSIGN  |PARTITIONED|
+                        project ([$$55]) [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          assign [$$55] <- [$$c.getField(3)] [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                          -- ASSIGN  |PARTITIONED|
+                            project ([$$c]) [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                data-scan []<-[$$60, $$c] <- tpch.Customer [cardinality: 150.0, op-cost: 150.0, total-cost: 150.0]
+                                -- DATASOURCE_SCAN  |PARTITIONED|
+                                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                    exchange [cardinality: 25.0, op-cost: 75.0, total-cost: 125.0]
+                    -- BROADCAST_EXCHANGE  |PARTITIONED|
+                      project ([$$56, $$59]) [cardinality: 25.0, op-cost: 0.0, total-cost: 50.0]
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        exchange [cardinality: 25.0, op-cost: 75.0, total-cost: 125.0]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          unnest-map [$$59, $$n] <- index-search("Nation", 0, "tpch", "Nation", true, true, 1, $$56, 1, $$56, true, true, true) [cardinality: 25.0, op-cost: 10.0, total-cost: 50.0]
+                          -- BTREE_SEARCH  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              order (ASC, $$56) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- STABLE_SORT [$$56(ASC)]  |PARTITIONED|
+                                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- HASH_PARTITION_EXCHANGE [$$56]  |PARTITIONED|
+                                  project ([$$56]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    assign [$$56] <- [$$s.getField(3)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- ASSIGN  |PARTITIONED|
+                                      project ([$$s]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          data-scan []<-[$$58, $$s] <- tpch.Supplier [cardinality: 10.0, op-cost: 10.0, total-cost: 10.0]
+                                          -- DATASOURCE_SCAN  |PARTITIONED|
+                                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/offset_without_limit/offset_without_limit.6.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/offset_without_limit/offset_without_limit.6.plan
new file mode 100644
index 0000000..dd287ae
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/offset_without_limit/offset_without_limit.6.plan
@@ -0,0 +1,22 @@
+distribute result [$$16] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit offset 98 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      project ([$$16]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+      -- STREAM_PROJECT  |PARTITIONED|
+        assign [$$16] <- [{"id": $$18, "dblpid": $$paper.getField(1)}] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+        -- ASSIGN  |PARTITIONED|
+          exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+          -- SORT_MERGE_EXCHANGE [$$18(ASC) ]  |PARTITIONED|
+            order (ASC, $$18) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+            -- STABLE_SORT [$$18(ASC)]  |PARTITIONED|
+              exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                data-scan []<-[$$18, $$paper] <- test.DBLP1 [cardinality: 100.0, op-cost: 100.0, total-cost: 100.0]
+                -- DATASOURCE_SCAN  |PARTITIONED|
+                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-external-scan-select/push-limit-to-external-scan-select.2.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-external-scan-select/push-limit-to-external-scan-select.2.plan
new file mode 100644
index 0000000..11d4b1a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-external-scan-select/push-limit-to-external-scan-select.2.plan
@@ -0,0 +1,22 @@
+distribute result [$$17] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 5 [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+        limit 5 [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+        -- STREAM_LIMIT  |PARTITIONED|
+          project ([$$17]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- STREAM_PROJECT  |PARTITIONED|
+            assign [$$17] <- [$$t.getField(0)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- ASSIGN  |PARTITIONED|
+              exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                data-scan []<-[$$t] <- test.ds1 condition (gt($$t.getField(0), 2)) limit 5 [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- DATASOURCE_SCAN  |PARTITIONED|
+                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-external-scan/push-limit-to-external-scan.2.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-external-scan/push-limit-to-external-scan.2.plan
new file mode 100644
index 0000000..55b2c18
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-external-scan/push-limit-to-external-scan.2.plan
@@ -0,0 +1,22 @@
+distribute result [$$14] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 5 [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+        project ([$$14]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+        -- STREAM_PROJECT  |PARTITIONED|
+          assign [$$14] <- [$$t.getField(0)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- ASSIGN  |PARTITIONED|
+            limit 5 [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- STREAM_LIMIT  |PARTITIONED|
+              exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                data-scan []<-[$$t] <- test.ds1 limit 5 [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- DATASOURCE_SCAN  |PARTITIONED|
+                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.3.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.3.plan
new file mode 100644
index 0000000..38c5f61
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.3.plan
@@ -0,0 +1,20 @@
+distribute result [$$c] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 5 offset 5 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+      -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+        limit 10 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+        -- STREAM_LIMIT  |PARTITIONED|
+          project ([$$c]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+          -- STREAM_PROJECT  |PARTITIONED|
+            exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              data-scan []<-[$$18, $$19, $$c] <- test.LineItem condition (and(lt($$c.getField(2), 150), lt($$c.getField(5), 10000))) limit 10 [cardinality: 6005.0, op-cost: 6005.0, total-cost: 6005.0]
+              -- DATASOURCE_SCAN  |PARTITIONED|
+                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.5.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.5.plan
new file mode 100644
index 0000000..a9d2b4f
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.5.plan
@@ -0,0 +1,26 @@
+distribute result [$$20] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 5 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+      -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+        project ([$$20]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+        -- STREAM_PROJECT  |PARTITIONED|
+          assign [$$20] <- [{"shipdate": substring($$c.getField(10), 0, 4), "suppkey": gt($$21, 0)}] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+          -- ASSIGN  |PARTITIONED|
+            limit 5 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+            -- STREAM_LIMIT  |PARTITIONED|
+              assign [$$21] <- [$$c.getField(2)] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+              -- ASSIGN  |PARTITIONED|
+                project ([$$c]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+                -- STREAM_PROJECT  |PARTITIONED|
+                  exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    data-scan []<-[$$22, $$23, $$c] <- test.LineItem condition (lt($$c.getField(2), 150)) limit 5 [cardinality: 6005.0, op-cost: 6005.0, total-cost: 6005.0]
+                    -- DATASOURCE_SCAN  |PARTITIONED|
+                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.3.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.3.plan
new file mode 100644
index 0000000..f5fb51e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.3.plan
@@ -0,0 +1,20 @@
+distribute result [$$c] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 5 offset 5 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+      -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+        limit 10 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+        -- STREAM_LIMIT  |PARTITIONED|
+          project ([$$c]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+          -- STREAM_PROJECT  |PARTITIONED|
+            exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              data-scan []<-[$$15, $$16, $$c] <- test.LineItem condition (lt($$c.getField(2), 150)) limit 10 [cardinality: 6005.0, op-cost: 6005.0, total-cost: 6005.0]
+              -- DATASOURCE_SCAN  |PARTITIONED|
+                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.5.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.5.plan
new file mode 100644
index 0000000..7aa0db8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.5.plan
@@ -0,0 +1,20 @@
+distribute result [$$c] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 5 offset 5 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+      -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+        limit 10 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+        -- STREAM_LIMIT  |PARTITIONED|
+          project ([$$c]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+          -- STREAM_PROJECT  |PARTITIONED|
+            exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              data-scan []<-[$$17, $$18, $$c] <- test.LineItem condition (lt($$c.getField(2), 150)) limit 10 [cardinality: 6005.0, op-cost: 6005.0, total-cost: 6005.0]
+              -- DATASOURCE_SCAN  |PARTITIONED|
+                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.3.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.3.plan
new file mode 100644
index 0000000..6427067
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.3.plan
@@ -0,0 +1,20 @@
+distribute result [$$paper] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 5 offset 5 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+      -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+        limit 10 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+        -- STREAM_LIMIT  |PARTITIONED|
+          project ([$$paper]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+          -- STREAM_PROJECT  |PARTITIONED|
+            exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              data-scan []<-[$$15, $$paper] <- test.DBLP1 condition (contains($$paper.getField(1), "kimL89")) limit 10 [cardinality: 100.0, op-cost: 100.0, total-cost: 100.0]
+              -- DATASOURCE_SCAN  |PARTITIONED|
+                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.5.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.5.plan
new file mode 100644
index 0000000..e175907
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.5.plan
@@ -0,0 +1,64 @@
+distribute result [$$37] [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 2 [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+      -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+        project ([$$37]) [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+        -- STREAM_PROJECT  |PARTITIONED|
+          assign [$$37] <- [{"dblpid": $$38}] [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+          -- ASSIGN  |PARTITIONED|
+            limit 2 [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+            -- STREAM_LIMIT  |PARTITIONED|
+              project ([$$38]) [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  join (eq($$38, $$41)) [cardinality: 100.0, op-cost: 200.0, total-cost: 600.0]
+                  -- HYBRID_HASH_JOIN [$$38][$$41]  |PARTITIONED|
+                    exchange [cardinality: 100.0, op-cost: 100.0, total-cost: 200.0]
+                    -- HASH_PARTITION_EXCHANGE [$$38]  |PARTITIONED|
+                      project ([$$38]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        assign [$$38] <- [$$d.getField(1)] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                        -- ASSIGN  |PARTITIONED|
+                          project ([$$d]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            exchange [cardinality: 100.0, op-cost: 100.0, total-cost: 200.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              data-scan []<-[$$39, $$d] <- test.DBLP1 [cardinality: 100.0, op-cost: 100.0, total-cost: 100.0]
+                              -- DATASOURCE_SCAN  |PARTITIONED|
+                                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                    exchange [cardinality: 100.0, op-cost: 100.0, total-cost: 200.0]
+                    -- HASH_PARTITION_EXCHANGE [$$41]  |PARTITIONED|
+                      project ([$$41]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                      -- STREAM_PROJECT  |UNPARTITIONED|
+                        assign [$$41] <- [get-item($$30, 0).getField(0).getField(1)] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                        -- ASSIGN  |UNPARTITIONED|
+                          aggregate [$$30] <- [listify($$29)] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                          -- AGGREGATE  |UNPARTITIONED|
+                            limit 1 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                            -- STREAM_LIMIT  |UNPARTITIONED|
+                              exchange [cardinality: 100.0, op-cost: 100.0, total-cost: 200.0]
+                              -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+                                project ([$$29]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                                -- STREAM_PROJECT  |PARTITIONED|
+                                  assign [$$29] <- [{"d": $$d}] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                                  -- ASSIGN  |PARTITIONED|
+                                    limit 1 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                                    -- STREAM_LIMIT  |PARTITIONED|
+                                      project ([$$d]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        exchange [cardinality: 100.0, op-cost: 100.0, total-cost: 200.0]
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          data-scan []<-[$$40, $$d] <- test.DBLP1 condition (ends-with($$d.getField(1), "Blakeley95")) limit 1 [cardinality: 100.0, op-cost: 100.0, total-cost: 100.0]
+                                          -- DATASOURCE_SCAN  |PARTITIONED|
+                                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.6.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.6.plan
new file mode 100644
index 0000000..9312e62
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.6.plan
@@ -0,0 +1,28 @@
+distribute result [$$19] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 1 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+      -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+        project ([$$19]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+        -- STREAM_PROJECT  |PARTITIONED|
+          assign [$$19] <- [{"$1": substring($$20, 0, 21)}] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+          -- ASSIGN  |PARTITIONED|
+            limit 1 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+            -- STREAM_LIMIT  |PARTITIONED|
+              project ([$$20]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                assign [$$20] <- [$$DBLP1.getField(1)] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                -- ASSIGN  |PARTITIONED|
+                  project ([$$DBLP1]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      data-scan []<-[$$21, $$DBLP1] <- test.DBLP1 condition (gt($$DBLP1.getField(1), "series")) limit 1 [cardinality: 100.0, op-cost: 100.0, total-cost: 100.0]
+                      -- DATASOURCE_SCAN  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.8.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.8.plan
new file mode 100644
index 0000000..190a98e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.8.plan
@@ -0,0 +1,28 @@
+distribute result [$$22] [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 2 [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      exchange [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+      -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+        limit 2 [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+        -- STREAM_LIMIT  |PARTITIONED|
+          project ([$$22]) [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+          -- STREAM_PROJECT  |PARTITIONED|
+            assign [$$22] <- [$$26.getField("lang")] [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+            -- ASSIGN  |PARTITIONED|
+              project ([$$26]) [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                assign [$$26] <- [$$t.getField("user")] [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+                -- ASSIGN  |PARTITIONED|
+                  project ([$$t]) [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    exchange [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      data-scan []<-[$$25, $$t] <- test.TweetMessages condition (and(ge($$t.getField("user").getField("friends_count"), 0), le($$t.getField("user").getField("friends_count"), 150))) limit 2 [cardinality: 12.0, op-cost: 12.0, total-cost: 12.0]
+                      -- DATASOURCE_SCAN  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan/push-limit-to-primary-scan.3.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan/push-limit-to-primary-scan.3.plan
new file mode 100644
index 0000000..44957ae
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan/push-limit-to-primary-scan.3.plan
@@ -0,0 +1,20 @@
+distribute result [$$paper] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 5 offset 5 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+      -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+        limit 10 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+        -- STREAM_LIMIT  |PARTITIONED|
+          project ([$$paper]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+          -- STREAM_PROJECT  |PARTITIONED|
+            exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              data-scan []<-[$$13, $$paper] <- test.DBLP1 limit 10 [cardinality: 100.0, op-cost: 100.0, total-cost: 100.0]
+              -- DATASOURCE_SCAN  |PARTITIONED|
+                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan/push-limit-to-primary-scan.5.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan/push-limit-to-primary-scan.5.plan
new file mode 100644
index 0000000..f85eabc
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan/push-limit-to-primary-scan.5.plan
@@ -0,0 +1,20 @@
+distribute result [$$paper] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 5 offset 5 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+      -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+        limit 10 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+        -- STREAM_LIMIT  |PARTITIONED|
+          project ([$$paper]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+          -- STREAM_PROJECT  |PARTITIONED|
+            exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              data-scan []<-[$$15, $$paper] <- test.DBLP1 limit 10 [cardinality: 100.0, op-cost: 100.0, total-cost: 100.0]
+              -- DATASOURCE_SCAN  |PARTITIONED|
+                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan/push-limit-to-primary-scan.8.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan/push-limit-to-primary-scan.8.plan
new file mode 100644
index 0000000..f4b5d03
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan/push-limit-to-primary-scan.8.plan
@@ -0,0 +1,43 @@
+distribute result [$$80] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 5 offset 5 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      project ([$$80]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+      -- STREAM_PROJECT  |PARTITIONED|
+        assign [$$80] <- [get-item($$78, 0)] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+        -- ASSIGN  |PARTITIONED|
+          project ([$$78]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+          -- STREAM_PROJECT  |PARTITIONED|
+            exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+            -- SORT_MERGE_EXCHANGE [$$82(ASC) ]  |PARTITIONED|
+              limit 10 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+              -- STREAM_LIMIT  |PARTITIONED|
+                exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  order (topK: 10) (ASC, $$82) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                  -- STABLE_SORT [topK: 10] [$$82(ASC)]  |PARTITIONED|
+                    exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      project ([$$78, $$82]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        subplan {
+                                  aggregate [$$78] <- [listify($$77)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- AGGREGATE  |LOCAL|
+                                    assign [$$77] <- [object-remove(object-remove(object-remove($$t0, "title"), "authors"), "misc")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- ASSIGN  |LOCAL|
+                                      unnest $$t0 <- scan-collection(to-array($$paper)) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                      -- UNNEST  |LOCAL|
+                                        nested tuple source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                        -- NESTED_TUPLE_SOURCE  |LOCAL|
+                               } [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                        -- SUBPLAN  |PARTITIONED|
+                          exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            data-scan []<-[$$82, $$paper] <- test.DBLP1 [cardinality: 100.0, op-cost: 100.0, total-cost: 100.0]
+                            -- DATASOURCE_SCAN  |PARTITIONED|
+                              exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/objects/load-record-fields/load-record-fields.4.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/objects/load-record-fields/load-record-fields.4.plan
new file mode 100644
index 0000000..494b0cc
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/objects/load-record-fields/load-record-fields.4.plan
@@ -0,0 +1,26 @@
+distribute result [$$30] [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$30]) [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$30] <- [$$md.getField("name")] [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+      -- ASSIGN  |PARTITIONED|
+        project ([$$md]) [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+        -- STREAM_PROJECT  |PARTITIONED|
+          exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+          -- SORT_MERGE_EXCHANGE [$$32(ASC) ]  |PARTITIONED|
+            order (ASC, $$32) [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+            -- STABLE_SORT [$$32(ASC)]  |PARTITIONED|
+              exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                select (neq(uuid(), uuid())) [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+                -- STREAM_SELECT  |PARTITIONED|
+                  exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    data-scan []<-[$$32, $$md] <- test.MyDataset [cardinality: 2.0, op-cost: 2.1, total-cost: 2.1]
+                    -- DATASOURCE_SCAN  |PARTITIONED|
+                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/objects/load-record-fields/load-record-fields.6.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/objects/load-record-fields/load-record-fields.6.plan
new file mode 100644
index 0000000..1998551
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/objects/load-record-fields/load-record-fields.6.plan
@@ -0,0 +1,26 @@
+distribute result [$$30] [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$30]) [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$30] <- [$$md.getField("name")] [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+      -- ASSIGN  |PARTITIONED|
+        project ([$$md]) [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+        -- STREAM_PROJECT  |PARTITIONED|
+          exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+          -- SORT_MERGE_EXCHANGE [$$32(ASC) ]  |PARTITIONED|
+            order (ASC, $$32) [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+            -- STABLE_SORT [$$32(ASC)]  |PARTITIONED|
+              exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                select (neq(current-date(), date: { 1980-09-10 })) [cardinality: 2.1, op-cost: 0.0, total-cost: 2.1]
+                -- STREAM_SELECT  |PARTITIONED|
+                  exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    data-scan []<-[$$32, $$md] <- test.MyDataset [cardinality: 2.0, op-cost: 2.1, total-cost: 2.1]
+                    -- DATASOURCE_SCAN  |PARTITIONED|
+                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.024.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.024.plan
new file mode 100644
index 0000000..52775a8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.024.plan
@@ -0,0 +1,20 @@
+distribute result [$$v] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$v]) [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$v] <- [{"SK0": $$14, "PK0": $$15}] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+        -- SORT_MERGE_EXCHANGE [$$15(ASC) ]  |PARTITIONED|
+          order (ASC, $$15) [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+          -- STABLE_SORT [$$15(ASC)]  |PARTITIONED|
+            exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              data-scan []<-[$$14, $$15] <- test.ds1.ds1_age.query-index [cardinality: 14.0, op-cost: 14.0, total-cost: 14.0]
+              -- DATASOURCE_SCAN  |PARTITIONED|
+                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.025.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.025.plan
new file mode 100644
index 0000000..316c2f8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.025.plan
@@ -0,0 +1,20 @@
+distribute result [$$v] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$v]) [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$v] <- [{"SK0": $$14, "SK1": $$15, "PK0": $$16}] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+        -- SORT_MERGE_EXCHANGE [$$16(ASC) ]  |PARTITIONED|
+          order (ASC, $$16) [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+          -- STABLE_SORT [$$16(ASC)]  |PARTITIONED|
+            exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              data-scan []<-[$$14, $$15, $$16] <- test.ds1.ds1_age_dept.query-index [cardinality: 14.0, op-cost: 14.0, total-cost: 14.0]
+              -- DATASOURCE_SCAN  |PARTITIONED|
+                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.026.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.026.plan
new file mode 100644
index 0000000..b2745d6
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.026.plan
@@ -0,0 +1,38 @@
+distribute result [$$57] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$57]) [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$57] <- [{"age": $$SK0, "dept": $$SK1, "cnt": $$63}] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+        -- SORT_MERGE_EXCHANGE [$$SK1(ASC), $$SK0(ASC) ]  |PARTITIONED|
+          group by ([$$SK1 := $$65; $$SK0 := $$66]) decor ([]) {
+                    aggregate [$$63] <- [agg-sql-sum($$64)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- AGGREGATE  |LOCAL|
+                      nested tuple source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- NESTED_TUPLE_SOURCE  |LOCAL|
+                 } [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+          -- SORT_GROUP_BY[$$65, $$66]  |PARTITIONED|
+            exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+            -- HASH_PARTITION_EXCHANGE [$$65, $$66]  |PARTITIONED|
+              group by ([$$65 := $$61; $$66 := $$60]) decor ([]) {
+                        aggregate [$$64] <- [agg-sql-count(1)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- AGGREGATE  |LOCAL|
+                          nested tuple source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- NESTED_TUPLE_SOURCE  |LOCAL|
+                     } [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+              -- SORT_GROUP_BY[$$61, $$60]  |PARTITIONED|
+                exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  project ([$$61, $$60]) [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      data-scan []<-[$$60, $$61, $$62] <- test.ds1.ds1_age_dept.query-index [cardinality: 14.0, op-cost: 14.0, total-cost: 14.0]
+                      -- DATASOURCE_SCAN  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.027.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.027.plan
new file mode 100644
index 0000000..f3a257e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.027.plan
@@ -0,0 +1,22 @@
+distribute result [$$33] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    project ([$$33]) [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      assign [$$33] <- [{"cnt": $$36}] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+      -- ASSIGN  |UNPARTITIONED|
+        aggregate [$$36] <- [agg-sql-sum($$37)] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+        -- AGGREGATE  |UNPARTITIONED|
+          exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            aggregate [$$37] <- [agg-sql-count(1)] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+            -- AGGREGATE  |PARTITIONED|
+              exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                data-scan []<-[$$34, $$35] <- test.ds1.ds1_dept.query-index [cardinality: 14.0, op-cost: 14.0, total-cost: 14.0]
+                -- DATASOURCE_SCAN  |PARTITIONED|
+                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.028.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.028.plan
new file mode 100644
index 0000000..6f5cd80
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.028.plan
@@ -0,0 +1,38 @@
+distribute result [$$45] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$45]) [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$45] <- [{"age": $$SK0, "cnt": $$49}] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+        -- SORT_MERGE_EXCHANGE [$$SK0(ASC) ]  |PARTITIONED|
+          group by ([$$SK0 := $$51]) decor ([]) {
+                    aggregate [$$49] <- [agg-sql-sum($$50)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- AGGREGATE  |LOCAL|
+                      nested tuple source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- NESTED_TUPLE_SOURCE  |LOCAL|
+                 } [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+          -- SORT_GROUP_BY[$$51]  |PARTITIONED|
+            exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+            -- HASH_PARTITION_EXCHANGE [$$51]  |PARTITIONED|
+              group by ([$$51 := $$47]) decor ([]) {
+                        aggregate [$$50] <- [agg-sql-count(1)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- AGGREGATE  |LOCAL|
+                          nested tuple source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- NESTED_TUPLE_SOURCE  |LOCAL|
+                     } [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+              -- PRE_CLUSTERED_GROUP_BY[$$47]  |PARTITIONED|
+                exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  project ([$$47]) [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      data-scan []<-[$$47, $$48] <- test.ds1.ds1_age.query-index [cardinality: 14.0, op-cost: 14.0, total-cost: 14.0]
+                      -- DATASOURCE_SCAN  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.029.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.029.plan
new file mode 100644
index 0000000..be22b9f
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.029.plan
@@ -0,0 +1,38 @@
+distribute result [$$45] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$45]) [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$45] <- [{"age": $$SK0, "cnt": $$50}] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+        -- SORT_MERGE_EXCHANGE [$$SK0(ASC) ]  |PARTITIONED|
+          group by ([$$SK0 := $$52]) decor ([]) {
+                    aggregate [$$50] <- [agg-sql-sum($$51)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- AGGREGATE  |LOCAL|
+                      nested tuple source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- NESTED_TUPLE_SOURCE  |LOCAL|
+                 } [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+          -- SORT_GROUP_BY[$$52]  |PARTITIONED|
+            exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+            -- HASH_PARTITION_EXCHANGE [$$52]  |PARTITIONED|
+              group by ([$$52 := $$47]) decor ([]) {
+                        aggregate [$$51] <- [agg-sql-count(1)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- AGGREGATE  |LOCAL|
+                          nested tuple source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- NESTED_TUPLE_SOURCE  |LOCAL|
+                     } [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+              -- PRE_CLUSTERED_GROUP_BY[$$47]  |PARTITIONED|
+                exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  project ([$$47]) [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      data-scan []<-[$$47, $$48, $$49] <- test.ds1.ds1_age_dept.query-index [cardinality: 14.0, op-cost: 14.0, total-cost: 14.0]
+                      -- DATASOURCE_SCAN  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.030.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.030.plan
new file mode 100644
index 0000000..9f4a8a0
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.030.plan
@@ -0,0 +1,38 @@
+distribute result [$$45] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$45]) [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$45] <- [{"age": $$SK1, "cnt": $$50}] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+        -- SORT_MERGE_EXCHANGE [$$SK1(ASC) ]  |PARTITIONED|
+          group by ([$$SK1 := $$52]) decor ([]) {
+                    aggregate [$$50] <- [agg-sql-sum($$51)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- AGGREGATE  |LOCAL|
+                      nested tuple source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- NESTED_TUPLE_SOURCE  |LOCAL|
+                 } [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+          -- SORT_GROUP_BY[$$52]  |PARTITIONED|
+            exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+            -- HASH_PARTITION_EXCHANGE [$$52]  |PARTITIONED|
+              group by ([$$52 := $$48]) decor ([]) {
+                        aggregate [$$51] <- [agg-sql-count(1)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- AGGREGATE  |LOCAL|
+                          nested tuple source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- NESTED_TUPLE_SOURCE  |LOCAL|
+                     } [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+              -- SORT_GROUP_BY[$$48]  |PARTITIONED|
+                exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  project ([$$48]) [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      data-scan []<-[$$47, $$48, $$49] <- test.ds1.ds1_dept_age.query-index [cardinality: 14.0, op-cost: 14.0, total-cost: 14.0]
+                      -- DATASOURCE_SCAN  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.031.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.031.plan
new file mode 100644
index 0000000..6e96235
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/query_index/q01/q01.031.plan
@@ -0,0 +1,38 @@
+distribute result [$$57] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$57]) [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$57] <- [{"age": $$SK0, "dept": $$SK1, "cnt": $$63}] [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+        -- SORT_MERGE_EXCHANGE [$$SK0(ASC), $$SK1(ASC) ]  |PARTITIONED|
+          group by ([$$SK0 := $$65; $$SK1 := $$66]) decor ([]) {
+                    aggregate [$$63] <- [agg-sql-sum($$64)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- AGGREGATE  |LOCAL|
+                      nested tuple source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- NESTED_TUPLE_SOURCE  |LOCAL|
+                 } [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+          -- SORT_GROUP_BY[$$65, $$66]  |PARTITIONED|
+            exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+            -- HASH_PARTITION_EXCHANGE [$$65, $$66]  |PARTITIONED|
+              group by ([$$65 := $$60; $$66 := $$61]) decor ([]) {
+                        aggregate [$$64] <- [agg-sql-count(1)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- AGGREGATE  |LOCAL|
+                          nested tuple source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- NESTED_TUPLE_SOURCE  |LOCAL|
+                     } [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+              -- PRE_CLUSTERED_GROUP_BY[$$60, $$61]  |PARTITIONED|
+                exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  project ([$$60, $$61]) [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    exchange [cardinality: 14.0, op-cost: 0.0, total-cost: 14.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      data-scan []<-[$$60, $$61, $$62] <- test.ds1.ds1_age_dept.query-index [cardinality: 14.0, op-cost: 14.0, total-cost: 14.0]
+                      -- DATASOURCE_SCAN  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.04.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.04.plan
new file mode 100644
index 0000000..0f6bbef
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.04.plan
@@ -0,0 +1,34 @@
+distribute result [$$94] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    project ([$$94]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      assign [$$94] <- [{"id": $$109, "review": $$114}] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      -- ASSIGN  |UNPARTITIONED|
+        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+        -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+          order (ASC, $$109) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- STABLE_SORT [$$109(ASC)]  |UNPARTITIONED|
+            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+              limit 3 [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- STREAM_LIMIT  |UNPARTITIONED|
+                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+                  project ([$$114, $$109]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    assign [$$109] <- [int64-default-null($$d.getField("id"))] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- ASSIGN  |PARTITIONED|
+                      limit 3 [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- STREAM_LIMIT  |PARTITIONED|
+                        assign [$$114] <- [string-default-null($$d.getField("review"))] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- ASSIGN  |PARTITIONED|
+                          exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            data-scan []<-[$$d] <- test.ExternalDataset condition (and(not(is-unknown(int64-default-null($$d.getField("year")))), not(is-unknown(int64-default-null($$d.getField("quarter")))), eq(string-default-null($$d.getField("review")), "good"))) limit 3 [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- DATASOURCE_SCAN  |PARTITIONED|
+                              exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.06.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.06.plan
new file mode 100644
index 0000000..1d3ef0b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.06.plan
@@ -0,0 +1,22 @@
+distribute result [$$69] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 3 [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+        project ([$$69]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+        -- STREAM_PROJECT  |PARTITIONED|
+          assign [$$69] <- [{"id": int64-default-null($$d.getField("id")), "review": string-default-null($$d.getField("review"))}] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- ASSIGN  |PARTITIONED|
+            limit 3 [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- STREAM_LIMIT  |PARTITIONED|
+              exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                data-scan []<-[$$d] <- test.ExternalDataset limit 3 [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- DATASOURCE_SCAN  |PARTITIONED|
+                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.08.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.08.plan
new file mode 100644
index 0000000..560e511
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.08.plan
@@ -0,0 +1,32 @@
+distribute result [$$88] [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    project ([$$88]) [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      assign [$$88] <- [{"id": $$91, "review": $$95}] [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+      -- ASSIGN  |UNPARTITIONED|
+        exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+        -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+          order (ASC, $$91) [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+          -- STABLE_SORT [$$91(ASC)]  |UNPARTITIONED|
+            exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+            -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+              limit 3 [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+              -- STREAM_LIMIT  |UNPARTITIONED|
+                exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+                  limit 3 [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                  -- STREAM_LIMIT  |PARTITIONED|
+                    project ([$$91, $$95]) [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      assign [$$95] <- [$$d.getField(1)] [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                      -- ASSIGN  |PARTITIONED|
+                        exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          data-scan []<-[$$91, $$d] <- test.DatasetWithKnownField condition (and(not(is-unknown(int64-default-null($$d.getField("year")))), not(is-unknown(int64-default-null($$d.getField("quarter")))), eq($$d.getField(1), "good"))) limit 3 [cardinality: 20.0, op-cost: 2.1, total-cost: 2.1]
+                          -- DATASOURCE_SCAN  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.10.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.10.plan
new file mode 100644
index 0000000..510a157
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.10.plan
@@ -0,0 +1,32 @@
+distribute result [$$63] [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 3 [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      project ([$$63]) [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+      -- STREAM_PROJECT  |PARTITIONED|
+        assign [$$63] <- [{"id": $$65, "review": $$69}] [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+        -- ASSIGN  |PARTITIONED|
+          exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+          -- SORT_MERGE_EXCHANGE [$$65(ASC) ]  |PARTITIONED|
+            limit 3 [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+            -- STREAM_LIMIT  |PARTITIONED|
+              exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                order (topK: 3) (ASC, $$65) [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                -- STABLE_SORT [topK: 3] [$$65(ASC)]  |PARTITIONED|
+                  exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    project ([$$65, $$69]) [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      assign [$$69] <- [$$d.getField(1)] [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                      -- ASSIGN  |PARTITIONED|
+                        exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          data-scan []<-[$$65, $$d] <- test.DatasetWithKnownField [cardinality: 20.0, op-cost: 2.1, total-cost: 2.1]
+                          -- DATASOURCE_SCAN  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.12.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.12.plan
new file mode 100644
index 0000000..142aa71
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.12.plan
@@ -0,0 +1,32 @@
+distribute result [$$94] [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    project ([$$94]) [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      assign [$$94] <- [{"id": $$97, "review": $$101}] [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+      -- ASSIGN  |UNPARTITIONED|
+        exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+        -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+          order (ASC, $$97) [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+          -- STABLE_SORT [$$97(ASC)]  |UNPARTITIONED|
+            exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+            -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+              limit 3 [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+              -- STREAM_LIMIT  |UNPARTITIONED|
+                exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+                  limit 3 [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                  -- STREAM_LIMIT  |PARTITIONED|
+                    project ([$$97, $$101]) [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      assign [$$101] <- [$$d.getField(1)] [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                      -- ASSIGN  |PARTITIONED|
+                        exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          data-scan []<-[$$97, $$d] <- test.DatasetWithKnownField condition (and(not(is-unknown(int64-default-null($$d.getField("year")))), not(is-unknown(int64-default-null($$d.getField("quarter")))), eq($$d.getField(1), "good"))) limit 3 [cardinality: 20.0, op-cost: 2.1, total-cost: 2.1]
+                          -- DATASOURCE_SCAN  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.14.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.14.plan
new file mode 100644
index 0000000..ac2e92f
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/view/view-pushdown/view-pushdown.14.plan
@@ -0,0 +1,32 @@
+distribute result [$$69] [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 3 [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      project ([$$69]) [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+      -- STREAM_PROJECT  |PARTITIONED|
+        assign [$$69] <- [{"id": $$71, "review": $$75}] [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+        -- ASSIGN  |PARTITIONED|
+          exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+          -- SORT_MERGE_EXCHANGE [$$71(ASC) ]  |PARTITIONED|
+            limit 3 [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+            -- STREAM_LIMIT  |PARTITIONED|
+              exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                order (topK: 3) (ASC, $$71) [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                -- STABLE_SORT [topK: 3] [$$71(ASC)]  |PARTITIONED|
+                  exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    project ([$$71, $$75]) [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      assign [$$75] <- [$$d.getField(1)] [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                      -- ASSIGN  |PARTITIONED|
+                        exchange [cardinality: 20.0, op-cost: 0.0, total-cost: 2.1]
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          data-scan []<-[$$71, $$d] <- test.DatasetWithKnownField [cardinality: 20.0, op-cost: 2.1, total-cost: 2.1]
+                          -- DATASOURCE_SCAN  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-test-framework/pom.xml b/asterixdb/asterix-test-framework/pom.xml
index 65a9989..1b5df94 100644
--- a/asterixdb/asterix-test-framework/pom.xml
+++ b/asterixdb/asterix-test-framework/pom.xml
@@ -86,6 +86,10 @@
       <groupId>org.apache.logging.log4j</groupId>
       <artifactId>log4j-api</artifactId>
     </dependency>
+      <dependency>
+          <groupId>com.google.guava</groupId>
+          <artifactId>guava</artifactId>
+      </dependency>
   </dependencies>
 
 </project>
diff --git a/asterixdb/asterix-test-framework/src/main/java/org/apache/asterix/testframework/context/TestCaseContext.java b/asterixdb/asterix-test-framework/src/main/java/org/apache/asterix/testframework/context/TestCaseContext.java
index 6e458a4..eded7445 100644
--- a/asterixdb/asterix-test-framework/src/main/java/org/apache/asterix/testframework/context/TestCaseContext.java
+++ b/asterixdb/asterix-test-framework/src/main/java/org/apache/asterix/testframework/context/TestCaseContext.java
@@ -23,9 +23,12 @@
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
 import java.util.regex.Pattern;
 
 import org.apache.asterix.testframework.template.TemplateHelper;
@@ -36,6 +39,8 @@
 import org.apache.asterix.testframework.xml.TestSuite;
 import org.apache.asterix.testframework.xml.TestSuiteParser;
 
+import com.google.common.collect.Sets;
+
 public class TestCaseContext {
 
     public static final String DEFAULT_TESTSUITE_XML_NAME = "testsuite.xml";
@@ -141,6 +146,23 @@
         return getFilesInDir(testSuite.getResultOffsetPath(), cUnit.getOutputDir().getValue(), false);
     }
 
+    public List<TestFileContext> getExpectedResultsAndDelta(CompilationUnit cUnit, String deltaBasePath) {
+        Comparator<TestFileContext> compOnFileName = Comparator.comparing(o -> o.getFile().getName());
+        Set<TestFileContext> deltaSet = new TreeSet<>(compOnFileName);
+        deltaSet.addAll(getFilesInDir(deltaBasePath, cUnit.getOutputDir().getValue(), false));
+        if (deltaSet.isEmpty()) {
+            return getExpectedResultFiles(cUnit);
+        }
+        Set<TestFileContext> baseSet = new TreeSet<>(compOnFileName);
+        baseSet.addAll(getExpectedResultFiles(cUnit));
+        Set<TestFileContext> diff = Sets.difference(baseSet, deltaSet);
+        List<TestFileContext> expectedWithDelta = new ArrayList<>();
+        expectedWithDelta.addAll(diff);
+        expectedWithDelta.addAll(deltaSet);
+        Collections.sort(expectedWithDelta);
+        return expectedWithDelta;
+    }
+
     public File getActualResultFile(CompilationUnit cUnit, File expectedFile, File actualResultsBase) {
         File path = actualResultsBase;
         String resultOffsetPath = removeUpward(testSuite.getResultOffsetPath());