Skip to content

fix(pretty_print): keep with admin option portable - #900

Merged
psteinroe merged 8 commits into
mainfrom
fix/pretty-print-grant-admin-option
Oct 26, 2026
Merged

psteinroe merged 8 commits into
mainfrom
fix/pretty-print-grant-admin-option

Conversation

@psteinroe

Copy link
Copy Markdown
Collaborator

Stacked on #898.

grant r1 to u1 with admin option was rewritten to with admin true. Both spellings parse to the same AST since Postgres 16, but Postgres 15 and older only accept with admin option, so formatting broke the statement for them:

-- before
grant r1 to u1 with admin option;   -- formatted as: grant r1 to u1 with admin true;
-- Postgres 15: ERROR:  syntax error at or near "true"

-- after
grant r1 to u1 with admin option;

The GRANT role option emitter now writes admin option when the admin option is true. admin false, inherit true|false and set true|false keep the boolean spelling: they only exist since Postgres 16, which also accepts it. A true admin mixed with them prints as with inherit false, admin option, which Postgres 16 accepts too.

I ran the output against the local Postgres 15.19: with admin option and with admin option granted by current_role work, and with admin true is a syntax error there.

Snapshot changes

All reviewed; one category: with admin true → with admin option (privileges, both widths).

Test: grant_role_with_admin_option_keeps_the_portable_spelling in source_fidelity.rs covers admin option, admin true and the Postgres 16 options.

@psteinroe
psteinroe changed the base branch from fix/pretty-print-function-call-groups to main October 26, 2026 08:01
@psteinroe
psteinroe merged commit e393adb into main Oct 26, 2026
11 checks passed
@psteinroe
psteinroe deleted the fix/pretty-print-grant-admin-option branch October 26, 2026 08:05
psteinroe added a commit that referenced this pull request Oct 26, 2026
Stacked on #900.

Postgres keeps the first 63 bytes of an identifier (`NAMEDATALEN - 1`), and so does the AST the formatter works from. A longer name was therefore rewritten to its truncated form:

```sql
grant select on t to a_role_name_that_is_definitely_way_too_long_for_anything_postgres_keeps;
-- before
grant select on table t to a_role_name_that_is_definitely_way_too_long_for_anything_postgr;
-- after
grant select on table t to a_role_name_that_is_definitely_way_too_long_for_anything_postgres_keeps;
```

A quoted name could even end up cut in the middle, with a space before the closing quote. Postgres truncates both spellings the same way, so the meaning didn't change, but `docs/features/formatting.md` says the formatter doesn't change what is written.

**What changed**

`format_statement` now scans the statement for identifier tokens longer than 63 bytes, and maps the name the parser cut each one down to back to the name as written: dequoted for a quoted identifier, lower-cased for an unquoted one. The truncation clips to whole characters like `truncate_identifier`, so a multibyte character at the cut is handled. The identifier emitters (`emit_identifier`, `emit_identifier_maybe_quoted`, `emit_type_identifier_maybe_quoted`) look names up there and write the full name, quoted where needed as before. The AST check still passes, because the output parses back to the same truncated names.

A name is only restored when that is unambiguous. If two spellings are cut to the same 63 bytes, or a name is written both in full and already truncated, the AST name is kept as is.

**Tests**

- Unit tests in `identifiers.rs`: unquoted and quoted names, a multibyte character at the cut, and the two ambiguous cases.
- `identifiers_longer_than_postgres_keeps_are_written_in_full` in `source_fidelity.rs` covers a role, a quoted column and a table.

No snapshot changes. The snapshot harness drives the emitter directly without the source, and no fixture has an identifier this long.

**Not included: deeply nested statements (finding P22)**

About 40 nested CASEs, 20 nested subqueries or 45 levels of AND/OR still fail with "recursion limit reached". This limit is prost's decode limit, a constant 100 in prost 0.13. The only way to change it is the `no-recursion-limit` feature, which removes the limit for every prost user in the build. Without a limit, hostile input would hit stack overflows, which abort the process, in the decoder and in the recursive AST walkers (formatter, `iter`, normalisation) instead of returning an error. Doing that safely needs a bounded decoder or parsing on a thread with a larger stack. That isn't a small change, so I left it out of this PR.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant