# 42601 — syntax_error

> PostgreSQL SQLSTATE 42601: syntax_error, source-backed diagnosis and recovery guidance.
---

# 42601

## At a glance {#at-a-glance}

SQLSTATE `42601` is **syntax_error** in Class `42`. `42601` is a syntax error. The selected parser path rejects `LIMIT 1, 2` with primary `LIMIT #,# syntax is not supported` and hint `Use separate LIMIT and OFFSET clauses.`

<!-- BEGIN SQLSTATE FACTS: generated by scripts/generate.py; do not edit -->

| Field | Value |
| --- | --- |
| SQLSTATE | `42601` |
| Condition | `syntax_error` |
| Status | `active` |
| Known present by | `7.4` |
| Locked snapshots | `9.0.23, 9.1.24, 9.2.24, 9.3.25, 9.4.26, 9.5.25, 9.6.24, 10.23, 11.22, 12.22, 13.23, 14.24, 15.19, 16.15, 17.11, 18.6, 19beta3` |
| Macros | `ERRCODE_SYNTAX_ERROR` |
| Aliases | `—` |

<!-- source facts: data/errcodes/42601.json -->
<!-- END SQLSTATE FACTS -->

## Meaning {#meaning}

`42601` is a syntax error. In the selected `gram.y` action, the comma form is rejected during parsing with the `ERROR` primary `LIMIT #,# syntax is not supported` and hint `Use separate LIMIT and OFFSET clauses.` The comma form is therefore not a request for “offset 1, then take 2” in PostgreSQL; the two values must be written as separate clauses.

The scanner and grammar share a general 42601 path. `scanner_yyerror` sets the SQLSTATE and reports either `syntax error at or near "%s"` for the current token or `syntax error at end of input` when the scanner is at the buffer end, with a lexer position. Grammar actions can supply a more specific message through the same formatter. The selected LIMIT branch is one producer among these parser and scanner branches, not a requirement for every `42601`.

## Diagnosis {#diagnosis}

Capture the exact statement, parser position, server version, and hint. For `at or near`, inspect the token named by `POSITION` and the text immediately before it; for `at end of input`, check an unclosed quote, dollar-quote, parenthesis, comment, or generated clause. Preserve the exact bytes sent by the query builder, including delimiters and substituted fragments, and replay that statement through the server parser before changing semantics. The parser rejects this grammar before execution, so the selected autocommit session remains `IDLE` and no rows are produced by the failed statement. In an explicit transaction, the same statement-level `ERROR` leaves the transaction in its failed state until `ROLLBACK` or an appropriate savepoint action; that is a different session boundary from the selected autocommit observation. Do not confuse a grammar error with an undefined object or a feature recognized but unsupported in its context.

## Response {#response}

Rewrite the statement using the grammar named by the hint. For the selected ordered three-row query, `LIMIT 1, 2` becomes `LIMIT 2 OFFSET 1` and returns rows `[2, 3]`; `LIMIT 1 OFFSET 0` would be a different result and is not an equivalent repair. Keep query builders version-aware when they emit dialect-specific pagination.

For a generic scanner error, repair the missing or misplaced token, quote, comment delimiter, or query-builder fragment identified by the position, then parse the complete statement again. Do not apply the LIMIT rewrite to a different 42601 message merely because it shares the SQLSTATE.

## Messages {#messages}

The selected parser branch raises an explicit `ERROR` with primary `LIMIT #,# syntax is not supported` and HINT `Use separate LIMIT and OFFSET clauses.` The scanner's generic fixed branches use `syntax error at or near "%s"` or `syntax error at end of input`, with the position generated from the lexer. The LIMIT primary/HINT identify the selected pagination subpath; a client exception without the server SQLSTATE and the diagnostics for the specific branch is not sufficient to identify that subpath, while other valid 42601 producers may have different primaries.

## Representative case {#case}

<!-- BEGIN SQLSTATE SNIPPET: syntax_limit_comma -->

The page uses the same statements as the runner registry. Generated names such as `syntax_schema` and `syntax_role` are replaced by disposable runner values when executed.

```sql
SELECT n FROM generate_series(1, 3) AS s(n) ORDER BY n LIMIT 1, 2;
SELECT n FROM generate_series(1, 3) AS s(n) ORDER BY n LIMIT 2 OFFSET 1;
```

<!-- END SQLSTATE SNIPPET -->

The selected 18.6 run reports the structured diagnostic and passes the repair assertions; the 10.21 run passes the same case-specific checks. The downloadable case and evidence projections are [`42601 case JSON`](../data/cases/42601.json) and [`authored evidence`](../data/evidence/42601.json). The runner manifest is `verify/cases/42601/cases.json`, and the page SQL is checked against its shared registry.

## Versions {#versions}

The selected natural runtime scope is PostgreSQL 18.6 and 10.21; it does not infer behavior for every intermediate release.

## Related {#related}

- [`42602` — invalid_name](../42602/)
- [`42703` — undefined_column](../42703/)

## Sources {#sources}

- `src.errcodes.18.6` — fixed `errcodes.txt` definition at commit `724edf9bde9d356724ad384a2e196edc3c9f80f7`; SHA-256 `6e8de346643ba84aa3c9c6a73360acfc7b2dfb89162c06c08ce9bf5bcd5bbcba` ([source](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/errcodes.txt)).
- `src.limit-comma.18.6` — `src/backend/parser/gram.y` at `REL_18_6` commit `724edf9bde9d356724ad384a2e196edc3c9f80f7`; fixed blob SHA-256 `7e548b673a1e03eb3a56c5eb9ad92d8e11095fac76e14cb258ca851f58274724` ([source](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/parser/gram.y#L13313-L13317)).
- `src.limit-comma.10.23` — `src/backend/parser/gram.y` at `REL_10_23` commit `02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4`; fixed blob SHA-256 `cebad6a5a2bdf68e9e5a4d2e04fa7f5dbf37efac99f5edb73cc661dae1536bad` ([source](https://github.com/postgres/postgres/blob/02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4/src/backend/parser/gram.y#L11200-L11204)).
- `src.scanner-syntax.18.6` / `src.scanner-syntax.10.23` — fixed `src/backend/parser/scan.l` `scanner_yyerror` branches for `at end of input` and `at or near`; blob SHA-256 `bf453f1ae3c22b84fea6f8e8bed4fda476b7cfa49b234efbf92ca228234d0293` / `a006581a25c659d59b29837010617503ff431f881e3cf0b54a99aa1cc445fe6c` ([18.6 source](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/parser/scan.l#L1228-L1240), [10.23 source](https://github.com/postgres/postgres/blob/02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4/src/backend/parser/scan.l#L1115-L1127)).
- `src.calls.REL_18_6` / `src.calls.REL_10_23` — fixed call scans, SHA-256 `9ee8a0e81d8f0825c5c1ae45583439859a26e602bdd4ce2f2a62aa278867ccbf` / `00d16d3eb01b71ccf1b245c8f3102f9d0ec9f36fb02777b8dd1b99fcb263040c`.
