Skip to content

feat: placeholders in execution plans#20169

Open
LLDay wants to merge 1 commit intoapache:mainfrom
LLDay:feat/placeholder-physical-expression
Open

feat: placeholders in execution plans#20169
LLDay wants to merge 1 commit intoapache:mainfrom
LLDay:feat/placeholder-physical-expression

Conversation

@LLDay
Copy link
Copy Markdown

@LLDay LLDay commented Feb 5, 2026

Rationale for this change

Part of #14342.

Previously, DataFusion required all placeholders to be resolved to literal values before physical planning. This limitation made it difficult to work in systems where physical plans might be cached or reused, or where resolution happens later in the execution pipeline.

By introducing PlaceholderExpr at the physical level, we allow the physical planner to handle unresolved placeholders. This is a logical next step for PR #20009 and part of the effort to improve plan reuse support in DataFusion.

What changes are included in this PR?

  • Introduced PlaceholderExpr as a new physical expression.
  • Updated the physical planner to support creating PlaceholderExpr when encountering Expr::Placeholder.
  • Updated the constant evaluator to correctly handle (skip) placeholders during optimization.

Are these changes tested?

Yes, SLTs have been added, which verifies that placeholders are correctly preserved in physical plans across a wide range of SQL queries.

Are there any user-facing changes?

Yes, users can now generate physical plans for queries containing unresolved placeholders. Attempting to execute these plans without resolving the placeholders will still result in an error, but the planning phase no longer requires their resolution.

@github-actions github-actions Bot added physical-expr Changes to the physical-expr crates sqllogictest SQL Logic Tests (.slt) proto Related to proto crate labels Feb 5, 2026
@LLDay LLDay marked this pull request as ready for review February 5, 2026 13:30
@LLDay LLDay changed the title Placeholders in execution plans feat: placeholders in execution plans Feb 5, 2026
@LLDay
Copy link
Copy Markdown
Author

LLDay commented Feb 5, 2026

@askalt, you've already read the code. Could you take another look at it?

@LLDay LLDay force-pushed the feat/placeholder-physical-expression branch from 0fd0fa2 to 787330f Compare February 10, 2026 07:33
@askalt
Copy link
Copy Markdown
Contributor

askalt commented Feb 10, 2026

@Omega359 could you please help to review the patch? There is a basic part of physical placeholders implementation: physical planning support and serde stuff.

@Omega359
Copy link
Copy Markdown
Contributor

Sorry @askalt I'm not well versed enough in that section of the codebase to properly review this. Perhaps @Jefffrey or @adriangb ?

@askalt
Copy link
Copy Markdown
Contributor

askalt commented Feb 10, 2026

Sorry @askalt I'm not well versed enough in that section of the codebase to properly review this. Perhaps @Jefffrey or @adriangb ?

Thank you! It would be nice.

Copy link
Copy Markdown
Contributor

@Jefffrey Jefffrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to have justification somewhere in code for this physical expression; e.g. normally this would not be created during planning as logical layer should handle resolution, but what cases this is useful for, etc.

Comment thread datafusion/physical-expr/src/expressions/placeholder.rs
Comment thread datafusion/physical-expr/src/expressions/placeholder.rs Outdated
Comment thread datafusion/physical-expr/src/planner.rs Outdated
if let Some(placeholder) = expr.as_any().downcast_ref::<PlaceholderExpr>()
&& placeholder.field.is_none()
{
// To maintain try_cast behavior, we initially resolve the placeholder with the
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit hacky 🤔

What does it mean to maintain the behaviour?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I slightly updated the comment. What I meant is that we set the data type to UTF8 so that try_cast can return NULL if the value cannot be converted to the required type. If we try to assign the required type to the placeholder right away, the call will fail with an error instead of returning NULL.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make more sense here to use DataType::Null instead of DataType::Utf8 then?

@LLDay LLDay force-pushed the feat/placeholder-physical-expression branch from 787330f to 9b2e08a Compare February 13, 2026 10:32
@github-actions github-actions Bot added the core Core DataFusion crate label Feb 13, 2026
Introduces `PlaceholderExpr`, allowing placeholder parameters to be
preserved in the physical plan. Previously, placeholders had to be
resolved to literals before physical planning.
@LLDay LLDay force-pushed the feat/placeholder-physical-expression branch from 9b2e08a to 6e45543 Compare February 26, 2026 08:43
Copy link
Copy Markdown
Contributor

@Jefffrey Jefffrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies it took a long time to take a look again. I think it generally looks good, though I have some questions on the handling logic for casting.

Also would be good to have this addressed:

It would be good to have justification somewhere in code for this physical expression; e.g. normally this would not be created during planning as logical layer should handle resolution, but what cases this is useful for, etc.

if let Some(placeholder) = expr.as_any().downcast_ref::<PlaceholderExpr>()
&& placeholder.field.is_none()
{
expr = expressions::placeholder(&placeholder.id, data_type.clone());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do wonder what will happen if we then replace the placeholder with a value of type that can't be casted; just a regular execution error? 🤔

Comment thread datafusion/physical-expr/src/planner.rs Outdated
if let Some(placeholder) = expr.as_any().downcast_ref::<PlaceholderExpr>()
&& placeholder.field.is_none()
{
// To maintain try_cast behavior, we initially resolve the placeholder with the
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make more sense here to use DataType::Null instead of DataType::Utf8 then?

@askalt
Copy link
Copy Markdown
Contributor

askalt commented Apr 22, 2026

It would be good to have justification somewhere in code for this physical expression; e.g. normally this would not be created during planning as logical layer should handle resolution, but what cases this is useful for, etc.

Is it ok to introduce some planning config flag to allow physical placeholders planning? If it is false -- we return an error (keep behavior as is).

@Jefffrey
Copy link
Copy Markdown
Contributor

It would be good to have justification somewhere in code for this physical expression; e.g. normally this would not be created during planning as logical layer should handle resolution, but what cases this is useful for, etc.

Is it ok to introduce some planning config flag to allow physical placeholders planning? If it is false -- we return an error (keep behavior as is).

Might be quite a niche config 🤔

@askalt
Copy link
Copy Markdown
Contributor

askalt commented Apr 23, 2026

Might be quite a niche config

Currently yes as physical placeholders are not supported directly by DF, but this patch target is to add these expressions (and allocate the corresponding protobuf tag to be sure that it will not change), but actually why they are useful for us -- in our fork we replace all placeholders before call .execute() on plan (using a physical binder, the code could be found here) and we plan to make a PR for upstream.

@Jefffrey
Copy link
Copy Markdown
Contributor

I suppose having a config would be a good middle ground to allow this new functionality whilst keeping things non-surprising for existing users (i.e. placeholders should fail during planning instead of execution) 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Core DataFusion crate physical-expr Changes to the physical-expr crates proto Related to proto crate sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants