Skip to content

Support Preimage for date_trunc - #22114

Merged
alamb merged 25 commits into
apache:mainfrom
sdf-jkl:preimage-date_trunc
Oct 18, 2026
Merged

alamb merged 25 commits into
apache:mainfrom
sdf-jkl:preimage-date_trunc

Conversation

@sdf-jkl

@sdf-jkl sdf-jkl commented May 11, 2026

Copy link
Copy Markdown
Member

Which issue does this PR close?

Rationale for this change

Enable pruning for predicates with date_trunc on the column side

What changes are included in this PR?

  • Added preimage implementation for date_trunc
  • Moved date_to_scalar to a common crate

Are these changes tested?

Yes, unit tests

Are there any user-facing changes?

No

@github-actions github-actions Bot added sqllogictest SQL Logic Tests (.slt) functions Changes to functions implementation labels May 11, 2026
@alamb

alamb commented May 12, 2026

Copy link
Copy Markdown
Contributor

I hope to review this soon

@github-actions

Copy link
Copy Markdown

Thank you for your contribution. Unfortunately, this pull request is stale because it has been open 60 days with no activity. Please remove the stale label or comment or this will be closed in 7 days.

@github-actions github-actions Bot added the Stale PR has not had any activity for some time label Jul 12, 2026
@github-actions github-actions Bot closed this Jul 19, 2026
@sdf-jkl

sdf-jkl commented Jul 20, 2026

Copy link
Copy Markdown
Member Author

@alamb 😢

@sdf-jkl

sdf-jkl commented Jul 20, 2026

Copy link
Copy Markdown
Member Author

@alamb 😢

please nvm, and don't worry about it 🐱

@alamb alamb reopened this Jul 22, 2026
# Conflicts:
#	datafusion/functions/src/datetime/date_trunc.rs
@alamb alamb changed the title Preimage date trunc Support Preimage for date_trunc Jul 22, 2026
@alamb

alamb commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

I merged up from main to resolve a conflict

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

Thanks @sdf-jkl -- I am sorry that this got left hanging

I started going through it with claude today

One thing if flagged that I haven't had a chance to review is a wrong results bug with wacky timezones -- maybe we can make the code more conservative

I will try and get this reviewed over the next day or two with a fresh set of 👓

🔴 High — wrong results: hour granularity with a named timezone

has_variable_width_in_tz (date_trunc.rs:145) excludes Day and coarser but permits Hour, so the timestamp arm applies a fixed 3600 s step. That holds for every whole-hour DST transition (I confirmed America/New_York fall-back round-trips exactly — _date_trunc_coarse_with_tz disambiguates by offset), but it breaks when a zone's offset shifts by a non-hour amount. Australia/Lord_Howe shifts 30 minutes:

create table lh as select arrow_cast(column1, 'Timestamp(Nanosecond, Some("Australia/Lord_Howe"))') as ts
from (values ('2024-10-05T15:00:00Z'),('2024-10-05T15:30:00Z'),('2024-10-05T15:45:00Z'),
('2024-10-05T16:00:00Z'),('2024-10-05T16:15:00Z'));

-- ground truth (eq on the cast result, so preimage can't fire): 2 rows
SELECT arrow_cast(ts,'Utf8') FROM lh
WHERE arrow_cast(date_trunc('hour', ts),'Utf8') = '2024-10-06T02:30:00+11:00';
-- 2024-10-06T02:30:00+11:00
-- 2024-10-06T02:45:00+11:00

-- with the preimage rewrite: 4 rows
SELECT arrow_cast(ts,'Utf8') FROM lh
WHERE date_trunc('hour', ts) = arrow_cast('2024-10-05T15:30:00Z','Timestamp(Nanosecond, Some("Australia/Lord_Howe"))');
-- 2024-10-06T02:30:00+11:00
-- 2024-10-06T02:45:00+11:00
-- 2024-10-06T03:00:00+11:00 <-- date_trunc gives 03:00, not 02:30
-- 2024-10-06T03:15:00+11:00 <-- same

The spring-forward bucket is 30 minutes wide, not 60. general_date_trunc(15:30) == 15:30 so the alignment check passes, and upper overshoots by 30 min. Since rewrite_with_preimage replaces the predicate rather than refining it, the interval has to be exact.

/// variants encode the bare date; Timestamp variants are anchored at the
/// date's midnight in the given timezone. Returns `None` for unsupported
/// target types.
pub(crate) fn date_to_scalar(

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.

What do you think about making this a method on ScalarValue (like ScalarValue::try_from_date, matching the existing methods like https://docs.rs/datafusion/latest/datafusion/common/enum.ScalarValue.html#method.try_from_string)?

That might make it easier to discover in the future

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.

we can do this as a follow on

@codecov-commenter

codecov-commenter commented Jul 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 69.96700% with 91 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.88%. Comparing base (1da3cf0) to head (d9420a0).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
datafusion/functions/src/datetime/date_trunc.rs 66.23% 53 Missing and 25 partials ⚠️
datafusion/common/src/scalar/mod.rs 81.42% 6 Missing and 7 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #22114      +/-   ##
==========================================
- Coverage   82.89%   82.88%   -0.01%     
==========================================
  Files        1153     1153              
  Lines      457487   457758     +271     
  Branches   457487   457758     +271     
==========================================
+ Hits       379213   379404     +191     
- Misses      55264    55311      +47     
- Partials    23010    23043      +33     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions Bot removed the Stale PR has not had any activity for some time label Jul 23, 2026
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

Thank you for your contribution. Unfortunately, this pull request is stale because it has been open 60 days with no activity. Please remove the stale label or comment or this will be closed in 7 days.

@github-actions github-actions Bot added the Stale PR has not had any activity for some time label Oct 6, 2026
@github-actions github-actions Bot closed this Oct 13, 2026
@sdf-jkl

sdf-jkl commented Oct 13, 2026

Copy link
Copy Markdown
Member Author

@alamb do you think there's value in this? If so I can get back to it.

@alamb

alamb commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

I do think there is value @sdf-jkl -- what I haven't been able to do is find enough time to reason through it and convince myself it is correct.

I had been hoping we would have some more engagement from others in the community as well 😢

@alamb alamb reopened this Oct 14, 2026
alamb added 3 commits October 14, 2026 06:53
# Conflicts:
#	datafusion/functions/src/datetime/date_part.rs
#	datafusion/functions/src/datetime/date_trunc.rs
@alamb

alamb commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

I got distracted by this PR this morning and spent a while merging it up to main, adding some more tests, and fixing a timezone related issue. Let me know what you think @sdf-jkl -- I am truly sorry it took so long

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

Thanks @sdf-jkl -- I went through this PR carefully, and added some more comments, tests and examples. Could you give it a look as well?

Comment thread datafusion/functions/src/datetime/date_trunc.rs Outdated
Comment thread datafusion/functions/src/datetime/date_trunc.rs
Comment thread datafusion/functions/src/datetime/date_trunc.rs Outdated
Comment thread datafusion/common/src/scalar/mod.rs Outdated
///
/// Returns `None` if `target_type` is not a date or timestamp type, or if
/// midnight on `date` cannot be represented such as when the timezone skips
/// midnight that day (for example a daylight saving transition at 00:00).

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.

How important is this last property (that it returns None for date's with no midnight)? The alternative is to return the first timestamp that does exist in the day, probably 01:00. Admittedly that is rather tricky to calculate just now, but once apache/arrow-rs#11535 is available it'd be much simpler.

With this current design there is always a risk of a sudden NULL appearing in a sorted array of dates so operators that use this function cannot be known to preserve the sorting, possibly requiring resorting of sorted data.

Comment thread datafusion/common/src/scalar/mod.rs Outdated
Comment thread datafusion/common/src/scalar/mod.rs Outdated
Comment thread datafusion/common/src/scalar/mod.rs Outdated
Comment thread datafusion/functions/src/datetime/date_trunc.rs Outdated
Comment thread datafusion/functions/src/datetime/date_trunc.rs Outdated
@github-actions github-actions Bot removed the Stale PR has not had any activity for some time label Oct 15, 2026
Comment thread datafusion/common/src/scalar/mod.rs Outdated
@alamb

alamb commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Thank you for the review @mhilton -- I think I implemented all your comments.

Ok(Arc::new(result))
}

fn date_to_scalar(date: NaiveDate, target_type: &DataType) -> Option<ScalarValue> {

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.

moved to method on ScalarValue

/// `date_trunc` floors a timestamp to the start of the given period, so
/// for any aligned literal `L` the preimage is the half-open interval
/// `[L, L + 1 granularity)`.
///

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.

Also added some examples

/// This is local midnight unless the timezone skips midnight that day (for
/// example a daylight saving transition from 00:00 to 01:00), in which case
/// it is the first instant after the gap. If midnight occurs twice (a
/// transition back from 01:00 to 00:00) the earlier instant is returned.

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.

If you want a reference for this it is the convention described in RFC5545§3.3.5

// less than a day, so that instant is before the transition), and
// use it to convert midnight to the transition instant.
let probe = midnight.checked_sub_signed(Duration::days(1))?;
let offset_before = tz.offset_from_utc_datetime(&probe).fix();

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'm pretty sure this works, but it's baffling.midnight is a local time so therefore is probe. I think there needs to be a comment saying why it is fine to use it as UTC, I think that's because we just care about some timestamp that is sufficiently before the discontinuity and it doesn't matter exactly what that time is.

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 added the comment to explain the reasoning (it was of course written by an LLM but I double checked it and clarified the comment)

@alamb
alamb added this pull request to the merge queue Oct 18, 2026
@alamb

alamb commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Thanks again @sdf-jkl and @mhilton

Merged via the queue into apache:main with commit 0ee3978 Oct 18, 2026
42 checks passed
@sdf-jkl
sdf-jkl deleted the preimage-date_trunc branch October 18, 2026 13:38
@sdf-jkl

sdf-jkl commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

Thanks for pushing this through @alamb @mhilton 🙏

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

Labels

common Related to common crate functions Changes to functions implementation sqllogictest SQL Logic Tests (.slt) v56.0.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

4 participants