[ASTERIXDB-3229][COMP] Part 2: Intoduce Pushdown Processor
- user model changes: no
- storage format changes: no
- interface changes: no
Details:
This patch adds the pushdown processors for columnar filters.
Change-Id: Id1f1b231a293abb1006ba4e906d64294c882b32d
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/17664
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Wail Alkowaileet <wael.y.k@gmail.com>
Reviewed-by: Ali Alsuliman <ali.al.solaiman@gmail.com>
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/ExpressionValueAccessPushdownVisitor.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/ExpressionValueAccessPushdownVisitor.java
index b71dcb4..c57f653 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/ExpressionValueAccessPushdownVisitor.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/ExpressionValueAccessPushdownVisitor.java
@@ -18,11 +18,12 @@
*/
package org.apache.asterix.optimizer.rules.pushdown;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import static org.apache.asterix.metadata.utils.PushdownUtil.ALLOWED_FUNCTIONS;
+import static org.apache.asterix.metadata.utils.PushdownUtil.SUPPORTED_FUNCTIONS;
-import org.apache.asterix.om.functions.BuiltinFunctions;
+import java.util.List;
+
+import org.apache.asterix.optimizer.rules.pushdown.schema.ExpectedSchemaBuilder;
import org.apache.commons.lang3.mutable.Mutable;
import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
@@ -30,19 +31,10 @@
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.IVariableTypeEnvironment;
-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.visitors.VariableUtilities;
import org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalExpressionReferenceTransform;
class ExpressionValueAccessPushdownVisitor implements ILogicalExpressionReferenceTransform {
- //Set of allowed functions that can request a type in its entirety
- static final Set<FunctionIdentifier> ALLOWED_FUNCTIONS = createAllowedFunctions();
- //Set of supported array functions
- static final Set<FunctionIdentifier> ARRAY_FUNCTIONS = createSupportedArrayFunctions();
- //Set of supported functions that we can push down
- static final Set<FunctionIdentifier> SUPPORTED_FUNCTIONS = createSupportedFunctions();
-
private final ExpectedSchemaBuilder builder;
private List<LogicalVariable> producedVariables;
private IVariableTypeEnvironment typeEnv;
@@ -169,24 +161,4 @@
builder.unregisterVariable(variable);
}
}
-
- private static Set<FunctionIdentifier> createSupportedArrayFunctions() {
- return Set.of(BuiltinFunctions.GET_ITEM, BuiltinFunctions.ARRAY_STAR, BuiltinFunctions.SCAN_COLLECTION);
- }
-
- private static Set<FunctionIdentifier> createSupportedFunctions() {
- Set<FunctionIdentifier> supportedFunctions = new HashSet<>();
- supportedFunctions.add(BuiltinFunctions.FIELD_ACCESS_BY_NAME);
- supportedFunctions.add(BuiltinFunctions.FIELD_ACCESS_BY_INDEX);
- supportedFunctions.addAll(ARRAY_FUNCTIONS);
- return supportedFunctions;
- }
-
- private static Set<FunctionIdentifier> createAllowedFunctions() {
- return Set.of(BuiltinFunctions.IS_ARRAY, BuiltinFunctions.IS_OBJECT, BuiltinFunctions.IS_ATOMIC,
- BuiltinFunctions.IS_NUMBER, BuiltinFunctions.IS_BOOLEAN, BuiltinFunctions.IS_STRING,
- AlgebricksBuiltinFunctions.IS_MISSING, AlgebricksBuiltinFunctions.IS_NULL, BuiltinFunctions.IS_UNKNOWN,
- BuiltinFunctions.LT, BuiltinFunctions.LE, BuiltinFunctions.EQ, BuiltinFunctions.GT, BuiltinFunctions.GE,
- BuiltinFunctions.SCALAR_SQL_COUNT);
- }
}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/ExpressionValueFilterPushdown.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/ExpressionValueFilterPushdown.java
index bff8bff..29a2465 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/ExpressionValueFilterPushdown.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/ExpressionValueFilterPushdown.java
@@ -37,10 +37,12 @@
import org.apache.asterix.om.constants.AsterixConstantValue;
import org.apache.asterix.om.functions.BuiltinFunctions;
import org.apache.asterix.om.types.ARecordType;
+import org.apache.asterix.optimizer.rules.pushdown.processor.ColumnRangeFilterPushdownProcessor;
import org.apache.asterix.optimizer.rules.pushdown.schema.AnyExpectedSchemaNode;
-import org.apache.asterix.optimizer.rules.pushdown.schema.ColumnFilterPathBuilderVisitor;
+import org.apache.asterix.optimizer.rules.pushdown.schema.ExpectedSchemaBuilder;
import org.apache.asterix.optimizer.rules.pushdown.schema.ExpectedSchemaNodeType;
import org.apache.asterix.optimizer.rules.pushdown.schema.IExpectedSchemaNode;
+import org.apache.asterix.optimizer.rules.pushdown.visitor.ColumnFilterPathBuilderVisitor;
import org.apache.asterix.runtime.projection.FunctionCallInformation;
import org.apache.asterix.runtime.projection.ProjectionFiltrationWarningFactoryProvider;
import org.apache.commons.lang3.mutable.Mutable;
@@ -66,6 +68,8 @@
* TODO Filter could prevent REPLICATE (i.e., we can scan a dataset twice due to the fact one scan is filtered and
* TODO the other is not or both have different filters)
* TODO part of this class could potentially be used for external data dynamic prefixes
+ *
+ * @deprecated use {@link ColumnRangeFilterPushdownProcessor} and {@link ColumnFilterPathBuilderVisitor}
*/
class ExpressionValueFilterPushdown {
private final ExpectedSchemaBuilder builder;
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/OperatorValueAccessPushdownVisitor.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/OperatorValueAccessPushdownVisitor.java
index 9eb25d0..8e49bbb 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/OperatorValueAccessPushdownVisitor.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/OperatorValueAccessPushdownVisitor.java
@@ -38,6 +38,7 @@
import org.apache.asterix.om.functions.BuiltinFunctions;
import org.apache.asterix.om.types.ARecordType;
import org.apache.asterix.om.utils.ConstantExpressionUtil;
+import org.apache.asterix.optimizer.rules.pushdown.schema.ExpectedSchemaBuilder;
import org.apache.asterix.optimizer.rules.pushdown.schema.RootExpectedSchemaNode;
import org.apache.asterix.runtime.projection.FunctionCallInformation;
import org.apache.commons.lang3.mutable.Mutable;
@@ -93,7 +94,10 @@
/**
* This visitor visits the entire plan and tries to build the information of the required values from all dataset
+ *
+ * @deprecated use {@link org.apache.asterix.optimizer.rules.pushdown.visitor.PushdownOperatorVisitor}
*/
+@Deprecated
public class OperatorValueAccessPushdownVisitor implements ILogicalOperatorVisitor<Void, Void> {
private static final List<LogicalVariable> EMPTY_VARIABLES = Collections.emptyList();
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/AbstractFilterPushdownProcessor.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/AbstractFilterPushdownProcessor.java
new file mode 100644
index 0000000..4dbb06d
--- /dev/null
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/AbstractFilterPushdownProcessor.java
@@ -0,0 +1,218 @@
+/*
+ * 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.pushdown.processor;
+
+import static org.apache.asterix.metadata.utils.PushdownUtil.getConstant;
+import static org.apache.asterix.metadata.utils.PushdownUtil.isAnd;
+import static org.apache.asterix.metadata.utils.PushdownUtil.isCompare;
+import static org.apache.asterix.metadata.utils.PushdownUtil.isConstant;
+import static org.apache.asterix.metadata.utils.PushdownUtil.isFilterPath;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.asterix.om.base.IAObject;
+import org.apache.asterix.optimizer.rules.pushdown.PushdownContext;
+import org.apache.asterix.optimizer.rules.pushdown.descriptor.DefineDescriptor;
+import org.apache.asterix.optimizer.rules.pushdown.descriptor.ScanDefineDescriptor;
+import org.apache.asterix.optimizer.rules.pushdown.descriptor.UseDescriptor;
+import org.apache.commons.lang3.mutable.Mutable;
+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.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.expressions.AbstractFunctionCallExpression;
+
+abstract class AbstractFilterPushdownProcessor extends AbstractPushdownProcessor {
+ private final Set<ILogicalOperator> visitedOperators;
+
+ public AbstractFilterPushdownProcessor(PushdownContext pushdownContext, IOptimizationContext context) {
+ super(pushdownContext, context);
+ visitedOperators = new HashSet<>();
+ }
+
+ @Override
+ public final void process() throws AlgebricksException {
+ List<ScanDefineDescriptor> scanDefineDescriptors = pushdownContext.getRegisteredScans();
+ for (ScanDefineDescriptor scanDefineDescriptor : scanDefineDescriptors) {
+ if (skip(scanDefineDescriptor)) {
+ continue;
+ }
+ prepareScan(scanDefineDescriptor);
+ pushdownFilter(scanDefineDescriptor, scanDefineDescriptor);
+ }
+ }
+
+ /**
+ * Should skip pushing down a filter for the given data-scan
+ *
+ * @param scanDefineDescriptor data-scan descriptor
+ * @return true to skip, false otherwise
+ */
+ protected abstract boolean skip(ScanDefineDescriptor scanDefineDescriptor);
+
+ /**
+ * Prepare data-scan for a pushdown
+ *
+ * @param scanDefineDescriptor data-scan descriptor
+ */
+ protected abstract void prepareScan(ScanDefineDescriptor scanDefineDescriptor);
+
+ /**
+ * Prepare to pushdown a SELECT expression in the use-descriptor
+ *
+ * @param useDescriptor contains the SELECT operator and its expression
+ */
+ protected abstract void preparePushdown(UseDescriptor useDescriptor) throws AlgebricksException;
+
+ /**
+ * Is an expression pushable
+ *
+ * @param expression the expression to push down
+ * @return true if it is pushable, false otherwise
+ */
+ protected abstract boolean isPushable(AbstractFunctionCallExpression expression);
+
+ /**
+ * Handle a compare function
+ *
+ * @param expression compare expression
+ * @return true if the pushdown should continue, false otherwise
+ */
+ protected abstract boolean handleCompare(AbstractFunctionCallExpression expression) throws AlgebricksException;
+
+ /**
+ * Handle a value access path expression
+ *
+ * @param expression path expression
+ * @return true if the pushdown should continue, false otherwise
+ */
+ protected abstract boolean handlePath(AbstractFunctionCallExpression expression) throws AlgebricksException;
+
+ /**
+ * Put the filter expression to data-scan
+ *
+ * @param scanDefineDescriptor data-scan descriptor
+ * @param inlinedExpr inlined filter expression
+ * @return true if the filter expression was set to data-scan, false otherwise
+ */
+ protected abstract boolean putFilterInformation(ScanDefineDescriptor scanDefineDescriptor,
+ ILogicalExpression inlinedExpr) throws AlgebricksException;
+
+ private boolean pushdownFilter(DefineDescriptor defineDescriptor, ScanDefineDescriptor scanDefineDescriptor)
+ throws AlgebricksException {
+ List<UseDescriptor> useDescriptors = pushdownContext.getUseDescriptors(defineDescriptor);
+ for (UseDescriptor useDescriptor : useDescriptors) {
+ /*
+ * Pushdown works only if the scope(use) and scope(scan) are the same, as we cannot pushdown when
+ * scope(use) > scope(scan) (e.g., after join or group-by)
+ */
+ if (useDescriptor.getScope() == scanDefineDescriptor.getScope()
+ && useDescriptor.getOperator().getOperatorTag() == LogicalOperatorTag.SELECT) {
+ if (!inlineAndPushdownFilter(useDescriptor, scanDefineDescriptor)) {
+ return false;
+ }
+ }
+ }
+
+ for (UseDescriptor useDescriptor : useDescriptors) {
+ DefineDescriptor nextDefineDescriptor = pushdownContext.getDefineDescriptor(useDescriptor);
+ if (useDescriptor.getScope() == scanDefineDescriptor.getScope() && nextDefineDescriptor != null) {
+ if (!pushdownFilter(nextDefineDescriptor, scanDefineDescriptor)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private boolean inlineAndPushdownFilter(UseDescriptor useDescriptor, ScanDefineDescriptor scanDefineDescriptor)
+ throws AlgebricksException {
+ ILogicalOperator selectOp = useDescriptor.getOperator();
+ if (visitedOperators.contains(selectOp)) {
+ // Skip and follow through to find any other selects that can be pushed down
+ return true;
+ }
+
+ // Get a clone of the SELECT expression and inline it
+ ILogicalExpression inlinedExpr = pushdownContext.cloneAndInlineExpression(useDescriptor);
+ // Prepare for pushdown
+ preparePushdown(useDescriptor);
+ boolean pushdown =
+ pushdownFilterExpression(inlinedExpr) && putFilterInformation(scanDefineDescriptor, inlinedExpr);
+
+ // Do not push down a select twice.
+ visitedOperators.add(selectOp);
+ return pushdown;
+ }
+
+ protected final boolean pushdownFilterExpression(ILogicalExpression expression) throws AlgebricksException {
+ boolean pushdown = false;
+ if (isConstant(expression)) {
+ IAObject constantValue = getConstant(expression);
+ // Only non-derived types are allowed
+ pushdown = !constantValue.getType().getTypeTag().isDerivedType();
+ } else if (isAnd(expression)) {
+ pushdown = handleAnd((AbstractFunctionCallExpression) expression);
+ } else if (isCompare(expression)) {
+ pushdown = handleCompare((AbstractFunctionCallExpression) expression);
+ } else if (isFilterPath(expression)) {
+ pushdown = handlePath((AbstractFunctionCallExpression) expression);
+ } else if (expression.getExpressionTag() == LogicalExpressionTag.FUNCTION_CALL) {
+ // All functions including OR
+ pushdown = handleFunction((AbstractFunctionCallExpression) expression);
+ }
+ // PK variable should have (pushdown = false) as we should not involve the PK (at least currently)
+ return pushdown;
+ }
+
+ private boolean handleAnd(AbstractFunctionCallExpression expression) throws AlgebricksException {
+ List<Mutable<ILogicalExpression>> args = expression.getArguments();
+ Iterator<Mutable<ILogicalExpression>> argIter = args.iterator();
+ while (argIter.hasNext()) {
+ ILogicalExpression arg = argIter.next().getValue();
+ // Allow for partial pushdown of AND operands
+ if (!pushdownFilterExpression(arg)) {
+ // Remove the expression that cannot be pushed down
+ argIter.remove();
+ }
+ }
+ return !args.isEmpty();
+ }
+
+ private boolean handleFunction(AbstractFunctionCallExpression expression) throws AlgebricksException {
+ if (!expression.getFunctionInfo().isFunctional() || !isPushable(expression)) {
+ return false;
+ }
+
+ for (Mutable<ILogicalExpression> argRef : expression.getArguments()) {
+ ILogicalExpression arg = argRef.getValue();
+ // Either all arguments are pushable or none
+ if (!pushdownFilterExpression(arg)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/AbstractPushdownProcessor.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/AbstractPushdownProcessor.java
new file mode 100644
index 0000000..7591765
--- /dev/null
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/AbstractPushdownProcessor.java
@@ -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.
+ */
+package org.apache.asterix.optimizer.rules.pushdown.processor;
+
+import org.apache.asterix.optimizer.rules.pushdown.PushdownContext;
+import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
+
+abstract class AbstractPushdownProcessor implements IPushdownProcessor {
+ protected final PushdownContext pushdownContext;
+ protected final IOptimizationContext context;
+
+ AbstractPushdownProcessor(PushdownContext pushdownContext, IOptimizationContext context) {
+ this.pushdownContext = pushdownContext;
+ this.context = context;
+ }
+}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/ColumnFilterPushdownProcessor.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/ColumnFilterPushdownProcessor.java
new file mode 100644
index 0000000..8db7969
--- /dev/null
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/ColumnFilterPushdownProcessor.java
@@ -0,0 +1,163 @@
+/*
+ * 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.pushdown.processor;
+
+import static org.apache.asterix.metadata.utils.PushdownUtil.RANGE_FILTER_PUSHABLE_FUNCTIONS;
+import static org.apache.asterix.metadata.utils.PushdownUtil.isNestedFunction;
+import static org.apache.asterix.metadata.utils.PushdownUtil.isTypeFunction;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.asterix.common.config.DatasetConfig;
+import org.apache.asterix.metadata.entities.Dataset;
+import org.apache.asterix.metadata.utils.DatasetUtil;
+import org.apache.asterix.metadata.utils.filter.ArrayPathCheckerVisitor;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.asterix.om.types.ARecordType;
+import org.apache.asterix.optimizer.rules.pushdown.PushdownContext;
+import org.apache.asterix.optimizer.rules.pushdown.descriptor.ScanDefineDescriptor;
+import org.apache.asterix.optimizer.rules.pushdown.descriptor.UseDescriptor;
+import org.apache.asterix.optimizer.rules.pushdown.schema.AnyExpectedSchemaNode;
+import org.apache.asterix.optimizer.rules.pushdown.schema.ExpectedSchemaNodeType;
+import org.apache.asterix.optimizer.rules.pushdown.schema.IExpectedSchemaNode;
+import org.apache.asterix.optimizer.rules.pushdown.visitor.ColumnFilterPathBuilderVisitor;
+import org.apache.asterix.optimizer.rules.pushdown.visitor.ExpressionToExpectedSchemaNodeVisitor;
+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.IOptimizationContext;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalOperatorTag;
+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.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.core.algebra.functions.IFunctionInfo;
+
+/**
+ * Computes a filter expression that can be pushed down to {@link DatasetConfig.DatasetFormat#COLUMN} datasets.
+ * The computed filter expression then will be evaluated to determine if a record should be assembled and returned as
+ * a result of a data-scan or not.
+ */
+public class ColumnFilterPushdownProcessor extends AbstractFilterPushdownProcessor {
+ protected final ExpressionToExpectedSchemaNodeVisitor exprToNodeVisitor;
+ protected final ColumnFilterPathBuilderVisitor pathBuilderVisitor;
+ protected final Map<ILogicalExpression, ARecordType> paths;
+ private final ArrayPathCheckerVisitor checkerVisitor;
+
+ public ColumnFilterPushdownProcessor(PushdownContext pushdownContext, IOptimizationContext context) {
+ super(pushdownContext, context);
+ exprToNodeVisitor = new ExpressionToExpectedSchemaNodeVisitor();
+ pathBuilderVisitor = new ColumnFilterPathBuilderVisitor();
+ paths = new HashMap<>();
+ checkerVisitor = new ArrayPathCheckerVisitor();
+ }
+
+ @Override
+ protected boolean skip(ScanDefineDescriptor scanDefineDescriptor) {
+ Dataset dataset = scanDefineDescriptor.getDataset();
+ LogicalOperatorTag scanOpTag = scanDefineDescriptor.getOperator().getOperatorTag();
+ /*
+ * Only use the filter with data-scan. For index-search (unnest-map), this could be expensive as this
+ * requires to rewind the columnar readers for each point-lookup -- decoding 1000s of values for each
+ * point-lookup. Hence, the query should rely on the secondary-index filtration and not the columnar filter.
+ */
+ return scanOpTag != LogicalOperatorTag.DATASOURCESCAN
+ || dataset.getDatasetFormatInfo().getFormat() != DatasetConfig.DatasetFormat.COLUMN
+ || !DatasetUtil.isFilterPushdownSupported(dataset);
+ }
+
+ @Override
+ protected void prepareScan(ScanDefineDescriptor scanDefineDescriptor) {
+ exprToNodeVisitor.reset(scanDefineDescriptor);
+ }
+
+ @Override
+ protected void preparePushdown(UseDescriptor useDescriptor) throws AlgebricksException {
+ exprToNodeVisitor.setTypeEnv(useDescriptor.getOperator().computeOutputTypeEnvironment(context));
+ paths.clear();
+ }
+
+ @Override
+ protected boolean isPushable(AbstractFunctionCallExpression expression) {
+ FunctionIdentifier fid = expression.getFunctionIdentifier();
+ return RANGE_FILTER_PUSHABLE_FUNCTIONS.contains(fid) || !isNestedFunction(fid) && !isTypeFunction(fid);
+ }
+
+ @Override
+ protected boolean handleCompare(AbstractFunctionCallExpression expression) throws AlgebricksException {
+ List<Mutable<ILogicalExpression>> args = expression.getArguments();
+
+ Mutable<ILogicalExpression> leftRef = args.get(0);
+ Mutable<ILogicalExpression> rightRef = args.get(1);
+
+ ILogicalExpression left = leftRef.getValue();
+ ILogicalExpression right = rightRef.getValue();
+
+ return pushdownFilterExpression(left) && pushdownFilterExpression(right);
+ }
+
+ @Override
+ protected boolean handlePath(AbstractFunctionCallExpression expression) throws AlgebricksException {
+ IExpectedSchemaNode node = expression.accept(exprToNodeVisitor, null);
+ if (node == null || node.getType() != ExpectedSchemaNodeType.ANY) {
+ return false;
+ }
+ paths.put(expression, pathBuilderVisitor.buildPath((AnyExpectedSchemaNode) node));
+ return true;
+ }
+
+ @Override
+ protected boolean putFilterInformation(ScanDefineDescriptor scanDefineDescriptor, ILogicalExpression inlinedExpr)
+ throws AlgebricksException {
+ ILogicalExpression filterExpr = scanDefineDescriptor.getFilterExpression();
+ if (filterExpr != null) {
+ filterExpr = orExpression(filterExpr, inlinedExpr);
+ scanDefineDescriptor.setFilterExpression(filterExpr);
+ } else {
+ scanDefineDescriptor.setFilterExpression(inlinedExpr);
+ }
+
+ if (checkerVisitor.containsMultipleArrayPaths(paths.values())) {
+ // Cannot pushdown a filter with multiple unnest
+ // TODO allow rewindable column readers for filters
+ // TODO this is a bit conservative (maybe too conservative) as we can push part of expression down
+ return false;
+ }
+
+ scanDefineDescriptor.getFilterPaths().putAll(paths);
+ return true;
+ }
+
+ protected final AbstractFunctionCallExpression orExpression(ILogicalExpression filterExpr,
+ ILogicalExpression inlinedExpr) {
+ AbstractFunctionCallExpression funcExpr = (AbstractFunctionCallExpression) filterExpr;
+ if (!BuiltinFunctions.AND.equals(funcExpr.getFunctionIdentifier())) {
+ IFunctionInfo fInfo = context.getMetadataProvider().lookupFunction(AlgebricksBuiltinFunctions.OR);
+ List<Mutable<ILogicalExpression>> args = new ArrayList<>();
+ args.add(new MutableObject<>(filterExpr));
+ funcExpr = new ScalarFunctionCallExpression(fInfo, args);
+ }
+ funcExpr.getArguments().add(new MutableObject<>(inlinedExpr));
+ return funcExpr;
+ }
+}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/ColumnRangeFilterPushdownProcessor.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/ColumnRangeFilterPushdownProcessor.java
new file mode 100644
index 0000000..47db154
--- /dev/null
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/ColumnRangeFilterPushdownProcessor.java
@@ -0,0 +1,147 @@
+/*
+ * 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.pushdown.processor;
+
+import static org.apache.asterix.metadata.utils.PushdownUtil.RANGE_FILTER_PUSHABLE_FUNCTIONS;
+import static org.apache.asterix.metadata.utils.PushdownUtil.isConstant;
+import static org.apache.asterix.metadata.utils.PushdownUtil.isFilterPath;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.asterix.common.config.DatasetConfig;
+import org.apache.asterix.metadata.entities.Dataset;
+import org.apache.asterix.metadata.utils.DatasetUtil;
+import org.apache.asterix.om.base.IAObject;
+import org.apache.asterix.om.constants.AsterixConstantValue;
+import org.apache.asterix.om.types.ARecordType;
+import org.apache.asterix.optimizer.rules.pushdown.PushdownContext;
+import org.apache.asterix.optimizer.rules.pushdown.descriptor.ScanDefineDescriptor;
+import org.apache.asterix.optimizer.rules.pushdown.descriptor.UseDescriptor;
+import org.apache.asterix.optimizer.rules.pushdown.schema.AnyExpectedSchemaNode;
+import org.apache.asterix.optimizer.rules.pushdown.schema.ExpectedSchemaNodeType;
+import org.apache.asterix.optimizer.rules.pushdown.schema.IExpectedSchemaNode;
+import org.apache.asterix.runtime.projection.FunctionCallInformation;
+import org.apache.asterix.runtime.projection.ProjectionFiltrationWarningFactoryProvider;
+import org.apache.commons.lang3.mutable.Mutable;
+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.IOptimizationContext;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.ConstantExpression;
+import org.apache.hyracks.api.exceptions.SourceLocation;
+
+/**
+ * Computes a range-filter expression for {@link DatasetConfig.DatasetFormat#COLUMN} datasets. Each column is such
+ * dataset contains a pair of normalized min-max values. The range filter expression can be utilized to filter out
+ * mega leaf nodes that do not satisfy the range filter expression.
+ */
+public class ColumnRangeFilterPushdownProcessor extends ColumnFilterPushdownProcessor {
+ private final Map<String, FunctionCallInformation> sourceInformationMap;
+
+ public ColumnRangeFilterPushdownProcessor(PushdownContext pushdownContext, IOptimizationContext context) {
+ super(pushdownContext, context);
+ sourceInformationMap = new HashMap<>();
+ }
+
+ @Override
+ protected boolean skip(ScanDefineDescriptor scanDefineDescriptor) {
+ Dataset dataset = scanDefineDescriptor.getDataset();
+ return dataset.getDatasetFormatInfo().getFormat() != DatasetConfig.DatasetFormat.COLUMN
+ || !DatasetUtil.isRangeFilterPushdownSupported(dataset);
+ }
+
+ @Override
+ protected void preparePushdown(UseDescriptor useDescriptor) throws AlgebricksException {
+ super.preparePushdown(useDescriptor);
+ sourceInformationMap.clear();
+ }
+
+ @Override
+ protected boolean isPushable(AbstractFunctionCallExpression expression) {
+ return RANGE_FILTER_PUSHABLE_FUNCTIONS.contains(expression.getFunctionIdentifier());
+ }
+
+ @Override
+ protected boolean handleCompare(AbstractFunctionCallExpression expression) throws AlgebricksException {
+ List<Mutable<ILogicalExpression>> args = expression.getArguments();
+
+ Mutable<ILogicalExpression> leftRef = args.get(0);
+ Mutable<ILogicalExpression> rightRef = args.get(1);
+
+ ILogicalExpression left = leftRef.getValue();
+ ILogicalExpression right = rightRef.getValue();
+
+ if (isConstant(left) && isFilterPath(right)) {
+ return pushdownRangeFilter(right, left, expression, true);
+ } else if (isConstant(right) && isFilterPath(left)) {
+ return pushdownRangeFilter(left, right, expression, false);
+ }
+ // Either it is a compare that doesn't involve a constant there's a function that wraps the value access path
+ return false;
+ }
+
+ @Override
+ protected boolean handlePath(AbstractFunctionCallExpression expression) {
+ // This means we got something like WHERE $r.getField("isVerified") -- where isVerified is a boolean field.
+ // Boolean range filters are not supported currently
+ return false;
+ }
+
+ @Override
+ protected boolean putFilterInformation(ScanDefineDescriptor scanDefineDescriptor, ILogicalExpression inlinedExpr) {
+ ILogicalExpression filterExpr = scanDefineDescriptor.getRangeFilterExpression();
+ if (filterExpr != null) {
+ filterExpr = orExpression(filterExpr, inlinedExpr);
+ scanDefineDescriptor.setRangeFilterExpression(filterExpr);
+ } else {
+ scanDefineDescriptor.setRangeFilterExpression(inlinedExpr);
+ }
+ scanDefineDescriptor.getFilterPaths().putAll(paths);
+ scanDefineDescriptor.getPathLocations().putAll(sourceInformationMap);
+
+ return true;
+ }
+
+ private boolean pushdownRangeFilter(ILogicalExpression pathExpr, ILogicalExpression constExpr,
+ AbstractFunctionCallExpression funcExpr, boolean leftConstant) throws AlgebricksException {
+ AnyExpectedSchemaNode node = getNode(pathExpr);
+ IAObject constantValue = ((AsterixConstantValue) ((ConstantExpression) constExpr).getValue()).getObject();
+ if (node == null || constantValue.getType().getTypeTag().isDerivedType()) {
+ return false;
+ }
+ String functionName = funcExpr.getFunctionIdentifier().getName();
+ SourceLocation sourceLocation = funcExpr.getSourceLocation();
+ FunctionCallInformation functionCallInfo = new FunctionCallInformation(functionName, sourceLocation,
+ ProjectionFiltrationWarningFactoryProvider.getIncomparableTypesFactory(leftConstant));
+ ARecordType path = pathBuilderVisitor.buildPath(node, constantValue, sourceInformationMap, functionCallInfo);
+ paths.put(pathExpr, path);
+ return true;
+ }
+
+ private AnyExpectedSchemaNode getNode(ILogicalExpression expression) throws AlgebricksException {
+ IExpectedSchemaNode node = expression.accept(exprToNodeVisitor, null);
+ if (node == null || node.getType() != ExpectedSchemaNodeType.ANY) {
+ return null;
+ }
+ return (AnyExpectedSchemaNode) node;
+ }
+
+}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/IPushdownProcessor.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/IPushdownProcessor.java
new file mode 100644
index 0000000..d7c7a40
--- /dev/null
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/IPushdownProcessor.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.optimizer.rules.pushdown.processor;
+
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+
+public interface IPushdownProcessor {
+ void process() throws AlgebricksException;
+}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/InlineFilterExpressionsProcessor.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/InlineFilterExpressionsProcessor.java
new file mode 100644
index 0000000..6cec455
--- /dev/null
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/processor/InlineFilterExpressionsProcessor.java
@@ -0,0 +1,95 @@
+/*
+ * 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.pushdown.processor;
+
+import static org.apache.asterix.metadata.utils.PushdownUtil.isAnd;
+import static org.apache.asterix.metadata.utils.PushdownUtil.isOr;
+import static org.apache.asterix.metadata.utils.PushdownUtil.isSameFunction;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.asterix.optimizer.rules.pushdown.PushdownContext;
+import org.apache.asterix.optimizer.rules.pushdown.descriptor.ScanDefineDescriptor;
+import org.apache.commons.lang3.mutable.Mutable;
+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.IOptimizationContext;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
+
+/**
+ * Inline filter expressions of a scan.
+ * E.g.,
+ * and(a > 2) --> a > 2
+ * and(a > 2, and(b > 2)) --> and(a > 2, b > 2)
+ */
+public class InlineFilterExpressionsProcessor extends AbstractPushdownProcessor {
+ public InlineFilterExpressionsProcessor(PushdownContext pushdownContext, IOptimizationContext context) {
+ super(pushdownContext, context);
+ }
+
+ @Override
+ public void process() throws AlgebricksException {
+ List<ScanDefineDescriptor> scanDefineDescriptors = pushdownContext.getRegisteredScans();
+ for (ScanDefineDescriptor scanDefineDescriptor : scanDefineDescriptors) {
+ scanDefineDescriptor.setFilterExpression(inline(scanDefineDescriptor.getFilterExpression()));
+ scanDefineDescriptor.setRangeFilterExpression(inline(scanDefineDescriptor.getRangeFilterExpression()));
+ }
+ }
+
+ private ILogicalExpression inline(ILogicalExpression expression) {
+ if (expression == null || expression.getExpressionTag() != LogicalExpressionTag.FUNCTION_CALL) {
+ return expression;
+ }
+
+ AbstractFunctionCallExpression funcExpr = (AbstractFunctionCallExpression) expression;
+ List<Mutable<ILogicalExpression>> args = funcExpr.getArguments();
+ if ((isAnd(funcExpr) || isOr(funcExpr)) && args.size() == 1) {
+ // Fix the incorrect AND with a single argument. This could happen if the AND expression is partially pushed
+ return inline(args.get(0).getValue());
+ }
+
+ if (isAnd(funcExpr) || isOr(funcExpr)) {
+ List<Mutable<ILogicalExpression>> inlinedArgs = new ArrayList<>();
+ for (Mutable<ILogicalExpression> argRef : args) {
+ // inline arg first
+ ILogicalExpression arg = inline(argRef.getValue());
+
+ if (isSameFunction(funcExpr, arg)) {
+ // funcExpr is AND/OR and arg is also the same. Inline AND/OR by adding arg arguments to funcExpr
+ AbstractFunctionCallExpression argFuncExpr = (AbstractFunctionCallExpression) arg;
+ inlinedArgs.addAll(argFuncExpr.getArguments());
+ } else {
+ // funcExpr and arg are different. Set the inlined arg to argRef (argRef has un-inlined version).
+ argRef.setValue(arg);
+ inlinedArgs.add(argRef);
+ }
+ }
+
+ // Clear the original argument
+ args.clear();
+ // Add the new inlined arguments
+ args.addAll(inlinedArgs);
+ }
+
+ // either the original expression or the inlined AND/OR expression
+ return expression;
+ }
+}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/ExpectedSchemaBuilder.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ExpectedSchemaBuilder.java
similarity index 82%
rename from asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/ExpectedSchemaBuilder.java
rename to asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ExpectedSchemaBuilder.java
index edebed9..345b545 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/ExpectedSchemaBuilder.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ExpectedSchemaBuilder.java
@@ -16,27 +16,19 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.asterix.optimizer.rules.pushdown;
+package org.apache.asterix.optimizer.rules.pushdown.schema;
-import static org.apache.asterix.optimizer.rules.pushdown.ExpressionValueAccessPushdownVisitor.ARRAY_FUNCTIONS;
-import static org.apache.asterix.optimizer.rules.pushdown.ExpressionValueAccessPushdownVisitor.SUPPORTED_FUNCTIONS;
+import static org.apache.asterix.metadata.utils.PushdownUtil.ARRAY_FUNCTIONS;
+import static org.apache.asterix.metadata.utils.PushdownUtil.SUPPORTED_FUNCTIONS;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import org.apache.asterix.metadata.utils.PushdownUtil;
import org.apache.asterix.om.functions.BuiltinFunctions;
import org.apache.asterix.om.types.ARecordType;
-import org.apache.asterix.om.utils.ConstantExpressionUtil;
-import org.apache.asterix.optimizer.rules.pushdown.schema.AbstractComplexExpectedSchemaNode;
-import org.apache.asterix.optimizer.rules.pushdown.schema.AnyExpectedSchemaNode;
-import org.apache.asterix.optimizer.rules.pushdown.schema.ArrayExpectedSchemaNode;
-import org.apache.asterix.optimizer.rules.pushdown.schema.ExpectedSchemaNodeType;
-import org.apache.asterix.optimizer.rules.pushdown.schema.IExpectedSchemaNode;
-import org.apache.asterix.optimizer.rules.pushdown.schema.ObjectExpectedSchemaNode;
-import org.apache.asterix.optimizer.rules.pushdown.schema.RootExpectedSchemaNode;
-import org.apache.asterix.optimizer.rules.pushdown.schema.UnionExpectedSchemaNode;
import org.apache.asterix.optimizer.rules.pushdown.visitor.ExpectedSchemaNodeToIATypeTranslatorVisitor;
import org.apache.asterix.runtime.projection.DataProjectionFiltrationInfo;
import org.apache.asterix.runtime.projection.FunctionCallInformation;
@@ -140,7 +132,7 @@
return varToNode.get(variable);
}
- IExpectedSchemaNode getNode(ILogicalExpression expr) {
+ public IExpectedSchemaNode getNode(ILogicalExpression expr) {
if (expr.getExpressionTag() == LogicalExpressionTag.VARIABLE) {
return getNode(VariableUtilities.getVariable(expr));
}
@@ -210,7 +202,7 @@
return newNode;
}
- private void addChild(AbstractFunctionCallExpression parentExpr, IVariableTypeEnvironment typeEnv,
+ public static void addChild(AbstractFunctionCallExpression parentExpr, IVariableTypeEnvironment typeEnv,
AbstractComplexExpectedSchemaNode parent, IExpectedSchemaNode child) throws AlgebricksException {
switch (parent.getType()) {
case OBJECT:
@@ -228,33 +220,7 @@
}
}
- private void handleObject(AbstractFunctionCallExpression parentExpr, IVariableTypeEnvironment typeEnv,
- AbstractComplexExpectedSchemaNode parent, IExpectedSchemaNode child) throws AlgebricksException {
- if (BuiltinFunctions.FIELD_ACCESS_BY_NAME.equals(parentExpr.getFunctionIdentifier())) {
- ObjectExpectedSchemaNode objectNode = (ObjectExpectedSchemaNode) parent;
- objectNode.addChild(ConstantExpressionUtil.getStringArgument(parentExpr, 1), child);
- } else {
- //FIELD_ACCESS_BY_INDEX
- ARecordType recordType = (ARecordType) typeEnv.getType(parentExpr.getArguments().get(0).getValue());
- ObjectExpectedSchemaNode objectNode = (ObjectExpectedSchemaNode) parent;
- int fieldIdx = ConstantExpressionUtil.getIntArgument(parentExpr, 1);
- objectNode.addChild(recordType.getFieldNames()[fieldIdx], child);
- }
- }
-
- private void handleArray(AbstractComplexExpectedSchemaNode parent, IExpectedSchemaNode child) {
- ArrayExpectedSchemaNode arrayNode = (ArrayExpectedSchemaNode) parent;
- arrayNode.addChild(child);
- }
-
- private void handleUnion(AbstractFunctionCallExpression parentExpr, AbstractComplexExpectedSchemaNode parent,
- IExpectedSchemaNode child) throws AlgebricksException {
- UnionExpectedSchemaNode unionNode = (UnionExpectedSchemaNode) parent;
- ExpectedSchemaNodeType parentType = getExpectedNestedNodeType(parentExpr);
- addChild(parentExpr, null, unionNode.getChild(parentType), child);
- }
-
- private static ExpectedSchemaNodeType getExpectedNestedNodeType(AbstractFunctionCallExpression funcExpr) {
+ public static ExpectedSchemaNodeType getExpectedNestedNodeType(AbstractFunctionCallExpression funcExpr) {
FunctionIdentifier fid = funcExpr.getFunctionIdentifier();
if (BuiltinFunctions.FIELD_ACCESS_BY_NAME.equals(fid) || BuiltinFunctions.FIELD_ACCESS_BY_INDEX.equals(fid)) {
return ExpectedSchemaNodeType.OBJECT;
@@ -264,6 +230,25 @@
throw new IllegalStateException("Function " + fid + " should not be pushed down");
}
+ private static void handleObject(AbstractFunctionCallExpression parentExpr, IVariableTypeEnvironment typeEnv,
+ AbstractComplexExpectedSchemaNode parent, IExpectedSchemaNode child) throws AlgebricksException {
+ String fieldName = PushdownUtil.getFieldName(parentExpr, typeEnv);
+ ObjectExpectedSchemaNode objectNode = (ObjectExpectedSchemaNode) parent;
+ objectNode.addChild(fieldName, child);
+ }
+
+ private static void handleArray(AbstractComplexExpectedSchemaNode parent, IExpectedSchemaNode child) {
+ ArrayExpectedSchemaNode arrayNode = (ArrayExpectedSchemaNode) parent;
+ arrayNode.addChild(child);
+ }
+
+ private static void handleUnion(AbstractFunctionCallExpression parentExpr, AbstractComplexExpectedSchemaNode parent,
+ IExpectedSchemaNode child) throws AlgebricksException {
+ UnionExpectedSchemaNode unionNode = (UnionExpectedSchemaNode) parent;
+ ExpectedSchemaNodeType parentType = getExpectedNestedNodeType(parentExpr);
+ addChild(parentExpr, null, unionNode.getChild(parentType), child);
+ }
+
private static boolean isVariable(ILogicalExpression expr) {
return expr.getExpressionTag() == LogicalExpressionTag.VARIABLE;
}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ObjectExpectedSchemaNode.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ObjectExpectedSchemaNode.java
index 69812a3..82a7214 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ObjectExpectedSchemaNode.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ObjectExpectedSchemaNode.java
@@ -67,7 +67,7 @@
return new ARecordType("typeName", fieldNames, fieldTypes, false);
}
- protected String getChildFieldName(IExpectedSchemaNode requestedChild) {
+ public String getChildFieldName(IExpectedSchemaNode requestedChild) {
String key = null;
for (Map.Entry<String, IExpectedSchemaNode> child : children.entrySet()) {
if (child.getValue() == requestedChild) {
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ColumnFilterPathBuilderVisitor.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/visitor/ColumnFilterPathBuilderVisitor.java
similarity index 88%
rename from asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ColumnFilterPathBuilderVisitor.java
rename to asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/visitor/ColumnFilterPathBuilderVisitor.java
index 492aea7..94fff57 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ColumnFilterPathBuilderVisitor.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/visitor/ColumnFilterPathBuilderVisitor.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.asterix.optimizer.rules.pushdown.schema;
+package org.apache.asterix.optimizer.rules.pushdown.visitor;
import java.util.Map;
@@ -27,6 +27,13 @@
import org.apache.asterix.om.types.BuiltinType;
import org.apache.asterix.om.types.IAType;
import org.apache.asterix.om.types.IATypeVisitor;
+import org.apache.asterix.optimizer.rules.pushdown.schema.AnyExpectedSchemaNode;
+import org.apache.asterix.optimizer.rules.pushdown.schema.ArrayExpectedSchemaNode;
+import org.apache.asterix.optimizer.rules.pushdown.schema.IExpectedSchemaNode;
+import org.apache.asterix.optimizer.rules.pushdown.schema.IExpectedSchemaNodeVisitor;
+import org.apache.asterix.optimizer.rules.pushdown.schema.ObjectExpectedSchemaNode;
+import org.apache.asterix.optimizer.rules.pushdown.schema.RootExpectedSchemaNode;
+import org.apache.asterix.optimizer.rules.pushdown.schema.UnionExpectedSchemaNode;
import org.apache.asterix.runtime.projection.FunctionCallInformation;
import org.apache.asterix.runtime.projection.ProjectionFiltrationWarningFactoryProvider;
@@ -38,6 +45,10 @@
private Map<String, FunctionCallInformation> sourceInformationMap;
private int counter = 0;
+ public ARecordType buildPath(AnyExpectedSchemaNode anyNode) {
+ return buildPath(anyNode, null, null, null);
+ }
+
public ARecordType buildPath(AnyExpectedSchemaNode anyNode, IAObject constant,
Map<String, FunctionCallInformation> sourceInformationMap, FunctionCallInformation compareFunctionInfo) {
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/visitor/ExpressionToExpectedSchemaNodeVisitor.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/visitor/ExpressionToExpectedSchemaNodeVisitor.java
new file mode 100644
index 0000000..5168505
--- /dev/null
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/visitor/ExpressionToExpectedSchemaNodeVisitor.java
@@ -0,0 +1,126 @@
+/*
+ * 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.pushdown.visitor;
+
+import static org.apache.asterix.metadata.utils.PushdownUtil.SUPPORTED_FUNCTIONS;
+
+import org.apache.asterix.optimizer.rules.pushdown.descriptor.ScanDefineDescriptor;
+import org.apache.asterix.optimizer.rules.pushdown.schema.AbstractComplexExpectedSchemaNode;
+import org.apache.asterix.optimizer.rules.pushdown.schema.AnyExpectedSchemaNode;
+import org.apache.asterix.optimizer.rules.pushdown.schema.ExpectedSchemaBuilder;
+import org.apache.asterix.optimizer.rules.pushdown.schema.ExpectedSchemaNodeType;
+import org.apache.asterix.optimizer.rules.pushdown.schema.IExpectedSchemaNode;
+import org.apache.asterix.optimizer.rules.pushdown.schema.RootExpectedSchemaNode;
+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.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.IVariableTypeEnvironment;
+import org.apache.hyracks.algebricks.core.algebra.expressions.ScalarFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.StatefulFunctionCallExpression;
+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.visitors.VariableUtilities;
+import org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalExpressionVisitor;
+
+public class ExpressionToExpectedSchemaNodeVisitor implements ILogicalExpressionVisitor<IExpectedSchemaNode, Void> {
+ private IVariableTypeEnvironment typeEnv;
+
+ private ScanDefineDescriptor scanDefineDescriptor;
+
+ public void reset(ScanDefineDescriptor scanDefineDescriptor) {
+ this.scanDefineDescriptor = scanDefineDescriptor;
+ }
+
+ public void setTypeEnv(IVariableTypeEnvironment typeEnv) {
+ this.typeEnv = typeEnv;
+ }
+
+ @Override
+ public IExpectedSchemaNode visitConstantExpression(ConstantExpression expr, Void arg) throws AlgebricksException {
+ return null;
+ }
+
+ @Override
+ public IExpectedSchemaNode visitVariableReferenceExpression(VariableReferenceExpression expr, Void arg)
+ throws AlgebricksException {
+ LogicalVariable variable = VariableUtilities.getVariable(expr);
+ if (scanDefineDescriptor.getVariable() == variable
+ || scanDefineDescriptor.getMetaRecordVariable() == variable) {
+ return RootExpectedSchemaNode.ALL_FIELDS_ROOT_NODE;
+ }
+ return null;
+ }
+
+ @Override
+ public IExpectedSchemaNode visitScalarFunctionCallExpression(ScalarFunctionCallExpression expr, Void arg)
+ throws AlgebricksException {
+ return handleFunction(expr);
+ }
+
+ // Disabled expressions
+ @Override
+ public IExpectedSchemaNode visitAggregateFunctionCallExpression(AggregateFunctionCallExpression expr, Void arg)
+ throws AlgebricksException {
+ return null;
+ }
+
+ @Override
+ public IExpectedSchemaNode visitStatefulFunctionCallExpression(StatefulFunctionCallExpression expr, Void arg)
+ throws AlgebricksException {
+ return null;
+ }
+
+ @Override
+ public IExpectedSchemaNode visitUnnestingFunctionCallExpression(UnnestingFunctionCallExpression expr, Void arg)
+ throws AlgebricksException {
+ return handleFunction(expr);
+ }
+
+ private IExpectedSchemaNode handleFunction(AbstractFunctionCallExpression expr) throws AlgebricksException {
+ FunctionIdentifier fid = expr.getFunctionIdentifier();
+ if (!SUPPORTED_FUNCTIONS.contains(fid)) {
+ // If not a supported function, return null
+ return null;
+ }
+
+ // All supported functions have the node at their first argument
+ ILogicalExpression parentExpr = expr.getArguments().get(0).getValue();
+ IExpectedSchemaNode parent = parentExpr.accept(this, null);
+ if (parent == null) {
+ return null;
+ }
+
+ AbstractComplexExpectedSchemaNode newParent = replaceIfNeeded(parent, expr);
+ IExpectedSchemaNode myNode =
+ new AnyExpectedSchemaNode(newParent, expr.getSourceLocation(), expr.getFunctionIdentifier().getName());
+ ExpectedSchemaBuilder.addChild(expr, typeEnv, newParent, myNode);
+ return myNode;
+ }
+
+ private AbstractComplexExpectedSchemaNode replaceIfNeeded(IExpectedSchemaNode parent,
+ AbstractFunctionCallExpression funcExpr) {
+ ExpectedSchemaNodeType expectedType = ExpectedSchemaBuilder.getExpectedNestedNodeType(funcExpr);
+ return (AbstractComplexExpectedSchemaNode) parent.replaceIfNeeded(expectedType, parent.getSourceLocation(),
+ parent.getFunctionName());
+ }
+}
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/PushdownUtil.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/PushdownUtil.java
new file mode 100644
index 0000000..3bdaca3
--- /dev/null
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/PushdownUtil.java
@@ -0,0 +1,179 @@
+/*
+ * 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.metadata.utils;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.asterix.om.base.ABoolean;
+import org.apache.asterix.om.base.AMissing;
+import org.apache.asterix.om.base.ANull;
+import org.apache.asterix.om.base.IAObject;
+import org.apache.asterix.om.constants.AsterixConstantValue;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.asterix.om.types.ARecordType;
+import org.apache.asterix.om.utils.ConstantExpressionUtil;
+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.LogicalExpressionTag;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.ConstantExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.IAlgebricksConstantValue;
+import org.apache.hyracks.algebricks.core.algebra.expressions.IVariableTypeEnvironment;
+import org.apache.hyracks.algebricks.core.algebra.functions.AlgebricksBuiltinFunctions;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+
+public class PushdownUtil {
+ //Set of allowed functions that can request a type in its entirety without marking it as leaf (i.e., ANY)
+ public static final Set<FunctionIdentifier> ALLOWED_FUNCTIONS = createAllowedFunctions();
+ //Set of supported array functions
+ public static final Set<FunctionIdentifier> ARRAY_FUNCTIONS = createSupportedArrayFunctions();
+ //Set of supported functions that we can push down (a.k.a. path functions)
+ public static final Set<FunctionIdentifier> SUPPORTED_FUNCTIONS = createSupportedFunctions();
+
+ public static final Set<FunctionIdentifier> FILTER_PUSHABLE_PATH_FUNCTIONS = createFilterPushablePathFunctions();
+ public static final Set<FunctionIdentifier> COMPARE_FUNCTIONS = createCompareFunctions();
+ public static final Set<FunctionIdentifier> RANGE_FILTER_PUSHABLE_FUNCTIONS = createRangeFilterPushableFunctions();
+
+ private PushdownUtil() {
+ }
+
+ public static String getFieldName(AbstractFunctionCallExpression fieldAccessExpr, IVariableTypeEnvironment typeEnv)
+ throws AlgebricksException {
+ if (BuiltinFunctions.FIELD_ACCESS_BY_NAME.equals(fieldAccessExpr.getFunctionIdentifier())) {
+ return ConstantExpressionUtil.getStringArgument(fieldAccessExpr, 1);
+ } else {
+ //FIELD_ACCESS_BY_INDEX
+ ARecordType recordType = (ARecordType) typeEnv.getType(fieldAccessExpr.getArguments().get(0).getValue());
+ int fieldIdx = ConstantExpressionUtil.getIntArgument(fieldAccessExpr, 1);
+ return recordType.getFieldNames()[fieldIdx];
+ }
+ }
+
+ public static boolean isConstant(ILogicalExpression expression) {
+ return expression.getExpressionTag() == LogicalExpressionTag.CONSTANT;
+ }
+
+ public static boolean isFilterPath(ILogicalExpression expression) {
+ FunctionIdentifier fid = getFunctionIdentifier(expression);
+ return fid != null && FILTER_PUSHABLE_PATH_FUNCTIONS.contains(fid);
+ }
+
+ public static boolean isCompare(ILogicalExpression expression) {
+ FunctionIdentifier fid = getFunctionIdentifier(expression);
+ return fid != null && COMPARE_FUNCTIONS.contains(fid);
+ }
+
+ public static boolean isAnd(ILogicalExpression expression) {
+ FunctionIdentifier fid = getFunctionIdentifier(expression);
+ return BuiltinFunctions.AND.equals(fid);
+ }
+
+ public static boolean isOr(ILogicalExpression expression) {
+ FunctionIdentifier fid = getFunctionIdentifier(expression);
+ return BuiltinFunctions.OR.equals(fid);
+ }
+
+ public static boolean isTypeFunction(FunctionIdentifier fid) {
+ return fid.getName().startsWith("is");
+ }
+
+ public static boolean isNestedFunction(FunctionIdentifier fid) {
+ return isObjectFunction(fid) || isArrayFunction(fid) || BuiltinFunctions.DEEP_EQUAL.equals(fid);
+ }
+
+ public static boolean isObjectFunction(FunctionIdentifier fid) {
+ String functionName = fid.getName();
+ return functionName.contains("object") || BuiltinFunctions.PAIRS.equals(fid);
+ }
+
+ public static boolean isArrayFunction(FunctionIdentifier fid) {
+ String functionName = fid.getName();
+ return functionName.startsWith("array") || functionName.startsWith("strict") || functionName.startsWith("sql")
+ || BuiltinFunctions.GET_ITEM.equals(fid);
+ }
+
+ public static boolean isSameFunction(ILogicalExpression expr1, ILogicalExpression expr2) {
+ FunctionIdentifier fid1 = getFunctionIdentifier(expr1);
+ FunctionIdentifier fid2 = getFunctionIdentifier(expr2);
+ return fid1 != null && fid1.equals(fid2);
+ }
+
+ public static IAObject getConstant(ILogicalExpression expr) {
+ IAlgebricksConstantValue algebricksConstant = ((ConstantExpression) expr).getValue();
+ if (algebricksConstant.isTrue()) {
+ return ABoolean.TRUE;
+ } else if (algebricksConstant.isFalse()) {
+ return ABoolean.FALSE;
+ } else if (algebricksConstant.isMissing()) {
+ return AMissing.MISSING;
+ } else if (algebricksConstant.isNull()) {
+ return ANull.NULL;
+ }
+
+ return ((AsterixConstantValue) algebricksConstant).getObject();
+ }
+
+ private static FunctionIdentifier getFunctionIdentifier(ILogicalExpression expression) {
+ if (expression.getExpressionTag() != LogicalExpressionTag.FUNCTION_CALL) {
+ return null;
+ }
+ AbstractFunctionCallExpression funcExpr = (AbstractFunctionCallExpression) expression;
+ return funcExpr.getFunctionIdentifier();
+ }
+
+ private static Set<FunctionIdentifier> createSupportedArrayFunctions() {
+ return Set.of(BuiltinFunctions.GET_ITEM, BuiltinFunctions.ARRAY_STAR, BuiltinFunctions.SCAN_COLLECTION);
+ }
+
+ private static Set<FunctionIdentifier> createSupportedFunctions() {
+ Set<FunctionIdentifier> supportedFunctions = new HashSet<>();
+ supportedFunctions.add(BuiltinFunctions.FIELD_ACCESS_BY_NAME);
+ supportedFunctions.add(BuiltinFunctions.FIELD_ACCESS_BY_INDEX);
+ supportedFunctions.addAll(ARRAY_FUNCTIONS);
+ return supportedFunctions;
+ }
+
+ private static Set<FunctionIdentifier> createAllowedFunctions() {
+ return Set.of(BuiltinFunctions.IS_ARRAY, BuiltinFunctions.IS_OBJECT, BuiltinFunctions.IS_ATOMIC,
+ BuiltinFunctions.IS_NUMBER, BuiltinFunctions.IS_BOOLEAN, BuiltinFunctions.IS_STRING,
+ AlgebricksBuiltinFunctions.IS_MISSING, AlgebricksBuiltinFunctions.IS_NULL, BuiltinFunctions.IS_UNKNOWN,
+ BuiltinFunctions.LT, BuiltinFunctions.LE, BuiltinFunctions.EQ, BuiltinFunctions.GT, BuiltinFunctions.GE,
+ BuiltinFunctions.SCALAR_SQL_COUNT);
+ }
+
+ private static Set<FunctionIdentifier> createFilterPushablePathFunctions() {
+ Set<FunctionIdentifier> pushablePathFunctions = new HashSet<>(SUPPORTED_FUNCTIONS);
+ // TODO Add support for GET_ITEM.
+ pushablePathFunctions.remove(BuiltinFunctions.GET_ITEM);
+ return pushablePathFunctions;
+ }
+
+ private static Set<FunctionIdentifier> createCompareFunctions() {
+ return Set.of(AlgebricksBuiltinFunctions.LE, AlgebricksBuiltinFunctions.GE, AlgebricksBuiltinFunctions.LT,
+ AlgebricksBuiltinFunctions.GT, AlgebricksBuiltinFunctions.EQ);
+ }
+
+ private static Set<FunctionIdentifier> createRangeFilterPushableFunctions() {
+ Set<FunctionIdentifier> pushableFunctions = new HashSet<>(COMPARE_FUNCTIONS);
+ pushableFunctions.add(AlgebricksBuiltinFunctions.AND);
+ pushableFunctions.add(AlgebricksBuiltinFunctions.OR);
+ return pushableFunctions;
+ }
+}
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/filter/ArrayPathCheckerVisitor.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/filter/ArrayPathCheckerVisitor.java
index 594cf9b..b1ed40b 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/filter/ArrayPathCheckerVisitor.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/filter/ArrayPathCheckerVisitor.java
@@ -42,7 +42,7 @@
private final Set<Object> seenCollections;
private boolean firstPath;
- ArrayPathCheckerVisitor() {
+ public ArrayPathCheckerVisitor() {
seenCollections = new HashSet<>();
}