[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<>();
     }