[ASTERIXDB-3395][COMP] Resolve common expected schema nodes

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

Details:
If a common node is used in multiple expressions
and those expressions assigned to multiple
variables, their parent nodes could be changed by
one of those variables. When replacing a node,
we won't be able to find its previous version.
This fix uses the expression instead of nodes'
references to find previously created nodes.

Ext-ref: MB-61604
Change-Id: I3491632ba51231eb176ea70cb15ae31b611798df
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/18624
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/schema/AbstractComplexExpectedSchemaNode.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/AbstractComplexExpectedSchemaNode.java
index 4ca0dd8..34ff582 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/AbstractComplexExpectedSchemaNode.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/AbstractComplexExpectedSchemaNode.java
@@ -18,13 +18,14 @@
  */
 package org.apache.asterix.optimizer.rules.pushdown.schema;
 
-import org.apache.hyracks.api.exceptions.SourceLocation;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
 
 public abstract class AbstractComplexExpectedSchemaNode extends AbstractExpectedSchemaNode {
 
-    AbstractComplexExpectedSchemaNode(AbstractComplexExpectedSchemaNode parent, SourceLocation sourceLocation,
-            String functionName) {
-        super(parent, sourceLocation, functionName);
+    AbstractComplexExpectedSchemaNode(AbstractComplexExpectedSchemaNode parent,
+            AbstractFunctionCallExpression parentExpression, ILogicalExpression expression) {
+        super(parent, parentExpression, expression);
     }
 
     @Override
@@ -33,8 +34,8 @@
     }
 
     @Override
-    public IExpectedSchemaNode replaceIfNeeded(ExpectedSchemaNodeType expectedNodeType, SourceLocation sourceLocation,
-            String functionName) {
+    public IExpectedSchemaNode replaceIfNeeded(ExpectedSchemaNodeType expectedNodeType,
+            AbstractFunctionCallExpression parentExpression, ILogicalExpression expression) {
         //If no change is required, return the same node
         IExpectedSchemaNode node = this;
         if (expectedNodeType == ExpectedSchemaNodeType.ANY) {
@@ -47,7 +48,7 @@
              * In this case, we first saw (t.hashtags[*].text), but the next expression (t.hashtags) requested
              * the entire hashtags. So, the expected type for hashtags should be ANY
              */
-            node = new AnyExpectedSchemaNode(getParent(), getSourceLocation(), getFunctionName(), false);
+            node = new AnyExpectedSchemaNode(getParent(), getParentExpression(), false);
             getParent().replaceChild(this, node);
         } else if (expectedNodeType != getType()) {
             /*
@@ -56,8 +57,7 @@
              */
 
             //Create UNION node and its parent is the parent of this
-            UnionExpectedSchemaNode unionSchemaNode =
-                    new UnionExpectedSchemaNode(getParent(), getSourceLocation(), getFunctionName());
+            UnionExpectedSchemaNode unionSchemaNode = new UnionExpectedSchemaNode(getParent(), getParentExpression());
 
             //Add this as a child of UNION
             unionSchemaNode.addChild(this);
@@ -78,7 +78,7 @@
              * Before: UNION <-- this
              * After:  UNION <-- (this, newChild)
              */
-            unionSchemaNode.createChild(expectedNodeType, sourceLocation, functionName);
+            unionSchemaNode.createChild(expectedNodeType, parentExpression, expression);
             node = unionSchemaNode;
         }
         return node;
@@ -113,14 +113,15 @@
     }
 
     public static AbstractComplexExpectedSchemaNode createNestedNode(ExpectedSchemaNodeType type,
-            AbstractComplexExpectedSchemaNode parent, SourceLocation sourceLocation, String functionName) {
+            AbstractComplexExpectedSchemaNode parent, AbstractFunctionCallExpression parentExpression,
+            ILogicalExpression myExpr) {
         switch (type) {
             case ARRAY:
-                return new ArrayExpectedSchemaNode(parent, sourceLocation, functionName);
+                return new ArrayExpectedSchemaNode(parent, parentExpression, myExpr);
             case OBJECT:
-                return new ObjectExpectedSchemaNode(parent, sourceLocation, functionName);
+                return new ObjectExpectedSchemaNode(parent, parentExpression, myExpr);
             case UNION:
-                return new UnionExpectedSchemaNode(parent, sourceLocation, functionName);
+                return new UnionExpectedSchemaNode(parent, parentExpression);
             default:
                 throw new IllegalStateException(type + " is not nested or unknown");
         }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/AbstractExpectedSchemaNode.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/AbstractExpectedSchemaNode.java
index b895781..379fea3 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/AbstractExpectedSchemaNode.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/AbstractExpectedSchemaNode.java
@@ -18,18 +18,21 @@
  */
 package org.apache.asterix.optimizer.rules.pushdown.schema;
 
+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.api.exceptions.SourceLocation;
 
 abstract class AbstractExpectedSchemaNode implements IExpectedSchemaNode {
+    protected final AbstractFunctionCallExpression parentExpression;
+    protected final ILogicalExpression expression;
     private AbstractComplexExpectedSchemaNode parent;
-    private final SourceLocation sourceLocation;
-    private final String functionName;
 
-    AbstractExpectedSchemaNode(AbstractComplexExpectedSchemaNode parent, SourceLocation sourceLocation,
-            String functionName) {
+    AbstractExpectedSchemaNode(AbstractComplexExpectedSchemaNode parent,
+            AbstractFunctionCallExpression parentExpression, ILogicalExpression expression) {
+        this.parentExpression = parentExpression;
+        this.expression = expression;
         this.parent = parent;
-        this.sourceLocation = sourceLocation;
-        this.functionName = functionName;
     }
 
     @Override
@@ -39,12 +42,25 @@
 
     @Override
     public final SourceLocation getSourceLocation() {
-        return sourceLocation;
+        return expression.getSourceLocation();
     }
 
     @Override
     public final String getFunctionName() {
-        return functionName;
+        if (expression.getExpressionTag() == LogicalExpressionTag.FUNCTION_CALL) {
+            return ((AbstractFunctionCallExpression) expression).getFunctionIdentifier().getName();
+        }
+        return null;
+    }
+
+    @Override
+    public AbstractFunctionCallExpression getParentExpression() {
+        return parentExpression;
+    }
+
+    @Override
+    public ILogicalExpression getExpression() {
+        return expression;
     }
 
     @Override
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/AnyExpectedSchemaNode.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/AnyExpectedSchemaNode.java
index 80069e3..fc3046b 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/AnyExpectedSchemaNode.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/AnyExpectedSchemaNode.java
@@ -18,20 +18,22 @@
  */
 package org.apache.asterix.optimizer.rules.pushdown.schema;
 
-import org.apache.hyracks.api.exceptions.SourceLocation;
+import static org.apache.asterix.optimizer.rules.pushdown.schema.AbstractComplexExpectedSchemaNode.createNestedNode;
+
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
 
 public class AnyExpectedSchemaNode extends AbstractExpectedSchemaNode {
     private boolean replaceable;
 
-    public AnyExpectedSchemaNode(AbstractComplexExpectedSchemaNode parent, SourceLocation sourceLocation,
-            String functionName) {
-        super(parent, sourceLocation, functionName);
+    public AnyExpectedSchemaNode(AbstractComplexExpectedSchemaNode parent, AbstractFunctionCallExpression expression) {
+        super(parent, expression, expression);
         replaceable = true;
     }
 
-    protected AnyExpectedSchemaNode(AbstractComplexExpectedSchemaNode parent, SourceLocation sourceLocation,
-            String functionName, boolean replaceable) {
-        super(parent, sourceLocation, functionName);
+    protected AnyExpectedSchemaNode(AbstractComplexExpectedSchemaNode parent, AbstractFunctionCallExpression expression,
+            boolean replaceable) {
+        super(parent, expression, expression);
         this.replaceable = replaceable;
     }
 
@@ -45,8 +47,8 @@
     }
 
     @Override
-    public IExpectedSchemaNode replaceIfNeeded(ExpectedSchemaNodeType expectedNodeType, SourceLocation sourceLocation,
-            String functionName) {
+    public IExpectedSchemaNode replaceIfNeeded(ExpectedSchemaNodeType expectedNodeType,
+            AbstractFunctionCallExpression parentExpression, ILogicalExpression expression) {
         if (expectedNodeType == ExpectedSchemaNodeType.ANY) {
             return this;
         }
@@ -57,8 +59,8 @@
          * the given nested type.
          */
         AbstractComplexExpectedSchemaNode parent = getParent();
-        AbstractComplexExpectedSchemaNode nestedNode = AbstractComplexExpectedSchemaNode
-                .createNestedNode(expectedNodeType, parent, getSourceLocation(), functionName);
+        AbstractComplexExpectedSchemaNode nestedNode =
+                createNestedNode(expectedNodeType, parent, this.parentExpression, expression);
         return parent.replaceChild(this, nestedNode);
     }
 
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ArrayExpectedSchemaNode.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ArrayExpectedSchemaNode.java
index e647862..b8135b7 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ArrayExpectedSchemaNode.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ArrayExpectedSchemaNode.java
@@ -18,14 +18,15 @@
  */
 package org.apache.asterix.optimizer.rules.pushdown.schema;
 
-import org.apache.hyracks.api.exceptions.SourceLocation;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
 
 public class ArrayExpectedSchemaNode extends AbstractComplexExpectedSchemaNode {
     private IExpectedSchemaNode child;
 
-    public ArrayExpectedSchemaNode(AbstractComplexExpectedSchemaNode parent, SourceLocation sourceLocation,
-            String functionName) {
-        super(parent, sourceLocation, functionName);
+    public ArrayExpectedSchemaNode(AbstractComplexExpectedSchemaNode parent,
+            AbstractFunctionCallExpression parentExpression, ILogicalExpression expression) {
+        super(parent, parentExpression, expression);
     }
 
     @Override
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ExpectedSchemaBuilder.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ExpectedSchemaBuilder.java
index d42ffed..e442d64 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ExpectedSchemaBuilder.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/ExpectedSchemaBuilder.java
@@ -59,8 +59,7 @@
         //Parent always nested
         AbstractComplexExpectedSchemaNode parent = (AbstractComplexExpectedSchemaNode) buildNestedNode(expr, typeEnv);
         if (parent != null) {
-            IExpectedSchemaNode leaf =
-                    new AnyExpectedSchemaNode(parent, expr.getSourceLocation(), expr.getFunctionIdentifier().getName());
+            IExpectedSchemaNode leaf = new AnyExpectedSchemaNode(parent, expr);
             IExpectedSchemaNode actualNode = addOrReplaceChild(expr, typeEnv, parent, leaf);
             if (producedVar != null) {
                 //Register the node if a variable is produced
@@ -84,8 +83,9 @@
             varToNode.put(variable, RootExpectedSchemaNode.ALL_FIELDS_ROOT_IRREPLACEABLE_NODE);
         } else {
             // If it is a nested node, replace it to a LEAF node
-            AnyExpectedSchemaNode leafNode = (AnyExpectedSchemaNode) node.replaceIfNeeded(ExpectedSchemaNodeType.ANY,
-                    parent.getSourceLocation(), parent.getFunctionName());
+            // Both expressions are null as they're the node isn't used anymore and the node ANY is not replaceable
+            AnyExpectedSchemaNode leafNode =
+                    (AnyExpectedSchemaNode) node.replaceIfNeeded(ExpectedSchemaNodeType.ANY, null, null);
             // make the leaf node irreplaceable
             leafNode.preventReplacing();
             varToNode.put(variable, leafNode);
@@ -118,7 +118,7 @@
         if (isVariable(parentExpr)) {
             //A variable could be the record's originated from data-scan or an expression from assign
             LogicalVariable sourceVar = VariableUtilities.getVariable(parentExpr);
-            return changeNodeForVariable(sourceVar, myExpr);
+            return changeNodeForVariable(sourceVar, myExpr, myExpr);
         }
 
         //Recursively create the parent nodes. Parent is always a nested node
@@ -134,8 +134,8 @@
              * Create 'myNode'. It is a nested node because the function is either getField() or a supported array
              * function
              */
-            AbstractComplexExpectedSchemaNode myNode = AbstractComplexExpectedSchemaNode.createNestedNode(myType,
-                    newParent, myExpr.getSourceLocation(), myExpr.getFunctionIdentifier().getName());
+            AbstractComplexExpectedSchemaNode myNode =
+                    AbstractComplexExpectedSchemaNode.createNestedNode(myType, newParent, parentFuncExpr, myExpr);
             // Add (or replace old child with) myNode to the parent
             return addOrReplaceChild(parentFuncExpr, typeEnv, newParent, myNode);
         }
@@ -148,7 +148,7 @@
     }
 
     private IExpectedSchemaNode changeNodeForVariable(LogicalVariable sourceVar,
-            AbstractFunctionCallExpression myExpr) {
+            AbstractFunctionCallExpression parentExpression, ILogicalExpression expression) {
         //Get the associated node with the sourceVar (if any)
         IExpectedSchemaNode oldNode = varToNode.get(sourceVar);
         if (oldNode == null || !oldNode.allowsReplacing()) {
@@ -157,10 +157,9 @@
             return null;
         }
         //What is the expected type of the variable
-        ExpectedSchemaNodeType varExpectedType = getExpectedNestedNodeType(myExpr);
+        ExpectedSchemaNodeType varExpectedType = getExpectedNestedNodeType(parentExpression);
         // Get the node associated with the variable (or change its type if needed).
-        IExpectedSchemaNode newNode = oldNode.replaceIfNeeded(varExpectedType, myExpr.getSourceLocation(),
-                myExpr.getFunctionIdentifier().getName());
+        IExpectedSchemaNode newNode = oldNode.replaceIfNeeded(varExpectedType, parentExpression, expression);
         //Map the sourceVar to the node
         varToNode.put(sourceVar, newNode);
         return newNode;
@@ -195,11 +194,12 @@
     private static IExpectedSchemaNode handleObject(AbstractFunctionCallExpression parentExpr,
             IVariableTypeEnvironment typeEnv, AbstractComplexExpectedSchemaNode parent, IExpectedSchemaNode child)
             throws AlgebricksException {
+        int fieldNameId = PushdownUtil.getFieldNameId(parentExpr);
         String fieldName = PushdownUtil.getFieldName(parentExpr, typeEnv);
         ObjectExpectedSchemaNode objectNode = (ObjectExpectedSchemaNode) parent;
         IExpectedSchemaNode actualChild = objectNode.getChildren().get(fieldName);
         if (actualChild == null) {
-            objectNode.addChild(fieldName, child);
+            objectNode.addChild(fieldName, fieldNameId, child);
             actualChild = child;
         } else {
             actualChild = objectNode.replaceChild(actualChild, child);
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/IExpectedSchemaNode.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/IExpectedSchemaNode.java
index 345cb84..decafec 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/IExpectedSchemaNode.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/IExpectedSchemaNode.java
@@ -18,7 +18,8 @@
  */
 package org.apache.asterix.optimizer.rules.pushdown.schema;
 
-import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
 import org.apache.hyracks.api.exceptions.SourceLocation;
 
 /**
@@ -42,11 +43,21 @@
     String getFunctionName();
 
     /**
+     * @return function expression of which determined the type of the parent node
+     */
+    AbstractFunctionCallExpression getParentExpression();
+
+    /**
      * @return the parent of a node
      */
     AbstractComplexExpectedSchemaNode getParent();
 
     /**
+     * @return this node's expression
+     */
+    ILogicalExpression getExpression();
+
+    /**
      * Set parent of a node
      *
      * @param parent new parent
@@ -75,11 +86,11 @@
      * - {@link ExpectedSchemaNodeType#OBJECT} to {@link ExpectedSchemaNodeType#UNION}
      *
      * @param expectedNodeType what is the other expected type
-     * @param sourceLocation   source location of the value access
-     * @param functionName     function name as in {@link FunctionIdentifier#getName()}
-     * @see AbstractComplexExpectedSchemaNode#replaceIfNeeded(ExpectedSchemaNodeType, SourceLocation, String)
-     * @see UnionExpectedSchemaNode#replaceIfNeeded(ExpectedSchemaNodeType, SourceLocation, String)
+     * @param parentExpression parent expression
+     * @param expression       this node's expression
+     * @see IExpectedSchemaNode#replaceIfNeeded(ExpectedSchemaNodeType, AbstractFunctionCallExpression, ILogicalExpression)
+     * @see IExpectedSchemaNode#replaceIfNeeded(ExpectedSchemaNodeType, AbstractFunctionCallExpression, ILogicalExpression)
      */
-    IExpectedSchemaNode replaceIfNeeded(ExpectedSchemaNodeType expectedNodeType, SourceLocation sourceLocation,
-            String functionName);
+    IExpectedSchemaNode replaceIfNeeded(ExpectedSchemaNodeType expectedNodeType,
+            AbstractFunctionCallExpression parentExpression, ILogicalExpression expression);
 }
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 a6d4399..e220437 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
@@ -21,27 +21,43 @@
 import java.util.HashMap;
 import java.util.Map;
 
-import org.apache.hyracks.api.exceptions.SourceLocation;
+import org.apache.asterix.metadata.utils.PushdownUtil;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
 
 public class ObjectExpectedSchemaNode extends AbstractComplexExpectedSchemaNode {
     private final Map<String, IExpectedSchemaNode> children;
+    private final Int2ObjectMap<String> fieldIdToFieldName;
 
-    public ObjectExpectedSchemaNode(AbstractComplexExpectedSchemaNode parent, SourceLocation sourceLocation,
-            String functionName) {
-        super(parent, sourceLocation, functionName);
+    public ObjectExpectedSchemaNode(AbstractComplexExpectedSchemaNode parent,
+            AbstractFunctionCallExpression parentExpression, ILogicalExpression expression) {
+        super(parent, parentExpression, expression);
         children = new HashMap<>();
+        fieldIdToFieldName = new Int2ObjectOpenHashMap<>();
     }
 
     public boolean isRoot() {
         return false;
     }
 
-    public Map<String, IExpectedSchemaNode> getChildren() {
-        return children;
+    public void addChild(String fieldName, int fieldId, IExpectedSchemaNode child) {
+        FunctionIdentifier fid = child.getParentExpression().getFunctionIdentifier();
+        children.put(fieldName, child);
+        if (fieldId > -1) {
+            fieldIdToFieldName.put(fieldId, fieldName);
+        }
     }
 
-    public void addChild(String fieldName, IExpectedSchemaNode child) {
-        children.put(fieldName, child);
+    public void addAllFieldNameIds(ObjectExpectedSchemaNode node) {
+        fieldIdToFieldName.putAll(node.fieldIdToFieldName);
+    }
+
+    public Map<String, IExpectedSchemaNode> getChildren() {
+        return children;
     }
 
     @Override
@@ -71,18 +87,13 @@
     }
 
     public String getChildFieldName(IExpectedSchemaNode requestedChild) {
-        String key = null;
-        for (Map.Entry<String, IExpectedSchemaNode> child : children.entrySet()) {
-            if (child.getValue() == requestedChild) {
-                key = child.getKey();
-                break;
-            }
+        AbstractFunctionCallExpression expr = requestedChild.getParentExpression();
+        int fieldNameId = PushdownUtil.getFieldNameId(requestedChild.getParentExpression());
+
+        if (fieldNameId > -1) {
+            return fieldIdToFieldName.get(fieldNameId);
         }
 
-        if (key == null) {
-            //this should not happen
-            throw new IllegalStateException("Node " + requestedChild.getType() + " is not a child");
-        }
-        return key;
+        return PushdownUtil.getFieldName(expr);
     }
 }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/RootExpectedSchemaNode.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/RootExpectedSchemaNode.java
index 72c5c52..e72e997 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/RootExpectedSchemaNode.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/RootExpectedSchemaNode.java
@@ -18,7 +18,8 @@
  */
 package org.apache.asterix.optimizer.rules.pushdown.schema;
 
-import org.apache.hyracks.api.exceptions.SourceLocation;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
 
 public class RootExpectedSchemaNode extends ObjectExpectedSchemaNode {
     //Root with zero fields
@@ -56,7 +57,7 @@
 
     @Override
     public AbstractComplexExpectedSchemaNode replaceIfNeeded(ExpectedSchemaNodeType expectedNodeType,
-            SourceLocation sourceLocation, String functionName) {
+            AbstractFunctionCallExpression parentExpression, ILogicalExpression expression) {
         if (rootType == ALL_FIELDS_ROOT) {
             //ALL_FIELDS_ROOT. Return a new CLIPPED_ROOT root
             return new RootExpectedSchemaNode();
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/UnionExpectedSchemaNode.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/UnionExpectedSchemaNode.java
index af6d2be..a15e359 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/UnionExpectedSchemaNode.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/schema/UnionExpectedSchemaNode.java
@@ -22,14 +22,15 @@
 import java.util.Map;
 import java.util.Set;
 
-import org.apache.hyracks.api.exceptions.SourceLocation;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
 
 public class UnionExpectedSchemaNode extends AbstractComplexExpectedSchemaNode {
     private final Map<ExpectedSchemaNodeType, AbstractComplexExpectedSchemaNode> children;
 
-    public UnionExpectedSchemaNode(AbstractComplexExpectedSchemaNode parent, SourceLocation sourceLocation,
-            String functionName) {
-        super(parent, sourceLocation, functionName);
+    public UnionExpectedSchemaNode(AbstractComplexExpectedSchemaNode parent,
+            AbstractFunctionCallExpression expression) {
+        super(parent, expression, expression);
         children = new EnumMap<>(ExpectedSchemaNodeType.class);
     }
 
@@ -46,8 +47,13 @@
         children.put(node.getType(), node);
     }
 
-    public void createChild(ExpectedSchemaNodeType nodeType, SourceLocation sourceLocation, String functionName) {
-        children.computeIfAbsent(nodeType, k -> createNestedNode(k, this, sourceLocation, functionName));
+    public void createChild(ExpectedSchemaNodeType nodeType, AbstractFunctionCallExpression parentExpression,
+            ILogicalExpression expression) {
+        if (parentExpression == null) {
+            // Should never happen
+            throw new NullPointerException("expression is null");
+        }
+        children.computeIfAbsent(nodeType, k -> createNestedNode(k, this, parentExpression, expression));
     }
 
     public AbstractComplexExpectedSchemaNode getChild(ExpectedSchemaNodeType type) {
@@ -73,15 +79,15 @@
      * UNION type - we simply return this. In case we want to fallback to ANY node, we call the super method.
      *
      * @param expectedNodeType the expected type
-     * @param sourceLocation   source location of the value access
-     * @param functionName     function name of the expression
+     * @param parentExpression
+     * @param expression
      * @return ANY or this
      */
     @Override
-    public IExpectedSchemaNode replaceIfNeeded(ExpectedSchemaNodeType expectedNodeType, SourceLocation sourceLocation,
-            String functionName) {
+    public IExpectedSchemaNode replaceIfNeeded(ExpectedSchemaNodeType expectedNodeType,
+            AbstractFunctionCallExpression parentExpression, ILogicalExpression expression) {
         if (expectedNodeType == ExpectedSchemaNodeType.ANY) {
-            return super.replaceIfNeeded(expectedNodeType, sourceLocation, functionName);
+            return super.replaceIfNeeded(expectedNodeType, parentExpression, expression);
         }
         return this;
     }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/visitor/ExpectedSchemaMergerVisitor.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/visitor/ExpectedSchemaMergerVisitor.java
index d9ab052..09046b2 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/visitor/ExpectedSchemaMergerVisitor.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/pushdown/visitor/ExpectedSchemaMergerVisitor.java
@@ -70,19 +70,23 @@
 
         // combine
         RootExpectedSchemaNode mergedRoot = (RootExpectedSchemaNode) RootExpectedSchemaNode.ALL_FIELDS_ROOT_NODE
-                .replaceIfNeeded(ExpectedSchemaNodeType.OBJECT, node.getSourceLocation(), node.getFunctionName());
+                .replaceIfNeeded(ExpectedSchemaNodeType.OBJECT, null, null);
         mergeObjectFields(mergedRoot, node.getChildren(), argRoot.getChildren());
+        mergedRoot.addAllFieldNameIds(node);
+        mergedRoot.addAllFieldNameIds(argRoot);
         return mergedRoot;
     }
 
     @Override
     public IExpectedSchemaNode visit(ObjectExpectedSchemaNode node, IExpectedSchemaNode arg) {
         ObjectExpectedSchemaNode mergedObject =
-                new ObjectExpectedSchemaNode(currentParent, node.getSourceLocation(), node.getFunctionName());
+                new ObjectExpectedSchemaNode(currentParent, node.getParentExpression(), node.getExpression());
         Map<String, IExpectedSchemaNode> argChildren = Collections.emptyMap();
+        mergedObject.addAllFieldNameIds(node);
         if (arg != null) {
             ObjectExpectedSchemaNode argObject = (ObjectExpectedSchemaNode) arg;
             argChildren = argObject.getChildren();
+            mergedObject.addAllFieldNameIds(argObject);
         }
 
         mergeObjectFields(mergedObject, node.getChildren(), argChildren);
@@ -99,7 +103,7 @@
             argItem = arrayArg.getChild();
         }
         ArrayExpectedSchemaNode mergedArray =
-                new ArrayExpectedSchemaNode(currentParent, node.getSourceLocation(), node.getFunctionName());
+                new ArrayExpectedSchemaNode(currentParent, node.getParentExpression(), node.getExpression());
         AbstractComplexExpectedSchemaNode previousParent = currentParent;
         currentParent = mergedArray;
         IExpectedSchemaNode mergedItem = merge(nodeItem, argItem);
@@ -111,8 +115,7 @@
 
     @Override
     public IExpectedSchemaNode visit(UnionExpectedSchemaNode node, IExpectedSchemaNode arg) {
-        UnionExpectedSchemaNode union =
-                new UnionExpectedSchemaNode(currentParent, node.getSourceLocation(), node.getFunctionName());
+        UnionExpectedSchemaNode union = new UnionExpectedSchemaNode(currentParent, node.getParentExpression());
         AbstractComplexExpectedSchemaNode previousParent = currentParent;
         currentParent = union;
 
@@ -148,7 +151,7 @@
 
     @Override
     public IExpectedSchemaNode visit(AnyExpectedSchemaNode node, IExpectedSchemaNode arg) {
-        return new AnyExpectedSchemaNode(currentParent, node.getSourceLocation(), node.getFunctionName());
+        return new AnyExpectedSchemaNode(currentParent, node.getParentExpression());
     }
 
     private void mergeObjectFields(ObjectExpectedSchemaNode objectNode, Map<String, IExpectedSchemaNode> left,
@@ -170,7 +173,7 @@
             }
             IExpectedSchemaNode rightChild = right.get(fieldName);
             IExpectedSchemaNode mergedChild = merge(leftChild.getValue(), rightChild);
-            objectNode.addChild(fieldName, mergedChild);
+            objectNode.addChild(fieldName, -1, mergedChild);
             mergedFields.add(fieldName);
         }
     }
@@ -196,8 +199,7 @@
     }
 
     private IExpectedSchemaNode createUnionNode(IExpectedSchemaNode leftChild, IExpectedSchemaNode rightChild) {
-        UnionExpectedSchemaNode union =
-                new UnionExpectedSchemaNode(currentParent, leftChild.getSourceLocation(), leftChild.getFunctionName());
+        UnionExpectedSchemaNode union = new UnionExpectedSchemaNode(currentParent, leftChild.getParentExpression());
         AbstractComplexExpectedSchemaNode previousParent = currentParent;
         currentParent = union;
         // Create a copy of leftChild
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
index cccc936..0ef81aa 100644
--- 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
@@ -111,8 +111,7 @@
         }
 
         AbstractComplexExpectedSchemaNode newParent = replaceIfNeeded(parent, expr);
-        IExpectedSchemaNode myNode =
-                new AnyExpectedSchemaNode(newParent, expr.getSourceLocation(), expr.getFunctionIdentifier().getName());
+        IExpectedSchemaNode myNode = new AnyExpectedSchemaNode(newParent, expr);
         ExpectedSchemaBuilder.addOrReplaceChild(expr, typeEnv, newParent, myNode);
         return myNode;
     }
@@ -120,7 +119,6 @@
     private AbstractComplexExpectedSchemaNode replaceIfNeeded(IExpectedSchemaNode parent,
             AbstractFunctionCallExpression funcExpr) {
         ExpectedSchemaNodeType expectedType = ExpectedSchemaBuilder.getExpectedNestedNodeType(funcExpr);
-        return (AbstractComplexExpectedSchemaNode) parent.replaceIfNeeded(expectedType, parent.getSourceLocation(),
-                parent.getFunctionName());
+        return (AbstractComplexExpectedSchemaNode) parent.replaceIfNeeded(expectedType, funcExpr, funcExpr);
     }
 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/pushdown/other-pushdowns/other-pushdowns.024.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/pushdown/other-pushdowns/other-pushdowns.024.query.sqlpp
new file mode 100644
index 0000000..52075b9
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/pushdown/other-pushdowns/other-pushdowns.024.query.sqlpp
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+SET `compiler.parallelism` "0";
+SET `compiler.sort.parallel` "false";
+EXPLAIN
+SELECT c.f1[0].f2[0][0],
+       c.f1[1].f3[1],
+       c.f1[1].f2[1][1]
+FROM ColumnDataset c
+ORDER BY c.x
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/pushdown/other-pushdowns/other-pushdowns.025.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/pushdown/other-pushdowns/other-pushdowns.025.query.sqlpp
new file mode 100644
index 0000000..4ee3627
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/pushdown/other-pushdowns/other-pushdowns.025.query.sqlpp
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+SET `compiler.parallelism` "0";
+SET `compiler.sort.parallel` "false";
+EXPLAIN
+SELECT c.f1[0].f2[0][0],
+       c.f1[1].f3[1],
+       c.f1[1].f2[1][1], c.x
+FROM ColumnDataset c
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/pushdown/other-pushdowns/other-pushdowns.026.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/pushdown/other-pushdowns/other-pushdowns.026.query.sqlpp
new file mode 100644
index 0000000..c2acf29
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/pushdown/other-pushdowns/other-pushdowns.026.query.sqlpp
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+SET `compiler.parallelism` "0";
+SET `compiler.sort.parallel` "false";
+EXPLAIN
+SELECT c.f1[0].f2[0][0],
+       c.f1[1].f3[1],
+       c.f1[1].f2[1][1], c.x
+FROM ColumnDataset c
+ORDER BY c.y
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/pushdown/other-pushdowns/other-pushdowns.027.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/pushdown/other-pushdowns/other-pushdowns.027.query.sqlpp
new file mode 100644
index 0000000..03ae71d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/column/pushdown/other-pushdowns/other-pushdowns.027.query.sqlpp
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+SET `compiler.parallelism` "0";
+SET `compiler.sort.parallel` "false";
+EXPLAIN
+SELECT c.f1[0].f2[0][0],
+       -- Access f3 as an array
+       c.f1[1].f3[1],
+       c.f1[1].f2[1][1],
+       -- Access f3 as an object
+       c.f1[1].f3.f4
+FROM ColumnDataset c
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/external-dataset/common/parquet/pushdown-plans/pushdown-plans.12.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/external-dataset/common/parquet/pushdown-plans/pushdown-plans.12.query.sqlpp
new file mode 100644
index 0000000..d2802d0
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/external-dataset/common/parquet/pushdown-plans/pushdown-plans.12.query.sqlpp
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+SET `compiler.parallelism` "0";
+SET `compiler.sort.parallel` "false";
+EXPLAIN
+SELECT p.f1[0].f2[0][0],
+       p.f1[1].f3[1],
+       p.f1[1].f2[1][1]
+FROM ParquetDataset1 p
+ORDER BY p.x
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/external-dataset/common/parquet/pushdown-plans/pushdown-plans.13.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/external-dataset/common/parquet/pushdown-plans/pushdown-plans.13.query.sqlpp
new file mode 100644
index 0000000..49d16af
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/external-dataset/common/parquet/pushdown-plans/pushdown-plans.13.query.sqlpp
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+SET `compiler.parallelism` "0";
+SET `compiler.sort.parallel` "false";
+EXPLAIN
+SELECT p.f1[0].f2[0][0],
+       p.f1[1].f3[1],
+       p.f1[1].f2[1][1], p.x
+FROM ParquetDataset1 p
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/external-dataset/common/parquet/pushdown-plans/pushdown-plans.14.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/external-dataset/common/parquet/pushdown-plans/pushdown-plans.14.query.sqlpp
new file mode 100644
index 0000000..1aa9d9c
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/external-dataset/common/parquet/pushdown-plans/pushdown-plans.14.query.sqlpp
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+SET `compiler.parallelism` "0";
+SET `compiler.sort.parallel` "false";
+EXPLAIN
+SELECT p.f1[0].f2[0][0],
+       p.f1[1].f3[1],
+       p.f1[1].f2[1][1], p.x
+FROM ParquetDataset1 p
+ORDER BY p.y
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/external-dataset/common/parquet/pushdown-plans/pushdown-plans.15.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/external-dataset/common/parquet/pushdown-plans/pushdown-plans.15.query.sqlpp
new file mode 100644
index 0000000..e8efb25
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/external-dataset/common/parquet/pushdown-plans/pushdown-plans.15.query.sqlpp
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+SET `compiler.parallelism` "0";
+SET `compiler.sort.parallel` "false";
+EXPLAIN
+SELECT p.f1[0].f2[0][0],
+       -- Access f3 as an array
+       p.f1[1].f3[1],
+       p.f1[1].f2[1][1],
+       -- Access f3 as an object
+       p.f1[1].f3.f4
+FROM ParquetDataset1 p
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/column/pushdown/other-pushdowns/other-pushdowns.024.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/pushdown/other-pushdowns/other-pushdowns.024.plan
new file mode 100644
index 0000000..cc4aea0
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/pushdown/other-pushdowns/other-pushdowns.024.plan
@@ -0,0 +1,38 @@
+distribute result [$$31] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$31]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$31] <- [{"$1": get-item(get-item($$36, 0), 0), "$2": get-item($$37, 1), "$3": get-item(get-item($$38, 1), 1)}] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      -- ASSIGN  |PARTITIONED|
+        project ([$$36, $$37, $$38]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+        -- STREAM_PROJECT  |PARTITIONED|
+          exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- SORT_MERGE_EXCHANGE [$$35(ASC) ]  |PARTITIONED|
+            order (ASC, $$35) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- STABLE_SORT [$$35(ASC)]  |PARTITIONED|
+              exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                project ([$$36, $$37, $$38, $$35]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- STREAM_PROJECT  |PARTITIONED|
+                  assign [$$38, $$37] <- [$$49.getField("f2"), $$49.getField("f3")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- ASSIGN  |PARTITIONED|
+                    project ([$$35, $$49, $$36]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      assign [$$49, $$36] <- [get-item($$33, 1), get-item($$33, 0).getField("f2")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- ASSIGN  |PARTITIONED|
+                        project ([$$33, $$35]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          assign [$$33, $$35] <- [$$c.getField("f1"), $$c.getField("x")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- ASSIGN  |PARTITIONED|
+                            project ([$$c]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                data-scan []<-[$$34, $$c] <- test.ColumnDataset project ({x:any,f1:[{f2:[[any]],f3:[any]}]}) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- DATASOURCE_SCAN  |PARTITIONED|
+                                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/column/pushdown/other-pushdowns/other-pushdowns.025.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/pushdown/other-pushdowns/other-pushdowns.025.plan
new file mode 100644
index 0000000..830e096
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/pushdown/other-pushdowns/other-pushdowns.025.plan
@@ -0,0 +1,22 @@
+distribute result [$$32] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$32]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$32] <- [{"$1": get-item(get-item(get-item($$33, 0).getField("f2"), 0), 0), "$2": get-item($$49.getField("f3"), 1), "$3": get-item(get-item($$49.getField("f2"), 1), 1), "x": $$c.getField("x")}] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      -- ASSIGN  |PARTITIONED|
+        assign [$$49] <- [get-item($$33, 1)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+        -- ASSIGN  |PARTITIONED|
+          assign [$$33] <- [$$c.getField("f1")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- ASSIGN  |PARTITIONED|
+            project ([$$c]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- STREAM_PROJECT  |PARTITIONED|
+              exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                data-scan []<-[$$34, $$c] <- test.ColumnDataset project ({x:any,f1:[{f2:[[any]],f3:[any]}]}) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- DATASOURCE_SCAN  |PARTITIONED|
+                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/column/pushdown/other-pushdowns/other-pushdowns.026.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/pushdown/other-pushdowns/other-pushdowns.026.plan
new file mode 100644
index 0000000..b65bd37
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/pushdown/other-pushdowns/other-pushdowns.026.plan
@@ -0,0 +1,38 @@
+distribute result [$$32] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$32]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$32] <- [{"$1": get-item(get-item($$37, 0), 0), "$2": get-item($$38, 1), "$3": get-item(get-item($$39, 1), 1), "x": $$40}] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      -- ASSIGN  |PARTITIONED|
+        project ([$$37, $$38, $$39, $$40]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+        -- STREAM_PROJECT  |PARTITIONED|
+          exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- SORT_MERGE_EXCHANGE [$$36(ASC) ]  |PARTITIONED|
+            order (ASC, $$36) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- STABLE_SORT [$$36(ASC)]  |PARTITIONED|
+              exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                project ([$$37, $$38, $$39, $$40, $$36]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- STREAM_PROJECT  |PARTITIONED|
+                  assign [$$39, $$38] <- [$$51.getField("f2"), $$51.getField("f3")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- ASSIGN  |PARTITIONED|
+                    project ([$$36, $$40, $$51, $$37]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      assign [$$51, $$37] <- [get-item($$34, 1), get-item($$34, 0).getField("f2")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- ASSIGN  |PARTITIONED|
+                        project ([$$34, $$36, $$40]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          assign [$$34, $$36, $$40] <- [$$c.getField("f1"), $$c.getField("y"), $$c.getField("x")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- ASSIGN  |PARTITIONED|
+                            project ([$$c]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                data-scan []<-[$$35, $$c] <- test.ColumnDataset project ({x:any,y:any,f1:[{f2:[[any]],f3:[any]}]}) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- DATASOURCE_SCAN  |PARTITIONED|
+                                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/column/pushdown/other-pushdowns/other-pushdowns.027.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/pushdown/other-pushdowns/other-pushdowns.027.plan
new file mode 100644
index 0000000..64d7359
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/column/pushdown/other-pushdowns/other-pushdowns.027.plan
@@ -0,0 +1,26 @@
+distribute result [$$35] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$35]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$35] <- [{"$1": get-item(get-item(get-item($$36, 0).getField("f2"), 0), 0), "$2": get-item($$43, 1), "$3": get-item(get-item($$55.getField("f2"), 1), 1), "f4": $$43.getField("f4")}] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      -- ASSIGN  |PARTITIONED|
+        assign [$$43] <- [$$55.getField("f3")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+        -- ASSIGN  |PARTITIONED|
+          assign [$$55] <- [get-item($$36, 1)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- ASSIGN  |PARTITIONED|
+            project ([$$36]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- STREAM_PROJECT  |PARTITIONED|
+              assign [$$36] <- [$$c.getField("f1")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- ASSIGN  |PARTITIONED|
+                project ([$$c]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- STREAM_PROJECT  |PARTITIONED|
+                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    data-scan []<-[$$37, $$c] <- test.ColumnDataset project ({f1:[{f2:[[any]],f3:<[any],{f4:any}>}]}) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- DATASOURCE_SCAN  |PARTITIONED|
+                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/external-dataset/common/parquet/pushdown-plans/pushdown-plans.12.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/external-dataset/common/parquet/pushdown-plans/pushdown-plans.12.plan
new file mode 100644
index 0000000..8b117a9
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/external-dataset/common/parquet/pushdown-plans/pushdown-plans.12.plan
@@ -0,0 +1,36 @@
+distribute result [$$31] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$31]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$31] <- [{"$1": get-item(get-item($$35, 0), 0), "$2": get-item($$36, 1), "$3": get-item(get-item($$37, 1), 1)}] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      -- ASSIGN  |PARTITIONED|
+        project ([$$35, $$36, $$37]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+        -- STREAM_PROJECT  |PARTITIONED|
+          exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- SORT_MERGE_EXCHANGE [$$34(ASC) ]  |PARTITIONED|
+            order (ASC, $$34) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- STABLE_SORT [$$34(ASC)]  |PARTITIONED|
+              exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                project ([$$35, $$36, $$37, $$34]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- STREAM_PROJECT  |PARTITIONED|
+                  assign [$$37, $$36] <- [$$48.getField("f2"), $$48.getField("f3")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- ASSIGN  |PARTITIONED|
+                    project ([$$34, $$48, $$35]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      assign [$$48, $$35] <- [get-item($$33, 1), get-item($$33, 0).getField("f2")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- ASSIGN  |PARTITIONED|
+                        project ([$$33, $$34]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          assign [$$33, $$34] <- [$$p.getField("f1"), $$p.getField("x")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- ASSIGN  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              data-scan []<-[$$p] <- test.ParquetDataset1 project ({x:any,f1:[{f2:[[any]],f3:[any]}]}) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- DATASOURCE_SCAN  |PARTITIONED|
+                                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/external-dataset/common/parquet/pushdown-plans/pushdown-plans.13.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/external-dataset/common/parquet/pushdown-plans/pushdown-plans.13.plan
new file mode 100644
index 0000000..3266da3
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/external-dataset/common/parquet/pushdown-plans/pushdown-plans.13.plan
@@ -0,0 +1,20 @@
+distribute result [$$32] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$32]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$32] <- [{"$1": get-item(get-item(get-item($$33, 0).getField("f2"), 0), 0), "$2": get-item($$48.getField("f3"), 1), "$3": get-item(get-item($$48.getField("f2"), 1), 1), "x": $$p.getField("x")}] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      -- ASSIGN  |PARTITIONED|
+        assign [$$48] <- [get-item($$33, 1)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+        -- ASSIGN  |PARTITIONED|
+          assign [$$33] <- [$$p.getField("f1")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- ASSIGN  |PARTITIONED|
+            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              data-scan []<-[$$p] <- test.ParquetDataset1 project ({x:any,f1:[{f2:[[any]],f3:[any]}]}) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- DATASOURCE_SCAN  |PARTITIONED|
+                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/external-dataset/common/parquet/pushdown-plans/pushdown-plans.14.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/external-dataset/common/parquet/pushdown-plans/pushdown-plans.14.plan
new file mode 100644
index 0000000..2b10297
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/external-dataset/common/parquet/pushdown-plans/pushdown-plans.14.plan
@@ -0,0 +1,36 @@
+distribute result [$$32] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$32]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$32] <- [{"$1": get-item(get-item($$36, 0), 0), "$2": get-item($$37, 1), "$3": get-item(get-item($$38, 1), 1), "x": $$39}] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      -- ASSIGN  |PARTITIONED|
+        project ([$$36, $$37, $$38, $$39]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+        -- STREAM_PROJECT  |PARTITIONED|
+          exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- SORT_MERGE_EXCHANGE [$$35(ASC) ]  |PARTITIONED|
+            order (ASC, $$35) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- STABLE_SORT [$$35(ASC)]  |PARTITIONED|
+              exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                project ([$$36, $$37, $$38, $$39, $$35]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- STREAM_PROJECT  |PARTITIONED|
+                  assign [$$38, $$37] <- [$$50.getField("f2"), $$50.getField("f3")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- ASSIGN  |PARTITIONED|
+                    project ([$$35, $$39, $$50, $$36]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      assign [$$50, $$36] <- [get-item($$34, 1), get-item($$34, 0).getField("f2")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- ASSIGN  |PARTITIONED|
+                        project ([$$34, $$35, $$39]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          assign [$$34, $$35, $$39] <- [$$p.getField("f1"), $$p.getField("y"), $$p.getField("x")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          -- ASSIGN  |PARTITIONED|
+                            exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              data-scan []<-[$$p] <- test.ParquetDataset1 project ({x:any,y:any,f1:[{f2:[[any]],f3:[any]}]}) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- DATASOURCE_SCAN  |PARTITIONED|
+                                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/external-dataset/common/parquet/pushdown-plans/pushdown-plans.15.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/external-dataset/common/parquet/pushdown-plans/pushdown-plans.15.plan
new file mode 100644
index 0000000..c575b1b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/external-dataset/common/parquet/pushdown-plans/pushdown-plans.15.plan
@@ -0,0 +1,24 @@
+distribute result [$$35] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$35]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$35] <- [{"$1": get-item(get-item(get-item($$36, 0).getField("f2"), 0), 0), "$2": get-item($$42, 1), "$3": get-item(get-item($$54.getField("f2"), 1), 1), "f4": $$42.getField("f4")}] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      -- ASSIGN  |PARTITIONED|
+        assign [$$42] <- [$$54.getField("f3")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+        -- ASSIGN  |PARTITIONED|
+          assign [$$54] <- [get-item($$36, 1)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- ASSIGN  |PARTITIONED|
+            project ([$$36]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+            -- STREAM_PROJECT  |PARTITIONED|
+              assign [$$36] <- [$$p.getField("f1")] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- ASSIGN  |PARTITIONED|
+                exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  data-scan []<-[$$p] <- test.ParquetDataset1 project ({f1:[{f2:[[any]],f3:<[any],{f4:any}>}]}) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- DATASOURCE_SCAN  |PARTITIONED|
+                    exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/column/pushdown/other-pushdowns/other-pushdowns.024.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/column/pushdown/other-pushdowns/other-pushdowns.024.plan
new file mode 100644
index 0000000..fde7f81
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/column/pushdown/other-pushdowns/other-pushdowns.024.plan
@@ -0,0 +1,38 @@
+distribute result [$$31] [cardinality: 2.0, op-cost: 0.0, total-cost: 4.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 4.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$31]) [cardinality: 2.0, op-cost: 0.0, total-cost: 4.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$31] <- [{"$1": get-item(get-item($$36, 0), 0), "$2": get-item($$37, 1), "$3": get-item(get-item($$38, 1), 1)}] [cardinality: 2.0, op-cost: 0.0, total-cost: 4.0]
+      -- ASSIGN  |PARTITIONED|
+        project ([$$36, $$37, $$38]) [cardinality: 2.0, op-cost: 0.0, total-cost: 4.0]
+        -- STREAM_PROJECT  |PARTITIONED|
+          exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 4.0]
+          -- SORT_MERGE_EXCHANGE [$$35(ASC) ]  |PARTITIONED|
+            order (ASC, $$35) [cardinality: 2.0, op-cost: 2.0, total-cost: 4.0]
+            -- STABLE_SORT [$$35(ASC)]  |PARTITIONED|
+              exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                project ([$$36, $$37, $$38, $$35]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                -- STREAM_PROJECT  |PARTITIONED|
+                  assign [$$38, $$37] <- [$$49.getField("f2"), $$49.getField("f3")] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                  -- ASSIGN  |PARTITIONED|
+                    project ([$$35, $$49, $$36]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      assign [$$49, $$36] <- [get-item($$33, 1), get-item($$33, 0).getField("f2")] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                      -- ASSIGN  |PARTITIONED|
+                        project ([$$33, $$35]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          assign [$$33, $$35] <- [$$c.getField("f1"), $$c.getField("x")] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                          -- ASSIGN  |PARTITIONED|
+                            project ([$$c]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                data-scan []<-[$$34, $$c] <- test.ColumnDataset project ({x:any,f1:[{f2:[[any]],f3:[any]}]}) [cardinality: 2.0, op-cost: 2.0, total-cost: 2.0]
+                                -- DATASOURCE_SCAN  |PARTITIONED|
+                                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/column/pushdown/other-pushdowns/other-pushdowns.025.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/column/pushdown/other-pushdowns/other-pushdowns.025.plan
new file mode 100644
index 0000000..deeb1eb
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/column/pushdown/other-pushdowns/other-pushdowns.025.plan
@@ -0,0 +1,22 @@
+distribute result [$$32] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$32]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$32] <- [{"$1": get-item(get-item(get-item($$33, 0).getField("f2"), 0), 0), "$2": get-item($$49.getField("f3"), 1), "$3": get-item(get-item($$49.getField("f2"), 1), 1), "x": $$c.getField("x")}] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+      -- ASSIGN  |PARTITIONED|
+        assign [$$49] <- [get-item($$33, 1)] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+        -- ASSIGN  |PARTITIONED|
+          assign [$$33] <- [$$c.getField("f1")] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+          -- ASSIGN  |PARTITIONED|
+            project ([$$c]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+            -- STREAM_PROJECT  |PARTITIONED|
+              exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                data-scan []<-[$$34, $$c] <- test.ColumnDataset project ({x:any,f1:[{f2:[[any]],f3:[any]}]}) [cardinality: 2.0, op-cost: 2.0, total-cost: 2.0]
+                -- DATASOURCE_SCAN  |PARTITIONED|
+                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/column/pushdown/other-pushdowns/other-pushdowns.026.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/column/pushdown/other-pushdowns/other-pushdowns.026.plan
new file mode 100644
index 0000000..2379a5a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/column/pushdown/other-pushdowns/other-pushdowns.026.plan
@@ -0,0 +1,38 @@
+distribute result [$$32] [cardinality: 2.0, op-cost: 0.0, total-cost: 4.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 4.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$32]) [cardinality: 2.0, op-cost: 0.0, total-cost: 4.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$32] <- [{"$1": get-item(get-item($$37, 0), 0), "$2": get-item($$38, 1), "$3": get-item(get-item($$39, 1), 1), "x": $$40}] [cardinality: 2.0, op-cost: 0.0, total-cost: 4.0]
+      -- ASSIGN  |PARTITIONED|
+        project ([$$37, $$38, $$39, $$40]) [cardinality: 2.0, op-cost: 0.0, total-cost: 4.0]
+        -- STREAM_PROJECT  |PARTITIONED|
+          exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 4.0]
+          -- SORT_MERGE_EXCHANGE [$$36(ASC) ]  |PARTITIONED|
+            order (ASC, $$36) [cardinality: 2.0, op-cost: 2.0, total-cost: 4.0]
+            -- STABLE_SORT [$$36(ASC)]  |PARTITIONED|
+              exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                project ([$$37, $$38, $$39, $$40, $$36]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                -- STREAM_PROJECT  |PARTITIONED|
+                  assign [$$39, $$38] <- [$$51.getField("f2"), $$51.getField("f3")] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                  -- ASSIGN  |PARTITIONED|
+                    project ([$$36, $$40, $$51, $$37]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      assign [$$51, $$37] <- [get-item($$34, 1), get-item($$34, 0).getField("f2")] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                      -- ASSIGN  |PARTITIONED|
+                        project ([$$34, $$36, $$40]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          assign [$$34, $$36, $$40] <- [$$c.getField("f1"), $$c.getField("y"), $$c.getField("x")] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                          -- ASSIGN  |PARTITIONED|
+                            project ([$$c]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                data-scan []<-[$$35, $$c] <- test.ColumnDataset project ({x:any,y:any,f1:[{f2:[[any]],f3:[any]}]}) [cardinality: 2.0, op-cost: 2.0, total-cost: 2.0]
+                                -- DATASOURCE_SCAN  |PARTITIONED|
+                                  exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/column/pushdown/other-pushdowns/other-pushdowns.027.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/column/pushdown/other-pushdowns/other-pushdowns.027.plan
new file mode 100644
index 0000000..66b024a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/column/pushdown/other-pushdowns/other-pushdowns.027.plan
@@ -0,0 +1,26 @@
+distribute result [$$35] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    project ([$$35]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+    -- STREAM_PROJECT  |PARTITIONED|
+      assign [$$35] <- [{"$1": get-item(get-item(get-item($$36, 0).getField("f2"), 0), 0), "$2": get-item($$43, 1), "$3": get-item(get-item($$55.getField("f2"), 1), 1), "f4": $$43.getField("f4")}] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+      -- ASSIGN  |PARTITIONED|
+        assign [$$43] <- [$$55.getField("f3")] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+        -- ASSIGN  |PARTITIONED|
+          assign [$$55] <- [get-item($$36, 1)] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+          -- ASSIGN  |PARTITIONED|
+            project ([$$36]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+            -- STREAM_PROJECT  |PARTITIONED|
+              assign [$$36] <- [$$c.getField("f1")] [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+              -- ASSIGN  |PARTITIONED|
+                project ([$$c]) [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                -- STREAM_PROJECT  |PARTITIONED|
+                  exchange [cardinality: 2.0, op-cost: 0.0, total-cost: 2.0]
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    data-scan []<-[$$37, $$c] <- test.ColumnDataset project ({f1:[{f2:[[any]],f3:<[any],{f4:any}>}]}) [cardinality: 2.0, op-cost: 2.0, total-cost: 2.0]
+                    -- DATASOURCE_SCAN  |PARTITIONED|
+                      exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-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
index b0cc50d..c94a86c 100644
--- 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
@@ -88,6 +88,27 @@
         }
     }
 
+    public static int getFieldNameId(AbstractFunctionCallExpression fieldAccessExpr) {
+        if (!BuiltinFunctions.FIELD_ACCESS_BY_INDEX.equals(fieldAccessExpr.getFunctionIdentifier())) {
+            return -1;
+        }
+
+        Integer fieldNameId = ConstantExpressionUtil.getIntArgument(fieldAccessExpr, 1);
+        if (fieldNameId == null) {
+            return -1;
+        }
+
+        return fieldNameId;
+    }
+
+    public static String getFieldName(AbstractFunctionCallExpression fieldAccessExpr) {
+        if (!BuiltinFunctions.FIELD_ACCESS_BY_NAME.equals(fieldAccessExpr.getFunctionIdentifier())) {
+            throw new IllegalStateException(
+                    "Cannot get field name from " + fieldAccessExpr.getFunctionIdentifier().getName());
+        }
+        return ConstantExpressionUtil.getStringArgument(fieldAccessExpr, 1);
+    }
+
     public static String getFieldName(AbstractFunctionCallExpression fieldAccessExpr, IVariableTypeEnvironment typeEnv)
             throws AlgebricksException {
         if (BuiltinFunctions.FIELD_ACCESS_BY_NAME.equals(fieldAccessExpr.getFunctionIdentifier())) {