fix(planner): Add filter predicate pushdown to AddExchangesForSingleNodeExecution#27456
Merged
swapsmagic merged 1 commit intoprestodb:masterfrom Mar 30, 2026
Conversation
Contributor
Reviewer's GuideAdds filter predicate pushdown support in AddExchangesForSingleNodeExecution for single-node execution, mirroring distributed-mode behavior and verifying it with new plan tests for SHOW COLUMNS/DESCRIBE and regular table scans. Sequence diagram for filter predicate pushdown in single-node AddExchangesForSingleNodeExecutionsequenceDiagram
participant Planner
participant Rewriter as AddExchangesForSingleNodeExecution_Rewriter
participant Metadata
participant PlanUtils as PlanNodeUtilities
Planner->>Rewriter: rewrite(FilterNode)
Rewriter->>Rewriter: visitFilter(filterNode)
alt source_is_TableScanNode_and_legacy_layout_supported
Rewriter->>Metadata: isLegacyGetLayoutSupported(session, tableHandle)
Metadata-->>Rewriter: legacySupported
Rewriter->>PlanUtils: pushPredicateIntoTableScan(tableScanNode, predicate, true, session, idAllocator, metadata)
PlanUtils-->>Rewriter: plan
Rewriter->>PlanUtils: containsSystemTableScan(plan)
PlanUtils-->>Rewriter: hasSystemTableScan
alt hasSystemTableScan
Rewriter->>PlanUtils: gatheringExchange(idAllocator.getNextId(), REMOTE_STREAMING, plan)
PlanUtils-->>Rewriter: planWithGather
Rewriter-->>Planner: planWithGather
else no_system_table_scan
Rewriter-->>Planner: plan
end
else non_table_scan_source_or_no_legacy_layout
Rewriter->>Rewriter: context.defaultRewrite(filterNode)
Rewriter-->>Planner: rewrittenSubtree
end
Updated class diagram for AddExchangesForSingleNodeExecution Rewriter with visitFilterclassDiagram
class AddExchangesForSingleNodeExecution_Rewriter {
- Session session
- PlanNodeIdAllocator idAllocator
- Metadata metadata
- boolean planChanged
+ PlanNode visitTableScan(TableScanNode node, RewriteContext context)
+ PlanNode visitFilter(FilterNode node, RewriteContext context)
+ PlanNode visitExplainAnalyze(ExplainAnalyzeNode node, RewriteContext context)
}
class TableScanNode {
+ TableHandle getTable()
+ Map~Symbol,ColumnHandle~ getAssignments()
+ TupleDomain~ColumnHandle~ getEnforcedConstraint()
}
class FilterNode {
+ RowExpression getPredicate()
+ PlanNode getSource()
}
class RewriteContext {
+ PlanNode defaultRewrite(PlanNode node)
}
class Metadata {
+ boolean isLegacyGetLayoutSupported(Session session, TableHandle table)
}
class PlanNodeUtilities {
+ PlanNode pushPredicateIntoTableScan(TableScanNode tableScanNode, RowExpression predicate, boolean forceNewTableScanNode, Session session, PlanNodeIdAllocator idAllocator, Metadata metadata)
+ boolean containsSystemTableScan(PlanNode plan)
+ PlanNode gatheringExchange(PlanNodeId id, ExchangeNodeScope scope, PlanNode source)
}
class PlanNode {
}
class Session {
}
class PlanNodeIdAllocator {
+ PlanNodeId getNextId()
}
class PlanNodeId {
}
class ExchangeNodeScope {
<<enum>>
REMOTE_STREAMING
}
AddExchangesForSingleNodeExecution_Rewriter --> Session
AddExchangesForSingleNodeExecution_Rewriter --> PlanNodeIdAllocator
AddExchangesForSingleNodeExecution_Rewriter --> Metadata
AddExchangesForSingleNodeExecution_Rewriter ..> TableScanNode : uses
AddExchangesForSingleNodeExecution_Rewriter ..> FilterNode : uses
AddExchangesForSingleNodeExecution_Rewriter ..> RewriteContext : uses
AddExchangesForSingleNodeExecution_Rewriter ..> PlanNodeUtilities : calls
PlanNodeUtilities ..> PlanNode : returns
TableScanNode --|> PlanNode
FilterNode --|> PlanNode
ExplainAnalyzeNode --|> PlanNode
class ExplainAnalyzeNode {
}
Flow diagram for logical plan shape before and after single-node DESCRIBE rewriteflowchart TD
A["Original logical plan for single-node DESCRIBE / SHOW COLUMNS"] --> B["AddExchangesForSingleNodeExecution_Rewriter visitFilter"]
subgraph Before_fix
direction TB
C["OutputNode"] --> D["SortNode"]
D --> E["ProjectNode"]
E --> F["FilterNode(table_schema='s' AND table_name='t')"]
F --> G["ExchangeNode(GATHER)"]
G --> H["TableScanNode(information_schema.columns, predicate=TRUE)"]
end
subgraph After_fix
direction TB
I["OutputNode"] --> J["SortNode"]
J --> K["ProjectNode"]
K --> L["ExchangeNode(GATHER)"]
L --> M["TableScanNode(information_schema.columns, predicate=table_schema='s' AND table_name='t')"]
end
B --> Before_fix
B --> After_fix
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Contributor
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The visitFilter implementation largely mirrors AddExchanges.visitFilter; consider extracting the shared predicate-pushdown logic into a common helper to avoid future divergence between distributed and single-node planning.
- In visitFilter, you repeatedly access node.getSource() and cast it; assigning the cast TableScanNode to a local before the metadata check would simplify the code and avoid the duplicate instanceof/cast sequence.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The visitFilter implementation largely mirrors AddExchanges.visitFilter; consider extracting the shared predicate-pushdown logic into a common helper to avoid future divergence between distributed and single-node planning.
- In visitFilter, you repeatedly access node.getSource() and cast it; assigning the cast TableScanNode to a local before the metadata check would simplify the code and avoid the duplicate instanceof/cast sequence.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
e2d898c to
ad43d93
Compare
ad43d93 to
bdf6c04
Compare
CONTEXT:
DESCRIBE / SHOW COLUMNS queries are rewritten into SELECTs against
information_schema.columns with a FilterNode (table_schema=X AND table_name=Y)
above a TableScanNode. In distributed mode (AddExchanges), visitFilter detects
this pattern and pushes the filter predicate into the InformationSchemaTableHandle
via pushPredicateIntoTableScan. The connector then knows which table's columns to
return.
In single-node execution (AddExchangesForSingleNodeExecution), there was no
visitFilter override. The FilterNode passed through unchanged, and visitTableScan
only pushed TRUE as the predicate — so the information_schema connector never
received the table name filter. This caused DESCRIBE to return wrong/empty results
in single-node execution.
WHAT:
Added a visitFilter override to AddExchangesForSingleNodeExecution.Rewriter that
mirrors the logic from AddExchanges.visitFilter:
1. Detects FilterNode -> TableScanNode where legacy layout is supported
2. Pushes the actual filter predicate into the table scan
3. Adds GATHER exchange only for system table scans (same as existing behavior)
4. Falls through to default rewriting for non-table-scan filters
Plan before fix (single-node DESCRIBE):
OutputNode
SortNode
ProjectNode
FilterNode (table_schema='s' AND table_name='t') <-- predicate stuck here
ExchangeNode (GATHER) <-- from visitTableScan
TableScanNode (info_schema.columns) <-- gets TRUE, no table info
Plan after fix (single-node DESCRIBE):
OutputNode
SortNode
ProjectNode
ExchangeNode (GATHER) <-- same exchange, moved up
TableScanNode (info_schema.columns) <-- gets real predicate
No new exchanges are introduced. The GATHER exchange for system tables was already
present (added by visitTableScan). This fix ensures the predicate is pushed down so
the connector scans only the target table's columns.
bdf6c04 to
3b8f46d
Compare
feilong-liu
approved these changes
Mar 30, 2026
This was referenced Mar 31, 2026
bibith4
pushed a commit
to bibith4/presto
that referenced
this pull request
Apr 1, 2026
…odeExecution (prestodb#27456) ## Description <!---Describe your changes in detail--> ## Motivation and Context CONTEXT: DESCRIBE / SHOW COLUMNS queries are rewritten into SELECTs against information_schema.columns with a FilterNode (table_schema=X AND table_name=Y) above a TableScanNode. In distributed mode (AddExchanges), visitFilter detects this pattern and pushes the filter predicate into the InformationSchemaTableHandle via pushPredicateIntoTableScan. The connector then knows which table's columns to return. In single-node execution (AddExchangesForSingleNodeExecution), there was no visitFilter override. The FilterNode passed through unchanged, and visitTableScan only pushed TRUE as the predicate — so the information_schema connector never received the table name filter. This caused DESCRIBE to return wrong/empty results in single-node execution. ## Impact WHAT: Added a visitFilter override to AddExchangesForSingleNodeExecution.Rewriter that mirrors the logic from AddExchanges.visitFilter: 1. Detects FilterNode -> TableScanNode where legacy layout is supported 2. Pushes the actual filter predicate into the table scan 3. Adds GATHER exchange only for system table scans (same as existing behavior) 4. Falls through to default rewriting for non-table-scan filters Plan before fix (single-node DESCRIBE): OutputNode SortNode ProjectNode FilterNode (table_schema='s' AND table_name='t') <-- predicate stuck here ExchangeNode (GATHER) <-- from visitTableScan TableScanNode (info_schema.columns) <-- gets TRUE, no table info Plan after fix (single-node DESCRIBE): OutputNode SortNode ProjectNode ExchangeNode (GATHER) <-- same exchange, moved up TableScanNode (info_schema.columns) <-- gets real predicate No new exchanges are introduced. The GATHER exchange for system tables was already present (added by visitTableScan). This fix ensures the predicate is pushed down so the connector scans only the target table's columns. ## Test Plan Unit tests and verifier run ## Contributor checklist - [x] Please make sure your submission complies with our [contributing guide](https://github.com/prestodb/presto/blob/master/CONTRIBUTING.md), in particular [code style](https://github.com/prestodb/presto/blob/master/CONTRIBUTING.md#code-style) and [commit standards](https://github.com/prestodb/presto/blob/master/CONTRIBUTING.md#commit-standards). - [x] PR description addresses the issue accurately and concisely. If the change is non-trivial, a GitHub Issue is referenced. - [x] Documented new properties (with its default value), SQL syntax, functions, or other functionality. - [ ] If release notes are required, they follow the [release notes guidelines](https://github.com/prestodb/presto/wiki/Release-Notes-Guidelines). - [ ] Adequate tests were added if applicable. - [ ] CI passed. - [ ] If adding new dependencies, verified they have an [OpenSSF Scorecard](https://securityscorecards.dev/#the-checks) score of 5.0 or higher (or obtained explicit TSC approval for lower scores). ## Release Notes Please follow [release notes guidelines](https://github.com/prestodb/presto/wiki/Release-Notes-Guidelines) and fill in the release notes below. ``` == RELEASE NOTES == General Changes * Fix DESCRIBE and SHOW COLUMNS queries hanging in PLANNING state on clusters with single-node execution enabled. ```
15 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Motivation and Context
CONTEXT:
DESCRIBE / SHOW COLUMNS queries are rewritten into SELECTs against information_schema.columns with a FilterNode (table_schema=X AND table_name=Y) above a TableScanNode. In distributed mode (AddExchanges), visitFilter detects this pattern and pushes the filter predicate into the InformationSchemaTableHandle via pushPredicateIntoTableScan. The connector then knows which table's columns to return.
In single-node execution (AddExchangesForSingleNodeExecution), there was no visitFilter override. The FilterNode passed through unchanged, and visitTableScan only pushed TRUE as the predicate — so the information_schema connector never received the table name filter. This caused DESCRIBE to return wrong/empty results in single-node execution.
Impact
WHAT:
Added a visitFilter override to AddExchangesForSingleNodeExecution.Rewriter that mirrors the logic from AddExchanges.visitFilter:
Plan before fix (single-node DESCRIBE):
OutputNode
SortNode
ProjectNode
FilterNode (table_schema='s' AND table_name='t') <-- predicate stuck here
ExchangeNode (GATHER) <-- from visitTableScan
TableScanNode (info_schema.columns) <-- gets TRUE, no table info
Plan after fix (single-node DESCRIBE):
OutputNode
SortNode
ProjectNode
ExchangeNode (GATHER) <-- same exchange, moved up
TableScanNode (info_schema.columns) <-- gets real predicate
No new exchanges are introduced. The GATHER exchange for system tables was already present (added by visitTableScan). This fix ensures the predicate is pushed down so the connector scans only the target table's columns.
Test Plan
Unit tests and verifier run
Contributor checklist
Release Notes
Please follow release notes guidelines and fill in the release notes below.