add index-friendly date extractors - #38
Open
manuelpelloni wants to merge 5 commits into
Open
Conversation
Rewrites date equality as a range that keeps the column bare, so a plain B-tree index can drive it: a closed BETWEEN on Date columns (ClosedRange), a half-open >= / < + 1 pair on Timestamp columns (HalfOpenRange), picked at compile time via DateLike::Range. Timestamptz is rejected: which instants fall on a calendar date depends on the session time zone.
extract_year/extract_month/extract_day emit
CAST(date_part('<field>', expr) AS integer), the exact form an
expression index must be built on (EXTRACT maps to a different catalog
function; the two diverged in PostgreSQL 14). On date columns, bounded
year types (u8, u16, U15) rewrite eq/between into a plain
BETWEEN make_date(..) AND make_date(..) range (YearRange) so a B-tree
index on the column works; i32 years keep the date_part comparison
because part of their range is rejected by make_date() at runtime.
…risons Time columns stay bare (BareTime) so a plain B-tree index drives the comparison, and their eq uses the same range form as extract_date. Timestamp columns are emitted as CAST(expr AS time) (CastToTime), which matches an expression index built on exactly that cast. Timestamptz is rejected: its cast to time depends on the session time zone (it is only STABLE), so PostgreSQL rejects the expression index too.
…zones The timezone! macro declares unit structs implementing TimeZone, whose NAME is spliced into the SQL as a literal (a bind would break SELECT/GROUP BY structural matching, and a runtime name the type-keyed prepared-statement cache). at_time_zone/TimeZone::at convert Timestamptz columns to the zone's wall clock and reinterpret naive Timestamp columns as UTC first, via the double (expr AT TIME ZONE 'UTC') AT TIME ZONE '<zone>' form, picked at compile time via InstantLike::Conversion; nullability propagates to the output. timezone(zone, ts) is IMMUTABLE, so an expression index built on the emitted form works, and the result is a plain Timestamp expression the whole extract_* family composes with — which is how extract_date/extract_time/extract_year work on Timestamptz columns.
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.
Adds diesel expressions for extracting and comparing dates, date parts and times of day on PostgreSQL columns. Wherever possible the comparison is rewritten into a form that keeps the column bare, so a plain B-tree index on the column can drive it — instead of the naive
date_part(..) = $1/col::date = $1forms, which force a seq scan.extract_date— date equalityextract_date(col).eq(date)emits, depending on the column type (picked at compile time viaDateLike::Range):Datecolumns:(col BETWEEN $1 AND $1)— a closed range, equivalent to= $1(ClosedRange)Timestampcolumns:(col >= $1 AND col < $1 + 1)— a half-open range covering every instant of the day (HalfOpenRange)Timestamptzcolumns are rejected at compile time: which instants fall on a calendar date depends on the session time zone.extract_year/extract_month/extract_day— date partsEmit
CAST(date_part('<field>', col) AS integer), the exact form an expression index must be built on (EXTRACT maps to a different catalog function — the two diverged in PostgreSQL 14 — and would not match).On date columns,
extract_year(..).eq(..)/.between(..)with bounded year types (u8,u16,U15) is rewritten into a plain date range:so a regular B-tree index on the column works with no expression index.
i32years keep thedate_partcomparison, since part of their range is rejected bymake_date()at runtime.extract_time— time of dayTimecolumns stay bare (BareTime);equses the same range form asextract_dateTimestampcolumns are emitted asCAST(col AS time)(CastToTime), matching an expression index built on exactly that castTimestamptzis rejected: its cast totimeis onlySTABLE, so PostgreSQL rejects the expression index too.All entry points have runnable doctests asserting the emitted SQL, plus integration tests via
debug_query.