[ASTERIXDB-2829][IDX] Adding support for composite atomic-array indexes
- user mode changes: no
- storage format changes: no
- interface changes: no
Adding support for composite atomic-array indexes. The only prefix
queries supported must involved the array component on closed fields.
Also expanded the test coverage to explore more paths (w.r.t the
optimizer).
Change-Id: I2579a9da8dcd8b8c808b20883f356aab3e1a7095
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/12764
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Dmitry Lychagin <dmitry.lychagin@couchbase.com>
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/IntroduceSecondaryIndexInsertDeleteRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/IntroduceSecondaryIndexInsertDeleteRule.java
index 98c3edc..9c784ad 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/IntroduceSecondaryIndexInsertDeleteRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/IntroduceSecondaryIndexInsertDeleteRule.java
@@ -18,12 +18,10 @@
*/
package org.apache.asterix.optimizer.rules;
-import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Deque;
import java.util.HashMap;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -678,31 +676,14 @@
boolean hasMetaPart) throws AlgebricksException {
Index.ArrayIndexDetails arrayIndexDetails = (Index.ArrayIndexDetails) index.getIndexDetails();
- // First, locate a field having the required UNNEST path. Queue this first, and all other keys will follow.
- Deque<Integer> keyPositionQueue = new ArrayDeque<>();
- for (int i = 0; i < arrayIndexDetails.getElementList().size(); i++) {
- Index.ArrayIndexElement e = arrayIndexDetails.getElementList().get(i);
- if (e.getUnnestList().isEmpty()) {
- keyPositionQueue.addLast(i);
- } else {
- keyPositionQueue.addFirst(i);
- }
- }
-
// Get the record variable associated with our record path.
- Index.ArrayIndexElement workingElement = arrayIndexDetails.getElementList().get(keyPositionQueue.getFirst());
- int sourceIndicatorForBaseRecord = workingElement.getSourceIndicator();
+ int sourceIndicatorForBaseRecord = arrayIndexDetails.getElementList().get(0).getSourceIndicator();
LogicalVariable sourceVarForBaseRecord = hasMetaPart
? ((sourceIndicatorForBaseRecord == Index.RECORD_INDICATOR) ? recordVar : metaVar) : recordVar;
UnnestBranchCreator branchCreator = new UnnestBranchCreator(sourceVarForBaseRecord, unnestSourceOp);
- int initialKeyPositionQueueSize = keyPositionQueue.size();
- Set<LogicalVariable> secondaryKeyVars = new HashSet<>();
- for (int i = 0; i < initialKeyPositionQueueSize; i++) {
-
- // Poll from our queue, and get a key position.
- int workingKeyPos = keyPositionQueue.pollFirst();
- workingElement = arrayIndexDetails.getElementList().get(workingKeyPos);
+ Set<LogicalVariable> secondaryKeyVars = new LinkedHashSet<>();
+ for (Index.ArrayIndexElement workingElement : arrayIndexDetails.getElementList()) {
int sourceIndicator = workingElement.getSourceIndicator();
ARecordType recordType =
hasMetaPart ? ((sourceIndicator == Index.RECORD_INDICATOR) ? recType : metaType) : recType;
@@ -714,7 +695,6 @@
isOpenOrNestedField =
(atomicFieldName.size() != 1) || !recordType.isClosedField(atomicFieldName.get(0));
- // The UNNEST path has already been created (we queued this first), so we look at the current top.
LogicalVariable newVar = context.newVar();
VariableReferenceExpression varRef = new VariableReferenceExpression(sourceVarForBaseRecord);
varRef.setSourceLocation(sourceLoc);
@@ -723,10 +703,14 @@
recordType.getFieldIndex(atomicFieldName.get(0)), atomicFieldName)
: getFieldAccessFunction(new MutableObject<>(varRef), -1, atomicFieldName);
+ // Add an assign on top to extract the atomic element.
AssignOperator newAssignOp = new AssignOperator(newVar, new MutableObject<>(newVarRef));
newAssignOp.setSourceLocation(sourceLoc);
branchCreator.currentTop = introduceNewOp(branchCreator.currentTop, newAssignOp, true);
secondaryKeyVars.add(newVar);
+ if (branchCreator.currentBottom == null) {
+ branchCreator.currentBottom = branchCreator.currentTop;
+ }
} else {
// We have an array element. Walk the array path.
@@ -735,6 +719,7 @@
List<Boolean> firstUnnestFlags = ArrayIndexUtil.getUnnestFlags(workingElement.getUnnestList(),
workingElement.getProjectList().get(0));
ArrayIndexUtil.walkArrayPath(recordType, flatFirstFieldName, firstUnnestFlags, branchCreator);
+ secondaryKeyVars.add(branchCreator.lastFieldVars.get(0));
// For all other elements in the PROJECT list, add an assign.
for (int j = 1; j < workingElement.getProjectList().size(); j++) {
@@ -749,15 +734,11 @@
secondaryKeyVars.add(newVar);
}
}
-
- branchCreator.lowerIsFirstWalkFlag();
- secondaryKeyVars.addAll(branchCreator.lastFieldVars);
}
// Update the variables we are to use for the head operators.
branchCreator.lastFieldVars.clear();
branchCreator.lastFieldVars.addAll(secondaryKeyVars);
-
return branchCreator;
}
@@ -1022,8 +1003,7 @@
private class UnnestBranchCreator implements ArrayIndexUtil.TypeTrackerCommandExecutor {
private final List<LogicalVariable> lastFieldVars;
private LogicalVariable lastRecordVar;
- private ILogicalOperator currentTop, currentBottom;
- private boolean isFirstWalk = true;
+ private ILogicalOperator currentTop, currentBottom = null;
public UnnestBranchCreator(LogicalVariable recordVar, ILogicalOperator sourceOperator) {
this.lastRecordVar = recordVar;
@@ -1035,10 +1015,6 @@
return new ALogicalPlanImpl(new MutableObject<>(currentTop));
}
- public void lowerIsFirstWalkFlag() {
- isFirstWalk = false;
- }
-
public VariableReferenceExpression createLastRecordVarRef() {
VariableReferenceExpression varRef = new VariableReferenceExpression(lastRecordVar);
varRef.setSourceLocation(sourceLoc);
@@ -1098,11 +1074,6 @@
public void executeActionOnEachArrayStep(ARecordType startingStepRecordType, IAType workingType,
List<String> fieldName, boolean isFirstArrayStep, boolean isLastUnnestInIntermediateStep)
throws AlgebricksException {
- if (!isFirstWalk) {
- // We have already built the UNNEST path, do not build again.
- return;
- }
-
// Get the field we want to UNNEST from our record.
ILogicalExpression accessToUnnestVar;
accessToUnnestVar = (startingStepRecordType != null)
@@ -1120,7 +1091,7 @@
UnnestOperator unnestOp = new UnnestOperator(unnestVar, new MutableObject<>(scanCollection));
unnestOp.setSourceLocation(sourceLoc);
this.currentTop = introduceNewOp(currentTop, unnestOp, true);
- if (isFirstArrayStep) {
+ if (isFirstArrayStep && this.currentBottom == null) {
this.currentBottom = unnestOp;
}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AbstractIntroduceAccessMethodRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AbstractIntroduceAccessMethodRule.java
index 8458fde..37109ab 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AbstractIntroduceAccessMethodRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AbstractIntroduceAccessMethodRule.java
@@ -487,6 +487,25 @@
if (lastFieldMatched < 0) {
indexExprAndVarIt.remove();
continue;
+
+ } else if (Index.IndexCategory.of(indexType).equals(Index.IndexCategory.ARRAY)) {
+ // For array indexes, we cannot make the decision to apply the prefix until we see a conjunct
+ // conditioning on an array. We should improve using array indexes for queries that don't involve
+ // the array component in the future.
+ Index.ArrayIndexDetails arrayIndexDetails = (Index.ArrayIndexDetails) index.getIndexDetails();
+ int indexOfFirstArrayField = 0;
+ for (Index.ArrayIndexElement e : arrayIndexDetails.getElementList()) {
+ if (!e.getUnnestList().isEmpty()) {
+ break;
+ }
+ for (List<String> ignored : e.getProjectList()) {
+ indexOfFirstArrayField++;
+ }
+ }
+ if (lastFieldMatched < indexOfFirstArrayField) {
+ indexExprAndVarIt.remove();
+ continue;
+ }
}
}
analysisCtx.putNumberOfMatchedKeys(index, numMatchedKeys);
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodAnalysisContext.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodAnalysisContext.java
index 7fa6de1..b066339 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodAnalysisContext.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodAnalysisContext.java
@@ -26,6 +26,7 @@
import org.apache.asterix.metadata.entities.Dataset;
import org.apache.asterix.metadata.entities.Index;
+import org.apache.asterix.optimizer.rules.am.array.ArrayIndexStructureMatcher;
import org.apache.commons.lang3.mutable.Mutable;
import org.apache.hyracks.algebricks.common.utils.Pair;
import org.apache.hyracks.algebricks.common.utils.Quadruple;
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/ArrayBTreeAccessMethod.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/ArrayBTreeAccessMethod.java
index d364b53..a482dc1 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/ArrayBTreeAccessMethod.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/ArrayBTreeAccessMethod.java
@@ -51,8 +51,8 @@
@Override
public boolean matchAllIndexExprs(Index index) {
- // Similar to BTree "matchAllIndexExprs", we only require all expressions to be matched if this is a composite
- // key index with an unknowable field.
+ // We only require all expressions to be matched if this is a composite key index with an unknowable field.
+ // TODO (GLENN): When nulls become stored in array indexes, this should return false.
return ((Index.ArrayIndexDetails) index.getIndexDetails()).getElementList().stream()
.map(e -> e.getProjectList().size()).reduce(0, Integer::sum) > 1 && hasUnknownableField(index);
}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceJoinAccessMethodRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceJoinAccessMethodRule.java
index f74c0c8..4e2eba5 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceJoinAccessMethodRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceJoinAccessMethodRule.java
@@ -27,7 +27,8 @@
import org.apache.asterix.metadata.entities.Dataset;
import org.apache.asterix.metadata.entities.Index;
import org.apache.asterix.om.functions.BuiltinFunctions;
-import org.apache.asterix.optimizer.rules.subplan.JoinFromSubplanCreator;
+import org.apache.asterix.optimizer.rules.am.array.IIntroduceAccessMethodRuleLocalRewrite;
+import org.apache.asterix.optimizer.rules.am.array.JoinFromSubplanRewrite;
import org.apache.commons.lang3.mutable.Mutable;
import org.apache.commons.lang3.mutable.MutableObject;
import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
@@ -92,7 +93,9 @@
protected final OptimizableOperatorSubTree rightSubTree = new OptimizableOperatorSubTree();
protected IVariableTypeEnvironment typeEnvironment = null;
protected List<Mutable<ILogicalOperator>> afterJoinRefs = null;
- private final JoinFromSubplanCreator joinFromSubplanCreator = new JoinFromSubplanCreator();
+
+ // For plan rewriting to recognize applicable array indexes.
+ private final JoinFromSubplanRewrite joinFromSubplanRewrite = new JoinFromSubplanRewrite();
// Registers access methods.
protected static Map<FunctionIdentifier, List<IAccessMethod>> accessMethods = new HashMap<>();
@@ -102,7 +105,7 @@
registerAccessMethod(BTreeAccessMethod.INSTANCE, accessMethods);
registerAccessMethod(RTreeAccessMethod.INSTANCE, accessMethods);
registerAccessMethod(InvertedIndexAccessMethod.INSTANCE, accessMethods);
- JoinFromSubplanCreator.addOptimizableFunction(BuiltinFunctions.EQ);
+ JoinFromSubplanRewrite.addOptimizableFunction(BuiltinFunctions.EQ);
}
/**
@@ -299,28 +302,16 @@
analyzedAMs = new HashMap<>();
}
- // If there exists a SUBPLAN in our plan, and we are conditioning on a variable,
- // attempt to rewrite this subplan to allow an array-index AM to be introduced.
- // If successful, this rewrite will transform into an index-nested-loop-join.
- // This rewrite is to be used for pushing the UNNESTs and ASSIGNs from the subplan into
- // the index branch and giving the join a condition for this rule to optimize. *No nodes*
- // from this rewrite will be used beyond this point.
if (continueCheck && context.getPhysicalOptimizationConfig().isArrayIndexEnabled()) {
- joinFromSubplanCreator.findAfterSubplanSelectOperator(afterJoinRefs);
- AbstractBinaryJoinOperator joinRewrite = joinFromSubplanCreator.createOperator(joinOp, context);
- boolean transformationResult = false;
- if (joinRewrite != null) {
- Mutable<ILogicalOperator> joinRuleInput = new MutableObject<>(joinRewrite);
- transformationResult = checkAndApplyJoinTransformation(joinRuleInput, context);
- }
-
- // Restore our state, so we can look for more INLJ optimizations if this transformation failed.
- joinOp = joinFromSubplanCreator.restoreBeforeRewrite(afterJoinRefs, context);
- joinRef = joinRefFromThisOp;
-
- if (transformationResult) {
- // Join rewrite was successful. Connect the after-join operators to the index subtree root before
- // this rewrite. This also avoids performing the secondary index validation step twice.
+ // If there exists a SUBPLAN in our plan, and we are conditioning on a variable, attempt to rewrite
+ // this subplan to allow an array-index AM to be introduced. If successful, this rewrite will transform
+ // into an index-nested-loop-join. This rewrite is to be used for pushing the UNNESTs and ASSIGNs from
+ // the subplan into the index branch and giving the join a condition for this rule to optimize.
+ // *No nodes* from this rewrite will be used beyond this point.
+ joinFromSubplanRewrite.findAfterSubplanSelectOperator(afterJoinRefs);
+ if (rewriteLocallyAndTransform(joinRef, context, joinFromSubplanRewrite)) {
+ // Connect the after-join operators to the index subtree root before this rewrite. This also avoids
+ // performing the secondary index validation step twice.
ILogicalOperator lastAfterJoinOp = afterJoinRefs.get(afterJoinRefs.size() - 1).getValue();
OperatorManipulationUtil.substituteOpInInput(lastAfterJoinOp, joinOp, joinOp.getInputs().get(1));
context.computeAndSetTypeEnvironmentForOperator(lastAfterJoinOp);
@@ -486,4 +477,18 @@
return false;
}
+ private boolean rewriteLocallyAndTransform(Mutable<ILogicalOperator> opRef, IOptimizationContext context,
+ IIntroduceAccessMethodRuleLocalRewrite<AbstractBinaryJoinOperator> rewriter) throws AlgebricksException {
+ AbstractBinaryJoinOperator joinRewrite = rewriter.createOperator(joinOp, context);
+ boolean transformationResult = false;
+ if (joinRewrite != null) {
+ Mutable<ILogicalOperator> joinRuleInput = new MutableObject<>(joinRewrite);
+ transformationResult = checkAndApplyJoinTransformation(joinRuleInput, context);
+ }
+
+ // Restore our state, so we can look for more optimizations if this transformation failed.
+ joinOp = rewriter.restoreBeforeRewrite(afterJoinRefs, context);
+ joinRef = opRef;
+ return transformationResult;
+ }
}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceSelectAccessMethodRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceSelectAccessMethodRule.java
index faabaef..3948726 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceSelectAccessMethodRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceSelectAccessMethodRule.java
@@ -30,7 +30,9 @@
import org.apache.asterix.common.exceptions.ErrorCode;
import org.apache.asterix.metadata.declared.MetadataProvider;
import org.apache.asterix.metadata.entities.Index;
-import org.apache.asterix.optimizer.rules.subplan.SelectFromSubplanCreator;
+import org.apache.asterix.optimizer.rules.am.array.IIntroduceAccessMethodRuleLocalRewrite;
+import org.apache.asterix.optimizer.rules.am.array.MergedSelectRewrite;
+import org.apache.asterix.optimizer.rules.am.array.SelectFromSubplanRewrite;
import org.apache.commons.lang3.mutable.Mutable;
import org.apache.commons.lang3.mutable.MutableObject;
import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
@@ -123,7 +125,10 @@
protected IVariableTypeEnvironment typeEnvironment = null;
protected final OptimizableOperatorSubTree subTree = new OptimizableOperatorSubTree();
protected List<Mutable<ILogicalOperator>> afterSelectRefs = null;
- private final SelectFromSubplanCreator selectFromSubplanCreator = new SelectFromSubplanCreator();
+
+ // For plan rewriting to recognize applicable array indexes.
+ private final SelectFromSubplanRewrite selectFromSubplanRewrite = new SelectFromSubplanRewrite();
+ private final MergedSelectRewrite mergedSelectRewrite = new MergedSelectRewrite();
// Register access methods.
protected static Map<FunctionIdentifier, List<IAccessMethod>> accessMethods = new HashMap<>();
@@ -134,7 +139,7 @@
registerAccessMethod(InvertedIndexAccessMethod.INSTANCE, accessMethods);
registerAccessMethod(ArrayBTreeAccessMethod.INSTANCE, accessMethods);
for (Pair<FunctionIdentifier, Boolean> f : ArrayBTreeAccessMethod.INSTANCE.getOptimizableFunctions()) {
- SelectFromSubplanCreator.addOptimizableFunction(f.first);
+ SelectFromSubplanRewrite.addOptimizableFunction(f.first);
}
}
@@ -376,26 +381,18 @@
analyzedAMs = new TreeMap<>();
}
- // If there exists a SUBPLAN in our plan, and we are conditioning on a variable,
- // attempt to rewrite this subplan to allow an array-index AM to be introduced.
- // This rewrite is to be used **solely** for the purpose of changing a DATA-SCAN into a
- // non-index-only plan branch. No nodes from this rewrite will be used beyond this point.
- // If successful, this will create a non-index only plan that replaces the subplan's
- // DATA-SCAN with a PIDX SEARCH <- DISTINCT <- ORDER <- SIDX SEARCH.
if (continueCheck && context.getPhysicalOptimizationConfig().isArrayIndexEnabled()) {
- SelectOperator selectRewrite = selectFromSubplanCreator.createOperator(selectOp, context);
- boolean transformationResult = false;
- if (selectRewrite != null) {
- Mutable<ILogicalOperator> selectRuleInput = new MutableObject<>(selectRewrite);
- transformationResult = checkAndApplyTheSelectTransformation(selectRuleInput, context);
+ // If there exists a composite atomic-array index, our conjuncts will be split across multiple
+ // SELECTs. This rewrite is to be used **solely** for the purpose of changing a DATA-SCAN into a
+ // non-index-only plan branch. No nodes introduced from this rewrite will be used beyond this point.
+ if (rewriteLocallyAndTransform(selectRef, context, mergedSelectRewrite)) {
+ return true;
}
- // Restore our state, so we can look for more optimizations if this transformation failed.
- selectOp = selectFromSubplanCreator.restoreBeforeRewrite(null, null);
- selectRef = selectRefFromThisOp;
-
- if (transformationResult) {
- // Rewrite was successful. Exit early.
+ // If there exists a SUBPLAN in our plan, and we are conditioning on a variable, attempt to rewrite
+ // this subplan to allow an array-index AM to be introduced. Again, this rewrite is to be used
+ // **solely** for the purpose of changing a DATA-SCAN into a non-index-only plan branch.
+ if (rewriteLocallyAndTransform(selectRef, context, selectFromSubplanRewrite)) {
return true;
}
}
@@ -493,6 +490,21 @@
return accessMethods;
}
+ private boolean rewriteLocallyAndTransform(Mutable<ILogicalOperator> opRef, IOptimizationContext context,
+ IIntroduceAccessMethodRuleLocalRewrite<SelectOperator> rewriter) throws AlgebricksException {
+ SelectOperator selectRewrite = rewriter.createOperator(selectOp, context);
+ boolean transformationResult = false;
+ if (selectRewrite != null) {
+ Mutable<ILogicalOperator> selectRuleInput = new MutableObject<>(selectRewrite);
+ transformationResult = checkAndApplyTheSelectTransformation(selectRuleInput, context);
+ }
+
+ // Restore our state, so we can look for more optimizations if this transformation failed.
+ selectOp = rewriter.restoreBeforeRewrite(null, null);
+ selectRef = opRef;
+ return transformationResult;
+ }
+
private void clear() {
afterSelectRefs = null;
selectRef = null;
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/AbstractOperatorFromSubplanRewrite.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/AbstractOperatorFromSubplanRewrite.java
new file mode 100644
index 0000000..4decd9d
--- /dev/null
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/AbstractOperatorFromSubplanRewrite.java
@@ -0,0 +1,478 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.optimizer.rules.am.array;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.om.base.AInt16;
+import org.apache.asterix.om.base.AInt32;
+import org.apache.asterix.om.base.AInt64;
+import org.apache.asterix.om.base.AInt8;
+import org.apache.asterix.om.constants.AsterixConstantValue;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableObject;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.algebricks.common.utils.Pair;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
+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.AbstractFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AggregateFunctionCallExpression;
+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.ScalarFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.UnnestingFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.VariableReferenceExpression;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractBinaryJoinOperator;
+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.SelectOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.SubplanOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestOperator;
+import org.apache.hyracks.algebricks.core.algebra.util.OperatorManipulationUtil;
+import org.apache.hyracks.api.exceptions.SourceLocation;
+
+abstract public class AbstractOperatorFromSubplanRewrite<T> implements IIntroduceAccessMethodRuleLocalRewrite<T> {
+ private final static List<IAlgebricksConstantValue> ZEROS_AS_ASTERIX_CONSTANTS =
+ Arrays.asList(new IAlgebricksConstantValue[] { new AsterixConstantValue(new AInt64(0)),
+ new AsterixConstantValue(new AInt32(0)), new AsterixConstantValue(new AInt16((short) 0)),
+ new AsterixConstantValue(new AInt8((byte) 0)) });
+
+ private Set<FunctionIdentifier> optimizableFunctions;
+ private IOptimizationContext context;
+ private SourceLocation sourceLocation;
+
+ protected void reset(SourceLocation sourceLocation, IOptimizationContext context,
+ Set<FunctionIdentifier> optimizableFunctions) {
+ this.optimizableFunctions = optimizableFunctions;
+ this.sourceLocation = sourceLocation;
+ this.context = context;
+ }
+
+ protected LogicalVariable getConditioningVariable(ILogicalExpression condition) {
+ List<Mutable<ILogicalExpression>> selectConjuncts = new ArrayList<>();
+ if (condition.splitIntoConjuncts(selectConjuncts)) {
+ for (Mutable<ILogicalExpression> conjunct : selectConjuncts) {
+ if (conjunct.getValue().getExpressionTag().equals(LogicalExpressionTag.VARIABLE)) {
+ return ((VariableReferenceExpression) conjunct.getValue()).getVariableReference();
+ }
+ }
+
+ } else if (condition.getExpressionTag().equals(LogicalExpressionTag.VARIABLE)) {
+ return ((VariableReferenceExpression) condition).getVariableReference();
+
+ }
+ return null;
+ }
+
+ protected Pair<SelectOperator, UnnestOperator> traverseSubplanBranch(SubplanOperator subplanOperator,
+ ILogicalOperator parentInput, boolean isConnectInput) throws AlgebricksException {
+ AggregateOperator workingSubplanRootAsAggregate = getAggregateFromSubplan(subplanOperator);
+ if (workingSubplanRootAsAggregate == null) {
+ return null;
+ }
+
+ // Find (or create, in the SOME AND EVERY case) a SELECT that we can potentially optimize.
+ SelectOperator optimizableSelect = getSelectFromPlan(workingSubplanRootAsAggregate);
+ if (optimizableSelect == null) {
+ return null;
+ }
+
+ // Ensure that this SELECT represents a predicate for an existential query, and is a query we can optimize.
+ ILogicalExpression normalizedSelectCondition =
+ normalizeCondition(workingSubplanRootAsAggregate, optimizableSelect.getCondition().getValue());
+ normalizedSelectCondition = keepOptimizableFunctions(normalizedSelectCondition);
+
+ // Create a copy of this SELECT, and set this to our rewrite root.
+ SelectOperator rewriteRootSelect = new SelectOperator(new MutableObject<>(normalizedSelectCondition),
+ optimizableSelect.getRetainMissing(), optimizableSelect.getMissingPlaceholderVariable());
+ rewriteRootSelect.setSourceLocation(sourceLocation);
+ rewriteRootSelect.setExecutionMode(optimizableSelect.getExecutionMode());
+
+ // Follow this SELECT to the root of our nested-plan branch (i.e. the NESTED-TUPLE-SOURCE).
+ ILogicalOperator workingNewOperator = rewriteRootSelect;
+ UnnestOperator bottommostNewUnnest = null;
+ ILogicalOperator workingOriginalOperator = optimizableSelect.getInputs().get(0).getValue();
+ while (!workingOriginalOperator.getOperatorTag().equals(LogicalOperatorTag.NESTEDTUPLESOURCE)) {
+ if (workingOriginalOperator.getInputs().isEmpty()) {
+ throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+ workingSubplanRootAsAggregate.getSourceLocation(),
+ "NESTED-TUPLE-SOURCE expected in nested plan branch, but not found.");
+ }
+
+ ScalarFunctionCallExpression updatedSelectCond;
+ SelectOperator updatedSelectOperator;
+ switch (workingOriginalOperator.getOperatorTag()) {
+ case UNNEST:
+ UnnestOperator originalUnnest = (UnnestOperator) workingOriginalOperator;
+ UnnestOperator newUnnest =
+ new UnnestOperator(originalUnnest.getVariable(), originalUnnest.getExpressionRef());
+ newUnnest.setSourceLocation(sourceLocation);
+ workingNewOperator.getInputs().add(new MutableObject<>(newUnnest));
+ workingNewOperator = newUnnest;
+ bottommostNewUnnest = (UnnestOperator) workingNewOperator;
+ break;
+
+ case ASSIGN:
+ AssignOperator originalAssign = (AssignOperator) workingOriginalOperator;
+ AssignOperator newAssign =
+ new AssignOperator(originalAssign.getVariables(), originalAssign.getExpressions());
+ newAssign.setSourceLocation(sourceLocation);
+ workingNewOperator.getInputs().add(new MutableObject<>(newAssign));
+ workingNewOperator = newAssign;
+ break;
+
+ case SELECT:
+ // If we encounter another SELECT, then we have multiple quantifiers. Transform our new SELECT to
+ // include this condition.
+ updatedSelectCond = coalesceConditions(rewriteRootSelect, workingOriginalOperator);
+ updatedSelectOperator = new SelectOperator(new MutableObject<>(updatedSelectCond),
+ rewriteRootSelect.getRetainMissing(), rewriteRootSelect.getMissingPlaceholderVariable());
+ updatedSelectOperator.setSourceLocation(sourceLocation);
+ updatedSelectOperator.getInputs().addAll(rewriteRootSelect.getInputs());
+ rewriteRootSelect = updatedSelectOperator;
+ if (workingNewOperator.getOperatorTag().equals(LogicalOperatorTag.SELECT)) {
+ workingNewOperator = rewriteRootSelect;
+ }
+ break;
+
+ case SUBPLAN:
+ // If we encounter another subplan, then we must look to include any SELECTs from here as well.
+ Pair<SelectOperator, UnnestOperator> traversalOutput =
+ traverseSubplanBranch((SubplanOperator) workingOriginalOperator, optimizableSelect, false);
+ if (traversalOutput != null) {
+ updatedSelectCond = coalesceConditions(rewriteRootSelect, traversalOutput.first);
+ updatedSelectOperator = new SelectOperator(new MutableObject<>(updatedSelectCond),
+ rewriteRootSelect.getRetainMissing(),
+ rewriteRootSelect.getMissingPlaceholderVariable());
+ updatedSelectOperator.setSourceLocation(sourceLocation);
+ updatedSelectOperator.getInputs().addAll(rewriteRootSelect.getInputs());
+ rewriteRootSelect = updatedSelectOperator;
+ if (workingNewOperator.getOperatorTag().equals(LogicalOperatorTag.SELECT)) {
+ workingNewOperator = rewriteRootSelect;
+ }
+
+ // Add the inputs from our subplan.
+ Mutable<ILogicalOperator> traversalOperator = traversalOutput.first.getInputs().get(0);
+ while (traversalOperator != null) {
+ workingNewOperator.getInputs().add(new MutableObject<>(
+ OperatorManipulationUtil.deepCopy(traversalOperator.getValue())));
+ workingNewOperator = workingNewOperator.getInputs().get(0).getValue();
+ traversalOperator = (traversalOperator.getValue().getInputs().isEmpty()) ? null
+ : traversalOperator.getValue().getInputs().get(0);
+ }
+ workingNewOperator.getInputs().clear();
+ bottommostNewUnnest = traversalOutput.second;
+ break;
+ }
+
+ default:
+ return null;
+ }
+
+ workingOriginalOperator = workingOriginalOperator.getInputs().get(0).getValue();
+ }
+
+ // Sanity check: we should always be working with an UNNEST at this stage.
+ if (bottommostNewUnnest == null) {
+ throw new CompilationException(ErrorCode.COMPILATION_ERROR,
+ workingSubplanRootAsAggregate.getSourceLocation(),
+ "UNNEST expected in nested plan branch, but not found.");
+ }
+
+ // If we are working with strict universal quantification, then we must also check whether we have a
+ // conjunct that asserts that the array should also be non-empty.
+ if (isStrictUniversalQuantification(workingSubplanRootAsAggregate)
+ && isArrayNonEmptyConjunctMissing(bottommostNewUnnest, subplanOperator.getInputs().get(0).getValue())
+ && (parentInput == null || isArrayNonEmptyConjunctMissing(bottommostNewUnnest, parentInput))) {
+ return null;
+ }
+
+ // We have added everything we need in our nested-plan branch. Now, connect the input of our SUBPLAN to our
+ // current working branch.
+ if (isConnectInput) {
+ bottommostNewUnnest.getInputs().addAll(subplanOperator.getInputs());
+ OperatorManipulationUtil.computeTypeEnvironmentBottomUp(rewriteRootSelect, context);
+ }
+
+ return new Pair<>(rewriteRootSelect, bottommostNewUnnest);
+ }
+
+ protected ScalarFunctionCallExpression coalesceConditions(SelectOperator selectOp, ILogicalOperator auxOp) {
+ ScalarFunctionCallExpression combinedCondition =
+ new ScalarFunctionCallExpression(BuiltinFunctions.getBuiltinFunctionInfo(BuiltinFunctions.AND));
+ combinedCondition.setSourceLocation(selectOp.getSourceLocation());
+
+ List<Mutable<ILogicalExpression>> conjuncts = new ArrayList<>();
+ if (selectOp.getCondition().getValue().splitIntoConjuncts(conjuncts)) {
+ combinedCondition.getArguments().addAll(conjuncts);
+ conjuncts.clear();
+ } else {
+ combinedCondition.getArguments().add(selectOp.getCondition());
+ }
+
+ switch (auxOp.getOperatorTag()) {
+ case LEFTOUTERJOIN:
+ case INNERJOIN:
+ AbstractBinaryJoinOperator joinOp = (AbstractBinaryJoinOperator) auxOp;
+ if (joinOp.getCondition().getValue().splitIntoConjuncts(conjuncts)) {
+ combinedCondition.getArguments().addAll(conjuncts);
+ } else {
+ combinedCondition.getArguments().add(joinOp.getCondition());
+ }
+ break;
+
+ case SELECT:
+ SelectOperator selectOp2 = (SelectOperator) auxOp;
+ if (selectOp2.getCondition().getValue().splitIntoConjuncts(conjuncts)) {
+ combinedCondition.getArguments().addAll(conjuncts);
+ } else {
+ combinedCondition.getArguments().add(selectOp2.getCondition());
+ }
+ break;
+ }
+
+ return combinedCondition;
+ }
+
+ private SelectOperator getSelectFromPlan(AggregateOperator subplanRoot) {
+ ILogicalExpression aggregateCondition = null;
+ boolean isNonEmptyStream = false;
+ for (Mutable<ILogicalExpression> expression : subplanRoot.getExpressions()) {
+ AggregateFunctionCallExpression aggExpression = (AggregateFunctionCallExpression) expression.getValue();
+ if (aggExpression.getFunctionIdentifier().equals(BuiltinFunctions.NON_EMPTY_STREAM)) {
+ isNonEmptyStream = true;
+
+ } else if (aggExpression.isTwoStep()
+ && aggExpression.getStepOneAggregate().getFunctionIdentifier().equals(BuiltinFunctions.SQL_COUNT)
+ && aggExpression.getStepTwoAggregate().getFunctionIdentifier().equals(BuiltinFunctions.SQL_SUM)
+ && aggExpression.getArguments().get(0).getValue().getExpressionTag()
+ .equals(LogicalExpressionTag.FUNCTION_CALL)) {
+ AbstractFunctionCallExpression switchExpression =
+ (AbstractFunctionCallExpression) aggExpression.getArguments().get(0).getValue();
+
+ ILogicalExpression arg1 = switchExpression.getArguments().get(0).getValue();
+ ILogicalExpression arg2 = switchExpression.getArguments().get(1).getValue();
+ ILogicalExpression arg3 = switchExpression.getArguments().get(2).getValue();
+ ILogicalExpression arg4 = switchExpression.getArguments().get(3).getValue();
+ if (arg2.getExpressionTag().equals(LogicalExpressionTag.CONSTANT)
+ && arg3.getExpressionTag().equals(LogicalExpressionTag.CONSTANT)
+ && arg4.getExpressionTag().equals(LogicalExpressionTag.CONSTANT)
+ && ((ConstantExpression) arg2).getValue().isTrue()
+ && ((ConstantExpression) arg3).getValue().isNull()
+ && ((ConstantExpression) arg4).getValue().isTrue()) {
+ aggregateCondition = arg1;
+ }
+ }
+ }
+
+ // First, try to create a SELECT from the aggregate itself (i.e. handle the SOME AND EVERY case).
+ if (isNonEmptyStream && aggregateCondition != null) {
+ SelectOperator selectFromAgg = new SelectOperator(new MutableObject<>(aggregateCondition), false, null);
+ selectFromAgg.getInputs().addAll(subplanRoot.getInputs());
+ selectFromAgg.setSourceLocation(sourceLocation);
+ return selectFromAgg;
+ }
+
+ // If we could not create a SELECT from the aggregate, try to find a SELECT inside the subplan itself.
+ ILogicalOperator workingOperator = subplanRoot.getInputs().get(0).getValue();
+ while (workingOperator != null) {
+ if (workingOperator.getOperatorTag().equals(LogicalOperatorTag.SELECT)) {
+ return (SelectOperator) workingOperator;
+ }
+ workingOperator =
+ (workingOperator.getInputs().isEmpty()) ? null : workingOperator.getInputs().get(0).getValue();
+ }
+
+ // We could not find a SELECT.
+ return null;
+ }
+
+ private ILogicalExpression keepOptimizableFunctions(ILogicalExpression cond) {
+ if (cond.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)) {
+ AbstractFunctionCallExpression func = (AbstractFunctionCallExpression) cond;
+ List<Mutable<ILogicalExpression>> conjuncts = new ArrayList<>();
+ if (func.splitIntoConjuncts(conjuncts)) {
+ List<Mutable<ILogicalExpression>> optimizableConjuncts = new ArrayList<>();
+ for (Mutable<ILogicalExpression> conjunct : conjuncts) {
+ if (conjunct.getValue().getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)
+ && optimizableFunctions.contains(
+ ((AbstractFunctionCallExpression) conjunct.getValue()).getFunctionIdentifier())) {
+ optimizableConjuncts.add(conjunct);
+ }
+ }
+
+ if (optimizableConjuncts.size() == 1) {
+ return optimizableConjuncts.get(0).getValue();
+
+ } else if (optimizableConjuncts.size() > 1) {
+ ScalarFunctionCallExpression andCond = new ScalarFunctionCallExpression(
+ BuiltinFunctions.getBuiltinFunctionInfo(BuiltinFunctions.AND));
+ andCond.setSourceLocation(cond.getSourceLocation());
+ andCond.getArguments().addAll(optimizableConjuncts);
+ return andCond;
+ }
+
+ } else if (func.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)
+ && optimizableFunctions.contains(func.getFunctionIdentifier())) {
+ return cond;
+
+ }
+ }
+
+ return ConstantExpression.TRUE;
+ }
+
+ private AggregateOperator getAggregateFromSubplan(SubplanOperator subplanOperator) {
+ // We only expect one plan, and one root.
+ if (subplanOperator.getNestedPlans().size() > 1
+ || subplanOperator.getNestedPlans().get(0).getRoots().size() > 1) {
+ return null;
+ }
+
+ // This root of our "subplan" should always be an aggregate.
+ ILogicalOperator workingSubplanRoot = subplanOperator.getNestedPlans().get(0).getRoots().get(0).getValue();
+ AggregateOperator workingSubplanRootAsAggregate;
+ if (!workingSubplanRoot.getOperatorTag().equals(LogicalOperatorTag.AGGREGATE)) {
+ return null;
+ }
+ workingSubplanRootAsAggregate = (AggregateOperator) workingSubplanRoot;
+ return workingSubplanRootAsAggregate;
+ }
+
+ private boolean isStrictUniversalQuantification(AggregateOperator workingSubplanRoot) {
+ for (Mutable<ILogicalExpression> expression : workingSubplanRoot.getExpressions()) {
+ AggregateFunctionCallExpression funcExpr = (AggregateFunctionCallExpression) expression.getValue();
+ if (funcExpr.getFunctionIdentifier().equals(BuiltinFunctions.EMPTY_STREAM)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isArrayNonEmptyConjunctMissing(UnnestOperator firstUnnestInNTS, ILogicalOperator subplanInput) {
+ UnnestingFunctionCallExpression unnestFunction =
+ (UnnestingFunctionCallExpression) firstUnnestInNTS.getExpressionRef().getValue();
+ VariableReferenceExpression unnestVarExpr =
+ (VariableReferenceExpression) unnestFunction.getArguments().get(0).getValue();
+ LogicalVariable arrayVariable = unnestVarExpr.getVariableReference();
+
+ if (!subplanInput.getOperatorTag().equals(LogicalOperatorTag.SELECT)) {
+ return true;
+ }
+
+ ILogicalExpression selectCondExpr =
+ normalizeCondition(null, ((SelectOperator) subplanInput).getCondition().getValue());
+ List<Mutable<ILogicalExpression>> conjunctsFromSelect = new ArrayList<>();
+ if (selectCondExpr.splitIntoConjuncts(conjunctsFromSelect)) {
+ // We have a collection of conjuncts. Analyze each conjunct w/ a function.
+ for (Mutable<ILogicalExpression> mutableConjunct : conjunctsFromSelect) {
+ ILogicalExpression workingConjunct = mutableConjunct.getValue();
+ if (workingConjunct.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)
+ && analyzeConjunctForArrayNonEmptiness(arrayVariable,
+ (ScalarFunctionCallExpression) workingConjunct)) {
+ return false;
+ }
+ }
+
+ // No such conjunct found.
+ return true;
+ }
+
+ if (!selectCondExpr.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)) {
+ return true;
+ }
+ return !analyzeConjunctForArrayNonEmptiness(arrayVariable, (ScalarFunctionCallExpression) selectCondExpr);
+ }
+
+ private boolean analyzeConjunctForArrayNonEmptiness(LogicalVariable arrayVariable,
+ ScalarFunctionCallExpression workingSelectCondExpr) {
+ // Handle the conjunct: LEN(arrayVar) > 0
+ if (workingSelectCondExpr.getFunctionIdentifier().equals(BuiltinFunctions.GT)) {
+ ILogicalExpression firstArg = workingSelectCondExpr.getArguments().get(0).getValue();
+ ILogicalExpression secondArg = workingSelectCondExpr.getArguments().get(1).getValue();
+
+ if (firstArg.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)
+ && ((ScalarFunctionCallExpression) firstArg).getFunctionIdentifier().equals(BuiltinFunctions.LEN)) {
+ ScalarFunctionCallExpression lenFunction = (ScalarFunctionCallExpression) firstArg;
+ List<LogicalVariable> usedVariables = new ArrayList<>();
+ lenFunction.getUsedVariables(usedVariables);
+
+ return usedVariables.contains(arrayVariable)
+ && secondArg.getExpressionTag().equals(LogicalExpressionTag.CONSTANT)
+ && ZEROS_AS_ASTERIX_CONSTANTS.contains(((ConstantExpression) secondArg).getValue());
+ }
+ }
+
+ // Handle the conjunct: 0 < LEN(arrayVar)
+ else if (workingSelectCondExpr.getFunctionIdentifier().equals(BuiltinFunctions.LT)) {
+ ILogicalExpression firstArg = workingSelectCondExpr.getArguments().get(0).getValue();
+ ILogicalExpression secondArg = workingSelectCondExpr.getArguments().get(1).getValue();
+
+ if (secondArg.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)
+ && ((ScalarFunctionCallExpression) secondArg).getFunctionIdentifier()
+ .equals(BuiltinFunctions.LEN)) {
+ ScalarFunctionCallExpression lenFunction = (ScalarFunctionCallExpression) secondArg;
+ List<LogicalVariable> usedVariables = new ArrayList<>();
+ lenFunction.getUsedVariables(usedVariables);
+
+ return usedVariables.contains(arrayVariable)
+ && firstArg.getExpressionTag().equals(LogicalExpressionTag.CONSTANT)
+ && ZEROS_AS_ASTERIX_CONSTANTS.contains(((ConstantExpression) firstArg).getValue());
+ }
+ }
+
+ return false;
+ }
+
+ private ILogicalExpression normalizeCondition(AggregateOperator aggregateOperator, ILogicalExpression expr) {
+ // The purpose of this function is to remove the NOT(IF-MISSING-OR-NULL(...)) functions for a strict universal
+ // quantification query. The {@code ArrayBTreeAccessMethod} does not recognize the former as optimizable
+ // functions, so we remove them here. This SELECT will never make it to the final query plan (after the
+ // {@code IntroduceSelectAccessMethodRule}), which allows us to get away with this logically incorrect branch.
+ if (aggregateOperator != null && !isStrictUniversalQuantification(aggregateOperator)) {
+ // We are working with an existential quantification OR an EACH AND EVERY query. Do not modify the SELECT.
+ return expr;
+
+ } else {
+ // We are working with a strict universal quantification query.
+ ScalarFunctionCallExpression notFunction = (ScalarFunctionCallExpression) expr;
+ if (!notFunction.getFunctionIdentifier().equals(BuiltinFunctions.NOT)) {
+ return expr;
+ }
+
+ ScalarFunctionCallExpression ifMissingOrNullFunction =
+ (ScalarFunctionCallExpression) notFunction.getArguments().get(0).getValue();
+ if (!ifMissingOrNullFunction.getFunctionIdentifier().equals(BuiltinFunctions.IF_MISSING_OR_NULL)) {
+ return expr;
+ }
+ return ifMissingOrNullFunction.getArguments().get(0).getValue().cloneExpression();
+ }
+ }
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/ArrayIndexStructureMatcher.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/ArrayIndexStructureMatcher.java
similarity index 98%
rename from asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/ArrayIndexStructureMatcher.java
rename to asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/ArrayIndexStructureMatcher.java
index 2e7f814..62b266a 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/ArrayIndexStructureMatcher.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/ArrayIndexStructureMatcher.java
@@ -17,7 +17,7 @@
* under the License.
*/
-package org.apache.asterix.optimizer.rules.am;
+package org.apache.asterix.optimizer.rules.am.array;
import java.util.ArrayList;
import java.util.Collections;
@@ -31,6 +31,7 @@
import org.apache.asterix.om.functions.BuiltinFunctions;
import org.apache.asterix.om.types.ARecordType;
import org.apache.asterix.om.types.IAType;
+import org.apache.asterix.optimizer.rules.am.OptimizableOperatorSubTree;
import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
import org.apache.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
@@ -126,8 +127,6 @@
lastMatchedPosition = searchPosition;
isStructureMatchedFoundForThisStep = true;
- } else {
- isStructureMatchedFoundForThisStep = false;
}
}
searchPosition++;
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/IIntroduceAccessMethodRuleLocalRewrite.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/IIntroduceAccessMethodRuleLocalRewrite.java
new file mode 100644
index 0000000..9942bbe
--- /dev/null
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/IIntroduceAccessMethodRuleLocalRewrite.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.asterix.optimizer.rules.am.array;
+
+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.ILogicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
+
+public interface IIntroduceAccessMethodRuleLocalRewrite<T> {
+ /**
+ * @param originalOperator Original operator before rewrite. Should be returned by {@code restoreBeforeRewrite}.
+ * @param context Optimization context.
+ * @return Null if no rewrite has occurred. Otherwise, a new plan from the given operator.
+ * @throws AlgebricksException
+ */
+ T createOperator(T originalOperator, IOptimizationContext context) throws AlgebricksException;
+
+ /**
+ * @param afterOperatorRefs Operators after the original operator that should be restored after the rewrite.
+ * @param context Optimization context.
+ * @return The original operator given at {@code createOperator} time.
+ * @throws AlgebricksException
+ */
+ T restoreBeforeRewrite(List<Mutable<ILogicalOperator>> afterOperatorRefs, IOptimizationContext context)
+ throws AlgebricksException;
+}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/subplan/JoinFromSubplanCreator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/JoinFromSubplanRewrite.java
similarity index 68%
rename from asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/subplan/JoinFromSubplanCreator.java
rename to asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/JoinFromSubplanRewrite.java
index 9a11b98..c6fb5fd 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/subplan/JoinFromSubplanCreator.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/JoinFromSubplanRewrite.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.asterix.optimizer.rules.subplan;
+package org.apache.asterix.optimizer.rules.am.array;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -29,16 +29,24 @@
import org.apache.commons.lang3.mutable.MutableObject;
import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
import org.apache.hyracks.algebricks.common.utils.Pair;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
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.AbstractFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.ScalarFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.VariableReferenceExpression;
+import org.apache.hyracks.algebricks.core.algebra.functions.AlgebricksBuiltinFunctions;
import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractBinaryJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AssignOperator;
import org.apache.hyracks.algebricks.core.algebra.operators.logical.InnerJoinOperator;
-import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterJoinOperator;
import org.apache.hyracks.algebricks.core.algebra.operators.logical.SelectOperator;
import org.apache.hyracks.algebricks.core.algebra.operators.logical.SubplanOperator;
import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.visitors.VariableUtilities;
import org.apache.hyracks.algebricks.core.algebra.util.OperatorManipulationUtil;
/**
@@ -91,7 +99,7 @@
*
* In the case of nested-subplans, we return a copy of the innermost SELECT followed by all relevant UNNEST/ASSIGNs.
*/
-public class JoinFromSubplanCreator extends AbstractOperatorFromSubplanCreator<AbstractBinaryJoinOperator> {
+public class JoinFromSubplanRewrite extends AbstractOperatorFromSubplanRewrite<AbstractBinaryJoinOperator> {
private final static Set<FunctionIdentifier> optimizableFunctions = new HashSet<>();
private final Deque<JoinFromSubplanContext> contextStack = new ArrayDeque<>();
@@ -174,27 +182,35 @@
SubplanOperator subplanOperator =
(SubplanOperator) joinContext.selectAfterSubplan.getInputs().get(0).getValue();
Pair<SelectOperator, UnnestOperator> traversalOutput =
- traverseSubplanBranch(subplanOperator, originalOperator.getInputs().get(1).getValue());
+ traverseSubplanBranch(subplanOperator, originalOperator.getInputs().get(1).getValue(), true);
if (traversalOutput == null) {
return null;
}
// We have successfully generated a SELECT branch. Create the new JOIN operator.
- if (originalOperator.getOperatorTag().equals(LogicalOperatorTag.INNERJOIN)) {
- joinContext.newJoinRoot = new InnerJoinOperator(traversalOutput.first.getCondition());
-
- } else { // originalOperator.getOperatorTag().equals(LogicalOperatorTag.LEFTOUTERJOIN)
- joinContext.newJoinRoot = new LeftOuterJoinOperator(traversalOutput.first.getCondition());
- }
+ ScalarFunctionCallExpression newCond = coalesceConditions(traversalOutput.first, joinContext.originalJoinRoot);
+ joinContext.newJoinRoot = new InnerJoinOperator(new MutableObject<>(newCond));
joinContext.newJoinRoot.getInputs().add(0, originalOperator.getInputs().get(0));
- // Create the index join branch.
+ // Connect the join branches together.
traversalOutput.second.getInputs().clear();
traversalOutput.second.getInputs().add(originalOperator.getInputs().get(1));
context.computeAndSetTypeEnvironmentForOperator(traversalOutput.second);
joinContext.newJoinRoot.getInputs().add(1, traversalOutput.first.getInputs().get(0));
context.computeAndSetTypeEnvironmentForOperator(joinContext.newJoinRoot);
+ // To support type casting that is performed on the index subtree and still make this expression recognizable,
+ // push all function calls to a lower ASSIGN.
+ List<Mutable<ILogicalExpression>> conjuncts = new ArrayList<>();
+ if (newCond.splitIntoConjuncts(conjuncts)) {
+ for (Mutable<ILogicalExpression> conjunct : conjuncts) {
+ extractFunctionCallToAssign(joinContext.newJoinRoot, context, conjunct.getValue());
+ }
+
+ } else {
+ extractFunctionCallToAssign(joinContext.newJoinRoot, context, newCond);
+ }
+
// Reconnect our after-join operator to our new join.
OperatorManipulationUtil.substituteOpInInput(joinContext.afterJoinOpForRewrite,
joinContext.removedAfterJoinOperators.get(0).getValue(), new MutableObject<>(joinContext.newJoinRoot));
@@ -217,6 +233,56 @@
return joinContext.originalJoinRoot;
}
+ private void extractFunctionCallToAssign(AbstractBinaryJoinOperator joinOp, IOptimizationContext context,
+ ILogicalExpression condition) throws AlgebricksException {
+ if (!condition.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)) {
+ return;
+ }
+ AbstractFunctionCallExpression conditionAsFuncCall = (AbstractFunctionCallExpression) condition;
+ if (!AlgebricksBuiltinFunctions.isComparisonFunction(conditionAsFuncCall.getFunctionIdentifier())) {
+ return;
+ }
+
+ for (Mutable<ILogicalExpression> arg : conditionAsFuncCall.getArguments()) {
+ if (!arg.getValue().getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)) {
+ continue;
+ }
+
+ LogicalVariable newVar = context.newVar();
+ VariableReferenceExpression newVarRef = new VariableReferenceExpression(newVar);
+ newVarRef.setSourceLocation(joinOp.getSourceLocation());
+ AssignOperator newAssign =
+ new AssignOperator(newVar, new MutableObject<>(arg.getValue().cloneExpression()));
+ newAssign.setSourceLocation(arg.getValue().getSourceLocation());
+ newAssign.setExecutionMode(joinOp.getExecutionMode());
+
+ // Place the new ASSIGN in the appropriate join branch.
+ ILogicalOperator leftBranchRoot = joinOp.getInputs().get(0).getValue();
+ ILogicalOperator rightBranchRoot = joinOp.getInputs().get(1).getValue();
+ List<LogicalVariable> usedVarsFromFunc = new ArrayList<>();
+ List<LogicalVariable> varsFromLeftBranch = new ArrayList<>();
+ List<LogicalVariable> varsFromRightBranch = new ArrayList<>();
+ VariableUtilities.getUsedVariables(newAssign, usedVarsFromFunc);
+ VariableUtilities.getProducedVariablesInDescendantsAndSelf(leftBranchRoot, varsFromLeftBranch);
+ VariableUtilities.getProducedVariablesInDescendantsAndSelf(rightBranchRoot, varsFromRightBranch);
+ if (varsFromLeftBranch.containsAll(usedVarsFromFunc)) {
+ newAssign.getInputs().add(new MutableObject<>(leftBranchRoot));
+ context.computeAndSetTypeEnvironmentForOperator(newAssign);
+ joinOp.getInputs().get(0).setValue(newAssign);
+ context.computeAndSetTypeEnvironmentForOperator(joinOp);
+ arg.setValue(newVarRef);
+
+ } else if (varsFromRightBranch.containsAll(usedVarsFromFunc)) {
+ newAssign.getInputs().add(new MutableObject<>(rightBranchRoot));
+ context.computeAndSetTypeEnvironmentForOperator(newAssign);
+ joinOp.getInputs().get(1).setValue(newAssign);
+ context.computeAndSetTypeEnvironmentForOperator(joinOp);
+ arg.setValue(newVarRef);
+
+ }
+ }
+ }
+
/**
* All state associated with a single call of {@code createOperator}.
*/
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/MergedSelectRewrite.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/MergedSelectRewrite.java
new file mode 100644
index 0000000..c9705ab
--- /dev/null
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/MergedSelectRewrite.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.asterix.optimizer.rules.am.array;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableObject;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalPlan;
+import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.ScalarFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.functions.AlgebricksBuiltinFunctions;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.SelectOperator;
+import org.apache.hyracks.algebricks.core.algebra.plan.ALogicalPlanImpl;
+import org.apache.hyracks.algebricks.core.algebra.util.OperatorManipulationUtil;
+
+/**
+ * For all expressions that pertain to a single dataset but are spread across various SELECTs due to requiring an
+ * intermediate UNNEST, "push up" the lower SELECT expressions into a single unifying SELECT. This is used to
+ * recognize composite atomic-array index applicability.
+ */
+public class MergedSelectRewrite implements IIntroduceAccessMethodRuleLocalRewrite<SelectOperator> {
+ private final Set<Mutable<ILogicalExpression>> selectConjuncts = new LinkedHashSet<>();
+ private final Deque<SelectOperator> selectRootStack = new ArrayDeque<>();
+ private Mutable<ILogicalOperator> originalDataSourceRef;
+
+ @Override
+ public SelectOperator createOperator(SelectOperator originalOperator, IOptimizationContext context)
+ throws AlgebricksException {
+ // Initialize the conjuncts for our SELECT.
+ selectConjuncts.clear();
+ List<Mutable<ILogicalExpression>> thisSelectConjuncts = new ArrayList<>();
+ if (!originalOperator.getCondition().getValue().splitIntoConjuncts(thisSelectConjuncts)) {
+ thisSelectConjuncts.add(originalOperator.getCondition());
+ }
+ selectConjuncts.addAll(thisSelectConjuncts);
+
+ // Explore all operators below this SELECT until the first data source operator.
+ selectRootStack.push(originalOperator);
+ collectSelectConjuncts(originalOperator.getInputs().get(0));
+
+ if (thisSelectConjuncts.size() == selectConjuncts.size()) {
+ // No other SELECTs were found. Return null to indicate there were no SELECTs to merge.
+ return null;
+
+ } else {
+ // We have found additional SELECTs. Form a conjunction.
+ AbstractFunctionCallExpression andCond = new ScalarFunctionCallExpression(
+ context.getMetadataProvider().lookupFunction(AlgebricksBuiltinFunctions.AND));
+ andCond.setSourceLocation(originalOperator.getSourceLocation());
+ for (Mutable<ILogicalExpression> conjunct : selectConjuncts) {
+ andCond.getArguments().add(conjunct);
+ }
+
+ // Return a new plan that removes all SELECTs that were pushed up.
+ SelectOperator newSelectOperator = new SelectOperator(new MutableObject<>(andCond),
+ originalOperator.getRetainMissing(), originalOperator.getMissingPlaceholderVariable());
+ newSelectOperator.setSourceLocation(originalOperator.getSourceLocation());
+ ILogicalPlan newSelectInputPlan = OperatorManipulationUtil
+ .deepCopy(new ALogicalPlanImpl(originalOperator.getInputs().get(0)), context);
+ newSelectOperator.getInputs().add(newSelectInputPlan.getRoots().get(0));
+ removeSelectsFromPlan(newSelectOperator, newSelectInputPlan.getRoots().get(0));
+ OperatorManipulationUtil.computeTypeEnvironmentBottomUp(newSelectOperator, context);
+ return newSelectOperator;
+ }
+ }
+
+ @Override
+ public SelectOperator restoreBeforeRewrite(List<Mutable<ILogicalOperator>> afterOperatorRefs,
+ IOptimizationContext context) throws AlgebricksException {
+ return selectRootStack.pop();
+ }
+
+ private void collectSelectConjuncts(Mutable<ILogicalOperator> workingOp) {
+ switch (workingOp.getValue().getOperatorTag()) {
+ case DATASOURCESCAN:
+ case EMPTYTUPLESOURCE:
+ case UNNEST_MAP:
+ // If we have reached a datasource operator, stop our search.
+ originalDataSourceRef = workingOp;
+ break;
+
+ case INNERJOIN:
+ case LEFTOUTERJOIN:
+ // We are not interested in exploring joins in this class.
+ break;
+
+ case SELECT:
+ SelectOperator selectOperator = (SelectOperator) workingOp.getValue();
+ List<Mutable<ILogicalExpression>> thisSelectConjuncts = new ArrayList<>();
+ if (!selectOperator.getCondition().getValue().splitIntoConjuncts(thisSelectConjuncts)) {
+ thisSelectConjuncts.add(selectOperator.getCondition());
+ }
+ selectConjuncts.addAll(thisSelectConjuncts);
+
+ default:
+ // Explore the rest of our plan in our DFS fashion.
+ for (Mutable<ILogicalOperator> input : workingOp.getValue().getInputs()) {
+ collectSelectConjuncts(input);
+ }
+ }
+ }
+
+ private void removeSelectsFromPlan(ILogicalOperator parentOp, Mutable<ILogicalOperator> workingOp) {
+ Mutable<ILogicalOperator> workingOpInParent =
+ parentOp.getInputs().stream().filter(i -> i.equals(workingOp)).collect(Collectors.toList()).get(0);
+ int indexOfWorkingOpInParent = parentOp.getInputs().indexOf(workingOpInParent);
+
+ switch (workingOp.getValue().getOperatorTag()) {
+ case DATASOURCESCAN:
+ case EMPTYTUPLESOURCE:
+ case UNNEST_MAP:
+ // If we have reached a datasource operator, stop and replace this with our original datasource.
+ // (IntroduceSelectAccessMethodRule replaces this specific operator, so this must be the original.)
+ parentOp.getInputs().set(indexOfWorkingOpInParent, originalDataSourceRef);
+ break;
+
+ case SELECT:
+ parentOp.getInputs().set(indexOfWorkingOpInParent, workingOp.getValue().getInputs().get(0));
+
+ default:
+ // Explore the rest of our plan in our DFS fashion.
+ for (Mutable<ILogicalOperator> input : workingOp.getValue().getInputs()) {
+ removeSelectsFromPlan(workingOp.getValue(), input);
+ }
+ }
+ }
+}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/subplan/SelectFromSubplanCreator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/SelectFromSubplanRewrite.java
similarity index 95%
rename from asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/subplan/SelectFromSubplanCreator.java
rename to asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/SelectFromSubplanRewrite.java
index 6211ea3..594ba41 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/subplan/SelectFromSubplanCreator.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/array/SelectFromSubplanRewrite.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.asterix.optimizer.rules.subplan;
+package org.apache.asterix.optimizer.rules.am.array;
import java.util.ArrayDeque;
import java.util.Deque;
@@ -78,7 +78,7 @@
*
* In the case of nested-subplans, we return a copy of the innermost SELECT followed by all relevant UNNEST/ASSIGNs.
*/
-public class SelectFromSubplanCreator extends AbstractOperatorFromSubplanCreator<SelectOperator> {
+public class SelectFromSubplanRewrite extends AbstractOperatorFromSubplanRewrite<SelectOperator> {
private final static Set<FunctionIdentifier> optimizableFunctions = new HashSet<>();
private final Deque<SelectOperator> selectRootStack = new ArrayDeque<>();
@@ -117,7 +117,7 @@
// Traverse our subplan and generate a SELECT branch if applicable.
SubplanOperator subplanOperator = (SubplanOperator) originalOperator.getInputs().get(0).getValue();
- Pair<SelectOperator, UnnestOperator> traversalOutput = traverseSubplanBranch(subplanOperator, null);
+ Pair<SelectOperator, UnnestOperator> traversalOutput = traverseSubplanBranch(subplanOperator, null, true);
return (traversalOutput == null) ? null : traversalOutput.first;
}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/subplan/AbstractOperatorFromSubplanCreator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/subplan/AbstractOperatorFromSubplanCreator.java
deleted file mode 100644
index 8fa7981..0000000
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/subplan/AbstractOperatorFromSubplanCreator.java
+++ /dev/null
@@ -1,504 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.optimizer.rules.subplan;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.om.base.AInt16;
-import org.apache.asterix.om.base.AInt32;
-import org.apache.asterix.om.base.AInt64;
-import org.apache.asterix.om.base.AInt8;
-import org.apache.asterix.om.constants.AsterixConstantValue;
-import org.apache.asterix.om.functions.BuiltinFunctions;
-import org.apache.commons.lang3.mutable.Mutable;
-import org.apache.commons.lang3.mutable.MutableObject;
-import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
-import org.apache.hyracks.algebricks.common.utils.Pair;
-import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
-import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
-import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
-import org.apache.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
-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.AbstractFunctionCallExpression;
-import org.apache.hyracks.algebricks.core.algebra.expressions.AggregateFunctionCallExpression;
-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.ScalarFunctionCallExpression;
-import org.apache.hyracks.algebricks.core.algebra.expressions.UnnestingFunctionCallExpression;
-import org.apache.hyracks.algebricks.core.algebra.expressions.VariableReferenceExpression;
-import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
-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.SelectOperator;
-import org.apache.hyracks.algebricks.core.algebra.operators.logical.SubplanOperator;
-import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestOperator;
-import org.apache.hyracks.algebricks.core.algebra.operators.logical.visitors.VariableUtilities;
-import org.apache.hyracks.algebricks.core.algebra.util.OperatorManipulationUtil;
-import org.apache.hyracks.api.exceptions.SourceLocation;
-
-abstract public class AbstractOperatorFromSubplanCreator<T> {
- private final static List<IAlgebricksConstantValue> ZEROS_AS_ASTERIX_CONSTANTS =
- Arrays.asList(new IAlgebricksConstantValue[] { new AsterixConstantValue(new AInt64(0)),
- new AsterixConstantValue(new AInt32(0)), new AsterixConstantValue(new AInt16((short) 0)),
- new AsterixConstantValue(new AInt8((byte) 0)) });
-
- private Set<FunctionIdentifier> optimizableFunctions;
- private IOptimizationContext context;
- private SourceLocation sourceLocation;
-
- abstract public T createOperator(T originalOperatorRef, IOptimizationContext context) throws AlgebricksException;
-
- abstract public T restoreBeforeRewrite(List<Mutable<ILogicalOperator>> afterOperatorRefs,
- IOptimizationContext context) throws AlgebricksException;
-
- protected void reset(SourceLocation sourceLocation, IOptimizationContext context,
- Set<FunctionIdentifier> optimizableFunctions) {
- this.optimizableFunctions = optimizableFunctions;
- this.sourceLocation = sourceLocation;
- this.context = context;
- }
-
- protected LogicalVariable getConditioningVariable(ILogicalExpression condition) {
- List<Mutable<ILogicalExpression>> selectConjuncts = new ArrayList<>();
- if (condition.splitIntoConjuncts(selectConjuncts)) {
- for (Mutable<ILogicalExpression> conjunct : selectConjuncts) {
- if (conjunct.getValue().getExpressionTag().equals(LogicalExpressionTag.VARIABLE)) {
- return ((VariableReferenceExpression) conjunct.getValue()).getVariableReference();
- }
- }
-
- } else if (condition.getExpressionTag().equals(LogicalExpressionTag.VARIABLE)) {
- return ((VariableReferenceExpression) condition).getVariableReference();
-
- }
- return null;
- }
-
- protected Pair<SelectOperator, UnnestOperator> traverseSubplanBranch(SubplanOperator subplanOperator,
- ILogicalOperator parentInput) throws AlgebricksException {
- // We only expect one plan, and one root.
- if (subplanOperator.getNestedPlans().size() > 1
- || subplanOperator.getNestedPlans().get(0).getRoots().size() > 1) {
- return null;
- }
-
- // This root of our "subplan" should always be an aggregate.
- ILogicalOperator workingSubplanRoot = subplanOperator.getNestedPlans().get(0).getRoots().get(0).getValue();
- AggregateOperator workingSubplanRootAsAggregate;
- if (!workingSubplanRoot.getOperatorTag().equals(LogicalOperatorTag.AGGREGATE)) {
- return null;
- }
- workingSubplanRootAsAggregate = (AggregateOperator) workingSubplanRoot;
-
- // Find (or create, in the SOME AND EVERY case) a SELECT that we can potentially optimize.
- SelectOperator optimizableSelect = getSelectFromPlan(workingSubplanRootAsAggregate);
- if (optimizableSelect == null) {
- return null;
- }
-
- // We have found a SELECT with a variable. Create a copy, and set this to our rewrite root.
- SelectOperator rewriteRootSelect = new SelectOperator(optimizableSelect.getCondition(),
- optimizableSelect.getRetainMissing(), optimizableSelect.getMissingPlaceholderVariable());
-
- // Ensure that this SELECT represents a predicate for an existential query, and is a query we can optimize.
- rewriteRootSelect = normalizeSelectCondition(workingSubplanRootAsAggregate, rewriteRootSelect);
- if (rewriteRootSelect == null) {
- return null;
- }
- rewriteRootSelect.setSourceLocation(sourceLocation);
- rewriteRootSelect.setExecutionMode(optimizableSelect.getExecutionMode());
-
- // Follow this SELECT to the root of our nested-plan branch (i.e. the NESTED-TUPLE-SOURCE).
- ILogicalOperator workingNewOperator = rewriteRootSelect;
- UnnestOperator bottommostNewUnnest = null;
- ILogicalOperator workingOriginalOperator = optimizableSelect.getInputs().get(0).getValue();
- while (!workingOriginalOperator.getOperatorTag().equals(LogicalOperatorTag.NESTEDTUPLESOURCE)) {
- if (workingOriginalOperator.getInputs().isEmpty()) {
- throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
- workingSubplanRoot.getSourceLocation(),
- "NESTED-TUPLE-SOURCE expected in nested plan branch, but not found.");
- }
-
- switch (workingOriginalOperator.getOperatorTag()) {
- case UNNEST:
- UnnestOperator originalUnnest = (UnnestOperator) workingOriginalOperator;
- UnnestOperator newUnnest =
- new UnnestOperator(originalUnnest.getVariable(), originalUnnest.getExpressionRef());
- newUnnest.setSourceLocation(sourceLocation);
- workingNewOperator.getInputs().add(new MutableObject<>(newUnnest));
- workingNewOperator = newUnnest;
- bottommostNewUnnest = (UnnestOperator) workingNewOperator;
- break;
-
- case ASSIGN:
- AssignOperator originalAssign = (AssignOperator) workingOriginalOperator;
- AssignOperator newAssign =
- new AssignOperator(originalAssign.getVariables(), originalAssign.getExpressions());
- newAssign.setSourceLocation(sourceLocation);
- workingNewOperator.getInputs().add(new MutableObject<>(newAssign));
- workingNewOperator = newAssign;
- break;
-
- case SELECT:
- // If we encounter another SELECT, then we have multiple quantifiers. Transform our new SELECT to
- // include this condition.
- List<Mutable<ILogicalExpression>> selectArguments = new ArrayList<>();
- if (!rewriteRootSelect.getCondition().getValue().splitIntoConjuncts(selectArguments)) {
- selectArguments.add(rewriteRootSelect.getCondition());
- }
- if (!((SelectOperator) workingOriginalOperator).getCondition().getValue()
- .splitIntoConjuncts(selectArguments)) {
- selectArguments.add(((SelectOperator) workingOriginalOperator).getCondition());
- }
- ScalarFunctionCallExpression andCond = new ScalarFunctionCallExpression(
- BuiltinFunctions.getBuiltinFunctionInfo(BuiltinFunctions.AND), selectArguments);
- SelectOperator updatedSelectOperator = new SelectOperator(new MutableObject<>(andCond),
- rewriteRootSelect.getRetainMissing(), rewriteRootSelect.getMissingPlaceholderVariable());
- updatedSelectOperator.setSourceLocation(sourceLocation);
- updatedSelectOperator.getInputs().addAll(rewriteRootSelect.getInputs());
- rewriteRootSelect = updatedSelectOperator;
- break;
-
- case AGGREGATE:
- break;
-
- default:
- return null;
- }
-
- workingOriginalOperator = workingOriginalOperator.getInputs().get(0).getValue();
- }
-
- // Sanity check: we should always be working with an UNNEST at this stage.
- if (bottommostNewUnnest == null) {
- throw new CompilationException(ErrorCode.COMPILATION_ERROR, workingSubplanRoot.getSourceLocation(),
- "UNNEST expected in nested plan branch, but not found.");
- }
-
- // If we are working with strict universal quantification, then we must also check whether or not we have a
- // conjunct that asserts that the array should also be non-empty.
- if (isStrictUniversalQuantification(workingSubplanRootAsAggregate)
- && isArrayNonEmptyConjunctMissing(bottommostNewUnnest, subplanOperator.getInputs().get(0).getValue())
- && (parentInput == null || isArrayNonEmptyConjunctMissing(bottommostNewUnnest, parentInput))) {
- return null;
- }
-
- // We have added everything we need in our nested-plan branch. Now, connect the input of our SUBPLAN to our
- // current working branch.
- bottommostNewUnnest.getInputs().addAll(subplanOperator.getInputs());
- OperatorManipulationUtil.computeTypeEnvironmentBottomUp(rewriteRootSelect, context);
-
- return new Pair<>(rewriteRootSelect, bottommostNewUnnest);
- }
-
- protected SelectOperator getSelectFromPlan(AggregateOperator subplanRoot) throws AlgebricksException {
- ILogicalOperator subplanRootInput = subplanRoot.getInputs().get(0).getValue();
- ILogicalOperator subplanOrSelect = findSubplanOrOptimizableSelect(subplanRootInput);
-
- if (subplanOrSelect != null && subplanOrSelect.getOperatorTag().equals(LogicalOperatorTag.SUBPLAN)) {
- // We have found a SUBPLAN. Recurse by calling our caller.
- Pair<SelectOperator, UnnestOperator> traversalOutput =
- traverseSubplanBranch((SubplanOperator) subplanOrSelect, subplanRootInput);
- return (traversalOutput != null) ? traversalOutput.first : null;
-
- } else if (subplanOrSelect != null && subplanOrSelect.getOperatorTag().equals(LogicalOperatorTag.SELECT)) {
- // We have found a SELECT. Return this to our caller.
- return (SelectOperator) subplanOrSelect;
-
- } else {
- // We were not able to find a SELECT or a SUBPLAN. Try to find an expression in our aggregate that we
- // can optimize (i.e. handle SOME AND EVERY case).
- AbstractFunctionCallExpression optimizableCondition = null;
- boolean isNonEmptyStream = false;
- for (Mutable<ILogicalExpression> expression : subplanRoot.getExpressions()) {
- AggregateFunctionCallExpression aggExpression = (AggregateFunctionCallExpression) expression.getValue();
- if (aggExpression.getFunctionIdentifier().equals(BuiltinFunctions.NON_EMPTY_STREAM)) {
- isNonEmptyStream = true;
-
- } else if (aggExpression.isTwoStep()
- && aggExpression.getStepOneAggregate().getFunctionIdentifier()
- .equals(BuiltinFunctions.SQL_COUNT)
- && aggExpression.getStepTwoAggregate().getFunctionIdentifier().equals(BuiltinFunctions.SQL_SUM)
- && aggExpression.getArguments().get(0).getValue().getExpressionTag()
- .equals(LogicalExpressionTag.FUNCTION_CALL)) {
- AbstractFunctionCallExpression switchExpression =
- (AbstractFunctionCallExpression) aggExpression.getArguments().get(0).getValue();
- if (!switchExpression.getArguments().get(0).getValue().getExpressionTag()
- .equals(LogicalExpressionTag.FUNCTION_CALL)) {
- continue;
- }
- AbstractFunctionCallExpression switchCondition =
- (AbstractFunctionCallExpression) switchExpression.getArguments().get(0).getValue();
-
- ILogicalExpression arg2 = switchExpression.getArguments().get(1).getValue();
- ILogicalExpression arg3 = switchExpression.getArguments().get(2).getValue();
- ILogicalExpression arg4 = switchExpression.getArguments().get(3).getValue();
- if (arg2.getExpressionTag().equals(LogicalExpressionTag.CONSTANT)
- && arg3.getExpressionTag().equals(LogicalExpressionTag.CONSTANT)
- && arg4.getExpressionTag().equals(LogicalExpressionTag.CONSTANT)
- && ((ConstantExpression) arg2).getValue().isTrue()
- && ((ConstantExpression) arg3).getValue().isNull()
- && ((ConstantExpression) arg4).getValue().isTrue()) {
- optimizableCondition = switchCondition;
- }
- }
- }
- if (isNonEmptyStream && optimizableCondition != null) {
- SelectOperator newSelectFromAggregate =
- new SelectOperator(new MutableObject<>(optimizableCondition), false, null);
- newSelectFromAggregate.getInputs().addAll(subplanRoot.getInputs());
- newSelectFromAggregate.setSourceLocation(sourceLocation);
- return newSelectFromAggregate;
- }
- return null;
- }
- }
-
- private boolean isStrictUniversalQuantification(AggregateOperator workingSubplanRoot) {
- for (Mutable<ILogicalExpression> expression : workingSubplanRoot.getExpressions()) {
- AggregateFunctionCallExpression funcExpr = (AggregateFunctionCallExpression) expression.getValue();
- if (funcExpr.getFunctionIdentifier().equals(BuiltinFunctions.EMPTY_STREAM)) {
- return true;
- }
- }
- return false;
- }
-
- private boolean isArrayNonEmptyConjunctMissing(UnnestOperator firstUnnestInNTS, ILogicalOperator subplanInput)
- throws AlgebricksException {
- UnnestingFunctionCallExpression unnestFunction =
- (UnnestingFunctionCallExpression) firstUnnestInNTS.getExpressionRef().getValue();
- VariableReferenceExpression unnestVarExpr =
- (VariableReferenceExpression) unnestFunction.getArguments().get(0).getValue();
- LogicalVariable arrayVariable = unnestVarExpr.getVariableReference();
-
- if (!subplanInput.getOperatorTag().equals(LogicalOperatorTag.SELECT)) {
- return true;
- }
- SelectOperator subplanInputAsSelect = normalizeSelectCondition(null, (SelectOperator) subplanInput);
- ILogicalExpression selectCondExpr = subplanInputAsSelect.getCondition().getValue();
- List<Mutable<ILogicalExpression>> conjunctsFromSelect = new ArrayList<>();
- if (selectCondExpr.splitIntoConjuncts(conjunctsFromSelect)) {
- // We have a collection of conjuncts. Analyze each conjunct w/ a function.
- for (Mutable<ILogicalExpression> mutableConjunct : conjunctsFromSelect) {
- ILogicalExpression workingConjunct = mutableConjunct.getValue();
- if (workingConjunct.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)
- && analyzeConjunctForArrayNonEmptiness(arrayVariable,
- (ScalarFunctionCallExpression) workingConjunct)) {
- return false;
- }
- }
-
- // No such conjunct found.
- return true;
- }
-
- if (!selectCondExpr.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)) {
- return true;
- }
- return !analyzeConjunctForArrayNonEmptiness(arrayVariable, (ScalarFunctionCallExpression) selectCondExpr);
- }
-
- private boolean analyzeConjunctForArrayNonEmptiness(LogicalVariable arrayVariable,
- ScalarFunctionCallExpression workingSelectCondExpr) {
- // Handle the conjunct: LEN(arrayVar) > 0
- if (workingSelectCondExpr.getFunctionIdentifier().equals(BuiltinFunctions.GT)) {
- ILogicalExpression firstArg = workingSelectCondExpr.getArguments().get(0).getValue();
- ILogicalExpression secondArg = workingSelectCondExpr.getArguments().get(1).getValue();
-
- if (firstArg.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)
- && ((ScalarFunctionCallExpression) firstArg).getFunctionIdentifier().equals(BuiltinFunctions.LEN)) {
- ScalarFunctionCallExpression lenFunction = (ScalarFunctionCallExpression) firstArg;
- List<LogicalVariable> usedVariables = new ArrayList<>();
- lenFunction.getUsedVariables(usedVariables);
-
- return usedVariables.contains(arrayVariable)
- && secondArg.getExpressionTag().equals(LogicalExpressionTag.CONSTANT)
- && ZEROS_AS_ASTERIX_CONSTANTS.contains(((ConstantExpression) secondArg).getValue());
- }
- }
-
- // Handle the conjunct: 0 < LEN(arrayVar)
- else if (workingSelectCondExpr.getFunctionIdentifier().equals(BuiltinFunctions.LT)) {
- ILogicalExpression firstArg = workingSelectCondExpr.getArguments().get(0).getValue();
- ILogicalExpression secondArg = workingSelectCondExpr.getArguments().get(1).getValue();
-
- if (secondArg.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)
- && ((ScalarFunctionCallExpression) secondArg).getFunctionIdentifier()
- .equals(BuiltinFunctions.LEN)) {
- ScalarFunctionCallExpression lenFunction = (ScalarFunctionCallExpression) secondArg;
- List<LogicalVariable> usedVariables = new ArrayList<>();
- lenFunction.getUsedVariables(usedVariables);
-
- return usedVariables.contains(arrayVariable)
- && firstArg.getExpressionTag().equals(LogicalExpressionTag.CONSTANT)
- && ZEROS_AS_ASTERIX_CONSTANTS.contains(((ConstantExpression) firstArg).getValue());
- }
- }
-
- return false;
- }
-
- private SelectOperator normalizeSelectCondition(AggregateOperator aggregateOperator, SelectOperator selectOperator)
- throws AlgebricksException {
- // The purpose of this function is to remove the NOT(IF-MISSING-OR-NULL(...)) functions for a strict universal
- // quantification query. The {@code ArrayBTreeAccessMethod} does not recognize the former as optimizable
- // functions, so we remove them here. This SELECT will never make it to the final query plan (after the
- // {@code IntroduceSelectAccessMethodRule}), which allows us to get away with this logically incorrect branch.
- if (aggregateOperator != null && !isStrictUniversalQuantification(aggregateOperator)) {
- // We are working with an existential quantification OR an EACH AND EVERY query. Do not modify the SELECT.
- return selectOperator;
-
- } else {
- // We are working with a strict universal quantification query.
- ScalarFunctionCallExpression notFunction =
- (ScalarFunctionCallExpression) selectOperator.getCondition().getValue();
- if (!notFunction.getFunctionIdentifier().equals(BuiltinFunctions.NOT)) {
- return selectOperator;
- }
-
- ScalarFunctionCallExpression ifMissingOrNullFunction =
- (ScalarFunctionCallExpression) notFunction.getArguments().get(0).getValue();
- if (!ifMissingOrNullFunction.getFunctionIdentifier().equals(BuiltinFunctions.IF_MISSING_OR_NULL)) {
- return selectOperator;
- }
-
- Mutable<ILogicalExpression> newSelectCondition =
- new MutableObject<>(ifMissingOrNullFunction.getArguments().get(0).getValue().cloneExpression());
- return new SelectOperator(newSelectCondition, selectOperator.getRetainMissing(),
- selectOperator.getMissingPlaceholderVariable());
-
- }
- }
-
- private ILogicalOperator findSubplanOrOptimizableSelect(ILogicalOperator operator) throws AlgebricksException {
- // We are trying to find a SELECT operator with an optimizable function call.
- if (operator.getOperatorTag().equals(LogicalOperatorTag.SELECT)) {
- SelectOperator selectOperator = (SelectOperator) operator;
- ILogicalExpression selectCondExpr = selectOperator.getCondition().getValue();
- boolean containsValidVar = isAnyVarFromUnnestOrAssign(operator);
- if (containsValidVar && selectCondExpr.getExpressionTag() == LogicalExpressionTag.FUNCTION_CALL) {
-
- // We have a NOT function call. Determine if this follows the NOT(IF-MISSING-OR-NULL(...)) pattern.
- ScalarFunctionCallExpression notExpr = (ScalarFunctionCallExpression) selectCondExpr;
- if (notExpr.getFunctionIdentifier().equals(BuiltinFunctions.NOT)) {
-
- // This does not follow the NOT(IF-MISSING-OR-NULL(...)) pattern, but NOT is an optimizable
- // function call. Return this.
- ILogicalExpression notCondExpr = notExpr.getArguments().get(0).getValue();
- if (!notCondExpr.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)
- && optimizableFunctions.contains(BuiltinFunctions.NOT)) {
- return selectOperator;
- }
-
- // Inside the NOT(IF-MISSING-OR-NULL(...)) is an optimizable function. Return this.
- ScalarFunctionCallExpression ifMissingOrNullExpr = (ScalarFunctionCallExpression) notCondExpr;
- ILogicalExpression finalExpr = ifMissingOrNullExpr.getArguments().get(0).getValue();
- if (doesExpressionContainOptimizableFunction(finalExpr)) {
- return selectOperator;
- }
-
- } else if (doesExpressionContainOptimizableFunction(selectCondExpr)) {
- // We have an optimizable function. Return this.
- return selectOperator;
-
- }
- }
- } else if (operator.getOperatorTag().equals(LogicalOperatorTag.SUBPLAN)) {
- // We have found an additional SUBPLAN branch to explore. Recurse w/ caller function.
- return operator;
- }
-
- // No matching operator found. Recurse on current operator input.
- return (operator.getInputs().isEmpty()) ? null
- : findSubplanOrOptimizableSelect(operator.getInputs().get(0).getValue());
- }
-
- private boolean doesExpressionContainOptimizableFunction(ILogicalExpression inputExpr) {
- if (!inputExpr.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)) {
- return false;
- }
-
- // Check if the input expression itself is an optimizable function.
- ScalarFunctionCallExpression inputExprAsFunc = (ScalarFunctionCallExpression) inputExpr;
- if (isFunctionOptimizable(inputExprAsFunc)) {
- return true;
- }
-
- // We have a collection of conjuncts. Return true if any of these conjuncts are optimizable.
- List<Mutable<ILogicalExpression>> conjuncts = new ArrayList<>();
- if (inputExprAsFunc.splitIntoConjuncts(conjuncts)) {
- for (Mutable<ILogicalExpression> mutableConjunct : conjuncts) {
- ILogicalExpression workingConjunct = mutableConjunct.getValue();
- if (workingConjunct.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)
- && isFunctionOptimizable((ScalarFunctionCallExpression) workingConjunct)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- private boolean isFunctionOptimizable(ScalarFunctionCallExpression inputExpr) {
- if (inputExpr.getFunctionIdentifier().equals(BuiltinFunctions.GT)) {
- // Avoid the GT(LEN(array-field), 0) function.
- ILogicalExpression gtExpr = inputExpr.getArguments().get(0).getValue();
- return ((!gtExpr.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL))
- || !((ScalarFunctionCallExpression) gtExpr).getFunctionIdentifier().equals(BuiltinFunctions.LEN))
- && optimizableFunctions.contains(BuiltinFunctions.GT);
-
- } else if (inputExpr.getFunctionIdentifier().equals(BuiltinFunctions.LT)) {
- // Avoid the LT(0, LEN(array-field)) function.
- ILogicalExpression ltExpr = inputExpr.getArguments().get(1).getValue();
- return ((!ltExpr.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL))
- || !((ScalarFunctionCallExpression) ltExpr).getFunctionIdentifier().equals(BuiltinFunctions.LEN))
- && optimizableFunctions.contains(BuiltinFunctions.LT);
-
- }
-
- // Otherwise, check if the function itself is optimizable.
- return (optimizableFunctions.contains(inputExpr.getFunctionIdentifier()));
- }
-
- private boolean isAnyVarFromUnnestOrAssign(ILogicalOperator op) throws AlgebricksException {
- List<LogicalVariable> opUsedVars = new ArrayList<>(), relevantVars = new ArrayList<>();
- VariableUtilities.getUsedVariables(op, opUsedVars);
- ILogicalOperator workingOp = op;
- boolean isMatchFound = false;
- while (workingOp != null) {
- if (workingOp.getOperatorTag().equals(LogicalOperatorTag.UNNEST)
- || workingOp.getOperatorTag().equals(LogicalOperatorTag.ASSIGN)) {
- VariableUtilities.getProducedVariables(workingOp, relevantVars);
- if (opUsedVars.stream().anyMatch(relevantVars::contains)) {
- isMatchFound = true;
- break;
- }
- }
- workingOp = (workingOp.getInputs().isEmpty()) ? null : workingOp.getInputs().get(0).getValue();
- }
- return isMatchFound;
- }
-}
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
index abef74e..cd58a69 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
@@ -1254,11 +1254,6 @@
throw new CompilationException(ErrorCode.COMPILATION_INCOMPATIBLE_INDEX_TYPE, sourceLoc,
String.valueOf(indexType));
}
- if (indexedElementsCount > 1) {
- // TODO (GLENN): Add in support for composite atomic / array indexes.
- throw new CompilationException(ErrorCode.COMPILATION_INCOMPATIBLE_INDEX_TYPE, sourceLoc,
- String.valueOf(indexType));
- }
List<Index.ArrayIndexElement> indexElementList = new ArrayList<>(indexedElementsCount);
for (int i = 0; i < indexedElementsCount; i++) {
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query1.sqlpp
new file mode 100644
index 0000000..e12eeaf
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query1.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.
+ */
+SET `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestDataverse IF EXISTS;
+CREATE DATAVERSE TestDataverse;
+USE TestDataverse;
+
+CREATE TYPE TestType AS {
+ _id: uuid
+};
+
+CREATE DATASET Dataset1 (TestType)
+PRIMARY KEY _id AUTOGENERATED;
+CREATE INDEX d1Idx
+ON Dataset1 (
+ field1 : bigint,
+ ( UNNEST items
+ SELECT field2 : bigint,
+ field3 : bigint ),
+ field4 : bigint
+);
+
+-- Explicit UNNEST query with all fields of a composite index.
+FROM Dataset1 D
+UNNEST D.items DI
+WHERE D.field1 = 1 AND
+ DI.field2 = 2 AND
+ DI.field3 = 3 AND
+ D.field4 = 4
+SELECT *;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query2.sqlpp
new file mode 100644
index 0000000..f832a76
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query2.sqlpp
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+SET `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestDataverse IF EXISTS;
+CREATE DATAVERSE TestDataverse;
+USE TestDataverse;
+
+CREATE TYPE TestType AS {
+ _id: uuid
+};
+
+CREATE DATASET Dataset1 (TestType)
+PRIMARY KEY _id AUTOGENERATED;
+CREATE INDEX d1Idx
+ON Dataset1 (
+ field1 : bigint,
+ ( UNNEST items
+ SELECT field2 : bigint,
+ field3 : bigint ),
+ field4 : bigint
+);
+
+-- Explicit UNNEST query with all fields of a composite index, with extra clauses on non-indexed fields.
+FROM Dataset1 D
+UNNEST D.items DI
+WHERE D.field1 = 1 AND
+ DI.field2 = 2 AND
+ DI.field3 = 3 AND
+ DI.field3_notindexed = 3 AND
+ D.field4 = 4 AND
+ D.field4_notindexed = 4
+SELECT *;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query3.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query3.sqlpp
new file mode 100644
index 0000000..6c1f9b7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query3.sqlpp
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+SET `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestDataverse IF EXISTS;
+CREATE DATAVERSE TestDataverse;
+USE TestDataverse;
+
+CREATE TYPE TestType AS {
+ _id: uuid
+};
+
+CREATE DATASET Dataset1 (TestType)
+PRIMARY KEY _id AUTOGENERATED;
+CREATE INDEX d1Idx
+ON Dataset1 (
+ field1 : bigint,
+ ( UNNEST outer_items
+ UNNEST inner_items
+ SELECT field2 : bigint,
+ field3 : bigint ),
+ field4 : bigint
+);
+
+-- Explicit UNNEST query with all fields of a composite index, with extra clauses on non-indexed fields (2).
+FROM Dataset1 D
+UNNEST D.outer_items DOI
+UNNEST DOI.inner_items DII
+WHERE D.field1 = 1 AND
+ DOI.field2_notindexed = 2 AND
+ DII.field2 = 2 AND
+ DII.field3 = 3 AND
+ DII.field3_notindexed = 3 AND
+ D.field4 = 4
+SELECT *;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query4.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query4.sqlpp
new file mode 100644
index 0000000..135a563
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query4.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.
+ */
+SET `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestDataverse IF EXISTS;
+CREATE DATAVERSE TestDataverse;
+USE TestDataverse;
+
+CREATE TYPE TestType AS {
+ _id: uuid
+};
+
+CREATE DATASET Dataset1 (TestType)
+PRIMARY KEY _id AUTOGENERATED;
+CREATE INDEX d1Idx
+ON Dataset1 (
+ field1 : bigint,
+ ( UNNEST items
+ SELECT field2 : bigint,
+ field3 : bigint ),
+ field4 : bigint
+);
+
+-- Existential quantification query with all fields of a composite index.
+FROM Dataset1 D
+WHERE D.field1 = 1 AND
+ ( SOME DI IN D.items
+ SATISFIES DI.field2 = 2 AND
+ DI.field3 = 3 ) AND
+ D.field4 = 4
+SELECT *;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query5.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query5.sqlpp
new file mode 100644
index 0000000..8ce7532
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query5.sqlpp
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+SET `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestDataverse IF EXISTS;
+CREATE DATAVERSE TestDataverse;
+USE TestDataverse;
+
+CREATE TYPE TestType AS {
+ _id: uuid
+};
+
+CREATE DATASET Dataset1 (TestType)
+PRIMARY KEY _id AUTOGENERATED;
+CREATE INDEX d1Idx
+ON Dataset1 (
+ field1 : bigint,
+ ( UNNEST outer_items
+ UNNEST inner_items
+ SELECT field2 : bigint,
+ field3 : bigint ),
+ field4 : bigint
+);
+
+-- Existential quantification query with all fields of a composite index, with extra clauses on non-indexed fields.
+FROM Dataset1 D
+WHERE D.field1 = 1 AND
+ ( SOME DOI IN D.outer_items
+ SATISFIES DOI.field2_notindexed = 2 AND
+ ( SOME DII IN DOI.inner_items
+ SATISFIES DII.field2 = 2 AND
+ DII.field3 = 3 AND
+ DII.field3_notindexed = 3 ) ) AND
+ D.field4 = 4
+SELECT *;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query6.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query6.sqlpp
new file mode 100644
index 0000000..39f2687
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query6.sqlpp
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+SET `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestDataverse IF EXISTS;
+CREATE DATAVERSE TestDataverse;
+USE TestDataverse;
+
+CREATE TYPE TestType AS {
+ _id: uuid
+};
+
+CREATE DATASET Dataset1 (TestType)
+PRIMARY KEY _id AUTOGENERATED;
+CREATE DATASET Dataset2 (TestType)
+PRIMARY KEY _id AUTOGENERATED;
+CREATE INDEX d1Idx
+ON Dataset1 (
+ field1 : bigint,
+ ( UNNEST items
+ SELECT field2 : bigint,
+ field3 : bigint ),
+ field4 : bigint
+);
+
+-- Explicit UNNEST join query with all fields of a composite index.
+FROM Dataset2 D2
+INNER JOIN (
+ FROM Dataset1 D1
+ UNNEST D1.items D1I
+ SELECT D1.field1, D1I.field2, D1I.field3, D1.field4
+) AS D1IV
+ON D1IV.field1 /* +indexnl */ = TO_BIGINT(D2.field1) AND
+ D1IV.field2 /* +indexnl */ = TO_BIGINT(D2.field2) AND
+ D1IV.field3 /* +indexnl */ = TO_BIGINT(D2.field3) AND
+ D1IV.field4 /* +indexnl */ = TO_BIGINT(D2.field4)
+SELECT *;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query7.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query7.sqlpp
new file mode 100644
index 0000000..5d0dece
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query7.sqlpp
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+SET `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestDataverse IF EXISTS;
+CREATE DATAVERSE TestDataverse;
+USE TestDataverse;
+
+CREATE TYPE TestType AS {
+ _id: uuid
+};
+
+CREATE DATASET Dataset1 (TestType)
+PRIMARY KEY _id AUTOGENERATED;
+CREATE DATASET Dataset2 (TestType)
+PRIMARY KEY _id AUTOGENERATED;
+CREATE INDEX d1Idx
+ON Dataset1 (
+ field1 : bigint,
+ ( UNNEST items
+ SELECT field2 : bigint,
+ field3 : bigint ),
+ field4 : bigint
+);
+
+-- Explicit UNNEST join query with all fields of a composite index, with extra clauses on non-indexed fields.
+FROM Dataset2 D2
+INNER JOIN (
+ FROM Dataset1 D1
+ UNNEST D1.items D1I
+ SELECT D1.field1_notindexed, D1.field1, D1I.field2, D1I.field3, D1I.field3_notindexed, D1.field4
+) AS D1IV
+ON D1IV.field1 /* +indexnl */ = TO_BIGINT(D2.field1) AND
+ D1IV.field1_notindexed = TO_BIGINT(D2.field1_notindexed) AND
+ D1IV.field2 /* +indexnl */ = TO_BIGINT(D2.field2) AND
+ D1IV.field3 /* +indexnl */ = TO_BIGINT(D2.field3) AND
+ D1IV.field3_notindexed = TO_BIGINT(D2.field3_notindexed) AND
+ D1IV.field4 /* +indexnl */ = TO_BIGINT(D2.field4)
+SELECT *;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query8.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query8.sqlpp
new file mode 100644
index 0000000..8801212
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query8.sqlpp
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+SET `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestDataverse IF EXISTS;
+CREATE DATAVERSE TestDataverse;
+USE TestDataverse;
+
+CREATE TYPE TestType AS {
+ _id: uuid
+};
+
+CREATE DATASET Dataset1 (TestType)
+PRIMARY KEY _id AUTOGENERATED;
+CREATE DATASET Dataset2 (TestType)
+PRIMARY KEY _id AUTOGENERATED;
+CREATE INDEX d1Idx
+ON Dataset1 (
+ field1 : bigint,
+ ( UNNEST items
+ SELECT field2 : bigint,
+ field3 : bigint ),
+ field4 : bigint
+);
+
+-- Existential quantification join query with all fields of a composite index.
+FROM Dataset2 D2,
+ Dataset1 D1
+WHERE D1.field1 /* +indexnl */ = TO_BIGINT(D2.field1) AND
+ ( SOME D1I IN D1.items
+ SATISFIES D1I.field2 /* +indexnl */ = TO_BIGINT(D2.field2) AND
+ D1I.field3 /* +indexnl */ = TO_BIGINT(D2.field3) ) AND
+ D1.field4 /* +indexnl */ = TO_BIGINT(D2.field4)
+SELECT *;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query9.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query9.sqlpp
new file mode 100644
index 0000000..ac2c8c9
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/atomic-and-array-queries/query9.sqlpp
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+SET `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestDataverse IF EXISTS;
+CREATE DATAVERSE TestDataverse;
+USE TestDataverse;
+
+CREATE TYPE TestType AS {
+ _id: uuid
+};
+
+CREATE DATASET Dataset1 (TestType)
+PRIMARY KEY _id AUTOGENERATED;
+CREATE DATASET Dataset2 (TestType)
+PRIMARY KEY _id AUTOGENERATED;
+CREATE INDEX d1Idx
+ON Dataset1 (
+ field1 : bigint,
+ ( UNNEST outer_items
+ UNNEST inner_items
+ SELECT field2 : bigint,
+ field3 : bigint ),
+ field4 : bigint
+);
+
+-- Existential quantification query with all fields of a composite index, with extra clauses on non-indexed fields.
+FROM Dataset2 D2,
+ Dataset1 D1
+WHERE D1.field1 /* +indexnl */ = TO_BIGINT(D2.field1) AND
+ ( SOME DOI IN D1.outer_items
+ SATISFIES DOI.field2_notindexed = TO_BIGINT(D2.field2_notindexed) AND
+ ( SOME DII IN DOI.inner_items
+ SATISFIES DII.field2 /* +indexnl */ = TO_BIGINT(D2.field2) AND
+ DII.field3 /* +indexnl */ = TO_BIGINT(D2.field3) AND
+ DII.field3_notindexed = TO_BIGINT(D2.field3_notindexed) ) ) AND
+ D1.field4 /* +indexnl */ = TO_BIGINT(D2.field4)
+SELECT *;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/multiple-quantifiers/query9.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/multiple-quantifiers/query9.sqlpp
new file mode 100644
index 0000000..32ae924
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/multiple-quantifiers/query9.sqlpp
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+SET `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestDataverse IF EXISTS;
+CREATE DATAVERSE TestDataverse;
+USE TestDataverse;
+
+CREATE TYPE TestType AS {
+ _id: uuid,
+ outer_items: [{
+ inner_item: bigint,
+ inner_items: [{
+ item: bigint
+ }]
+ }]
+};
+CREATE DATASET Dataset1 (TestType)
+PRIMARY KEY _id AUTOGENERATED;
+CREATE INDEX d1Idx
+ON Dataset1 ( UNNEST outer_items
+ UNNEST inner_items
+ SELECT item );
+
+-- Double UNNEST w/ SELECT, open index and multiple quantification.
+FROM Dataset1 D1
+WHERE SOME AND EVERY I1 IN D1.outer_items,
+ J IN D1.other_items
+ SATISFIES J.item = 2 AND
+ I1.inner_item = 3 AND
+ ( SOME AND EVERY I2 IN I1.inner_items,
+ J2 IN I1.other_inner_items
+ SATISFIES I2.item = 1 AND
+ J2.item = 2 )
+SELECT *;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-1/query3.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-1/query3.sqlpp
index d2dc752..cc2346c 100644
--- a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-1/query3.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-1/query3.sqlpp
@@ -33,6 +33,6 @@
SELECT COUNT(*)
FROM YelpCheckin C
-WHERE LEN(C.dates) > 0 AND
+WHERE 0 < LEN(C.dates) AND
(EVERY D IN C.dates
SATISFIES D > "2016" AND D < "2017");
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-1/query5.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-1/query5.sqlpp
new file mode 100644
index 0000000..b2407a8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-1/query5.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.
+ */
+SET `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+ checkin_id: uuid,
+ business_id: string,
+ dates: [string]
+};
+
+CREATE DATASET YelpCheckin(CheckinType)
+PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates
+ON YelpCheckin (UNNEST dates);
+
+-- The following query should NOT be optimized.
+FROM YelpCheckin C
+WHERE 0 < LEN(C.dates_notindexed) AND
+ 0 < LEN(C.dates_notindexed_2) AND
+ ( EVERY D IN C.dates
+ SATISFIES D > "2016" AND D < "2017" )
+SELECT COUNT(*);
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-2/query4.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-2/query4.sqlpp
new file mode 100644
index 0000000..4a4a322
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-2/query4.sqlpp
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+SET `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+ checkin_id: uuid,
+ business_id: string,
+ checkin_times: {
+ dates: [string],
+ times: [string]
+ }
+};
+
+CREATE DATASET YelpCheckin(CheckinType)
+PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates
+ON YelpCheckin ( UNNEST checkin_times.dates );
+
+-- This query should NOT be optimized.
+FROM YelpCheckin C
+WHERE SOME AND EVERY D IN C.checkin_times.dates_notindexed
+ SATISFIES D > "2016" AND D < "2017"
+SELECT COUNT(*);
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-2/query5.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-2/query5.sqlpp
new file mode 100644
index 0000000..7f67c73
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-2/query5.sqlpp
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+SET `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+ checkin_id: uuid,
+ business_id: string,
+ checkin_times: {
+ dates: [string],
+ times: [string]
+ }
+};
+
+CREATE DATASET YelpCheckin(CheckinType)
+PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates
+ON YelpCheckin ( UNNEST checkin_times.dates );
+
+-- This query should NOT be optimized.
+FROM YelpCheckin C
+WHERE SOME AND EVERY D IN C.checkin_times.dates
+ SATISFIES LOWER(D) = "2016"
+SELECT COUNT(*);
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-3/query4.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-3/query4.sqlpp
new file mode 100644
index 0000000..129f151
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-3/query4.sqlpp
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+SET `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+ checkin_id: uuid,
+ business_id: string,
+ checkin_times: [{
+ date: string,
+ time: string
+ }]
+};
+
+CREATE DATASET YelpCheckin(CheckinType)
+PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates
+ON YelpCheckin ( UNNEST checkin_times SELECT date );
+
+-- The following query should NOT be optimized.
+FROM YelpCheckin C
+WHERE SOME AND EVERY D IN C.checkin_times
+ SATISFIES D.date_notindexed BETWEEN "2016" AND "2017"
+SELECT C.business_id;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/error-handling/index-mixed-composite/index-mixed-composite.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-4/query7.sqlpp
similarity index 66%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/error-handling/index-mixed-composite/index-mixed-composite.1.ddl.sqlpp
rename to asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-4/query7.sqlpp
index c26bf92..0d54915 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/error-handling/index-mixed-composite/index-mixed-composite.1.ddl.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-4/query7.sqlpp
@@ -16,24 +16,29 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-/*
- * Description: Verify that an error is thrown when trying to create a mixed composite atomic and array index (not supported for now).
- */
+SET `compiler.arrayindex` "true";
DROP DATAVERSE TestYelp IF EXISTS;
CREATE DATAVERSE TestYelp;
USE TestYelp;
-CREATE TYPE CheckinTimeType AS {
- time: string
-};
CREATE TYPE CheckinType AS {
checkin_id: uuid,
business_id: string,
- checkin_times: [CheckinTimeType]
+ checkin_times: [{
+ dates: [string],
+ times: [string]
+ }]
};
CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
-CREATE INDEX IdxYelpCheckinTimeBusiness ON YelpCheckin ((UNNEST checkin_times SELECT time), (business_id));
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times UNNEST dates);
+FROM YelpCheckin C
+WHERE 0 < LEN(C.checkin_times) AND
+ ( EVERY CT IN C.checkin_times
+ SATISFIES ( ABS(CT.num) > 0 AND
+ 0 < LEN(CT.dates) AND
+ ( EVERY D IN CT.dates
+ SATISFIES D > "2019-06-07" ) ) )
+SELECT C.business_id;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-composite-sk/query3.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-composite-sk/query3.sqlpp
new file mode 100644
index 0000000..b37fe19
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-composite-sk/query3.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.
+ */
+SET `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+ checkin_id: uuid,
+ business_id: string,
+ checkin_times: [{
+ date: string,
+ time: string
+ }]
+};
+
+CREATE DATASET YelpCheckin(CheckinType)
+PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDatesTimes
+ON YelpCheckin ( UNNEST checkin_times SELECT date, time );
+
+-- The following query should NOT be optimized.
+FROM YelpCheckin C,
+ C.checkin_times D
+WHERE D.date_notindexed BETWEEN "2016" AND "2017" AND
+ D.time BETWEEN "00:00:00" AND "12:00:00"
+SELECT COUNT(*);
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query1.plan
new file mode 100644
index 0000000..d53724e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query1.plan
@@ -0,0 +1,23 @@
+-- DISTRIBUTE_RESULT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- UNNEST |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.Dataset1) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- PRE_SORTED_DISTINCT_BY |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STABLE_SORT [$$52(ASC)] |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.d1Idx) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query2.plan
new file mode 100644
index 0000000..ab9c768
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query2.plan
@@ -0,0 +1,23 @@
+-- DISTRIBUTE_RESULT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- UNNEST |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.Dataset1) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- PRE_SORTED_DISTINCT_BY |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STABLE_SORT [$$58(ASC)] |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.d1Idx) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query3.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query3.plan
new file mode 100644
index 0000000..2c6241a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query3.plan
@@ -0,0 +1,27 @@
+-- DISTRIBUTE_RESULT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- UNNEST |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- UNNEST |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.Dataset1) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- PRE_SORTED_DISTINCT_BY |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STABLE_SORT [$$70(ASC)] |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.d1Idx) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query4.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query4.plan
new file mode 100644
index 0000000..d25efc7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query4.plan
@@ -0,0 +1,31 @@
+-- DISTRIBUTE_RESULT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- SUBPLAN |PARTITIONED|
+ {
+ -- AGGREGATE |LOCAL|
+ -- STREAM_SELECT |LOCAL|
+ -- ASSIGN |LOCAL|
+ -- UNNEST |LOCAL|
+ -- NESTED_TUPLE_SOURCE |LOCAL|
+ }
+ -- STREAM_SELECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.Dataset1) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- PRE_SORTED_DISTINCT_BY |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STABLE_SORT [$$54(ASC)] |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.d1Idx) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query5.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query5.plan
new file mode 100644
index 0000000..df8e08b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query5.plan
@@ -0,0 +1,40 @@
+-- DISTRIBUTE_RESULT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- SUBPLAN |PARTITIONED|
+ {
+ -- AGGREGATE |LOCAL|
+ -- STREAM_SELECT |LOCAL|
+ -- SUBPLAN |LOCAL|
+ {
+ -- AGGREGATE |LOCAL|
+ -- STREAM_SELECT |LOCAL|
+ -- ASSIGN |LOCAL|
+ -- UNNEST |LOCAL|
+ -- NESTED_TUPLE_SOURCE |LOCAL|
+ }
+ -- STREAM_SELECT |LOCAL|
+ -- ASSIGN |LOCAL|
+ -- UNNEST |LOCAL|
+ -- NESTED_TUPLE_SOURCE |LOCAL|
+ }
+ -- STREAM_SELECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.Dataset1) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- PRE_SORTED_DISTINCT_BY |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STABLE_SORT [$$76(ASC)] |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.d1Idx) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query6.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query6.plan
new file mode 100644
index 0000000..9c4ba10
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query6.plan
@@ -0,0 +1,31 @@
+-- DISTRIBUTE_RESULT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- UNNEST |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.Dataset1) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- PRE_SORTED_DISTINCT_BY |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STABLE_SORT [$$93(ASC), $$70(ASC)] |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.d1Idx) |PARTITIONED|
+ -- BROADCAST_EXCHANGE |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- DATASOURCE_SCAN (TestDataverse.Dataset2) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query7.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query7.plan
new file mode 100644
index 0000000..3dc4d4f
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query7.plan
@@ -0,0 +1,31 @@
+-- DISTRIBUTE_RESULT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- UNNEST |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.Dataset1) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- PRE_SORTED_DISTINCT_BY |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STABLE_SORT [$$111(ASC), $$80(ASC)] |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.d1Idx) |PARTITIONED|
+ -- BROADCAST_EXCHANGE |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- DATASOURCE_SCAN (TestDataverse.Dataset2) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query8.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query8.plan
new file mode 100644
index 0000000..9617727
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query8.plan
@@ -0,0 +1,35 @@
+-- DISTRIBUTE_RESULT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- SUBPLAN |PARTITIONED|
+ {
+ -- AGGREGATE |LOCAL|
+ -- STREAM_SELECT |LOCAL|
+ -- ASSIGN |LOCAL|
+ -- UNNEST |LOCAL|
+ -- NESTED_TUPLE_SOURCE |LOCAL|
+ }
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.Dataset1) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- PRE_SORTED_DISTINCT_BY |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STABLE_SORT [$$74(ASC), $$55(ASC)] |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.d1Idx) |PARTITIONED|
+ -- BROADCAST_EXCHANGE |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- DATASOURCE_SCAN (TestDataverse.Dataset2) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query9.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query9.plan
new file mode 100644
index 0000000..0f26753
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/atomic-and-array-queries/query9.plan
@@ -0,0 +1,44 @@
+-- DISTRIBUTE_RESULT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- SUBPLAN |PARTITIONED|
+ {
+ -- AGGREGATE |LOCAL|
+ -- STREAM_SELECT |LOCAL|
+ -- SUBPLAN |LOCAL|
+ {
+ -- AGGREGATE |LOCAL|
+ -- STREAM_SELECT |LOCAL|
+ -- ASSIGN |LOCAL|
+ -- UNNEST |LOCAL|
+ -- NESTED_TUPLE_SOURCE |LOCAL|
+ }
+ -- STREAM_SELECT |LOCAL|
+ -- ASSIGN |LOCAL|
+ -- UNNEST |LOCAL|
+ -- NESTED_TUPLE_SOURCE |LOCAL|
+ }
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.Dataset1) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- PRE_SORTED_DISTINCT_BY |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STABLE_SORT [$$104(ASC), $$76(ASC)] |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.d1Idx) |PARTITIONED|
+ -- BROADCAST_EXCHANGE |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- DATASOURCE_SCAN (TestDataverse.Dataset2) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/multiple-quantifiers/query9.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/multiple-quantifiers/query9.plan
new file mode 100644
index 0000000..c9574d6
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/multiple-quantifiers/query9.plan
@@ -0,0 +1,40 @@
+-- DISTRIBUTE_RESULT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- SUBPLAN |PARTITIONED|
+ {
+ -- AGGREGATE |LOCAL|
+ -- SUBPLAN |LOCAL|
+ {
+ -- AGGREGATE |LOCAL|
+ -- ASSIGN |LOCAL|
+ -- UNNEST |LOCAL|
+ -- ASSIGN |LOCAL|
+ -- UNNEST |LOCAL|
+ -- NESTED_TUPLE_SOURCE |LOCAL|
+ }
+ -- ASSIGN |LOCAL|
+ -- UNNEST |LOCAL|
+ -- ASSIGN |LOCAL|
+ -- UNNEST |LOCAL|
+ -- NESTED_TUPLE_SOURCE |LOCAL|
+ }
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.Dataset1) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- PRE_SORTED_DISTINCT_BY |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STABLE_SORT [$$88(ASC)] |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestDataverse.Dataset1.d1Idx) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-1/query5.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-1/query5.plan
new file mode 100644
index 0000000..9120584
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-1/query5.plan
@@ -0,0 +1,24 @@
+-- DISTRIBUTE_RESULT |UNPARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |UNPARTITIONED|
+ -- STREAM_PROJECT |UNPARTITIONED|
+ -- ASSIGN |UNPARTITIONED|
+ -- AGGREGATE |UNPARTITIONED|
+ -- RANDOM_MERGE_EXCHANGE |PARTITIONED|
+ -- AGGREGATE |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- SUBPLAN |PARTITIONED|
+ {
+ -- AGGREGATE |LOCAL|
+ -- STREAM_SELECT |LOCAL|
+ -- UNNEST |LOCAL|
+ -- NESTED_TUPLE_SOURCE |LOCAL|
+ }
+ -- STREAM_PROJECT |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- DATASOURCE_SCAN (TestYelp.YelpCheckin) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-2/query4.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-2/query4.plan
new file mode 100644
index 0000000..2d561f4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-2/query4.plan
@@ -0,0 +1,22 @@
+-- DISTRIBUTE_RESULT |UNPARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |UNPARTITIONED|
+ -- STREAM_PROJECT |UNPARTITIONED|
+ -- ASSIGN |UNPARTITIONED|
+ -- AGGREGATE |UNPARTITIONED|
+ -- RANDOM_MERGE_EXCHANGE |PARTITIONED|
+ -- AGGREGATE |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- SUBPLAN |PARTITIONED|
+ {
+ -- AGGREGATE |LOCAL|
+ -- UNNEST |LOCAL|
+ -- NESTED_TUPLE_SOURCE |LOCAL|
+ }
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- DATASOURCE_SCAN (TestYelp.YelpCheckin) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-2/query5.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-2/query5.plan
new file mode 100644
index 0000000..2d561f4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-2/query5.plan
@@ -0,0 +1,22 @@
+-- DISTRIBUTE_RESULT |UNPARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |UNPARTITIONED|
+ -- STREAM_PROJECT |UNPARTITIONED|
+ -- ASSIGN |UNPARTITIONED|
+ -- AGGREGATE |UNPARTITIONED|
+ -- RANDOM_MERGE_EXCHANGE |PARTITIONED|
+ -- AGGREGATE |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- SUBPLAN |PARTITIONED|
+ {
+ -- AGGREGATE |LOCAL|
+ -- UNNEST |LOCAL|
+ -- NESTED_TUPLE_SOURCE |LOCAL|
+ }
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- DATASOURCE_SCAN (TestYelp.YelpCheckin) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-3/query4.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-3/query4.plan
new file mode 100644
index 0000000..7e503bb
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-3/query4.plan
@@ -0,0 +1,21 @@
+-- DISTRIBUTE_RESULT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- SUBPLAN |PARTITIONED|
+ {
+ -- AGGREGATE |LOCAL|
+ -- ASSIGN |LOCAL|
+ -- UNNEST |LOCAL|
+ -- NESTED_TUPLE_SOURCE |LOCAL|
+ }
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- DATASOURCE_SCAN (TestYelp.YelpCheckin) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-4/query7.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-4/query7.plan
new file mode 100644
index 0000000..0f3a84c
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-4/query7.plan
@@ -0,0 +1,39 @@
+-- DISTRIBUTE_RESULT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- SUBPLAN |PARTITIONED|
+ {
+ -- AGGREGATE |LOCAL|
+ -- STREAM_SELECT |LOCAL|
+ -- SUBPLAN |LOCAL|
+ {
+ -- AGGREGATE |LOCAL|
+ -- STREAM_SELECT |LOCAL|
+ -- UNNEST |LOCAL|
+ -- NESTED_TUPLE_SOURCE |LOCAL|
+ }
+ -- ASSIGN |LOCAL|
+ -- UNNEST |LOCAL|
+ -- NESTED_TUPLE_SOURCE |LOCAL|
+ }
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- PRE_SORTED_DISTINCT_BY |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STABLE_SORT [$$58(ASC)] |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-composite-sk/query3.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-composite-sk/query3.plan
new file mode 100644
index 0000000..4af61e1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-composite-sk/query3.plan
@@ -0,0 +1,19 @@
+-- DISTRIBUTE_RESULT |UNPARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |UNPARTITIONED|
+ -- STREAM_PROJECT |UNPARTITIONED|
+ -- ASSIGN |UNPARTITIONED|
+ -- AGGREGATE |UNPARTITIONED|
+ -- RANDOM_MERGE_EXCHANGE |PARTITIONED|
+ -- AGGREGATE |PARTITIONED|
+ -- STREAM_SELECT |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- UNNEST |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ASSIGN |PARTITIONED|
+ -- STREAM_PROJECT |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- DATASOURCE_SCAN (TestYelp.YelpCheckin) |PARTITIONED|
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.1.ddl.sqlpp
new file mode 100644
index 0000000..a650bd0
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.1.ddl.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.
+ */
+DROP DATAVERSE TestDataverse IF EXISTS;
+CREATE DATAVERSE TestDataverse;
+USE TestDataverse;
+
+CREATE TYPE TestType AS {
+ _id: uuid
+};
+
+CREATE DATASET Dataset1 (TestType)
+PRIMARY KEY _id AUTOGENERATED;
+CREATE DATASET Dataset2 (TestType)
+PRIMARY KEY _id AUTOGENERATED;
+CREATE INDEX d1Idx
+ON Dataset1 (
+ field1 : bigint,
+ ( UNNEST items
+ SELECT field2 : bigint,
+ field3 : bigint ),
+ field4 : bigint
+);
+CREATE INDEX d2Idx
+ON Dataset1 (
+ field1 : bigint,
+ ( UNNEST outer_items
+ UNNEST inner_items
+ SELECT field2 : bigint,
+ field3 : bigint ),
+ field4 : bigint
+);
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.10.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.10.query.sqlpp
new file mode 100644
index 0000000..1f113ef
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.10.query.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+SET `compiler.arrayindex` "true";
+USE TestDataverse;
+
+-- Existential quantification query with all fields of a composite index, with extra clauses on non-indexed fields.
+FROM Dataset2 D2,
+ Dataset1 D1
+WHERE D1.field1 /* +indexnl */ = TO_BIGINT(D2.field1) AND
+ ( SOME DOI IN D1.outer_items
+ SATISFIES DOI.field2_notindexed = TO_BIGINT(D2.field2_notindexed) AND
+ ( SOME DII IN DOI.inner_items
+ SATISFIES DII.field2 /* +indexnl */ = TO_BIGINT(D2.field2) AND
+ DII.field3 /* +indexnl */ = TO_BIGINT(D2.field3) AND
+ DII.field3_notindexed = TO_BIGINT(D2.field3_notindexed) ) ) AND
+ D1.field4 /* +indexnl */ = TO_BIGINT(D2.field4)
+SELECT D1.field1, D1.outer_items, D1.field4
+ORDER BY D1.field1, D1.outer_items, D1.field4;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.2.update.sqlpp
new file mode 100644
index 0000000..f66b749
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.2.update.sqlpp
@@ -0,0 +1,36 @@
+/*
+ * 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 TestDataverse;
+
+INSERT INTO Dataset1 [
+ { "field1": 1, "items": [ { "field2": 2, "field3": 3, "field3_notindexed": 3 } ], "outer_items": [ { "field2_notindexed": 2, "inner_items": [ { "field2": 2, "field3": 3, "field3_notindexed": 3 } ] } ], "field4": 4, "field4_notindexed": 4 },
+ { "field1": 1, "items": [ { "field2": 2, "field3": 3, "field3_notindexed": 3 } ], "outer_items": [ { "field2_notindexed": 2, "inner_items": [ { "field2": 2, "field3": 3, "field3_notindexed": 3 } ] } ], "field4": 4, "field4_notindexed": 4 },
+ { "field1": 1, "items": [ { "field2": 2, "field3": 3, "field3_notindexed": 300 } ], "outer_items": [ { "field2_notindexed": 2, "inner_items": [ { "field2": 2, "field3": 3, "field3_notindexed": 300 } ] } ], "field4": 4, "field4_notindexed": 400 },
+ { "field1": 100, "items": [ { "field2": 200, "field3": 300, "field3_notindexed": 3 } ], "outer_items": [ { "field2_notindexed": 200, "inner_items": [ { "field2": 200, "field3": 300, "field3_notindexed": 3 } ] } ], "field4": 400, "field4_notindexed": 4 },
+ { "field1": -1, "items": [ { "field2": -2, "field3": -3, "field3_notindexed": -3 } ], "outer_items": [ { "field2_notindexed": -2, "inner_items": [ { "field2": -2, "field3": -3, "field3_notindexed": -3 } ] } ], "field4": -4, "field4_notindexed": -4 },
+ { "field1": -1, "items": [ { "field2": -2, "field3": -3, "field3_notindexed": -3 } ], "outer_items": [ { "field2_notindexed": -2, "inner_items": [ { "field2": -2, "field3": -3, "field3_notindexed": -3 } ] } ], "field4": -4, "field4_notindexed": -4 }
+];
+
+INSERT INTO Dataset2 [
+ { "field1": 1, "field2_notindexed": 2, "field2": 2, "field3": 3, "field3_notindexed": 3, "field4": 4, "field4_notindexed": 4 },
+ { "field1": 1, "field2_notindexed": 200, "field2": 2, "field3": 3, "field3_notindexed": 3, "field4": 4, "field4_notindexed": 4 },
+ { "field1": 100, "field2_notindexed": 2, "field2": 2, "field3": 3, "field3_notindexed": 3, "field4": 4, "field4_notindexed": 4 },
+ { "field1": 100, "field2_notindexed": 200, "field2": 200, "field3": 300, "field3_notindexed": 300, "field4": 400, "field4_notindexed": 400 },
+ { "field1": -100, "field2_notindexed": -200, "field2": -200, "field3": -300, "field3_notindexed": -300, "field4": -400, "field4_notindexed": -400 }
+];
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.3.query.sqlpp
new file mode 100644
index 0000000..e860165
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.3.query.sqlpp
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+SET `compiler.arrayindex` "true";
+USE TestDataverse;
+
+-- Explicit UNNEST query with all fields of a composite index.
+FROM Dataset1 D
+UNNEST D.items DI
+WHERE D.field1 = 1 AND
+ DI.field2 = 2 AND
+ DI.field3 = 3 AND
+ D.field4 = 4
+SELECT D.field1, DI.field2, DI.field3, DI.field3_notindexed, D.field4, D.field4_notindexed
+ORDER BY D.field1, DI.field2, DI.field3, DI.field3_notindexed, D.field4, D.field4_notindexed;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.4.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.4.query.sqlpp
new file mode 100644
index 0000000..b19f711
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.4.query.sqlpp
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+SET `compiler.arrayindex` "true";
+USE TestDataverse;
+
+-- Explicit UNNEST query with all fields of a composite index, with extra clauses on non-indexed fields.
+FROM Dataset1 D
+UNNEST D.items DI
+WHERE D.field1 = 1 AND
+ DI.field2 = 2 AND
+ DI.field3 = 3 AND
+ DI.field3_notindexed = 3 AND
+ D.field4 = 4 AND
+ D.field4_notindexed = 4
+SELECT D.field1, DI.field2, DI.field3, DI.field3_notindexed, D.field4, D.field4_notindexed
+ORDER BY D.field1, DI.field2, DI.field3, DI.field3_notindexed, D.field4, D.field4_notindexed;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.5.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.5.query.sqlpp
new file mode 100644
index 0000000..e6002c3
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.5.query.sqlpp
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+SET `compiler.arrayindex` "true";
+USE TestDataverse;
+
+-- Existential quantification query with all fields of a composite index.
+FROM Dataset1 D
+WHERE D.field1 = 1 AND
+ ( SOME DI IN D.items
+ SATISFIES DI.field2 = 2 AND
+ DI.field3 = 3 ) AND
+ D.field4 = 4
+SELECT D.field1, D.items, D.field4, D.field4_notindexed
+ORDER BY D.field1, D.items, D.field4, D.field4_notindexed;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.6.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.6.query.sqlpp
new file mode 100644
index 0000000..12b6f4f
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.6.query.sqlpp
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+SET `compiler.arrayindex` "true";
+USE TestDataverse;
+
+-- Existential quantification query with all fields of a composite index, with extra clauses on non-indexed fields.
+FROM Dataset1 D
+WHERE D.field1 = 1 AND
+ ( SOME DOI IN D.outer_items
+ SATISFIES DOI.field2_notindexed = 2 AND
+ ( SOME DII IN DOI.inner_items
+ SATISFIES DII.field2 = 2 AND
+ DII.field3 = 3 AND
+ DII.field3_notindexed = 3 ) ) AND
+ D.field4 = 4
+SELECT D.field1, D.outer_items, D.field4
+ORDER BY D.field1, D.outer_items, D.field4;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.7.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.7.query.sqlpp
new file mode 100644
index 0000000..9ab9213
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.7.query.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+SET `compiler.arrayindex` "true";
+USE TestDataverse;
+
+-- Explicit UNNEST join query with all fields of a composite index.
+FROM Dataset2 D2
+INNER JOIN (
+ FROM Dataset1 D1
+ UNNEST D1.items D1I
+ SELECT D1.field1, D1I.field2, D1I.field3, D1.field4
+) AS D1IV
+ON D1IV.field1 /* +indexnl */ = D2.field1 AND
+ D1IV.field2 /* +indexnl */ = D2.field2 AND
+ D1IV.field3 /* +indexnl */ = D2.field3 AND
+ D1IV.field4 /* +indexnl */ = D2.field4
+SELECT D2.field1, D2.field2, D2.field3, D2.field4
+ORDER BY D2.field1, D2.field2, D2.field3, D2.field4;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.8.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.8.query.sqlpp
new file mode 100644
index 0000000..907817b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.8.query.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.
+ */
+SET `compiler.arrayindex` "true";
+USE TestDataverse;
+
+-- Explicit UNNEST join query with all fields of a composite index, with extra clauses on non-indexed fields.
+FROM Dataset2 D2
+INNER JOIN (
+ FROM Dataset1 D1
+ UNNEST D1.items D1I
+ SELECT D1.field1, D1I.field2, D1I.field3, D1I.field3_notindexed, D1.field4
+) AS D1IV
+ON D1IV.field1 /* +indexnl */ = TO_BIGINT(D2.field1) AND
+ D1IV.field2 /* +indexnl */ = TO_BIGINT(D2.field2) AND
+ D1IV.field3 /* +indexnl */ = TO_BIGINT(D2.field3) AND
+ D1IV.field3_notindexed = D2.field3_notindexed AND
+ D1IV.field4 /* +indexnl */ = TO_BIGINT(D2.field4)
+SELECT D2.field1, D2.field2, D2.field3, D2.field3_notindexed, D2.field4, D2.field4_notindexed
+ORDER BY D2.field1, D2.field2, D2.field3, D2.field3_notindexed, D2.field4, D2.field4_notindexed;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.9.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.9.query.sqlpp
new file mode 100644
index 0000000..f84f82d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/composite-index-queries/composite-index-queries.9.query.sqlpp
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+SET `compiler.arrayindex` "true";
+USE TestDataverse;
+
+-- Existential quantification join query with all fields of a composite index.
+FROM Dataset2 D2,
+ Dataset1 D1
+WHERE D1.field1 /* +indexnl */ = TO_BIGINT(D2.field1) AND
+ ( SOME D1I IN D1.items
+ SATISFIES D1I.field2 /* +indexnl */ = TO_BIGINT(D2.field2) AND
+ D1I.field3 /* +indexnl */ = TO_BIGINT(D2.field3) ) AND
+ D1.field4 /* +indexnl */ = TO_BIGINT(D2.field4)
+SELECT D2.field1, D2.field2, D2.field3, D2.field4
+ORDER BY D2.field1, D2.field2, D2.field3, D2.field4;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/metadata/open/with-composite-sk/with-composite-sk.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/metadata/open/with-composite-sk/with-composite-sk.1.ddl.sqlpp
index 2a9497a..cea2674 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/metadata/open/with-composite-sk/with-composite-sk.1.ddl.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/metadata/open/with-composite-sk/with-composite-sk.1.ddl.sqlpp
@@ -30,4 +30,24 @@
};
CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
-CREATE INDEX IdxYelpBusinessCheckinDates ON YelpCheckin (business_id : string ?, UNNEST dates : string ?) ;
+CREATE INDEX IdxYelpBusinessCheckinDates ON YelpCheckin (business_id : string, UNNEST dates : string);
+
+-- More complex indexes created below.
+CREATE INDEX IdxYelp1 ON YelpCheckin (
+ ( UNNEST outer_dates_1
+ SELECT date : string ),
+ backup_business_id : string
+);
+CREATE INDEX IdxYelp2 ON YelpCheckin (
+ business_id : string,
+ ( UNNEST outer_dates_2
+ SELECT time : string )
+);
+CREATE INDEX IdxYelp3 ON YelpCheckin (
+ business_id : string,
+ ( UNNEST outer_dates
+ UNNEST inner_dates
+ SELECT date : string,
+ time : string ),
+ backup_business_id : string
+);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/metadata/open/with-composite-sk/with-composite-sk.2.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/metadata/open/with-composite-sk/with-composite-sk.2.query.sqlpp
index bee279e..738a69b 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/metadata/open/with-composite-sk/with-composite-sk.2.query.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/metadata/open/with-composite-sk/with-composite-sk.2.query.sqlpp
@@ -17,6 +17,7 @@
* under the License.
*/
-SELECT D.SearchKey, D.SearchKeyElements, D.SearchKeyType
-FROM Metadata.`Index` D
-WHERE D.IndexName = "IdxYelpBusinessCheckinDates" AND D.DataverseName = "TestYelp";
+FROM Metadata.`Index` D
+WHERE D.IndexName LIKE "IdxYelp%" AND D.DataverseName = "TestYelp"
+SELECT D.SearchKey, D.SearchKeyElements, D.SearchKeyType
+ORDER BY D.IndexName;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.1.adm
new file mode 100644
index 0000000..b811602
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.1.adm
@@ -0,0 +1,3 @@
+{ "field1": 1, "field2": 2, "field3": 3, "field3_notindexed": 3, "field4": 4, "field4_notindexed": 4 }
+{ "field1": 1, "field2": 2, "field3": 3, "field3_notindexed": 3, "field4": 4, "field4_notindexed": 4 }
+{ "field1": 1, "field2": 2, "field3": 3, "field3_notindexed": 300, "field4": 4, "field4_notindexed": 400 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.2.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.2.adm
new file mode 100644
index 0000000..27ffff6
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.2.adm
@@ -0,0 +1,2 @@
+{ "field1": 1, "field2": 2, "field3": 3, "field3_notindexed": 3, "field4": 4, "field4_notindexed": 4 }
+{ "field1": 1, "field2": 2, "field3": 3, "field3_notindexed": 3, "field4": 4, "field4_notindexed": 4 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.3.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.3.adm
new file mode 100644
index 0000000..ee8a69b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.3.adm
@@ -0,0 +1,3 @@
+{ "field1": 1, "items": [ { "field2": 2, "field3": 3, "field3_notindexed": 3 } ], "field4": 4, "field4_notindexed": 4 }
+{ "field1": 1, "items": [ { "field2": 2, "field3": 3, "field3_notindexed": 3 } ], "field4": 4, "field4_notindexed": 4 }
+{ "field1": 1, "items": [ { "field2": 2, "field3": 3, "field3_notindexed": 300 } ], "field4": 4, "field4_notindexed": 400 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.4.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.4.adm
new file mode 100644
index 0000000..327735d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.4.adm
@@ -0,0 +1,2 @@
+{ "field1": 1, "outer_items": [ { "field2_notindexed": 2, "inner_items": [ { "field2": 2, "field3": 3, "field3_notindexed": 3 } ] } ], "field4": 4 }
+{ "field1": 1, "outer_items": [ { "field2_notindexed": 2, "inner_items": [ { "field2": 2, "field3": 3, "field3_notindexed": 3 } ] } ], "field4": 4 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.5.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.5.adm
new file mode 100644
index 0000000..e6bba45
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.5.adm
@@ -0,0 +1,7 @@
+{ "field1": 1, "field2": 2, "field3": 3, "field4": 4 }
+{ "field1": 1, "field2": 2, "field3": 3, "field4": 4 }
+{ "field1": 1, "field2": 2, "field3": 3, "field4": 4 }
+{ "field1": 1, "field2": 2, "field3": 3, "field4": 4 }
+{ "field1": 1, "field2": 2, "field3": 3, "field4": 4 }
+{ "field1": 1, "field2": 2, "field3": 3, "field4": 4 }
+{ "field1": 100, "field2": 200, "field3": 300, "field4": 400 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.6.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.6.adm
new file mode 100644
index 0000000..34afde6
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.6.adm
@@ -0,0 +1,4 @@
+{ "field1": 1, "field2": 2, "field3": 3, "field3_notindexed": 3, "field4": 4, "field4_notindexed": 4 }
+{ "field1": 1, "field2": 2, "field3": 3, "field3_notindexed": 3, "field4": 4, "field4_notindexed": 4 }
+{ "field1": 1, "field2": 2, "field3": 3, "field3_notindexed": 3, "field4": 4, "field4_notindexed": 4 }
+{ "field1": 1, "field2": 2, "field3": 3, "field3_notindexed": 3, "field4": 4, "field4_notindexed": 4 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.7.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.7.adm
new file mode 100644
index 0000000..e6bba45
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.7.adm
@@ -0,0 +1,7 @@
+{ "field1": 1, "field2": 2, "field3": 3, "field4": 4 }
+{ "field1": 1, "field2": 2, "field3": 3, "field4": 4 }
+{ "field1": 1, "field2": 2, "field3": 3, "field4": 4 }
+{ "field1": 1, "field2": 2, "field3": 3, "field4": 4 }
+{ "field1": 1, "field2": 2, "field3": 3, "field4": 4 }
+{ "field1": 1, "field2": 2, "field3": 3, "field4": 4 }
+{ "field1": 100, "field2": 200, "field3": 300, "field4": 400 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.8.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.8.adm
new file mode 100644
index 0000000..327735d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/composite-index-queries/composite-index-queries.8.adm
@@ -0,0 +1,2 @@
+{ "field1": 1, "outer_items": [ { "field2_notindexed": 2, "inner_items": [ { "field2": 2, "field3": 3, "field3_notindexed": 3 } ] } ], "field4": 4 }
+{ "field1": 1, "outer_items": [ { "field2_notindexed": 2, "inner_items": [ { "field2": 2, "field3": 3, "field3_notindexed": 3 } ] } ], "field4": 4 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/metadata/open/with-composite-sk/with-composite-sk.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/metadata/open/with-composite-sk/with-composite-sk.1.adm
index a2eea1e..9ba69cb 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/metadata/open/with-composite-sk/with-composite-sk.1.adm
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/array-index/metadata/open/with-composite-sk/with-composite-sk.1.adm
@@ -1 +1,4 @@
-{ "SearchKey": [ ], "SearchKeyElements": [ [ [ "business_id" ] ], { "UnnestList": [ [ "dates" ] ] } ], "SearchKeyType": [ [ "string" ], [ "string" ] ] }
+{ "SearchKey": [ ], "SearchKeyElements": [ { "UnnestList": [ [ "outer_dates_1" ] ], "ProjectList": [ [ "date" ] ] }, [ [ "backup_business_id" ] ] ], "SearchKeyType": [ [ "string" ], [ "string" ] ] }
+{ "SearchKey": [ ], "SearchKeyElements": [ [ [ "business_id" ] ], { "UnnestList": [ [ "outer_dates_2" ] ], "ProjectList": [ [ "time" ] ] } ], "SearchKeyType": [ [ "string" ], [ "string" ] ] }
+{ "SearchKey": [ ], "SearchKeyElements": [ [ [ "business_id" ] ], { "UnnestList": [ [ "outer_dates" ], [ "inner_dates" ] ], "ProjectList": [ [ "date" ], [ "time" ] ] }, [ [ "backup_business_id" ] ] ], "SearchKeyType": [ [ "string" ], [ "string", "string" ], [ "string" ] ] }
+{ "SearchKey": [ ], "SearchKeyElements": [ [ [ "business_id" ] ], { "UnnestList": [ [ "dates" ] ] } ], "SearchKeyType": [ [ "string" ], [ "string" ] ] }
\ No newline at end of file
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 29526f3..9211032 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -8071,13 +8071,6 @@
<source-location>false</source-location>
</compilation-unit>
</test-case>
- <test-case FilePath="array-index/error-handling">
- <compilation-unit name="index-mixed-composite">
- <output-dir compare="Text">index-mixed-composite</output-dir>
- <expected-error>ASX1155: Incompatible index type ARRAY</expected-error>
- <source-location>false</source-location>
- </compilation-unit>
- </test-case>
</test-group>
<test-group name="array-index/metadata">
<test-case FilePath="array-index/metadata/closed">
@@ -8125,16 +8118,16 @@
<output-dir compare="Text">complex-structures</output-dir>
</compilation-unit>
</test-case>
- <!-- <test-case FilePath="array-index/metadata/closed">-->
- <!-- <compilation-unit name="with-composite-sk">-->
- <!-- <output-dir compare="Text">with-composite-sk</output-dir>-->
- <!-- </compilation-unit>-->
- <!-- </test-case>-->
- <!-- <test-case FilePath="array-index/metadata/open">-->
- <!-- <compilation-unit name="with-composite-sk">-->
- <!-- <output-dir compare="Text">with-composite-sk</output-dir>-->
- <!-- </compilation-unit>-->
- <!-- </test-case>-->
+ <test-case FilePath="array-index/metadata/closed">
+ <compilation-unit name="with-composite-sk">
+ <output-dir compare="Text">with-composite-sk</output-dir>
+ </compilation-unit>
+ </test-case>
+ <test-case FilePath="array-index/metadata/open">
+ <compilation-unit name="with-composite-sk">
+ <output-dir compare="Text">with-composite-sk</output-dir>
+ </compilation-unit>
+ </test-case>
<test-case FilePath="array-index/metadata/closed">
<compilation-unit name="with-composite-array-different-indicators">
<output-dir compare="Text">with-composite-array-different-indicators</output-dir>
@@ -8475,6 +8468,13 @@
</compilation-unit>
</test-case>
</test-group>
+ <test-group name="array-index/composite-index-queries">
+ <test-case FilePath="array-index">
+ <compilation-unit name="composite-index-queries">
+ <output-dir compare="Text">composite-index-queries</output-dir>
+ </compilation-unit>
+ </test-case>
+ </test-group>
</test-group>
<test-group name="nestrecords">
<test-case FilePath="nestrecords">