Skip to content

Conversation

@LLDay
Copy link

@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
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
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
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
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
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.

}

impl PlaceholderExpr {
/// Create a new placeholder expression.
Copy link
Contributor

Choose a reason for hiding this comment

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

Personally I find it a bit weird to see new, new_with_X, new_without_Y in succession as constructor methods; would it be better to have as new, new_with_datatype, new_with_field so they are strictly additive semantically or is the new (with datatype) common enough to be the default name?

Copy link
Author

Choose a reason for hiding this comment

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

Thank you for the review! Yes, I agree with you. I’ve done it as you suggested.

self.field
.as_ref()
.map(Arc::clone)
.ok_or_else(|| self.execution_error())
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this error message would complain about not having value but we're looking at field here?

Copy link
Author

Choose a reason for hiding this comment

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

I changed the error message text, and along with that I had to update the error message in some tests with placeholders in the logical plans.

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
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
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.

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 787330f to 9b2e08a Compare February 13, 2026 10:32
@github-actions github-actions bot added the core Core DataFusion crate label Feb 13, 2026
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