Files
Mark ba3b07bffb Improved error reporting for the parser (#27387)
The PEG parser reported every syntax error as `syntax error at or near
"<token>"`, using the furthest token any alternative reached. This PR
keeps that message as the first line and, when it can say something
useful, adds one hint line below it. Hints are computed only after a
statement has failed to parse, so valid input pays nothing.

## Hints

Every hint is verified against the grammar before it is shown: a
correction or insertion it only suggested if re-parsing the statement
with that change makes the parser get past the original failure.

### Did you mean "KEYWORD"?

A word that is one or two edits away from a keyword the grammar accepts
at that point (Damerau-Levenshtein, one edit for words of up to four
letters). Keywords that would have been accepted as an identifier at
that position are never suggested, so an alias such as `alias` is not
"corrected" to `ALWAYS`.

```sql
SELECT * FROM t WHERE x = 1 GRUOP BY x;
Parser Error: syntax error at or near "GRUOP"
Did you mean "GROUP"?
```

A misspelled keyword is often swallowed as an alias, so the parser fails
one token late. The hint looks one token back in that case and reports
the error on the misspelling itself. Before, `SELECT a FORM t` was
reported at `t`.

```sql
SELECT a FORM t;
Parser Error: syntax error at or near "FORM"
Did you mean "FROM"?
LINE 1: SELECT a FORM t;
                 ^^^^
```

Other examples: `SELEC 1` -> `SELECT`, `WERE` -> `WHERE`, `LIMT` ->
`LIMIT`, `TABEL` -> `TABLE`,
`VALEUS` -> `VALUES`, `HAVNG` -> `HAVING`, `WHRE` -> `WHERE`.

### Missing "TOKEN"

Given where the grammar accepts only literal tokens, each expected
keyword, comma or closing bracket is inserted at the failure point in
turn. If exactly one of them makes the whole statement parse, it is
reported as missing.

```sql
SELECT (1 + 2 FROM t;
Parser Error: syntax error at or near "FROM"
Missing ")"
LINE 1: SELECT (1 + 2 FROM t;
                      ^^^^
```

```sql
SELECT CASE WHEN x THEN 1 FROM t;     -> Missing "END"
SELECT a b c FROM t;                  -> Missing ","
SELECT * FROM t ORDER x;              -> Missing "BY"
SELECT [1, 2 FROM t;                  -> Missing "]"
SELECT {'a': 1 FROM t;                -> Missing "}"
SELECT * FROM t WHERE a IN (1, 2      -> Missing ")"
```

The insertion search is skipped where an identifier or expression is
accepted: a missing identifier cannot be guessed, and a keyword that
happens to parse there is rarely what was meant (`LIMIT ALL` for `...
LIMIT;`).

### Expected ... / Expected one of: ...

What the grammar accepts at the failure point, gathered by re-running
the matcher with an autocomplete cursor there. This reuses the existing
autocomplete suggestion walk.

```sql
SELECT * FROM t WHERE
Parser Error: syntax error at end of input
Expected an expression
```

```sql
SELECT a::;                               -> Expected a type
SELECT * FROM t WHERE a = 1 AND OR x = 2; -> Expected an expression
SELECT * FROM t AS;                       -> Expected an identifier
SELECT * FROM t ORDER BY a LIMIT;         -> Expected one of: "ALL", an expression
SELECT * FROM;                            -> Expected one of: "(", "LATERAL", an identifier,
                                             a catalog name, a schema name,
                                             a table function name, a table name
CREATE TABLE t2(;                         -> Expected one of: ")", "CHECK", "CONSTRAINT",
                                             "FOREIGN", "PRIMARY", "UNIQUE", an identifier
```

Two rules keep this readable:

- Keywords that the identifier positions at that point accept as
identifiers are left out,
  using the same keyword-category flags `IdentifierMatcher` uses.
- Whole sub-grammars are summarised by a label. `rule_labels` in
`scripts/parser/grammar_types.yml` maps the expression precedence chain
to "an expression" and `Type` to "a type"; the `LabelRule` calls in
`matcher_factory.cpp` are generated from it and unknown rule names fail
generation.

If the list would still exceed eight entries and no label summarises it,
no hint is printed. `SELECT 1 2` stays a plain `syntax error at or near
"2"`.

### syntax error at end of input

When the statement ends where more input was required, the first line
now says so, as PostgreSQL does, instead of naming the last token that
happened to be there. The caret still points at that last token.
2026-10-27 09:53:01 +00:00
..
2026-10-26 09:08:55 +01:00
2026-04-22 08:53:31 +02:00
2026-10-10 13:41:48 +02:00
2026-08-03 12:25:10 +02:00
2026-08-26 16:32:54 +02:00
2024-12-10 17:03:09 +01:00
2026-10-14 09:53:22 +02:00
2026-03-13 09:01:16 +01:00
2025-11-27 15:27:32 +01:00
2025-10-21 09:54:29 +02:00