[ASTERIXDB-3231][COMP] Fix the index-only plan of indexnl left outer join

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

Details:
For SELECT op, include missingPlaceholderVar when substituting
SELECT op variables.

Change-Id: I07e619e2fbeef0e0679548d9c1b2e108d8bc3128
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/17665
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Ali Alsuliman <ali.al.solaiman@gmail.com>
diff --git a/asterixdb/asterix-app/pom.xml b/asterixdb/asterix-app/pom.xml
index a0ff91a..ef74df4 100644
--- a/asterixdb/asterix-app/pom.xml
+++ b/asterixdb/asterix-app/pom.xml
@@ -267,6 +267,8 @@
                 <exclude>src/test/resources/**/results_parser_sqlpp/**</exclude>
                 <exclude>src/test/resources/**/results/**</exclude>
                 <exclude>src/test/resources/**/results_cbo/**</exclude>
+                <exclude>src/test/resources/**/results_less_parallelism/**</exclude>
+                <exclude>src/test/resources/**/results_full_parallelism/**</exclude>
                 <exclude>src/test/resources/fuzzyjoin/pub/fuzzy-join-aql*.dot</exclude>
                 <exclude>src/test/resources/fuzzyjoin/pub/fuzzy-join-aql*.json</exclude>
                 <exclude>**/data/**</exclude>
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppExecutionFullParallelismIT.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppExecutionFullParallelismIT.java
index df7976b..5d3eac0 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppExecutionFullParallelismIT.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppExecutionFullParallelismIT.java
@@ -39,7 +39,7 @@
 
     @BeforeClass
     public static void setUp() throws Exception {
-        LangExecutionUtil.setUp(TEST_CONFIG_FILE_NAME, new TestExecutor());
+        LangExecutionUtil.setUp(TEST_CONFIG_FILE_NAME, new TestExecutor("results_full_parallelism"));
     }
 
     @AfterClass
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppExecutionLessParallelismIT.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppExecutionLessParallelismIT.java
index d99590d..dd84bef 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppExecutionLessParallelismIT.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppExecutionLessParallelismIT.java
@@ -39,7 +39,7 @@
 
     @BeforeClass
     public static void setUp() throws Exception {
-        LangExecutionUtil.setUp(TEST_CONFIG_FILE_NAME, new TestExecutor());
+        LangExecutionUtil.setUp(TEST_CONFIG_FILE_NAME, new TestExecutor("results_less_parallelism"));
     }
 
     @AfterClass
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.001.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.001.ddl.sqlpp
new file mode 100644
index 0000000..4ae9ae8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.001.ddl.sqlpp
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+DROP DATAVERSE test IF EXISTS;
+CREATE DATAVERSE test;
+USE test;
+
+CREATE TYPE untyped AS {id: string};
+CREATE TYPE typed AS {id: string, c_int32: int32?};
+
+CREATE DATASET ds_outer_untyped(untyped) primary key id;
+CREATE DATASET ds_outer_typed(typed) primary key id;
+
+CREATE DATASET ds_inner_untyped(untyped) primary key id;
+CREATE DATASET ds_inner_typed(typed) primary key id;
+
+CREATE INDEX idx_c_int32 ON ds_inner_untyped(c_int32: int32);
+CREATE INDEX idx_c_int32 ON ds_inner_typed(c_int32);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.002.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.002.update.sqlpp
new file mode 100644
index 0000000..6d916a9
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.002.update.sqlpp
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+UPSERT INTO ds_outer_untyped [
+{'id': "o_untyped:01", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null},
+{'id': "o_untyped:02", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null}
+];
+
+UPSERT INTO ds_outer_typed [
+{'id': "o_untyped:01", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null},
+{'id': "o_untyped:02", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null}
+];
+
+UPSERT INTO ds_inner_untyped [
+{ 'id': "i_untyped:01", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null},
+{ 'id': "i_untyped:02", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null},
+{ 'id': "i_untyped:03", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null}
+];
+
+UPSERT INTO ds_inner_typed [
+{ 'id': "i_typed:01", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null},
+{ 'id': "i_typed:02", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null},
+{ 'id': "i_typed:03", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null}
+];
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.003.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.003.query.sqlpp
new file mode 100644
index 0000000..27a4019
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.003.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SET `compiler.sort.parallel` 'false';
+SET `compiler.indexonly` 'true';
+SELECT t1.id AS t1_id, t2.id AS t2_id
+FROM ds_outer_untyped t1 LEFT JOIN ds_inner_untyped t2 ON int32(t1.c_int32) /* +indexnl */ =(t2.c_int32) ORDER BY t1_id, t2_id;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.004.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.004.query.sqlpp
new file mode 100644
index 0000000..6bff38d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.004.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SET `compiler.sort.parallel` 'false';
+SET `compiler.indexonly` 'true';
+SELECT t1.id AS t1_id, t2.id AS t2_id
+FROM ds_outer_untyped t1 LEFT JOIN ds_inner_typed t2 ON int32(t1.c_int32) /* +indexnl */ = (t2.c_int32) ORDER BY t1_id, t2_id;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.005.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.005.query.sqlpp
new file mode 100644
index 0000000..1ac4c6b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.005.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SET `compiler.sort.parallel` 'false';
+SET `compiler.indexonly` 'true';
+SELECT t1.id AS t1_id, t2.id AS t2_id
+FROM ds_outer_typed t1 LEFT JOIN ds_inner_untyped t2 ON int32(t1.c_int32) /* +indexnl */ =(t2.c_int32) ORDER BY t1_id, t2_id;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.006.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.006.query.sqlpp
new file mode 100644
index 0000000..6e0740f
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.006.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SET `compiler.sort.parallel` 'false';
+SET `compiler.indexonly` 'true';
+SELECT t1.id AS t1_id, t2.id AS t2_id
+FROM ds_outer_typed t1 LEFT JOIN ds_inner_typed t2 ON int32(t1.c_int32) /* +indexnl */ = (t2.c_int32) ORDER BY t1_id, t2_id;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.007.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.007.query.sqlpp
new file mode 100644
index 0000000..b78ca99
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.007.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SET `compiler.sort.parallel` 'false';
+SET `compiler.indexonly` 'true';
+EXPLAIN SELECT t1.id AS t1_id, t2.id AS t2_id
+FROM ds_outer_untyped t1 LEFT JOIN ds_inner_untyped t2 ON int32(t1.c_int32) /* +indexnl */ = (t2.c_int32) ORDER BY t1_id, t2_id;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.008.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.008.query.sqlpp
new file mode 100644
index 0000000..9ca5ca6
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.008.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SET `compiler.sort.parallel` 'false';
+SET `compiler.indexonly` 'true';
+EXPLAIN SELECT t1.id AS t1_id, t2.id AS t2_id
+FROM ds_outer_untyped t1 LEFT JOIN ds_inner_typed t2 ON int32(t1.c_int32) /* +indexnl */ = (t2.c_int32) ORDER BY t1_id, t2_id;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.009.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.009.query.sqlpp
new file mode 100644
index 0000000..c3fefcb
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.009.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SET `compiler.sort.parallel` 'false';
+SET `compiler.indexonly` 'true';
+EXPLAIN SELECT t1.id AS t1_id, t2.id AS t2_id
+FROM ds_outer_typed t1 LEFT JOIN ds_inner_untyped t2 ON int32(t1.c_int32) /* +indexnl */ = (t2.c_int32) ORDER BY t1_id, t2_id;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.010.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.010.query.sqlpp
new file mode 100644
index 0000000..0c79392
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.010.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SET `compiler.sort.parallel` 'false';
+SET `compiler.indexonly` 'true';
+EXPLAIN SELECT t1.id AS t1_id, t2.id AS t2_id
+FROM ds_outer_typed t1 LEFT JOIN ds_inner_typed t2 ON int32(t1.c_int32) /* +indexnl */ = (t2.c_int32) ORDER BY t1_id, t2_id;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.011.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.011.query.sqlpp
new file mode 100644
index 0000000..c66ae86
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.011.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SET `compiler.sort.parallel` 'false';
+SET `compiler.indexonly` 'false';
+SELECT t1.id AS t1_id, t2.id AS t2_id
+FROM ds_outer_untyped t1 LEFT JOIN ds_inner_untyped t2 ON int32(t1.c_int32) = (t2.c_int32) ORDER BY t1_id, t2_id;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.003.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.003.adm
new file mode 100644
index 0000000..1696860
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.003.adm
@@ -0,0 +1,2 @@
+{ "t1_id": "o_untyped:01" }
+{ "t1_id": "o_untyped:02" }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.004.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.004.adm
new file mode 100644
index 0000000..1696860
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.004.adm
@@ -0,0 +1,2 @@
+{ "t1_id": "o_untyped:01" }
+{ "t1_id": "o_untyped:02" }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.005.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.005.adm
new file mode 100644
index 0000000..1696860
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.005.adm
@@ -0,0 +1,2 @@
+{ "t1_id": "o_untyped:01" }
+{ "t1_id": "o_untyped:02" }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.006.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.006.adm
new file mode 100644
index 0000000..1696860
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.006.adm
@@ -0,0 +1,2 @@
+{ "t1_id": "o_untyped:01" }
+{ "t1_id": "o_untyped:02" }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.007.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.007.plan
new file mode 100644
index 0000000..f4cf2d4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.007.plan
@@ -0,0 +1,62 @@
+distribute result [$$52] [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 ([$$52]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$52] <- [{"t1_id": $$53, "t2_id": $$54}] [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]
+        -- SORT_MERGE_EXCHANGE [$$53(ASC), $$54(ASC) ]  |PARTITIONED|
+          order (ASC, $$53) (ASC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- STABLE_SORT [$$53(ASC), $$54(ASC)]  |PARTITIONED|
+            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$53, $$54]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                select ($$62) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- STREAM_SELECT  |PARTITIONED|
+                  window-aggregate [$$62] <- [win-mark-first-missing-impl($$54)] partition [$$53] order (DESC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- WINDOW_STREAM  |PARTITIONED|
+                    exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      order (ASC, $$53) (DESC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- STABLE_SORT [$$53(ASC), $$54(DESC)]  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- HASH_PARTITION_EXCHANGE [$$53]  |PARTITIONED|
+                          project ([$$53, $$54]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            select (eq($$55, $$t2.getField("c_int32"))) retain-untrue ($$54 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- STREAM_SELECT  |PARTITIONED|
+                              project ([$$53, $$55, $$54, $$t2]) [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|
+                                  left-outer-unnest-map [$$54, $$t2] <- index-search("ds_inner_untyped", 0, "test", "ds_inner_untyped", true, false, 1, $$61, 1, $$61, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- BTREE_SEARCH  |PARTITIONED|
+                                    exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      order (ASC, $$61) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                      -- STABLE_SORT [$$61(ASC)]  |PARTITIONED|
+                                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          project ([$$53, $$55, $$61]) [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|
+                                              left-outer-unnest-map [$$60, $$61] <- index-search("idx_c_int32", 0, "test", "ds_inner_untyped", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- BTREE_SEARCH  |PARTITIONED|
+                                                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                  project ([$$53, $$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                  -- STREAM_PROJECT  |PARTITIONED|
+                                                    assign [$$55] <- [int32($$t1.getField("c_int32"))] [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 []<-[$$53, $$t1] <- test.ds_outer_untyped [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/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.008.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.008.plan
new file mode 100644
index 0000000..b1db9f8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.008.plan
@@ -0,0 +1,94 @@
+distribute result [$$52] [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 ([$$52]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$52] <- [{"t1_id": $$73, "t2_id": $$54}] [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]
+        -- SORT_MERGE_EXCHANGE [$$73(ASC), $$54(ASC) ]  |PARTITIONED|
+          order (ASC, $$73) (ASC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- STABLE_SORT [$$73(ASC), $$54(ASC)]  |PARTITIONED|
+            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$73, $$54]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                select ($$74) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- STREAM_SELECT  |PARTITIONED|
+                  window-aggregate [$$74] <- [win-mark-first-missing-impl($$54)] partition [$$73] order (DESC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- WINDOW_STREAM  |PARTITIONED|
+                    exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      order (ASC, $$73) (DESC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- STABLE_SORT [$$73(ASC), $$54(DESC)]  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- HASH_PARTITION_EXCHANGE [$$73]  |PARTITIONED|
+                          union ($$70, $$61, $$54) ($$53, $$53, $$73) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- UNION_ALL  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              project ([$$70, $$53]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                select (eq($$55, $$71.getField(1))) retain-untrue ($$70 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- STREAM_SELECT  |PARTITIONED|
+                                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    left-outer-unnest-map [$$70, $$71] <- index-search("ds_inner_typed", 0, "test", "ds_inner_typed", true, false, 1, $$61, 1, $$61, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- BTREE_SEARCH  |PARTITIONED|
+                                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        project ([$$53, $$55, $$61]) [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|
+                                            split ($$62) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                            -- SPLIT  |PARTITIONED|
+                                              exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                left-outer-unnest-map [$$60, $$61, $$62] <- index-search("idx_c_int32", 0, "test", "ds_inner_typed", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                -- BTREE_SEARCH  |PARTITIONED|
+                                                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                  -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                    project ([$$53, $$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                    -- STREAM_PROJECT  |PARTITIONED|
+                                                      assign [$$55] <- [int32($$t1.getField("c_int32"))] [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 []<-[$$53, $$t1] <- test.ds_outer_untyped [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|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              project ([$$61, $$53]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                select (eq($$55, $$60)) retain-untrue ($$61 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- STREAM_SELECT  |PARTITIONED|
+                                  project ([$$53, $$55, $$60, $$61]) [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|
+                                      split ($$62) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                      -- SPLIT  |PARTITIONED|
+                                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          left-outer-unnest-map [$$60, $$61, $$62] <- index-search("idx_c_int32", 0, "test", "ds_inner_typed", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                          -- BTREE_SEARCH  |PARTITIONED|
+                                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                            -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                              project ([$$53, $$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- STREAM_PROJECT  |PARTITIONED|
+                                                assign [$$55] <- [int32($$t1.getField("c_int32"))] [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 []<-[$$53, $$t1] <- test.ds_outer_untyped [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/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.009.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.009.plan
new file mode 100644
index 0000000..591375e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.009.plan
@@ -0,0 +1,62 @@
+distribute result [$$52] [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 ([$$52]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$52] <- [{"t1_id": $$53, "t2_id": $$54}] [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]
+        -- SORT_MERGE_EXCHANGE [$$53(ASC), $$54(ASC) ]  |PARTITIONED|
+          order (ASC, $$53) (ASC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- STABLE_SORT [$$53(ASC), $$54(ASC)]  |PARTITIONED|
+            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$53, $$54]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                select ($$62) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- STREAM_SELECT  |PARTITIONED|
+                  window-aggregate [$$62] <- [win-mark-first-missing-impl($$54)] partition [$$53] order (DESC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- WINDOW_STREAM  |PARTITIONED|
+                    exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      order (ASC, $$53) (DESC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- STABLE_SORT [$$53(ASC), $$54(DESC)]  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- HASH_PARTITION_EXCHANGE [$$53]  |PARTITIONED|
+                          project ([$$53, $$54]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            select (eq($$55, $$t2.getField("c_int32"))) retain-untrue ($$54 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- STREAM_SELECT  |PARTITIONED|
+                              project ([$$53, $$55, $$54, $$t2]) [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|
+                                  left-outer-unnest-map [$$54, $$t2] <- index-search("ds_inner_untyped", 0, "test", "ds_inner_untyped", true, false, 1, $$61, 1, $$61, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- BTREE_SEARCH  |PARTITIONED|
+                                    exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      order (ASC, $$61) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                      -- STABLE_SORT [$$61(ASC)]  |PARTITIONED|
+                                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          project ([$$53, $$55, $$61]) [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|
+                                              left-outer-unnest-map [$$60, $$61] <- index-search("idx_c_int32", 0, "test", "ds_inner_untyped", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- BTREE_SEARCH  |PARTITIONED|
+                                                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                  project ([$$53, $$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                  -- STREAM_PROJECT  |PARTITIONED|
+                                                    assign [$$55] <- [int32($$t1.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 []<-[$$53, $$t1] <- test.ds_outer_typed [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/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.010.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.010.plan
new file mode 100644
index 0000000..3d31e11
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.010.plan
@@ -0,0 +1,94 @@
+distribute result [$$52] [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 ([$$52]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$52] <- [{"t1_id": $$73, "t2_id": $$54}] [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]
+        -- SORT_MERGE_EXCHANGE [$$73(ASC), $$54(ASC) ]  |PARTITIONED|
+          order (ASC, $$73) (ASC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- STABLE_SORT [$$73(ASC), $$54(ASC)]  |PARTITIONED|
+            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$73, $$54]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                select ($$74) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- STREAM_SELECT  |PARTITIONED|
+                  window-aggregate [$$74] <- [win-mark-first-missing-impl($$54)] partition [$$73] order (DESC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- WINDOW_STREAM  |PARTITIONED|
+                    exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      order (ASC, $$73) (DESC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- STABLE_SORT [$$73(ASC), $$54(DESC)]  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- HASH_PARTITION_EXCHANGE [$$73]  |PARTITIONED|
+                          union ($$70, $$61, $$54) ($$53, $$53, $$73) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- UNION_ALL  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              project ([$$70, $$53]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                select (eq($$55, $$71.getField(1))) retain-untrue ($$70 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- STREAM_SELECT  |PARTITIONED|
+                                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    left-outer-unnest-map [$$70, $$71] <- index-search("ds_inner_typed", 0, "test", "ds_inner_typed", true, false, 1, $$61, 1, $$61, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- BTREE_SEARCH  |PARTITIONED|
+                                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        project ([$$53, $$55, $$61]) [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|
+                                            split ($$62) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                            -- SPLIT  |PARTITIONED|
+                                              exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                left-outer-unnest-map [$$60, $$61, $$62] <- index-search("idx_c_int32", 0, "test", "ds_inner_typed", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                -- BTREE_SEARCH  |PARTITIONED|
+                                                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                  -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                    project ([$$53, $$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                    -- STREAM_PROJECT  |PARTITIONED|
+                                                      assign [$$55] <- [int32($$t1.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 []<-[$$53, $$t1] <- test.ds_outer_typed [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|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              project ([$$61, $$53]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                select (eq($$55, $$60)) retain-untrue ($$61 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- STREAM_SELECT  |PARTITIONED|
+                                  project ([$$53, $$55, $$60, $$61]) [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|
+                                      split ($$62) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                      -- SPLIT  |PARTITIONED|
+                                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          left-outer-unnest-map [$$60, $$61, $$62] <- index-search("idx_c_int32", 0, "test", "ds_inner_typed", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                          -- BTREE_SEARCH  |PARTITIONED|
+                                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                            -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                              project ([$$53, $$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- STREAM_PROJECT  |PARTITIONED|
+                                                assign [$$55] <- [int32($$t1.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 []<-[$$53, $$t1] <- test.ds_outer_typed [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/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.011.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.011.adm
new file mode 100644
index 0000000..1696860
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.011.adm
@@ -0,0 +1,2 @@
+{ "t1_id": "o_untyped:01" }
+{ "t1_id": "o_untyped:02" }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.007.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.007.plan
new file mode 100644
index 0000000..a4bd07b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.007.plan
@@ -0,0 +1,62 @@
+distribute result [$$52] [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$52]) [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$52] <- [{"t1_id": $$53, "t2_id": $$54}] [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+        -- SORT_MERGE_EXCHANGE [$$53(ASC), $$54(ASC) ]  |PARTITIONED|
+          order (ASC, $$53) (ASC, $$54) [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+          -- STABLE_SORT [$$53(ASC), $$54(ASC)]  |PARTITIONED|
+            exchange [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$53, $$54]) [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+              -- STREAM_PROJECT  |PARTITIONED|
+                select ($$62) [cardinality: 3.15, op-cost: 2.1, total-cost: 12.6]
+                -- STREAM_SELECT  |PARTITIONED|
+                  window-aggregate [$$62] <- [win-mark-first-missing-impl($$54)] partition [$$53] order (DESC, $$54) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                  -- WINDOW_STREAM  |PARTITIONED|
+                    exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      order (ASC, $$53) (DESC, $$54) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                      -- STABLE_SORT [$$53(ASC), $$54(DESC)]  |PARTITIONED|
+                        exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                        -- HASH_PARTITION_EXCHANGE [$$53]  |PARTITIONED|
+                          project ([$$53, $$54]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            select (eq($$55, $$t2.getField("c_int32"))) retain-untrue ($$54 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- STREAM_SELECT  |PARTITIONED|
+                              project ([$$53, $$55, $$54, $$t2]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  left-outer-unnest-map [$$54, $$t2] <- index-search("ds_inner_untyped", 0, "test", "ds_inner_untyped", true, false, 1, $$61, 1, $$61, true, true, true) [cardinality: 3.0, op-cost: 3.0, total-cost: 3.0]
+                                  -- BTREE_SEARCH  |PARTITIONED|
+                                    exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      order (ASC, $$61) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                      -- STABLE_SORT [$$61(ASC)]  |PARTITIONED|
+                                        exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          project ([$$53, $$55, $$61]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                          -- STREAM_PROJECT  |PARTITIONED|
+                                            exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              left-outer-unnest-map [$$60, $$61] <- index-search("idx_c_int32", 0, "test", "ds_inner_untyped", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- BTREE_SEARCH  |PARTITIONED|
+                                                exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                  project ([$$53, $$55]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                  -- STREAM_PROJECT  |PARTITIONED|
+                                                    assign [$$55] <- [int32($$t1.getField("c_int32"))] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                    -- ASSIGN  |PARTITIONED|
+                                                      exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        data-scan []<-[$$53, $$t1] <- test.ds_outer_untyped [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/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.008.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.008.plan
new file mode 100644
index 0000000..21db7f6
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.008.plan
@@ -0,0 +1,94 @@
+distribute result [$$52] [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$52]) [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$52] <- [{"t1_id": $$73, "t2_id": $$54}] [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+        -- SORT_MERGE_EXCHANGE [$$73(ASC), $$54(ASC) ]  |PARTITIONED|
+          order (ASC, $$73) (ASC, $$54) [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+          -- STABLE_SORT [$$73(ASC), $$54(ASC)]  |PARTITIONED|
+            exchange [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$73, $$54]) [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+              -- STREAM_PROJECT  |PARTITIONED|
+                select ($$74) [cardinality: 3.15, op-cost: 2.1, total-cost: 12.6]
+                -- STREAM_SELECT  |PARTITIONED|
+                  window-aggregate [$$74] <- [win-mark-first-missing-impl($$54)] partition [$$73] order (DESC, $$54) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                  -- WINDOW_STREAM  |PARTITIONED|
+                    exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      order (ASC, $$73) (DESC, $$54) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                      -- STABLE_SORT [$$73(ASC), $$54(DESC)]  |PARTITIONED|
+                        exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                        -- HASH_PARTITION_EXCHANGE [$$73]  |PARTITIONED|
+                          union ($$70, $$61, $$54) ($$53, $$53, $$73) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                          -- UNION_ALL  |PARTITIONED|
+                            exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              project ([$$70, $$53]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                select (eq($$55, $$71.getField(1))) retain-untrue ($$70 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- STREAM_SELECT  |PARTITIONED|
+                                  exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    left-outer-unnest-map [$$70, $$71] <- index-search("ds_inner_typed", 0, "test", "ds_inner_typed", true, false, 1, $$61, 1, $$61, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- BTREE_SEARCH  |PARTITIONED|
+                                      exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        project ([$$53, $$55, $$61]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            split ($$62) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                            -- SPLIT  |PARTITIONED|
+                                              exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                left-outer-unnest-map [$$60, $$61, $$62] <- index-search("idx_c_int32", 0, "test", "ds_inner_typed", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                -- BTREE_SEARCH  |PARTITIONED|
+                                                  exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                  -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                    project ([$$53, $$55]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                    -- STREAM_PROJECT  |PARTITIONED|
+                                                      assign [$$55] <- [int32($$t1.getField("c_int32"))] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                      -- ASSIGN  |PARTITIONED|
+                                                        exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                          data-scan []<-[$$53, $$t1] <- test.ds_outer_untyped [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|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              project ([$$61, $$53]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                select (eq($$55, $$60)) retain-untrue ($$61 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- STREAM_SELECT  |PARTITIONED|
+                                  project ([$$53, $$55, $$60, $$61]) [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|
+                                      split ($$62) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                      -- SPLIT  |PARTITIONED|
+                                        exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          left-outer-unnest-map [$$60, $$61, $$62] <- index-search("idx_c_int32", 0, "test", "ds_inner_typed", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                          -- BTREE_SEARCH  |PARTITIONED|
+                                            exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                            -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                              project ([$$53, $$55]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                              -- STREAM_PROJECT  |PARTITIONED|
+                                                assign [$$55] <- [int32($$t1.getField("c_int32"))] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                -- ASSIGN  |PARTITIONED|
+                                                  exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    data-scan []<-[$$53, $$t1] <- test.ds_outer_untyped [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/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.009.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.009.plan
new file mode 100644
index 0000000..33f5c67
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.009.plan
@@ -0,0 +1,62 @@
+distribute result [$$52] [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$52]) [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$52] <- [{"t1_id": $$53, "t2_id": $$54}] [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+        -- SORT_MERGE_EXCHANGE [$$53(ASC), $$54(ASC) ]  |PARTITIONED|
+          order (ASC, $$53) (ASC, $$54) [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+          -- STABLE_SORT [$$53(ASC), $$54(ASC)]  |PARTITIONED|
+            exchange [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$53, $$54]) [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+              -- STREAM_PROJECT  |PARTITIONED|
+                select ($$62) [cardinality: 3.15, op-cost: 2.1, total-cost: 12.6]
+                -- STREAM_SELECT  |PARTITIONED|
+                  window-aggregate [$$62] <- [win-mark-first-missing-impl($$54)] partition [$$53] order (DESC, $$54) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                  -- WINDOW_STREAM  |PARTITIONED|
+                    exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      order (ASC, $$53) (DESC, $$54) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                      -- STABLE_SORT [$$53(ASC), $$54(DESC)]  |PARTITIONED|
+                        exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                        -- HASH_PARTITION_EXCHANGE [$$53]  |PARTITIONED|
+                          project ([$$53, $$54]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            select (eq($$55, $$t2.getField("c_int32"))) retain-untrue ($$54 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- STREAM_SELECT  |PARTITIONED|
+                              project ([$$53, $$55, $$54, $$t2]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  left-outer-unnest-map [$$54, $$t2] <- index-search("ds_inner_untyped", 0, "test", "ds_inner_untyped", true, false, 1, $$61, 1, $$61, true, true, true) [cardinality: 3.0, op-cost: 3.0, total-cost: 3.0]
+                                  -- BTREE_SEARCH  |PARTITIONED|
+                                    exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      order (ASC, $$61) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                      -- STABLE_SORT [$$61(ASC)]  |PARTITIONED|
+                                        exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          project ([$$53, $$55, $$61]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                          -- STREAM_PROJECT  |PARTITIONED|
+                                            exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              left-outer-unnest-map [$$60, $$61] <- index-search("idx_c_int32", 0, "test", "ds_inner_untyped", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- BTREE_SEARCH  |PARTITIONED|
+                                                exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                  project ([$$53, $$55]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                  -- STREAM_PROJECT  |PARTITIONED|
+                                                    assign [$$55] <- [int32($$t1.getField(1))] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                    -- ASSIGN  |PARTITIONED|
+                                                      exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        data-scan []<-[$$53, $$t1] <- test.ds_outer_typed [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/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.010.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.010.plan
new file mode 100644
index 0000000..66c4cf3
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.010.plan
@@ -0,0 +1,94 @@
+distribute result [$$52] [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$52]) [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$52] <- [{"t1_id": $$73, "t2_id": $$54}] [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+      -- ASSIGN  |PARTITIONED|
+        exchange [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+        -- SORT_MERGE_EXCHANGE [$$73(ASC), $$54(ASC) ]  |PARTITIONED|
+          order (ASC, $$73) (ASC, $$54) [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+          -- STABLE_SORT [$$73(ASC), $$54(ASC)]  |PARTITIONED|
+            exchange [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$73, $$54]) [cardinality: 3.15, op-cost: 0.0, total-cost: 12.6]
+              -- STREAM_PROJECT  |PARTITIONED|
+                select ($$74) [cardinality: 3.15, op-cost: 2.1, total-cost: 12.6]
+                -- STREAM_SELECT  |PARTITIONED|
+                  window-aggregate [$$74] <- [win-mark-first-missing-impl($$54)] partition [$$73] order (DESC, $$54) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                  -- WINDOW_STREAM  |PARTITIONED|
+                    exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      order (ASC, $$73) (DESC, $$54) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                      -- STABLE_SORT [$$73(ASC), $$54(DESC)]  |PARTITIONED|
+                        exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                        -- HASH_PARTITION_EXCHANGE [$$73]  |PARTITIONED|
+                          union ($$70, $$61, $$54) ($$53, $$53, $$73) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                          -- UNION_ALL  |PARTITIONED|
+                            exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              project ([$$70, $$53]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                select (eq($$55, $$71.getField(1))) retain-untrue ($$70 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- STREAM_SELECT  |PARTITIONED|
+                                  exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    left-outer-unnest-map [$$70, $$71] <- index-search("ds_inner_typed", 0, "test", "ds_inner_typed", true, false, 1, $$61, 1, $$61, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- BTREE_SEARCH  |PARTITIONED|
+                                      exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        project ([$$53, $$55, $$61]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            split ($$62) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                            -- SPLIT  |PARTITIONED|
+                                              exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                left-outer-unnest-map [$$60, $$61, $$62] <- index-search("idx_c_int32", 0, "test", "ds_inner_typed", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                -- BTREE_SEARCH  |PARTITIONED|
+                                                  exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                  -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                    project ([$$53, $$55]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                    -- STREAM_PROJECT  |PARTITIONED|
+                                                      assign [$$55] <- [int32($$t1.getField(1))] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                      -- ASSIGN  |PARTITIONED|
+                                                        exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                          data-scan []<-[$$53, $$t1] <- test.ds_outer_typed [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|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              project ([$$61, $$53]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                select (eq($$55, $$60)) retain-untrue ($$61 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- STREAM_SELECT  |PARTITIONED|
+                                  project ([$$53, $$55, $$60, $$61]) [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|
+                                      split ($$62) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                      -- SPLIT  |PARTITIONED|
+                                        exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          left-outer-unnest-map [$$60, $$61, $$62] <- index-search("idx_c_int32", 0, "test", "ds_inner_typed", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                          -- BTREE_SEARCH  |PARTITIONED|
+                                            exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                            -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                              project ([$$53, $$55]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                              -- STREAM_PROJECT  |PARTITIONED|
+                                                assign [$$55] <- [int32($$t1.getField(1))] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                -- ASSIGN  |PARTITIONED|
+                                                  exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.1]
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    data-scan []<-[$$53, $$t1] <- test.ds_outer_typed [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_full_parallelism/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.008.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_full_parallelism/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.008.plan
new file mode 100644
index 0000000..1fa96f4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_full_parallelism/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.008.plan
@@ -0,0 +1,102 @@
+distribute result [$$52] [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 ([$$52]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$52] <- [{"t1_id": $$73, "t2_id": $$54}] [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]
+        -- SORT_MERGE_EXCHANGE [$$73(ASC), $$54(ASC) ]  |PARTITIONED|
+          order (ASC, $$73) (ASC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- STABLE_SORT [$$73(ASC), $$54(ASC)]  |PARTITIONED|
+            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$73, $$54]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                select ($$74) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- STREAM_SELECT  |PARTITIONED|
+                  window-aggregate [$$74] <- [win-mark-first-missing-impl($$54)] partition [$$73] order (DESC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- WINDOW_STREAM  |PARTITIONED|
+                    exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      order (ASC, $$73) (DESC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- STABLE_SORT [$$73(ASC), $$54(DESC)]  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- HASH_PARTITION_EXCHANGE [$$73]  |PARTITIONED|
+                          union ($$70, $$61, $$54) ($$53, $$53, $$73) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- UNION_ALL  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              project ([$$70, $$53]) [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]
+                                -- RANDOM_PARTITION_EXCHANGE  |PARTITIONED|
+                                  project ([$$53, $$61, $$70]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    select (eq($$55, $$71.getField(1))) retain-untrue ($$70 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- STREAM_SELECT  |PARTITIONED|
+                                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        left-outer-unnest-map [$$70, $$71] <- index-search("ds_inner_typed", 0, "test", "ds_inner_typed", true, false, 1, $$61, 1, $$61, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                        -- BTREE_SEARCH  |PARTITIONED|
+                                          exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            project ([$$53, $$55, $$61]) [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|
+                                                split ($$62) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                -- SPLIT  |PARTITIONED|
+                                                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    left-outer-unnest-map [$$60, $$61, $$62] <- index-search("idx_c_int32", 0, "test", "ds_inner_typed", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                    -- BTREE_SEARCH  |PARTITIONED|
+                                                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                      -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                        project ([$$53, $$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                        -- STREAM_PROJECT  |PARTITIONED|
+                                                          assign [$$55] <- [int32($$t1.getField("c_int32"))] [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 []<-[$$53, $$t1] <- test.ds_outer_untyped [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|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              project ([$$61, $$53]) [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]
+                                -- RANDOM_PARTITION_EXCHANGE  |PARTITIONED|
+                                  project ([$$53, $$61]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    select (eq($$55, $$60)) retain-untrue ($$61 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- STREAM_SELECT  |PARTITIONED|
+                                      project ([$$53, $$55, $$60, $$61]) [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|
+                                          split ($$62) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                          -- SPLIT  |PARTITIONED|
+                                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              left-outer-unnest-map [$$60, $$61, $$62] <- index-search("idx_c_int32", 0, "test", "ds_inner_typed", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- BTREE_SEARCH  |PARTITIONED|
+                                                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                  project ([$$53, $$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                  -- STREAM_PROJECT  |PARTITIONED|
+                                                    assign [$$55] <- [int32($$t1.getField("c_int32"))] [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 []<-[$$53, $$t1] <- test.ds_outer_untyped [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_full_parallelism/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.010.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_full_parallelism/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.010.plan
new file mode 100644
index 0000000..4939297
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_full_parallelism/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.010.plan
@@ -0,0 +1,102 @@
+distribute result [$$52] [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 ([$$52]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$52] <- [{"t1_id": $$73, "t2_id": $$54}] [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]
+        -- SORT_MERGE_EXCHANGE [$$73(ASC), $$54(ASC) ]  |PARTITIONED|
+          order (ASC, $$73) (ASC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- STABLE_SORT [$$73(ASC), $$54(ASC)]  |PARTITIONED|
+            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$73, $$54]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                select ($$74) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- STREAM_SELECT  |PARTITIONED|
+                  window-aggregate [$$74] <- [win-mark-first-missing-impl($$54)] partition [$$73] order (DESC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- WINDOW_STREAM  |PARTITIONED|
+                    exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      order (ASC, $$73) (DESC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- STABLE_SORT [$$73(ASC), $$54(DESC)]  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- HASH_PARTITION_EXCHANGE [$$73]  |PARTITIONED|
+                          union ($$70, $$61, $$54) ($$53, $$53, $$73) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- UNION_ALL  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              project ([$$70, $$53]) [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]
+                                -- RANDOM_PARTITION_EXCHANGE  |PARTITIONED|
+                                  project ([$$53, $$61, $$70]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    select (eq($$55, $$71.getField(1))) retain-untrue ($$70 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- STREAM_SELECT  |PARTITIONED|
+                                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        left-outer-unnest-map [$$70, $$71] <- index-search("ds_inner_typed", 0, "test", "ds_inner_typed", true, false, 1, $$61, 1, $$61, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                        -- BTREE_SEARCH  |PARTITIONED|
+                                          exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            project ([$$53, $$55, $$61]) [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|
+                                                split ($$62) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                -- SPLIT  |PARTITIONED|
+                                                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    left-outer-unnest-map [$$60, $$61, $$62] <- index-search("idx_c_int32", 0, "test", "ds_inner_typed", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                    -- BTREE_SEARCH  |PARTITIONED|
+                                                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                      -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                        project ([$$53, $$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                        -- STREAM_PROJECT  |PARTITIONED|
+                                                          assign [$$55] <- [int32($$t1.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 []<-[$$53, $$t1] <- test.ds_outer_typed [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|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              project ([$$61, $$53]) [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]
+                                -- RANDOM_PARTITION_EXCHANGE  |PARTITIONED|
+                                  project ([$$53, $$61]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    select (eq($$55, $$60)) retain-untrue ($$61 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- STREAM_SELECT  |PARTITIONED|
+                                      project ([$$53, $$55, $$60, $$61]) [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|
+                                          split ($$62) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                          -- SPLIT  |PARTITIONED|
+                                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              left-outer-unnest-map [$$60, $$61, $$62] <- index-search("idx_c_int32", 0, "test", "ds_inner_typed", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- BTREE_SEARCH  |PARTITIONED|
+                                                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                  project ([$$53, $$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                  -- STREAM_PROJECT  |PARTITIONED|
+                                                    assign [$$55] <- [int32($$t1.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 []<-[$$53, $$t1] <- test.ds_outer_typed [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_less_parallelism/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.008.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_less_parallelism/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.008.plan
new file mode 100644
index 0000000..1fa96f4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_less_parallelism/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.008.plan
@@ -0,0 +1,102 @@
+distribute result [$$52] [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 ([$$52]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$52] <- [{"t1_id": $$73, "t2_id": $$54}] [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]
+        -- SORT_MERGE_EXCHANGE [$$73(ASC), $$54(ASC) ]  |PARTITIONED|
+          order (ASC, $$73) (ASC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- STABLE_SORT [$$73(ASC), $$54(ASC)]  |PARTITIONED|
+            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$73, $$54]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                select ($$74) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- STREAM_SELECT  |PARTITIONED|
+                  window-aggregate [$$74] <- [win-mark-first-missing-impl($$54)] partition [$$73] order (DESC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- WINDOW_STREAM  |PARTITIONED|
+                    exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      order (ASC, $$73) (DESC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- STABLE_SORT [$$73(ASC), $$54(DESC)]  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- HASH_PARTITION_EXCHANGE [$$73]  |PARTITIONED|
+                          union ($$70, $$61, $$54) ($$53, $$53, $$73) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- UNION_ALL  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              project ([$$70, $$53]) [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]
+                                -- RANDOM_PARTITION_EXCHANGE  |PARTITIONED|
+                                  project ([$$53, $$61, $$70]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    select (eq($$55, $$71.getField(1))) retain-untrue ($$70 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- STREAM_SELECT  |PARTITIONED|
+                                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        left-outer-unnest-map [$$70, $$71] <- index-search("ds_inner_typed", 0, "test", "ds_inner_typed", true, false, 1, $$61, 1, $$61, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                        -- BTREE_SEARCH  |PARTITIONED|
+                                          exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            project ([$$53, $$55, $$61]) [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|
+                                                split ($$62) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                -- SPLIT  |PARTITIONED|
+                                                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    left-outer-unnest-map [$$60, $$61, $$62] <- index-search("idx_c_int32", 0, "test", "ds_inner_typed", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                    -- BTREE_SEARCH  |PARTITIONED|
+                                                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                      -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                        project ([$$53, $$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                        -- STREAM_PROJECT  |PARTITIONED|
+                                                          assign [$$55] <- [int32($$t1.getField("c_int32"))] [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 []<-[$$53, $$t1] <- test.ds_outer_untyped [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|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              project ([$$61, $$53]) [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]
+                                -- RANDOM_PARTITION_EXCHANGE  |PARTITIONED|
+                                  project ([$$53, $$61]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    select (eq($$55, $$60)) retain-untrue ($$61 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- STREAM_SELECT  |PARTITIONED|
+                                      project ([$$53, $$55, $$60, $$61]) [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|
+                                          split ($$62) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                          -- SPLIT  |PARTITIONED|
+                                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              left-outer-unnest-map [$$60, $$61, $$62] <- index-search("idx_c_int32", 0, "test", "ds_inner_typed", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- BTREE_SEARCH  |PARTITIONED|
+                                                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                  project ([$$53, $$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                  -- STREAM_PROJECT  |PARTITIONED|
+                                                    assign [$$55] <- [int32($$t1.getField("c_int32"))] [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 []<-[$$53, $$t1] <- test.ds_outer_untyped [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_less_parallelism/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.010.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_less_parallelism/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.010.plan
new file mode 100644
index 0000000..4939297
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_less_parallelism/leftouterjoin/index-only-leftouterjoin/index-only-leftouterjoin.010.plan
@@ -0,0 +1,102 @@
+distribute result [$$52] [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 ([$$52]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$52] <- [{"t1_id": $$73, "t2_id": $$54}] [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]
+        -- SORT_MERGE_EXCHANGE [$$73(ASC), $$54(ASC) ]  |PARTITIONED|
+          order (ASC, $$73) (ASC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- STABLE_SORT [$$73(ASC), $$54(ASC)]  |PARTITIONED|
+            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              project ([$$73, $$54]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- STREAM_PROJECT  |PARTITIONED|
+                select ($$74) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- STREAM_SELECT  |PARTITIONED|
+                  window-aggregate [$$74] <- [win-mark-first-missing-impl($$54)] partition [$$73] order (DESC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- WINDOW_STREAM  |PARTITIONED|
+                    exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      order (ASC, $$73) (DESC, $$54) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- STABLE_SORT [$$73(ASC), $$54(DESC)]  |PARTITIONED|
+                        exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- HASH_PARTITION_EXCHANGE [$$73]  |PARTITIONED|
+                          union ($$70, $$61, $$54) ($$53, $$53, $$73) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- UNION_ALL  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              project ([$$70, $$53]) [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]
+                                -- RANDOM_PARTITION_EXCHANGE  |PARTITIONED|
+                                  project ([$$53, $$61, $$70]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    select (eq($$55, $$71.getField(1))) retain-untrue ($$70 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- STREAM_SELECT  |PARTITIONED|
+                                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        left-outer-unnest-map [$$70, $$71] <- index-search("ds_inner_typed", 0, "test", "ds_inner_typed", true, false, 1, $$61, 1, $$61, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                        -- BTREE_SEARCH  |PARTITIONED|
+                                          exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            project ([$$53, $$55, $$61]) [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|
+                                                split ($$62) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                -- SPLIT  |PARTITIONED|
+                                                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    left-outer-unnest-map [$$60, $$61, $$62] <- index-search("idx_c_int32", 0, "test", "ds_inner_typed", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                    -- BTREE_SEARCH  |PARTITIONED|
+                                                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                      -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                        project ([$$53, $$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                        -- STREAM_PROJECT  |PARTITIONED|
+                                                          assign [$$55] <- [int32($$t1.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 []<-[$$53, $$t1] <- test.ds_outer_typed [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|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              project ([$$61, $$53]) [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]
+                                -- RANDOM_PARTITION_EXCHANGE  |PARTITIONED|
+                                  project ([$$53, $$61]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    select (eq($$55, $$60)) retain-untrue ($$61 <- missing) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- STREAM_SELECT  |PARTITIONED|
+                                      project ([$$53, $$55, $$60, $$61]) [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|
+                                          split ($$62) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                          -- SPLIT  |PARTITIONED|
+                                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              left-outer-unnest-map [$$60, $$61, $$62] <- index-search("idx_c_int32", 0, "test", "ds_inner_typed", true, true, 1, $$55, 1, $$55, true, true, true) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                              -- BTREE_SEARCH  |PARTITIONED|
+                                                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                  project ([$$53, $$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                                  -- STREAM_PROJECT  |PARTITIONED|
+                                                    assign [$$55] <- [int32($$t1.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 []<-[$$53, $$t1] <- test.ds_outer_typed [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/testsuite_sqlpp.xml b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
index d24873e..fc0ceca 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -14300,6 +14300,11 @@
         <output-dir compare="Text">right_branch_opt_1</output-dir>
       </compilation-unit>
     </test-case>
+    <test-case FilePath="leftouterjoin">
+      <compilation-unit name="index-only-leftouterjoin">
+        <output-dir compare="Text">index-only-leftouterjoin</output-dir>
+      </compilation-unit>
+    </test-case>
   </test-group>
   <test-group name="index-leftouterjoin">
     <test-case FilePath="index-leftouterjoin">
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/SelectOperator.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/SelectOperator.java
index 0b2c32d..3bcf29c 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/SelectOperator.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/SelectOperator.java
@@ -46,19 +46,19 @@
 public class SelectOperator extends AbstractLogicalOperator {
     private final Mutable<ILogicalExpression> condition;
     private final IAlgebricksConstantValue retainMissingAsValue;
-    private final LogicalVariable nullPlaceholderVar;
+    private LogicalVariable missingPlaceholderVar;
 
     public SelectOperator(Mutable<ILogicalExpression> condition) {
         this(condition, null, null);
     }
 
     public SelectOperator(Mutable<ILogicalExpression> condition, IAlgebricksConstantValue retainMissingAsValue,
-            LogicalVariable nullPlaceholderVar) {
+            LogicalVariable missingPlaceholderVar) {
         this.condition = condition;
         if (retainMissingAsValue == null) {
             this.retainMissingAsValue = null;
-            if (nullPlaceholderVar != null) {
-                throw new IllegalArgumentException(nullPlaceholderVar.toString());
+            if (missingPlaceholderVar != null) {
+                throw new IllegalArgumentException(missingPlaceholderVar.toString());
             }
         } else if (retainMissingAsValue.isMissing()) {
             this.retainMissingAsValue = ConstantExpression.MISSING.getValue();
@@ -67,7 +67,7 @@
         } else {
             throw new IllegalArgumentException(retainMissingAsValue.toString());
         }
-        this.nullPlaceholderVar = nullPlaceholderVar;
+        this.missingPlaceholderVar = missingPlaceholderVar;
     }
 
     @Override
@@ -84,12 +84,19 @@
     }
 
     public LogicalVariable getMissingPlaceholderVariable() {
-        return nullPlaceholderVar;
+        return missingPlaceholderVar;
+    }
+
+    public void setMissingPlaceholderVar(LogicalVariable var) {
+        if (var != null && retainMissingAsValue == null) {
+            throw new IllegalArgumentException("NULL/MISSING var " + var + " is set, but its value not specified");
+        }
+        missingPlaceholderVar = var;
     }
 
     @Override
     public void recomputeSchema() {
-        schema = new ArrayList<LogicalVariable>(inputs.get(0).getValue().getSchema());
+        schema = new ArrayList<>(inputs.get(0).getValue().getSchema());
     }
 
     @Override
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/SubstituteVariableVisitor.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/SubstituteVariableVisitor.java
index 8349945..faf3c11 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/SubstituteVariableVisitor.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/SubstituteVariableVisitor.java
@@ -257,6 +257,10 @@
     public Void visitSelectOperator(SelectOperator op, Pair<LogicalVariable, LogicalVariable> pair)
             throws AlgebricksException {
         substUsedVariablesInExpr(op.getCondition(), pair.first, pair.second);
+        LogicalVariable missingPlaceholderVar = op.getMissingPlaceholderVariable();
+        if (missingPlaceholderVar != null && missingPlaceholderVar.equals(pair.first)) {
+            op.setMissingPlaceholderVar(pair.second);
+        }
         // SELECT operator may add its used variable
         // to its own output type environment as 'nonMissableVariable' (not(is-missing($used_var))
         // therefore we need perform variable substitution in its own type environment