Skip to content

Optimize the evaluation of date_part(<col>) == <constant> when pushed down - #19733

Merged
alamb merged 34 commits into
apache:mainfrom
sdf-jkl:smaller-preimage-pr-2
Feb 3, 2026
Merged

Optimize the evaluation of date_part(<col>) == <constant> when pushed down#19733
alamb merged 34 commits into
apache:mainfrom
sdf-jkl:smaller-preimage-pr-2

Conversation

@sdf-jkl

@sdf-jklsdf-jkl commented Jan 10, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

Check issue.

What changes are included in this PR?

Added preimage impl for date_part udf.

Added sqllogictests for the impl.

Are these changes tested?

Yes, sqllogictests.

Are there any user-facing changes?

No

@github-actionsgithub-actionsBot added logical-expr Logical plan and expressions optimizer Optimizer rules core Core DataFusion crate sqllogictest SQL Logic Tests (.slt) functions Changes to functions implementation labels Jan 10, 2026
@sdf-jklsdf-jkl changed the title Smaller preimage pr 2 Support "pre-image" for pruning predicate evaluation #2Jan 10, 2026
@github-actionsgithub-actionsBot removed the core Core DataFusion crate label Jan 19, 2026
@sdf-jklsdf-jkl changed the title Support "pre-image" for pruning predicate evaluation #2Optimize the evaluation of date_part(<col>) == <constant> when pushed downJan 19, 2026
@github-actionsgithub-actionsBot removed logical-expr Logical plan and expressions optimizer Optimizer rules labels Jan 25, 2026

@kosiewkosiew left a comment

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.

LGTM

Left some minor suggestions.

// date_part(col, YEAR) = 2024 => col >= '2024-01-01' and col < '2025-01-01'
// But for anything less than YEAR simplifying is not possible without specifying the bigger interval
// date_part(col, MONTH) = 1 => col = '2023-01-01' or col = '2024-01-01' or ... or col = '3000-01-01'
fn preimage(

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.

Consider adding a section to docs/source/library-user-guide/functions/adding-udfs.md explaining:

  • What preimage is and when to implement it
  • How it enables predicate pushdown
  • Example implementation (perhaps referencing date_part)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

There is a PR for preimage doc improvement here #20008.

I am however, not sure that this doc needs to explain preimage. I think the doc's goal is to be a very minimal guide on adding and registering a function. There is also no mention of simplify too.

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 agree that the API docs is probably adequate. We could potentially add a note to adding-udfs.md that says something generic like "The ScalarUDFImpl has additional methods that support specialized optimizations such as preimage -- see the API documentation for additional details"

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A separate PR should do.

Comment threaddatafusion/functions/src/datetime/date_part.rs Outdated
@sdf-jkl

Copy link
Copy Markdown
ContributorAuthor

Thanks for your review @kosiew. I've addressed your comments and also added proper handling for timestamps with timezones, please check it out too when you have time.

@sdf-jkl

Copy link
Copy Markdown
ContributorAuthor

@alamb I'm still thinking about is_literal_or_literal_cast check in expr_simplifier: #19722 (comment)

@alambalamb left a comment

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.

Thank you @sdf-jkl -- this is looking very close

I have some suggestions on avoiding adding new date/time arithmetic here.

I also recommend an additional test for non-int32 arguments (coercion works fine, but it is good to show that):

diff --git a/datafusion/sqllogictest/test_files/udf_preimage.slt b/datafusion/sqllogictest/test_files/udf_preimage.slt
index 12f3a9a45b..c9d408227a 100644
--- a/datafusion/sqllogictest/test_files/udf_preimage.slt+++ b/datafusion/sqllogictest/test_files/udf_preimage.slt@@ -30,6 +30,11 @@ select c from t1 where extract(year from c) = 2024;
----
2024-01-01
+query D+select c from t1 where extract(year from c) = cast(2024 as bigint);+----+2024-01-01+
query D
select c from t1 where extract(year from c) <> 2024;
----
@@ -83,6 +88,16 @@ physical_plan
01)FilterExec: c@0 >= 2024-01-01 AND c@0 < 2025-01-01
02)--DataSourceExec: partitions=1, partition_sizes=[1]
+query TT+explain select c from t1 where extract (year from c) = cast(2024 as bigint)+----+logical_plan+01)Filter: t1.c >= Date32("2024-01-01") AND t1.c < Date32("2025-01-01")+02)--TableScan: t1 projection=[c]+physical_plan+01)FilterExec: c@0 >= 2024-01-01 AND c@0 < 2025-01-01+02)--DataSourceExec: partitions=1, partition_sizes=[1]+
query TT
explain select c from t1 where extract (year from c) <> 2024
----

Comment threaddatafusion/sqllogictest/test_files/udf_preimage.slt Outdated
// date_part(col, YEAR) = 2024 => col >= '2024-01-01' and col < '2025-01-01'
// But for anything less than YEAR simplifying is not possible without specifying the bigger interval
// date_part(col, MONTH) = 1 => col = '2023-01-01' or col = '2024-01-01' or ... or col = '3000-01-01'
fn preimage(

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 agree that the API docs is probably adequate. We could potentially add a note to adding-udfs.md that says something generic like "The ScalarUDFImpl has additional methods that support specialized optimizations such as preimage -- see the API documentation for additional details"

Date32 => ScalarValue::Date32(Some(days as i32)),
Date64 => ScalarValue::Date64(Some(days * MILLISECONDS_IN_DAY)),

Timestamp(unit, tz_opt) => {

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.

It feels to me like this code should be able to re-use the conversion functions in arrow rather than re-implementing them here

For example

Date32 => ScalarValue::Date32(Some(Date32Type::from_naive_date(date))),Date64 => ScalarValue::Date64(Some(Date64Type::from_naive_date(date))),
...

I didn't have a chance to figure out how to do it for Timestamp, but it seems like there should be a function like this for the timestamps too -- for example

https://docs.rs/arrow/latest/arrow/array/types/struct.TimestampSecondType.html and https://docs.rs/arrow/latest/arrow/datatypes/trait.ArrowTimestampType.html

Maybe something like

TimestampSecondType::make_value(date)

(We will have to figure out timestamps)

@sdf-jklsdf-jklJan 29, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Fixed date32/64 62b0841

As for timestamp: dyn TimestampType::make_value() is using a NaiveDate, not DateTime<Tz>. We'd still have to do some tz math to create an offset for NaiveDate. (if only there was an existing API to help...)

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.

lit_expr: &Expr,
info: &SimplifyContext,
) -> Result<PreimageResult> {
let [part, col_expr] = take_function_args(self.name(), args)?;

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 think we should be able to avoid writing all this code

@alambalamb left a comment

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.

Looks great to me -- thank you @sdf-jkl for pushing this over the line

@alamb
alamb added this pull request to the merge queueFeb 3, 2026
@alamb

alamb commented Feb 3, 2026

Copy link
Copy Markdown
Contributor

Thanks again @kosiew for your help here

Merged via the queue into apache:main with commit 35e78caFeb 3, 2026
28 checks passed
de-bgunter pushed a commit to de-bgunter/datafusion that referenced this pull request Mar 24, 2026
… down (apache#19733)
## Which issue does this PR close?
- closesapache#19889.
## Rationale for this change
Check issue.
## What changes are included in this PR?
Added `preimage` impl for `date_part` udf.
Added sqllogictests for the impl.
## Are these changes tested?
Yes, sqllogictests.
## Are there any user-facing changes?
No
---------
Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

functionsChanges to functions implementationsqllogictestSQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimize the evaluation of date_part(<col>) == <constant> when pushed down

3 participants

@sdf-jkl@alamb@kosiew