This is an automated email from the ASF dual-hosted git repository.

wyk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb.git


The following commit(s) were added to refs/heads/master by this push:
     new e9aed32bfb [MULTIPLE ISSUES][COMP][STO] Columnar compiler and cursor 
fixes
e9aed32bfb is described below

commit e9aed32bfb64bf0e6549ae7751660050399754de
Author: Wail Alkowaileet <[email protected]>
AuthorDate: Wed Nov 15 18:28:12 2023 -0800

    [MULTIPLE ISSUES][COMP][STO] Columnar compiler and cursor fixes
    
    - user model changes: no
    - storage format changes: no
    - interface changes: no
    
    Details:
    Fixes both ASTERIXDB-3311 and ASTERIXDB-3312
    
    Change-Id: I0415bf4876ffe0e5bc6b5fa4c60aee7f9f6afce2
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/17956
    Integration-Tests: Jenkins <[email protected]>
    Tested-by: Jenkins <[email protected]>
    Reviewed-by: Wail Alkowaileet <[email protected]>
    Reviewed-by: Ali Alsuliman <[email protected]>
---
 .../operators/physical/BTreeSearchPOperator.java   | 27 ++++++++-----
 .../rules/pushdown/PushdownProcessorsExecutor.java |  4 ++
 .../pushdown/visitor/PushdownOperatorVisitor.java  | 21 ++++++----
 .../composite-pks/composite-pks.005.query.sqlpp    | 25 ++++++++++++
 .../outer-join/outer_join.01.ddl.sqlpp             | 44 ++++++++++++++++++++
 .../outer-join/outer_join.02.update.sqlpp          | 47 ++++++++++++++++++++++
 .../outer-join/outer_join.10.query.sqlpp           | 26 ++++++++++++
 .../outer-join/outer_join.11.query.sqlpp           | 26 ++++++++++++
 .../outer-join/outer_join.12.query.sqlpp           | 26 ++++++++++++
 .../outer-join/outer_join.13.query.sqlpp           | 26 ++++++++++++
 .../outer-join/outer_join.20.ddl.sqlpp             | 26 ++++++++++++
 .../outer-join/outer_join.30.query.sqlpp           | 26 ++++++++++++
 .../outer-join/outer_join.31.query.sqlpp           | 26 ++++++++++++
 .../outer-join/outer_join.32.query.sqlpp           | 26 ++++++++++++
 .../outer-join/outer_join.33.query.sqlpp           | 26 ++++++++++++
 .../column/composite-pks/composite-pks.005.adm     |  1 +
 .../secondary-index/outer-join/outer_join.10.adm   |  9 +++++
 .../secondary-index/outer-join/outer_join.11.adm   |  9 +++++
 .../secondary-index/outer-join/outer_join.12.adm   |  6 +++
 .../secondary-index/outer-join/outer_join.13.adm   |  6 +++
 .../secondary-index/outer-join/outer_join.30.adm   |  9 +++++
 .../secondary-index/outer-join/outer_join.31.adm   |  9 +++++
 .../secondary-index/outer-join/outer_join.32.adm   |  6 +++
 .../secondary-index/outer-join/outer_join.33.adm   |  6 +++
 .../src/test/resources/runtimets/sqlpp_queries.xml |  5 +++
 .../column/operation/query/ColumnAssembler.java    |  4 ++
 .../operation/query/QueryColumnTupleProjector.java |  2 +-
 .../tuple/AbstractAsterixColumnTupleReference.java |  3 +-
 .../column/tuple/QueryColumnTupleReference.java    |  9 ++++-
 .../tuple/QueryColumnWithMetaTupleReference.java   | 15 ++++++-
 .../logical/LeftOuterUnnestMapOperator.java        | 19 +++++++++
 .../visitors/IsomorphismOperatorVisitor.java       |  3 +-
 ...calOperatorDeepCopyWithNewVariablesVisitor.java |  4 +-
 .../logical/visitors/OperatorDeepCopyVisitor.java  |  1 +
 .../LogicalOperatorPrettyPrintVisitor.java         |  4 +-
 .../LogicalOperatorPrettyPrintVisitorJson.java     |  9 ++++-
 .../impls/btree/ColumnBTreeRangeSearchCursor.java  | 18 +++++----
 .../lsm/tuples/AbstractColumnTupleReference.java   | 10 +++++
 38 files changed, 534 insertions(+), 35 deletions(-)

diff --git 
a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/BTreeSearchPOperator.java
 
b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/BTreeSearchPOperator.java
index 821be72962..b965512407 100644
--- 
a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/BTreeSearchPOperator.java
+++ 
b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/BTreeSearchPOperator.java
@@ -39,6 +39,7 @@ import 
org.apache.hyracks.algebricks.common.constraints.AlgebricksPartitionConst
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.common.utils.ListSet;
 import org.apache.hyracks.algebricks.common.utils.Pair;
+import 
org.apache.hyracks.algebricks.core.algebra.base.DefaultProjectionFiltrationInfo;
 import org.apache.hyracks.algebricks.core.algebra.base.IHyracksJobBuilder;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
@@ -133,27 +134,20 @@ public class BTreeSearchPOperator extends 
IndexSearchPOperator {
         long outputLimit = -1;
         boolean retainMissing = false;
         IMissingWriterFactory nonMatchWriterFactory = null;
-        ITupleProjectorFactory tupleProjectorFactory = 
DefaultTupleProjectorFactory.INSTANCE;
+        IProjectionFiltrationInfo projectionFiltrationInfo = 
DefaultProjectionFiltrationInfo.INSTANCE;
         switch (unnestMap.getOperatorTag()) {
             case UNNEST_MAP:
                 UnnestMapOperator unnestMapOp = (UnnestMapOperator) unnestMap;
+                projectionFiltrationInfo = 
unnestMapOp.getProjectionFiltrationInfo();
                 outputLimit = unnestMapOp.getOutputLimit();
                 if (unnestMapOp.getSelectCondition() != null) {
                     tupleFilterFactory = 
metadataProvider.createTupleFilterFactory(new IOperatorSchema[] { opSchema },
                             typeEnv, 
unnestMapOp.getSelectCondition().getValue(), context);
                 }
-                DatasetFormatInfo formatInfo = dataset.getDatasetFormatInfo();
-                if (isPrimaryIndex && formatInfo.getFormat() == 
DatasetConfig.DatasetFormat.COLUMN) {
-                    IProjectionFiltrationInfo projectionFiltrationInfo = 
unnestMapOp.getProjectionFiltrationInfo();
-                    ARecordType datasetType = (ARecordType) 
metadataProvider.findType(dataset);
-                    ARecordType metaItemType = (ARecordType) 
metadataProvider.findMetaType(dataset);
-                    datasetType = (ARecordType) 
metadataProvider.findTypeForDatasetWithoutType(datasetType,
-                            metaItemType, dataset);
-                    tupleProjectorFactory = 
IndexUtil.createTupleProjectorFactory(context, typeEnv, formatInfo,
-                            projectionFiltrationInfo, datasetType, 
metaItemType, dataset.getPrimaryKeys().size());
-                }
                 break;
             case LEFT_OUTER_UNNEST_MAP:
+                LeftOuterUnnestMapOperator outerUnnestMapOperator = 
(LeftOuterUnnestMapOperator) unnestMap;
+                projectionFiltrationInfo = 
outerUnnestMapOperator.getProjectionFiltrationInfo();
                 // By nature, LEFT_OUTER_UNNEST_MAP should generate missing 
(or null) values for non-matching tuples.
                 retainMissing = true;
                 nonMatchWriterFactory =
@@ -165,6 +159,17 @@ public class BTreeSearchPOperator extends 
IndexSearchPOperator {
                         String.valueOf(unnestMap.getOperatorTag()));
         }
 
+        ITupleProjectorFactory tupleProjectorFactory = 
DefaultTupleProjectorFactory.INSTANCE;
+        DatasetFormatInfo formatInfo = dataset.getDatasetFormatInfo();
+        if (isPrimaryIndex && formatInfo.getFormat() == 
DatasetConfig.DatasetFormat.COLUMN) {
+            ARecordType datasetType = (ARecordType) 
metadataProvider.findType(dataset);
+            ARecordType metaItemType = (ARecordType) 
metadataProvider.findMetaType(dataset);
+            datasetType =
+                    (ARecordType) 
metadataProvider.findTypeForDatasetWithoutType(datasetType, metaItemType, 
dataset);
+            tupleProjectorFactory = 
IndexUtil.createTupleProjectorFactory(context, typeEnv, formatInfo,
+                    projectionFiltrationInfo, datasetType, metaItemType, 
dataset.getPrimaryKeys().size());
+        }
+
         Pair<IOperatorDescriptor, AlgebricksPartitionConstraint> btreeSearch = 
metadataProvider.getBtreeSearchRuntime(
                 builder.getJobSpec(), opSchema, typeEnv, context, 
jobGenParams.getRetainInput(), retainMissing,
                 nonMatchWriterFactory, dataset, jobGenParams.getIndexName(), 
lowKeyIndexes, highKeyIndexes,
diff --git 
a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/PushdownProcessorsExecutor.java
 
b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/PushdownProcessorsExecutor.java
index 01d3aeb222..023e4da61e 100644
--- 
a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/PushdownProcessorsExecutor.java
+++ 
b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/PushdownProcessorsExecutor.java
@@ -43,6 +43,7 @@ import 
org.apache.hyracks.algebricks.core.algebra.base.LogicalOperatorTag;
 import 
org.apache.hyracks.algebricks.core.algebra.metadata.IProjectionFiltrationInfo;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractScanOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.DataSourceScanOperator;
+import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestMapOperator;
 
 public class PushdownProcessorsExecutor {
@@ -135,6 +136,9 @@ public class PushdownProcessorsExecutor {
         } else if (scanOp.getOperatorTag() == LogicalOperatorTag.UNNEST_MAP) {
             UnnestMapOperator unnestMapOp = (UnnestMapOperator) scanOp;
             unnestMapOp.setProjectionFiltrationInfo(info);
+        } else if (scanOp.getOperatorTag() == 
LogicalOperatorTag.LEFT_OUTER_UNNEST_MAP) {
+            LeftOuterUnnestMapOperator outerUnnestMapOp = 
(LeftOuterUnnestMapOperator) scanOp;
+            outerUnnestMapOp.setProjectionFiltrationInfo(info);
         }
     }
 }
diff --git 
a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/visitor/PushdownOperatorVisitor.java
 
b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/visitor/PushdownOperatorVisitor.java
index 2a8e999678..b00f8a1e97 100644
--- 
a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/visitor/PushdownOperatorVisitor.java
+++ 
b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/visitor/PushdownOperatorVisitor.java
@@ -53,6 +53,7 @@ import 
org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractScanOperator;
+import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractUnnestMapOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AggregateOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AssignOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.DataSourceScanOperator;
@@ -167,6 +168,18 @@ public class PushdownOperatorVisitor implements 
ILogicalOperatorVisitor<Void, Vo
         return null;
     }
 
+    /**
+     * From the {@link LeftOuterUnnestMapOperator}, we need to register the 
payload variable (record variable) to check
+     * which expression in the plan is using it.
+     */
+    @Override
+    public Void visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, 
Void arg) throws AlgebricksException {
+        visitInputs(op);
+        DatasetDataSource datasetDataSource = 
getDatasetDataSourceIfApplicable(getDataSourceFromUnnestMapOperator(op));
+        registerDatasetIfApplicable(datasetDataSource, op);
+        return null;
+    }
+
     @Override
     public Void visitAggregateOperator(AggregateOperator op, Void arg) throws 
AlgebricksException {
         visitInputs(op, op.getVariables());
@@ -227,7 +240,7 @@ public class PushdownOperatorVisitor implements 
ILogicalOperatorVisitor<Void, Vo
      * @param unnest unnest map operator
      * @return datasource
      */
-    private DataSource getDataSourceFromUnnestMapOperator(UnnestMapOperator 
unnest) throws AlgebricksException {
+    private DataSource 
getDataSourceFromUnnestMapOperator(AbstractUnnestMapOperator unnest) throws 
AlgebricksException {
         AbstractFunctionCallExpression funcExpr = 
(AbstractFunctionCallExpression) unnest.getExpressionRef().getValue();
         String dataverse = ConstantExpressionUtil.getStringArgument(funcExpr, 
DATAVERSE_NAME_POS);
         String dataset = ConstantExpressionUtil.getStringArgument(funcExpr, 
DATASET_NAME_POS);
@@ -477,12 +490,6 @@ public class PushdownOperatorVisitor implements 
ILogicalOperatorVisitor<Void, Vo
         return null;
     }
 
-    @Override
-    public Void visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, 
Void arg) throws AlgebricksException {
-        visitInputs(op);
-        return null;
-    }
-
     @Override
     public Void visitDistinctOperator(DistinctOperator op, Void arg) throws 
AlgebricksException {
         visitInputs(op);
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/composite-pks/composite-pks.005.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/composite-pks/composite-pks.005.query.sqlpp
new file mode 100644
index 0000000000..63413cd027
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/composite-pks/composite-pks.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;
+
+SELECT VALUE c
+FROM CompositeKey c
+WHERE k1 = "2"
+ORDER BY to_bigint(k1);
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.01.ddl.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.01.ddl.sqlpp
new file mode 100644
index 0000000000..65c9402fa6
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.01.ddl.sqlpp
@@ -0,0 +1,44 @@
+/*
+ * 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 DATASET ds1Row PRIMARY KEY (id: int)
+WITH {
+  "storage-format": {"format": "row"}
+};
+
+CREATE DATASET ds2Row PRIMARY KEY (id: int)
+WITH {
+  "storage-format": {"format": "row"}
+};
+
+
+CREATE DATASET ds1Column PRIMARY KEY (id: int)
+WITH {
+  "storage-format": {"format": "column"}
+};
+
+CREATE DATASET ds2Column PRIMARY KEY (id: int)
+WITH {
+  "storage-format": {"format": "column"}
+};
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.02.update.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.02.update.sqlpp
new file mode 100644
index 0000000000..5afc3630b4
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.02.update.sqlpp
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use test;
+
+-- Row inserts
+INSERT INTO ds1Row ({"id": 1, "a": 1, "b": 1 });
+INSERT INTO ds1Row ({"id": 2, "a": 1, "b": 2 });
+INSERT INTO ds1Row ({"id": 3, "a": 2, "b": 3 });
+INSERT INTO ds1Row ({"id": 4, "a": 2, "b": 4 });
+INSERT INTO ds1Row ({"id": 5, "a": 3, "b": 5 });
+INSERT INTO ds1Row ({"id": 6, "a": 3, "b": 6 });
+INSERT INTO ds1Row ({"id": 7, "a": 4, "b": 7 });
+
+INSERT INTO ds2Row ({"id": 100, "x": 2, "y": 100 });
+INSERT INTO ds2Row ({"id": 101, "x": 2, "y": 101 });
+INSERT INTO ds2Row ({"id": 102, "x": 3, "y": 102 });
+
+
+-- Column inserts
+INSERT INTO ds1Column ({"id": 1, "a": 1, "b": 1 });
+INSERT INTO ds1Column ({"id": 2, "a": 1, "b": 2 });
+INSERT INTO ds1Column ({"id": 3, "a": 2, "b": 3 });
+INSERT INTO ds1Column ({"id": 4, "a": 2, "b": 4 });
+INSERT INTO ds1Column ({"id": 5, "a": 3, "b": 5 });
+INSERT INTO ds1Column ({"id": 6, "a": 3, "b": 6 });
+INSERT INTO ds1Column ({"id": 7, "a": 4, "b": 7 });
+
+INSERT INTO ds2Column ({"id": 100, "x": 2, "y": 100 });
+INSERT INTO ds2Column ({"id": 101, "x": 2, "y": 101 });
+INSERT INTO ds2Column ({"id": 102, "x": 3, "y": 102 });
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.10.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.10.query.sqlpp
new file mode 100644
index 0000000000..a1f17bd5e7
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.10.query.sqlpp
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SELECT ds1Row.b, ds2Row.y
+FROM            ds1Row
+LEFT OUTER JOIN ds2Row
+ON ds1Row.a = ds2Row.x
+ORDER BY ds1Row.b, ds2Row.y;
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.11.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.11.query.sqlpp
new file mode 100644
index 0000000000..a1e5d73b00
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.11.query.sqlpp
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SELECT ds1Column.b, ds2Column.y
+FROM            ds1Column
+LEFT OUTER JOIN ds2Column
+ON  ds1Column.a = ds2Column.x
+ORDER BY ds1Column.b, ds2Column.y;
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.12.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.12.query.sqlpp
new file mode 100644
index 0000000000..6524c49d51
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.12.query.sqlpp
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SELECT ds1Row.b, ds2Row.y
+FROM            ds1Row
+RIGHT OUTER JOIN ds2Row
+ON ds1Row.a = ds2Row.x
+ORDER BY ds1Row.b, ds2Row.y;
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.13.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.13.query.sqlpp
new file mode 100644
index 0000000000..e118cbd77c
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.13.query.sqlpp
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SELECT ds1Column.b, ds2Column.y
+FROM            ds1Column
+RIGHT OUTER JOIN ds2Column
+ON  ds1Column.a = ds2Column.x
+ORDER BY ds1Column.b, ds2Column.y;
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.20.ddl.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.20.ddl.sqlpp
new file mode 100644
index 0000000000..b40850b1e5
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.20.ddl.sqlpp
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+CREATE INDEX idxRow_a ON ds1Row(a:int);
+CREATE INDEX idxRow_x ON ds2Row(x:int);
+
+CREATE INDEX idxColumn_a ON ds1Column(a:int);
+CREATE INDEX idxColumn_x ON ds2Column(x:int);
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.30.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.30.query.sqlpp
new file mode 100644
index 0000000000..b674f2a45c
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.30.query.sqlpp
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SELECT ds1Row.b, ds2Row.y
+FROM            ds1Row
+LEFT OUTER JOIN ds2Row
+ON to_bigint(ds1Row.a) /*+indexnl*/ = ds2Row.x
+ORDER BY ds1Row.b, ds2Row.y;
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.31.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.31.query.sqlpp
new file mode 100644
index 0000000000..7acb04c18b
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.31.query.sqlpp
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SELECT ds1Column.b, ds2Column.y
+FROM            ds1Column
+LEFT OUTER JOIN ds2Column
+ON  to_bigint(ds1Column.a) /*+indexnl*/ = ds2Column.x
+ORDER BY ds1Column.b, ds2Column.y;
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.32.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.32.query.sqlpp
new file mode 100644
index 0000000000..504b9f5ac6
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.32.query.sqlpp
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SELECT ds1Row.b, ds2Row.y
+FROM            ds1Row
+RIGHT OUTER JOIN ds2Row
+ON ds1Row.a /*+indexnl*/ = to_bigint(ds2Row.x)
+ORDER BY ds1Row.b, ds2Row.y;
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.33.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.33.query.sqlpp
new file mode 100644
index 0000000000..ed68895af6
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/secondary-index/outer-join/outer_join.33.query.sqlpp
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+SELECT ds1Column.b, ds2Column.y
+FROM            ds1Column
+RIGHT OUTER JOIN ds2Column
+ON  ds1Column.a /*+indexnl*/ = to_bigint(ds2Column.x)
+ORDER BY ds1Column.b, ds2Column.y;
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/column/composite-pks/composite-pks.005.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/composite-pks/composite-pks.005.adm
new file mode 100644
index 0000000000..2fa2fd8e30
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/composite-pks/composite-pks.005.adm
@@ -0,0 +1 @@
+{ "k1": "2", "k2": "The quick brown fox jumps over the lazy dog" }
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.10.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.10.adm
new file mode 100644
index 0000000000..49404265db
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.10.adm
@@ -0,0 +1,9 @@
+{ "b": 1 }
+{ "b": 2 }
+{ "b": 3, "y": 100 }
+{ "b": 3, "y": 101 }
+{ "b": 4, "y": 100 }
+{ "b": 4, "y": 101 }
+{ "b": 5, "y": 102 }
+{ "b": 6, "y": 102 }
+{ "b": 7 }
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.11.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.11.adm
new file mode 100644
index 0000000000..49404265db
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.11.adm
@@ -0,0 +1,9 @@
+{ "b": 1 }
+{ "b": 2 }
+{ "b": 3, "y": 100 }
+{ "b": 3, "y": 101 }
+{ "b": 4, "y": 100 }
+{ "b": 4, "y": 101 }
+{ "b": 5, "y": 102 }
+{ "b": 6, "y": 102 }
+{ "b": 7 }
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.12.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.12.adm
new file mode 100644
index 0000000000..488285739b
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.12.adm
@@ -0,0 +1,6 @@
+{ "b": 3, "y": 100 }
+{ "b": 3, "y": 101 }
+{ "b": 4, "y": 100 }
+{ "b": 4, "y": 101 }
+{ "b": 5, "y": 102 }
+{ "b": 6, "y": 102 }
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.13.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.13.adm
new file mode 100644
index 0000000000..488285739b
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.13.adm
@@ -0,0 +1,6 @@
+{ "b": 3, "y": 100 }
+{ "b": 3, "y": 101 }
+{ "b": 4, "y": 100 }
+{ "b": 4, "y": 101 }
+{ "b": 5, "y": 102 }
+{ "b": 6, "y": 102 }
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.30.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.30.adm
new file mode 100644
index 0000000000..49404265db
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.30.adm
@@ -0,0 +1,9 @@
+{ "b": 1 }
+{ "b": 2 }
+{ "b": 3, "y": 100 }
+{ "b": 3, "y": 101 }
+{ "b": 4, "y": 100 }
+{ "b": 4, "y": 101 }
+{ "b": 5, "y": 102 }
+{ "b": 6, "y": 102 }
+{ "b": 7 }
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.31.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.31.adm
new file mode 100644
index 0000000000..49404265db
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.31.adm
@@ -0,0 +1,9 @@
+{ "b": 1 }
+{ "b": 2 }
+{ "b": 3, "y": 100 }
+{ "b": 3, "y": 101 }
+{ "b": 4, "y": 100 }
+{ "b": 4, "y": 101 }
+{ "b": 5, "y": 102 }
+{ "b": 6, "y": 102 }
+{ "b": 7 }
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.32.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.32.adm
new file mode 100644
index 0000000000..488285739b
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.32.adm
@@ -0,0 +1,6 @@
+{ "b": 3, "y": 100 }
+{ "b": 3, "y": 101 }
+{ "b": 4, "y": 100 }
+{ "b": 4, "y": 101 }
+{ "b": 5, "y": 102 }
+{ "b": 6, "y": 102 }
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.33.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.33.adm
new file mode 100644
index 0000000000..488285739b
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/secondary-index/outer-join/outer_join.33.adm
@@ -0,0 +1,6 @@
+{ "b": 3, "y": 100 }
+{ "b": 3, "y": 101 }
+{ "b": 4, "y": 100 }
+{ "b": 4, "y": 101 }
+{ "b": 5, "y": 102 }
+{ "b": 6, "y": 102 }
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml 
b/asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml
index f65f6431c7..10fda37325 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml
@@ -16398,6 +16398,11 @@
         <output-dir 
compare="Text">secondary-index/create-index/after-upsert-with-meta</output-dir>
       </compilation-unit>
     </test-case>
+    <test-case FilePath="column">
+      <compilation-unit name="secondary-index/outer-join">
+        <output-dir compare="Text">secondary-index/outer-join</output-dir>
+      </compilation-unit>
+    </test-case>
     <test-case FilePath="column">
       <compilation-unit name="analyze-dataset">
         <output-dir compare="Text">analyze-dataset</output-dir>
diff --git 
a/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/operation/query/ColumnAssembler.java
 
b/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/operation/query/ColumnAssembler.java
index 30d01d5346..f5a327deee 100644
--- 
a/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/operation/query/ColumnAssembler.java
+++ 
b/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/operation/query/ColumnAssembler.java
@@ -101,6 +101,10 @@ public final class ColumnAssembler {
         return rootAssembler.getValue();
     }
 
+    public IValueReference getPreviousValue() {
+        return rootAssembler.getValue();
+    }
+
     public int getNumberOfColumns() {
         return assemblers.length;
     }
diff --git 
a/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/operation/query/QueryColumnTupleProjector.java
 
b/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/operation/query/QueryColumnTupleProjector.java
index b7b97458e9..369a891aa7 100644
--- 
a/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/operation/query/QueryColumnTupleProjector.java
+++ 
b/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/operation/query/QueryColumnTupleProjector.java
@@ -112,7 +112,7 @@ public class QueryColumnTupleProjector implements 
IColumnTupleProjector {
     }
 
     protected void writeMeta(ITupleReference tuple, DataOutput dos, 
ArrayTupleBuilder tb) throws IOException {
-        //NoOp
+        // NoOp
     }
 
     protected int getNumberOfTupleFields() {
diff --git 
a/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/tuple/AbstractAsterixColumnTupleReference.java
 
b/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/tuple/AbstractAsterixColumnTupleReference.java
index 8beae4eb4a..87ec700bbb 100644
--- 
a/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/tuple/AbstractAsterixColumnTupleReference.java
+++ 
b/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/tuple/AbstractAsterixColumnTupleReference.java
@@ -236,9 +236,10 @@ public abstract class AbstractAsterixColumnTupleReference 
extends AbstractColumn
         }
     }
 
-    protected void appendExceptionInformation(ColumnarValueException e) {
+    protected void appendExceptionInformation(ColumnarValueException e, int 
previousIndex) {
         ObjectNode node = e.createNode(getClass().getSimpleName());
         node.put("isAntiMatter", isAntimatter());
+        node.put("previousIndex", previousIndex);
         ArrayNode pkNodes = node.putArray("primaryKeyReaders");
         for (IColumnValuesReader reader : primaryKeyReaders) {
             reader.appendReaderInformation(pkNodes.addObject());
diff --git 
a/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/tuple/QueryColumnTupleReference.java
 
b/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/tuple/QueryColumnTupleReference.java
index ba5ffb7594..19cb753300 100644
--- 
a/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/tuple/QueryColumnTupleReference.java
+++ 
b/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/tuple/QueryColumnTupleReference.java
@@ -53,6 +53,7 @@ public final class QueryColumnTupleReference extends 
AbstractAsterixColumnTupleR
     private final IFilterApplier filterApplier;
     private final List<IColumnValuesReader> filterColumnReaders;
     private final AbstractBytesInputStream[] filteredColumnStreams;
+    private int previousIndex;
 
     public QueryColumnTupleReference(int componentIndex, 
ColumnBTreeReadLeafFrame frame,
             QueryColumnMetadata columnMetadata, IColumnReadMultiPageOp 
multiPageOp) {
@@ -78,6 +79,7 @@ public final class QueryColumnTupleReference extends 
AbstractAsterixColumnTupleR
                 filteredColumnStreams[i] = new ByteBufferInputStream();
             }
         }
+        previousIndex = -1;
     }
 
     @Override
@@ -98,6 +100,7 @@ public final class QueryColumnTupleReference extends 
AbstractAsterixColumnTupleR
         boolean readColumns = rangeFilterEvaluator.evaluate();
         assembler.reset(readColumns ? numberOfTuples : 0);
         columnFilterEvaluator.reset();
+        previousIndex = -1;
         return readColumns;
     }
 
@@ -129,9 +132,13 @@ public final class QueryColumnTupleReference extends 
AbstractAsterixColumnTupleR
 
     public IValueReference getAssembledValue() throws HyracksDataException {
         try {
+            if (previousIndex == tupleIndex) {
+                return assembler.getPreviousValue();
+            }
+            previousIndex = tupleIndex;
             return filterApplier.getTuple();
         } catch (ColumnarValueException e) {
-            appendExceptionInformation(e);
+            appendExceptionInformation(e, previousIndex);
             throw e;
         }
 
diff --git 
a/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/tuple/QueryColumnWithMetaTupleReference.java
 
b/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/tuple/QueryColumnWithMetaTupleReference.java
index a2d6b549af..fc1f1d2b16 100644
--- 
a/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/tuple/QueryColumnWithMetaTupleReference.java
+++ 
b/asterixdb/asterix-column/src/main/java/org/apache/asterix/column/tuple/QueryColumnWithMetaTupleReference.java
@@ -55,6 +55,7 @@ public final class QueryColumnWithMetaTupleReference extends 
AbstractAsterixColu
     private final IFilterApplier filterApplier;
     private final List<IColumnValuesReader> filterColumnReaders;
     private final AbstractBytesInputStream[] filteredColumnStreams;
+    private int previousIndex;
 
     public QueryColumnWithMetaTupleReference(int componentIndex, 
ColumnBTreeReadLeafFrame frame,
             QueryColumnMetadata columnMetadata, IColumnReadMultiPageOp 
multiPageOp) {
@@ -81,6 +82,7 @@ public final class QueryColumnWithMetaTupleReference extends 
AbstractAsterixColu
                 filteredColumnStreams[i] = new ByteBufferInputStream();
             }
         }
+        previousIndex = -1;
     }
 
     @Override
@@ -102,6 +104,7 @@ public final class QueryColumnWithMetaTupleReference 
extends AbstractAsterixColu
         assembler.reset(readColumns ? numberOfTuples : 0);
         metaAssembler.reset(readColumns ? numberOfTuples : 0);
         columnFilterEvaluator.reset();
+        previousIndex = -1;
         return readColumns;
     }
 
@@ -139,18 +142,26 @@ public final class QueryColumnWithMetaTupleReference 
extends AbstractAsterixColu
 
     public IValueReference getAssembledValue() throws HyracksDataException {
         try {
+            if (previousIndex == tupleIndex) {
+                return assembler.getPreviousValue();
+            }
             return filterApplier.getTuple();
         } catch (ColumnarValueException e) {
-            appendExceptionInformation(e);
+            appendExceptionInformation(e, previousIndex);
             throw e;
         }
     }
 
     public IValueReference getMetaAssembledValue() throws HyracksDataException 
{
         try {
+            if (previousIndex == tupleIndex) {
+                return assembler.getPreviousValue();
+            }
+            // Update the previous index only after calling meta
+            previousIndex = tupleIndex;
             return metaAssembler.nextValue();
         } catch (ColumnarValueException e) {
-            appendExceptionInformation(e);
+            appendExceptionInformation(e, previousIndex);
             throw e;
         }
     }
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/LeftOuterUnnestMapOperator.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/LeftOuterUnnestMapOperator.java
index d91cf72836..5eaecbcfdb 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/LeftOuterUnnestMapOperator.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/LeftOuterUnnestMapOperator.java
@@ -22,12 +22,14 @@ import java.util.List;
 
 import org.apache.commons.lang3.mutable.Mutable;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import 
org.apache.hyracks.algebricks.core.algebra.base.DefaultProjectionFiltrationInfo;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalOperatorTag;
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.ConstantExpression;
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.IAlgebricksConstantValue;
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.IVariableTypeEnvironment;
+import 
org.apache.hyracks.algebricks.core.algebra.metadata.IProjectionFiltrationInfo;
 import org.apache.hyracks.algebricks.core.algebra.typing.ITypingContext;
 import 
org.apache.hyracks.algebricks.core.algebra.typing.PropagatingTypeEnvironment;
 import 
org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalOperatorVisitor;
@@ -42,12 +44,20 @@ import org.apache.hyracks.api.exceptions.ErrorCode;
 public class LeftOuterUnnestMapOperator extends AbstractUnnestMapOperator {
 
     private IAlgebricksConstantValue missingValue;
+    private IProjectionFiltrationInfo projectionFiltrationInfo;
 
     public LeftOuterUnnestMapOperator(List<LogicalVariable> variables, 
Mutable<ILogicalExpression> expression,
             List<Object> variableTypes, IAlgebricksConstantValue missingValue) 
{
+        this(variables, expression, variableTypes, missingValue, 
DefaultProjectionFiltrationInfo.INSTANCE);
+    }
+
+    public LeftOuterUnnestMapOperator(List<LogicalVariable> variables, 
Mutable<ILogicalExpression> expression,
+            List<Object> variableTypes, IAlgebricksConstantValue missingValue,
+            IProjectionFiltrationInfo projectionFiltrationInfo) {
         // propagateInput is always set to true for this operator.
         super(variables, expression, variableTypes, true);
         setMissingValue(missingValue);
+        setProjectionFiltrationInfo(projectionFiltrationInfo);
     }
 
     public IAlgebricksConstantValue getMissingValue() {
@@ -101,4 +111,13 @@ public class LeftOuterUnnestMapOperator extends 
AbstractUnnestMapOperator {
             throw new IllegalArgumentException(String.valueOf(value));
         }
     }
+
+    public void setProjectionFiltrationInfo(IProjectionFiltrationInfo 
projectionFiltrationInfo) {
+        this.projectionFiltrationInfo =
+                projectionFiltrationInfo == null ? 
DefaultProjectionFiltrationInfo.INSTANCE : projectionFiltrationInfo;
+    }
+
+    public IProjectionFiltrationInfo getProjectionFiltrationInfo() {
+        return projectionFiltrationInfo;
+    }
 }
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/IsomorphismOperatorVisitor.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/IsomorphismOperatorVisitor.java
index 137dd2edc8..05f41a0535 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/IsomorphismOperatorVisitor.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/IsomorphismOperatorVisitor.java
@@ -464,7 +464,8 @@ public class IsomorphismOperatorVisitor implements 
ILogicalOperatorVisitor<Boole
             return Boolean.FALSE;
         }
         LeftOuterUnnestMapOperator loUnnestOpArg = 
(LeftOuterUnnestMapOperator) copyAndSubstituteVar(op, arg);
-        boolean isomorphic = 
VariableUtilities.varListEqualUnordered(op.getVariables(), 
loUnnestOpArg.getVariables());
+        boolean isomorphic = 
VariableUtilities.varListEqualUnordered(op.getVariables(), 
loUnnestOpArg.getVariables())
+                && Objects.equals(op.getProjectionFiltrationInfo(), 
loUnnestOpArg.getProjectionFiltrationInfo());;
         if (!isomorphic) {
             return Boolean.FALSE;
         }
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/LogicalOperatorDeepCopyWithNewVariablesVisitor.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/LogicalOperatorDeepCopyWithNewVariablesVisitor.java
index 6377d44d65..ba49233bcf 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/LogicalOperatorDeepCopyWithNewVariablesVisitor.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/LogicalOperatorDeepCopyWithNewVariablesVisitor.java
@@ -548,9 +548,11 @@ public class LogicalOperatorDeepCopyWithNewVariablesVisitor
     @Override
     public ILogicalOperator 
visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, ILogicalOperator 
arg)
             throws AlgebricksException {
+        IProjectionFiltrationInfo projectionFiltrationInfo =
+                op.getProjectionFiltrationInfo() != null ? 
op.getProjectionFiltrationInfo().createCopy() : null;
         LeftOuterUnnestMapOperator opCopy = new 
LeftOuterUnnestMapOperator(deepCopyVariableList(op.getVariables()),
                 
exprDeepCopyVisitor.deepCopyExpressionReference(op.getExpressionRef()), 
op.getVariableTypes(),
-                op.getMissingValue());
+                op.getMissingValue(), projectionFiltrationInfo);
         deepCopyInputsAnnotationsAndExecutionMode(op, arg, opCopy);
         return opCopy;
     }
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/OperatorDeepCopyVisitor.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/OperatorDeepCopyVisitor.java
index b3828df20e..fa47ae502e 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/OperatorDeepCopyVisitor.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/OperatorDeepCopyVisitor.java
@@ -261,6 +261,7 @@ public class OperatorDeepCopyVisitor implements 
ILogicalOperatorVisitor<ILogical
             throws AlgebricksException {
         ArrayList<LogicalVariable> newInputList = new ArrayList<>();
         newInputList.addAll(op.getVariables());
+        IProjectionFiltrationInfo projectionFiltrationInfo = 
op.getProjectionFiltrationInfo().createCopy();
         return new LeftOuterUnnestMapOperator(newInputList, 
deepCopyExpressionRef(op.getExpressionRef()),
                 new ArrayList<>(op.getVariableTypes()), op.getMissingValue());
     }
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
index e1e8c50405..8c50bcdff1 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
@@ -380,7 +380,9 @@ public class LogicalOperatorPrettyPrintVisitor extends 
AbstractLogicalOperatorPr
     @Override
     public Void visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, 
Integer indent)
             throws AlgebricksException {
-        printAbstractUnnestMapOperator(op, indent, "left-outer-unnest-map", 
op.getMissingValue());
+        AlgebricksStringBuilderWriter plan =
+                printAbstractUnnestMapOperator(op, indent, 
"left-outer-unnest-map", op.getMissingValue());
+        op.getProjectionFiltrationInfo().print(plan);
         return null;
     }
 
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitorJson.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitorJson.java
index a60308adb1..3e4e09f74d 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitorJson.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitorJson.java
@@ -625,8 +625,13 @@ public class LogicalOperatorPrettyPrintVisitorJson extends 
AbstractLogicalOperat
 
     @Override
     public Void visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, 
Void indent) throws AlgebricksException {
-        writeUnnestMapOperator(op, indent, "left-outer-unnest-map", 
op.getMissingValue());
-        return null;
+        try {
+            writeUnnestMapOperator(op, indent, "left-outer-unnest-map", 
op.getMissingValue());
+            op.getProjectionFiltrationInfo().print(jsonGenerator);
+            return null;
+        } catch (IOException e) {
+            throw AlgebricksException.create(ErrorCode.ERROR_PRINTING_PLAN, e, 
String.valueOf(e));
+        }
     }
 
     @Override
diff --git 
a/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-btree-column/src/main/java/org/apache/hyracks/storage/am/lsm/btree/column/impls/btree/ColumnBTreeRangeSearchCursor.java
 
b/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-btree-column/src/main/java/org/apache/hyracks/storage/am/lsm/btree/column/impls/btree/ColumnBTreeRangeSearchCursor.java
index 77f161acd2..fd726cdca2 100644
--- 
a/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-btree-column/src/main/java/org/apache/hyracks/storage/am/lsm/btree/column/impls/btree/ColumnBTreeRangeSearchCursor.java
+++ 
b/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-btree-column/src/main/java/org/apache/hyracks/storage/am/lsm/btree/column/impls/btree/ColumnBTreeRangeSearchCursor.java
@@ -169,12 +169,14 @@ public class ColumnBTreeRangeSearchCursor extends 
EnforcedIndexCursor
         //Next tuple
         frameTuple.next();
         //Check whether the frameTuple is not consumed and also include the 
search key
-        return highKey == null || isLessOrEqual(frameTuple, highKey, 
pred.isHighKeyInclusive());
+        return highKey == null
+                || isLessOrEqual(frameTuple, highKey, 
pred.isHighKeyInclusive(), pred.getHighKeyComparator());
     }
 
     protected boolean shouldYieldFirstCall() throws HyracksDataException {
         // Proceed if the highKey is null or the current tuple's key is less 
than (or equal) the highKey
-        return highKey == null || isLessOrEqual(frameTuple, highKey, 
pred.isHighKeyInclusive());
+        return highKey == null
+                || isLessOrEqual(frameTuple, highKey, 
pred.isHighKeyInclusive(), pred.getHighKeyComparator());
     }
 
     protected void releasePages() throws HyracksDataException {
@@ -185,16 +187,17 @@ public class ColumnBTreeRangeSearchCursor extends 
EnforcedIndexCursor
         }
     }
 
-    private boolean isLessOrEqual(ITupleReference left, ITupleReference right, 
boolean inclusive)
-            throws HyracksDataException {
-        int cmp = originalKeyCmp.compare(left, right);
+    private boolean isLessOrEqual(ITupleReference left, ITupleReference right, 
boolean inclusive,
+            MultiComparator comparator) throws HyracksDataException {
+        int cmp = comparator.compare(left, right);
         return cmp < 0 || inclusive && cmp == 0;
     }
 
     protected int getLowKeyIndex() throws HyracksDataException {
         if (lowKey == null) {
             return 0;
-        } else if (isLessOrEqual(frame.getRightmostTuple(), lowKey, 
!pred.isLowKeyInclusive())) {
+        } else if (isLessOrEqual(frame.getRightmostTuple(), lowKey, 
!pred.isLowKeyInclusive(),
+                pred.getLowKeyComparator())) {
             //The highest key from the frame is less than the requested lowKey
             return frame.getTupleCount();
         }
@@ -214,7 +217,8 @@ public class ColumnBTreeRangeSearchCursor extends 
EnforcedIndexCursor
     protected int getHighKeyIndex() throws HyracksDataException {
         if (highKey == null) {
             return frame.getTupleCount() - 1;
-        } else if (isLessOrEqual(highKey, frame.getLeftmostTuple(), 
!pred.isHighKeyInclusive())) {
+        } else if (isLessOrEqual(highKey, frame.getLeftmostTuple(), 
!pred.isHighKeyInclusive(),
+                pred.getHighKeyComparator())) {
             return -1;
         }
 
diff --git 
a/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-btree-column/src/main/java/org/apache/hyracks/storage/am/lsm/btree/column/impls/lsm/tuples/AbstractColumnTupleReference.java
 
b/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-btree-column/src/main/java/org/apache/hyracks/storage/am/lsm/btree/column/impls/lsm/tuples/AbstractColumnTupleReference.java
index 283522aecb..e638a4ad60 100644
--- 
a/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-btree-column/src/main/java/org/apache/hyracks/storage/am/lsm/btree/column/impls/lsm/tuples/AbstractColumnTupleReference.java
+++ 
b/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-btree-column/src/main/java/org/apache/hyracks/storage/am/lsm/btree/column/impls/lsm/tuples/AbstractColumnTupleReference.java
@@ -155,6 +155,16 @@ public abstract class AbstractColumnTupleReference 
implements IColumnTupleIterat
 
     @Override
     public final void setAt(int startIndex) throws HyracksDataException {
+        if (tupleIndex == startIndex) {
+            /*
+             * This case happens when we ask for the same tuple again when 
utilizing a secondary index. To illustrate,
+             * assume that the secondary index search yielded the following 
PKs [1, 1, 1, 2] -- keys are always sorted.
+             * We see that the secondary index asked for the PK '1' three 
times. Asking for the same tuple multiple
+             * times is possible when we do index nested-loop join (indexnl).
+             * See ASTERIX-3311
+             */
+            return;
+        }
         /*
          * Let say that tupleIndex = 5 and startIndex = 12
          * Then, skipCount = 12 - 5 - 1 = 6.

Reply via email to