Skip to content

fix: name the unsupported specifier in date.to_text errors - #6351

Open
prql-bot wants to merge 4 commits into
mainfrom
fix/date-format-specifier-errors
Open

prql-bot wants to merge 4 commits into
mainfrom
fix/date-format-specifier-errors

Conversation

@prql-bot

@prql-bot prql-bot commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

date.to_text rejects a format specifier the target dialect can't express, but until now only BigQuery said which specifier — every other dialect raised a bare "PRQL doesn't support this format specifier". The error's span covers the whole format string, so on a realistic format there was nothing to act on:

 3 │ derive y = (date.to_text "%d %P" a)
   │                          ───┬───
   │                             ╰───── PRQL doesn't support this format specifier

That now reads format specifier `%P` is not supported for Postgres, and the same holds for Redshift, MSSQL, MySQL, ClickHouse, DuckDB and BigQuery — all seven dialects go through one unsupported_format_specifier constructor instead of repeating a message each.

BigQuery's existing message had a second problem this fixes. chrono parses a % escape it doesn't recognize into Item::Error, which has no specifier to name, so date.to_text "%Q" reported format specifier `Error` is not supported for BigQuery — a chrono Debug variant name presented as if it were something the author had typed, and blamed on BigQuery rather than on the invalid escape. It now reports "date format string contains an unrecognized specifier" on every dialect.

To keep that class of leak from coming back, chrono_item_to_strftime returns Option<String> rather than falling back to format!("{item:?}"). A specifier chrono understands but the helper has no spelling for — %j is Numeric::Ordinal — falls back to the generic message rather than printing Ordinal. Widening that mapping so those specifiers get named too is a separate change; today's behavior for them is unchanged.

One limitation worth a maintainer's eye: the specifier is named by chrono's canonical spelling of the parsed item, not by the text that was typed, because chrono keeps no source offsets. date.to_text "%e" on Postgres therefore reports format specifier `%_d` is not supported for Postgres — chrono documents %e as the same specifier as %_d, so the name is correct but isn't literally in the source, and the span doesn't narrow it down either. The same applies to %k (%_H) and %l (%_I). Naming the typed text would need our own scan of the format string alongside chrono's parse; the alternative available here is the old generic message, which names nothing. Pinned by alias_specifier_is_named_by_its_canonical_spelling so it reads as a decision rather than an oversight.

Four tests in dialect.rs cover this: the per-dialect naming, the Item::Error message, the generic fallback for an unnameable specifier, and the alias spelling above. The first three fail before the change.

Verification

At this head, cargo clippy -p prqlc --all-targets -- -D warnings is clean and the 21 sql::dialect unit tests pass. task prqlc:pull-request was run against the code change (commit 0d9d8732, before the alias test was added) and passed everything except queries::results::read_csv, which fails in this sandbox because DuckDB cannot reach extensions.duckdb.org to install its json extension (ERROR Could not establish connection) — a network restriction rather than anything this change touches. CI runs the full matrix with network access.

The user-facing messages were checked end to end against target:sql.postgres:

format message
%d %P format specifier `%P` is not supported for Postgres
%Q date format string contains an unrecognized specifier
%j PRQL doesn't support this format specifier
%e format specifier `%_d` is not supported for Postgres

Every dialect but BigQuery rejected an unmappable format specifier with
"PRQL doesn't support this format specifier", and the error's span covers
the whole format string — so `"%d %P"` gave no clue which of the two the
dialect couldn't express.

Route all seven mapping dialects through one constructor that names the
specifier and the dialect. `Item::Error` — chrono's stand-in for a `%`
escape it doesn't recognize at all — now reports a malformed format string
instead of BigQuery's `format specifier \`Error\` is not supported for
BigQuery`, and `chrono_item_to_strftime` returns `Option` so an item with
no strftime spelling falls back to the generic message rather than leaking
a chrono `Debug` variant name.
chrono parses %e down to the same Item as %_d and keeps no source
offsets, so the error names the canonical spelling rather than the text
that was typed. Record that as intended with a test and a note on
unsupported_format_specifier.

@prql-bot prql-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Two gaps worth closing; I'm pushing both as a follow-up commit rather than leaving them open.

The Item::Error branch rests on a chrono behaviour no test pins. unrecognized_escape_reports_a_malformed_format_string constructs Item::Error directly, so it pins the message but not the premise underneath it — that chrono yields Item::Error for an escape it doesn't recognize. If a chrono upgrade changed that (to Literal("%Q"), say), date.to_text "%Q" would silently fall back to the generic message and every test in the file would stay green. alias_specifier_is_named_by_its_canonical_spelling already goes end to end through translate_prql_date_format; one assertion of the same shape for "%Q" pins both halves.

The book's alias list omits %e. web/book/src/reference/stdlib/date.md says space-padded specifiers — "chrono's %_-prefixed forms and their %k / %l aliases" — are unsupported, but %e is the third alias of that family, and it is the one this PR's new test pins. Someone who hits the new format specifier `%_d` is not supported for Postgres message and goes looking for %e in the book finds nothing.

Also add `%e` to the book's list of chrono's space-padded aliases, alongside
`%k` and `%l` — it is the alias the new `%_d` error text reports.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant