Conversation
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
left a comment
There was a problem hiding this comment.
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.
date.to_textrejects 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: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 oneunsupported_format_specifierconstructor instead of repeating a message each.BigQuery's existing message had a second problem this fixes. chrono parses a
%escape it doesn't recognize intoItem::Error, which has no specifier to name, sodate.to_text "%Q"reportedformat specifier `Error` is not supported for BigQuery— a chronoDebugvariant 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_strftimereturnsOption<String>rather than falling back toformat!("{item:?}"). A specifier chrono understands but the helper has no spelling for —%jisNumeric::Ordinal— falls back to the generic message rather than printingOrdinal. 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 reportsformat specifier `%_d` is not supported for Postgres— chrono documents%eas 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 byalias_specifier_is_named_by_its_canonical_spellingso it reads as a decision rather than an oversight.Four tests in
dialect.rscover this: the per-dialect naming, theItem::Errormessage, 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 warningsis clean and the 21sql::dialectunit tests pass.task prqlc:pull-requestwas run against the code change (commit0d9d8732, before the alias test was added) and passed everything exceptqueries::results::read_csv, which fails in this sandbox because DuckDB cannot reachextensions.duckdb.orgto install itsjsonextension (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:%d %Pformat specifier `%P` is not supported for Postgres%Qdate format string contains an unrecognized specifier%jPRQL doesn't support this format specifier%eformat specifier `%_d` is not supported for Postgres