# 2202E — array_subscript_error

> PostgreSQL uses SQLSTATE 2202E for array subscript and shape errors. Separate an out-of-bounds read that returns NULL from assignment, slice, constructor, and concatenation paths that validate array dimensions.
---

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

`2202E` is PostgreSQL's `array_subscript_error` condition in Class 22, `Data Exception`. The directory keeps `ERRCODE_ARRAY_ELEMENT_ERROR` as a compatibility alias and uses `ERRCODE_ARRAY_SUBSCRIPT_ERROR` for the named condition. Both macros encode the same SQLSTATE.

Do not treat every out-of-range-looking expression as an error. PostgreSQL documents that an array subscript read outside the current bounds returns `NULL`; a read with the wrong number of subscripts also returns `NULL`. Array slices have their own historical rules: a slice wholly outside the bounds can produce an empty zero-dimensional array, while a partially overlapping slice is reduced to the overlap.

`2202E` is raised when another path validates a shape or subscript and rejects it. In the core source this includes incompatible dimensions during array concatenation or construction, invalid slice boundaries, and some subscripted assignment checks. The operation matters as much as the index value.

The executable representative case is `incompatible_array_dimensions`. It passed on PostgreSQL 18.6 and 10.21. The case raises `2202E` for an incompatible concatenation, then proves that the same autocommit connection is `IDLE` and can execute a valid follow-up concatenation. The SQL excerpt below is the complete ordered pair from the shared snippet registry; the runner remains the single owner of setup and cleanup.

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

| Field | Value |
| --- | --- |
| SQLSTATE | `2202E` |
| Condition | `array_subscript_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_ARRAY_ELEMENT_ERROR, ERRCODE_ARRAY_SUBSCRIPT_ERROR` |
| Aliases | `ERRCODE_ARRAY_ELEMENT_ERROR` |

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

## Meaning and trigger paths {#meaning}

The 18.6 directory row is `2202E E ERRCODE_ARRAY_SUBSCRIPT_ERROR array_subscript_error`. The preceding alias row is `2202E E ERRCODE_ARRAY_ELEMENT_ERROR`; the source comment explains that SQL99's “array element error” is the subscript error. Code that still names the alias is therefore referring to this same SQLSTATE, not to a second condition.

An array has a rank, a length for each dimension, and lower bounds. PostgreSQL does not require every array to start at one, so diagnostics should inspect the actual bounds rather than assume them. The documented `array_ndims`, `array_dims`, `array_lower`, `array_upper`, and `cardinality` functions expose the metadata needed to do that. Use them in the same session when an error needs to be diagnosed; they are inspection tools, not evidence that a particular error was triggered.

The main core paths are:

1. **Array element or slice assignment.** `arrayfuncs.c` validates subscripts and slice boundaries. One-dimensional arrays can be enlarged by assigning to a new element, with intervening positions filled by `NULL`; multidimensional enlargement is not supported. A slice assigned to an empty array must provide both boundaries. These rules mean that an assignment outside the current read bounds must be analyzed as an assignment operation, not inferred from `SELECT a[n]`.
2. **Array concatenation.** `array_cat` raises `2202E` when equal-rank arrays have different lengths or lower bounds on a non-concatenated dimension. This is the path exercised by the representative case.
3. **Multidimensional construction.** The expression evaluator raises the same condition when non-empty array expressions used to form a multidimensional array have incompatible dimensions.

The source tree contains other callers, including data-type helper paths. Class 22 is the broad data-exception class; the `2202E` row and its source function identify when this particular shape or subscript contract failed.

## Messages and diagnostics {#messages}

The canonical executable excerpt is:

<!-- BEGIN SQLSTATE SNIPPET: incompatible_array_dimensions -->
```sql
SELECT ARRAY[[1,2]] || ARRAY[[3]];
SELECT ARRAY[1,2] || ARRAY[3,4];
```
<!-- END SQLSTATE SNIPPET -->

The first statement has two two-dimensional arrays whose inner dimensions differ. PostgreSQL 18.6 reports:

```text
SQLSTATE: 2202E
severity: ERROR
message_primary: cannot concatenate incompatible arrays
message_detail: Arrays with differing element dimensions are not compatible for concatenation.
source: array_userfuncs.c / array_cat / line 450
```

The same case on PostgreSQL 10.21 reports the same primary and detail text, with the historical `array_userfuncs.c` source line 356. The second statement returns `{1,2,3,4}`. The message and detail are templates from the concatenation path, not a universal wording for every `2202E`.

Other source-confirmed templates include `array subscript out of range`, `array slice subscript must provide both boundaries` with a detail explaining empty-array assignment, and `upper bound cannot be less than lower bound`. Preserve `message_detail`, `message_hint`, `source_file`, `source_function`, and `source_line` when the driver exposes them; they often distinguish a slice check from a concatenation check.

## Diagnosis {#diagnosis}

Start by classifying the expression:

- A plain element read such as `a[999]` can legitimately produce `NULL`. Check `a IS NULL`, the subscript expressions, and the stored bounds before calling it a server error.
- A slice read can return `NULL`, an empty zero-dimensional array, or a reduced overlap according to the documented slice rules. Do not map those values to `2202E` without an error response.
- Assignment, array construction, and concatenation execute validation code. Record the array rank, dimensions, lower bounds, number of supplied subscripts, and whether the statement is changing the value.

For a real error, record SQLSTATE, severity, primary message, detail, hint, statement position, and the relation or function context. Compare the failing expression with `array_ndims`, `array_dims`, `array_lower`, and `array_upper` from the same value or source expression. For concatenation, compare every non-concatenated dimension and lower bound. For a slice, verify both boundaries and their order. For construction, inspect every subarray's shape.

In PL/pgSQL, a handler can catch `array_subscript_error` or `SQLSTATE '2202E'`, but the handler's transaction behavior depends on the block. A block with an `EXCEPTION` clause runs its protected body in a subtransaction; if the body errors, persistent changes made there are rolled back before the handler runs. A broad `OTHERS` handler also has documented exclusions, so use a specific condition when the application intends to repair an array operation.

## Response {#response}

Repair the operation that violated the shape contract. Normalize dimensions and lower bounds before concatenating; provide complete, ordered slice boundaries; use a one-dimensional expansion only when its `NULL` fill behavior is intended; or rebuild multidimensional values with matching subarrays. If `NULL` is a valid result of a read, handle it as a value and do not “fix” it with `COALESCE` before deciding whether the application should distinguish an absent element from a stored `NULL` element.

The representative error ran in autocommit. After the failed statement the connection was `IDLE`, and the valid concatenation succeeded. In an explicit transaction, an `ERROR` normally aborts the transaction until `ROLLBACK` or a rollback to a savepoint; the connection itself need not be closed. A PL/pgSQL block with an exception clause can contain the failure in its protected subtransaction body, rolling back that body's changes before its handler runs. Verify the actual client transaction status rather than deriving connection fate from `2202E` alone.

## Versions {#versions}

The catalogue records `2202E` in the locked 7.4–8.4.22 pre-9.0 formal sources, every formal snapshot from 9.0.23 through 18.6, and the 19 Beta 3 preview. The same-tag `REL8_1_4` `errcodes.sgml` table already lists `2202E` as `array_subscript_error`, confirming that condition-name observation by 8.1.4. Candidate source gaps remain for 7.0–7.3, so the 7.4 observation is a presence boundary rather than an exact introduction version. The fixed 18.6 source commit is `724edf9bde9d356724ad384a2e196edc3c9f80f7`; the alias and named macro are both present in its `errcodes.txt` row.

The incompatible-dimension case passed on PostgreSQL 18.6 and 10.21 with the same primary/detail wording and different source line numbers. That cross-version result does not guarantee unchanged wording for every older minor release or every other `2202E` call path.

## Related {#related}

[`22000` — `data_exception`](../22000/) is the broad Class 22 category. [`22005` — `error_in_assignment`](../22005/) is a distinct assignment condition and should not be substituted for an array-specific `2202E` path. [`22P02` — `invalid_text_representation`](../22p02/) concerns input text parsing. `2202E` also differs from a successful out-of-bounds read, which returns `NULL` under the documented array rules.

## Sources {#sources}

The structured evidence is recorded in the [public evidence JSON](../data/evidence/2202e.json). Source records are pinned to PostgreSQL commit `724edf9bde9d356724ad384a2e196edc3c9f80f7`; runtime records retain the shared snippet registry, both target versions, and run `2202E-snippet-registry-final-20260909`.

- `src.errcodes.18.6` — [`errcodes.txt`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/errcodes.txt#L159-L164)
- `src.array-userfuncs.18.6` — [`array_userfuncs.c`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/adt/array_userfuncs.c#L443-L450)
- `src.arrayfuncs.18.6` — [`arrayfuncs.c`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/adt/arrayfuncs.c#L2644-L2653)
- `src.exec-expr.18.6` — [`execExprInterp.c`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/executor/execExprInterp.c#L3549-L3565)
- `doc.array.18` and `doc.func.18` — [Arrays](https://www.postgresql.org/docs/18/arrays.html) and [Array Functions and Operators](https://www.postgresql.org/docs/18/functions-array.html)
- `doc.transactions.18` — [Transactions](https://www.postgresql.org/docs/18/tutorial-transactions.html)
