Skip to content

fix: Fix SQL casts - #28986

Merged
ritchie46 merged 7 commits into
mainfrom
worktree-sql-string-temporal-cast
Aug 26, 2026
Merged

ritchie46 merged 7 commits into
mainfrom
worktree-sql-string-temporal-cast

Conversation

@ritchie46

@ritchie46 ritchie46 commented Aug 26, 2026 •

Copy link
Copy Markdown
Member

This was regressed because of the 2.0 cast semantics which don't allow for this native casts.

As a drive by, fix the streaming date infer, which took the first value, even if it was flawed.

Made with opus 5

@github-actions github-actions Bot added A-sql Area: Polars SQL functionality fix Bug fix python Related to Python Polars rust Related to Rust Polars labels Aug 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

The uncompressed lib size after this PR is 60.0713 MB.

ritchie46 and others added 4 commits August 26, 2026 16:10
Extends the SQL string->temporal cast tests with literal operands, which
exercise the constant-folded (eager) path rather than the streaming node.

Moves the engine-consistency check out of the SQL suite into
test_to_datetime.py, parametrised over both engines and all three of
to_date/to_datetime/to_time, since it is about strptime rather than casts.

Also includes a doc-comment trim on parse_string_as_temporal that was
already present in the working tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MsiVh8nEYU5sBSXDcMgGSB
Format inference had three implementations. Two of them inferred from a
single value: `as_time`/`as_date_not_exact`/`as_datetime_not_exact` used the
first non-null value of the column, and the streaming `StrptimeInferNode`
used the first non-null value of each morsel, emitting an all-null morsel
when that value yielded no pattern.

The streaming case gave silently wrong results on the default engine. With
one unparseable value at the head of a 200k-row frame, `to_date(strict=False)`
returned 8334 nulls (one morsel) where the in-memory engine returned 1, so
the answer depended on where the bad values sat and on the morsel size.
`to_time` was wrong in both engines for the same reason.

Extracts the scan `infer.rs` already performed into `infer_from_values` and
uses it everywhere, so a value is null only when it is genuinely unparseable.

Also stops the streaming strict error reporting the internal alias assigned
during lowering ('_POLARS_TMP_0') rather than the column the user named.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MsiVh8nEYU5sBSXDcMgGSB
`convert_temporal_strings` re-inlined the same Date/Time/Datetime dispatch
that `parse_string_as_temporal` performs, so both now call the helper. It
returns `Option<Expr>` rather than `PolarsResult<Expr>`, which drops an
unreachable error arm.

The strptime dispatch repeated the same tail -- settle a failed inference,
then report the values that failed under `strict` -- in `to_date`,
`to_datetime` and `to_time`; that tail moves into `finish_strptime`.

Also drops a `match` on `first_non_null()` whose binding is no longer used,
for the early-return form the sibling module already uses.

No behavior change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MsiVh8nEYU5sBSXDcMgGSB
`first_non_null().is_some()` reads as a search when the predicate is just
"not entirely null"; state that directly. `first_non_null` already answers
both ends with an integer comparison, so this costs nothing.

Also trims comments that narrated the previous behaviour rather than
describing the code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MsiVh8nEYU5sBSXDcMgGSB
if infer_slot.is_none() {
let df = morsel.df().await;
let ca = df.columns()[0].str()?;
if let Some(idx) = ca.first_non_null() {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

first_non_null may be an invalid str

@github-actions

Copy link
Copy Markdown
Contributor

The uncompressed lib size after this PR is 60.3345 MB.

@ritchie46
ritchie46 merged commit eeabf77 into main Aug 26, 2026
@ritchie46
ritchie46 deleted the worktree-sql-string-temporal-cast branch August 26, 2026 16:40
@github-actions

Copy link
Copy Markdown
Contributor

The uncompressed lib size after this PR is 60.3345 MB.

dsprenkels added a commit to dsprenkels/polars that referenced this pull request Sep 8, 2026
The claim that `::date`/`::timestamp` on a SQL string literal no longer
works was true when written (pola-rs#28788), but pola-rs#28986/pola-rs#29007 made SQL parse
string->temporal instead of casting, so those all succeed again.

This commit was generated using Claude Opus 5.
paddymul added a commit to buckaroo-data/tallyman that referenced this pull request Sep 24, 2026
…231)

polars' CSV reader, given pl.Datetime(unit, time_zone=tz), parses text with
no UTC offset as UTC and converts it into the zone. A zoned column is now read
as text and parsed with str.to_datetime(time_unit, time_zone=tz), which
attaches the zone to offset-less text and converts text that has an offset,
as ADR-005 D9(a) says. The declared unit is kept (#145).

It raises instead of shifting a value when offset-less text names a time the
zone skips or repeats at a DST change, and when a column mixes text with and
without an offset: the streaming engine infers one format per column from its
first non-null value and parses every later batch with it.

polars 1.40.1's streaming parse writes a batch as nulls, without an error,
when that first value matches no format (fixed upstream in
pola-rs/polars#28986). The first non-null value of each zoned column is
parsed eagerly before the read, which raises in exactly that case.

After a failed read, _explain_zoned_failure reads the zoned columns again as
text and names the column, the row and the value: a mixed column, a skipped
or repeated wall-clock time, or text that is not a timestamp. The ladder now
also catches InvalidOperationError, which is how str.to_datetime reports
text that does not match the column's format.

Guard tests, which pass before and after: offset text converts, UTC reads
offset-less text as UTC, a naive timestamp stays naive, a zoned column
renamed by position beside inferred columns, and text that is not a
timestamp raises as the first value or a later one. ADR-005 D9 gets an
implementation note: (a) and (b) were both wrong about polars 1.40.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-sql Area: Polars SQL functionality fix Bug fix python Related to Python Polars rust Related to Rust Polars

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant