From 34521816badd5f8eb8f199642bbed396170d5784 Mon Sep 17 00:00:00 2001 From: "Md. Mosaddek Ali" Date: Fri, 27 Mar 2026 04:04:04 +0600 Subject: [PATCH] Allow partial pushdown for column filters when multiple array paths exist; extract pushable AND operands --- .../ColumnFilterPushdownProcessor.java | 68 +++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) 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 index 445da7eb827..82200085f01 100644 --- 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 @@ -159,10 +159,51 @@ protected boolean pushdownFilter(ScanDefineDescriptor scanDescriptor) throws Alg protected void putFilterInformation(ScanDefineDescriptor scanDefineDescriptor, ILogicalExpression inlinedExpr) throws AlgebricksException { 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; + // Conservative fallback: attempt to push a subset of top-level AND operands + // that do not collectively introduce multiple array paths. + if (inlinedExpr instanceof AbstractFunctionCallExpression + && BuiltinFunctions.AND.equals(((AbstractFunctionCallExpression) inlinedExpr).getFunctionIdentifier())) { + List> args = ((AbstractFunctionCallExpression) inlinedExpr) + .getArguments(); + List> selectedArgs = new ArrayList<>(); + List selectedPaths = new ArrayList<>(); + + for (Mutable argRef : args) { + ILogicalExpression arg = argRef.getValue(); + List argPaths = collectPathsForExpression(arg); + // Test if adding this arg's paths would cause multiple array paths + checkerVisitor.beforeVisit(); + List merged = new ArrayList<>(selectedPaths); + merged.addAll(argPaths); + if (!checkerVisitor.containsMultipleArrayPaths(merged)) { + selectedArgs.add(new MutableObject<>(arg)); + selectedPaths.addAll(argPaths); + } + } + + if (selectedArgs.isEmpty()) { + // nothing pushable + return; + } + + // Build new inlined expression from selectedArgs + AbstractFunctionCallExpression newExpr; + if (selectedArgs.size() == 1) { + // single argument + ILogicalExpression single = selectedArgs.get(0).getValue(); + putFilterInformation(scanDefineDescriptor, single); + return; + } else { + IFunctionInfo fInfo = context.getMetadataProvider().lookupFunction(AlgebricksBuiltinFunctions.AND); + newExpr = new ScalarFunctionCallExpression(fInfo, selectedArgs); + inlinedExpr = newExpr; + } + } else { + // 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; + } } ILogicalExpression filterExpr = scanDefineDescriptor.getFilterExpression(); @@ -181,6 +222,25 @@ protected IExpectedSchemaNode getPathNode(AbstractFunctionCallExpression express return expression.accept(exprToNodeVisitor, null); } + private List collectPathsForExpression(ILogicalExpression expr) { + List result = new ArrayList<>(); + collectPathsForExpressionInternal(expr, result); + return result; + } + + private void collectPathsForExpressionInternal(ILogicalExpression expr, List out) { + if (expr instanceof AbstractFunctionCallExpression) { + AbstractFunctionCallExpression f = (AbstractFunctionCallExpression) expr; + ARecordType t = paths.get(f); + if (t != null) { + out.add(t); + } + for (Mutable argRef : f.getArguments()) { + collectPathsForExpressionInternal(argRef.getValue(), out); + } + } + } + protected final AbstractFunctionCallExpression andExpression(ILogicalExpression filterExpr, ILogicalExpression inlinedExpr) { AbstractFunctionCallExpression funcExpr = (AbstractFunctionCallExpression) filterExpr;