# postgres.sqlstate

> A module of postgres's API.

SQLSTATE handling: structured errors, and the codes libpq reports.

Every error a PostgreSQL server sends carries a five-character SQLSTATE code
(`23505`, `40P01`, ...) alongside a human-readable message — see
https://www.postgresql.org/docs/current/errcodes-appendix.html. Mojo 1.x can
only `raise Error(String)`, so there is no field to hang the code on when an
error crosses a `raises` boundary. Instead this module packs the code into a
parseable token inside the message text (`"[SQLSTATE 23505]"`), and gives
callers two ways back out:

- `PostgresError` is the structured value: build one from a libpq result,
  call `.to_error()` to raise it, and keep the original struct around
  wherever a `try`/`except` block has an `Error` in hand and wants the
  fields back — reconstruct a `PostgresError(...)`, or just pull the code
  via `sqlstate_of(err)`.
- `sqlstate_of()` recovers the raw code from an `Error` or a `String` even
  when the `PostgresError` itself is gone — the common case, since Mojo's
  `except err:` only ever hands back an `Error`.

`08001` and `08006` are not real server SQLSTATEs — libpq itself reports no
SQLSTATE at all for a connection that never came up or that dropped mid-use
(`PQerrorMessage` on a failed/lost connection carries only free text). The FFI
layer synthesizes `SQLCLIENT_UNABLE_TO_ESTABLISH` (08001) for a failed
`PQconnectdb` and `CONNECTION_FAILURE` (08006) for a connection lost after it
was established, mirroring the meaning class 08 has for every other client.

## Structs

### `PostgresError`

```mojo
struct PostgresError
```

A PostgreSQL error, decomposed the way `PQresultErrorField` reports it: severity, SQLSTATE, primary message, and the optional detail/hint the server adds to explain or suggest a fix.

All fields are `String` and empty when the server didn't supply them.
`sql` is not one of libpq's fields — it's the statement text the caller
was executing, attached here so the formatted error is self-contained
in a log line.

Implements: `Copyable`, `Movable`, `Writable`

Fields:

- `severity: String`: `ERROR`, `FATAL`, `PANIC`, or a localized equivalent. Empty if unknown.
- `sqlstate: String`: The five-character SQLSTATE code, e.g. `23505`. Empty if the source of this error didn't have one (see the module docstring).
- `message: String`: The primary human-readable message.
- `detail: String`: An optional secondary message with more detail. Empty if absent.
- `hint: String`: An optional suggestion of how to fix the problem. Empty if absent.
- `sql: String`: The SQL text being executed when the error occurred. Empty if not attached.

#### `__init__`

```mojo
def __init__(out self, severity: String = "", sqlstate: String = "", message: String = "", detail: String = "", hint: String = "", sql: String = "")
```

#### `format`

```mojo
def format(self) -> String
```

Render the multi-line form this type prints and raises as.

Line one is `"postgres [SQLSTATE 23505] <message>"`, or
`"postgres: <message>"` when there's no SQLSTATE to report. A
`DETAIL:` line follows if `detail` is set, then a `HINT:` line if
`hint` is set, then a `SQL:` line — collapsed to one line and
capped at 200 characters — if `sql` is set.

#### `__str__`

```mojo
def __str__(self) -> String
```

Same as `format()`.

#### `write_to`

```mojo
def write_to(self, mut writer: T)
```

Write `format()` to `writer` — what makes `String(err)` and `print(err)` produce the formatted form.

#### `to_error`

```mojo
def to_error(self) -> Error
```

Wrap `format()` in an `Error`, ready to `raise`.

`sqlstate_of()` recovers `self.sqlstate` back out of the raised
`Error` on the far side of a `try`/`except`.

#### `is_unique_violation`

```mojo
def is_unique_violation(self) -> Bool
```

See the free function of the same name.

#### `is_serialization_failure`

```mojo
def is_serialization_failure(self) -> Bool
```

See the free function of the same name.

#### `is_deadlock`

```mojo
def is_deadlock(self) -> Bool
```

See the free function of the same name.

#### `is_integrity_violation`

```mojo
def is_integrity_violation(self) -> Bool
```

See the free function of the same name.

#### `is_connection_error`

```mojo
def is_connection_error(self) -> Bool
```

See the free function of the same name.

#### `is_retryable`

```mojo
def is_retryable(self) -> Bool
```

See the free function of the same name.

## Functions

### `sqlstate_of`

```mojo
def sqlstate_of(message: String) -> String
```

Extract the code inside the first `"[SQLSTATE XXXXX]"` token in `message`.

Returns `""` if no such token is present, or if what's between the
brackets isn't exactly five uppercase-alphanumeric characters (a
truncated or hand-edited message).

```mojo
def sqlstate_of(err: Error) -> String
```

Extract the SQLSTATE code from a caught `Error` — the usual case, since Mojo's `except err:` hands back only an `Error`. See the `String` overload for the parsing rules.

### `sqlstate_class`

```mojo
def sqlstate_class(code: String) -> String
```

The two-character SQLSTATE class (`"23"` for `"23505"`), or `""` if `code` is shorter than two characters.

The class is the coarse-grained grouping PostgreSQL itself uses in the
errcodes appendix — class `23` is every integrity-constraint violation,
class `08` is every connection error, and so on.

### `is_unique_violation`

```mojo
def is_unique_violation(code: String) -> Bool
```

True for `UNIQUE_VIOLATION` (`23505`).

### `is_serialization_failure`

```mojo
def is_serialization_failure(code: String) -> Bool
```

True for `SERIALIZATION_FAILURE` (`40001`), the code a `SERIALIZABLE` transaction gets when it loses a conflict.

### `is_deadlock`

```mojo
def is_deadlock(code: String) -> Bool
```

True for `DEADLOCK_DETECTED` (`40P01`).

### `is_integrity_violation`

```mojo
def is_integrity_violation(code: String) -> Bool
```

True for class `23` — every constraint violation (unique, foreign key, not-null, check).

### `is_connection_error`

```mojo
def is_connection_error(code: String) -> Bool
```

True for class `08` — every connection-establishment or connection-loss error, including the synthesized `08001`/`08006` documented at the top of this module.

### `is_retryable`

```mojo
def is_retryable(code: String) -> Bool
```

True for `SERIALIZATION_FAILURE` or `DEADLOCK_DETECTED` — the two codes PostgreSQL's own docs recommend retrying the whole transaction for, rather than surfacing to the caller.

## Aliases

### `UNIQUE_VIOLATION`

```mojo
comptime UNIQUE_VIOLATION = StringSpan("23505")
```

### `FOREIGN_KEY_VIOLATION`

```mojo
comptime FOREIGN_KEY_VIOLATION = StringSpan("23503")
```

### `NOT_NULL_VIOLATION`

```mojo
comptime NOT_NULL_VIOLATION = StringSpan("23502")
```

### `CHECK_VIOLATION`

```mojo
comptime CHECK_VIOLATION = StringSpan("23514")
```

### `SERIALIZATION_FAILURE`

```mojo
comptime SERIALIZATION_FAILURE = StringSpan("40001")
```

### `DEADLOCK_DETECTED`

```mojo
comptime DEADLOCK_DETECTED = StringSpan("40P01")
```

### `UNDEFINED_TABLE`

```mojo
comptime UNDEFINED_TABLE = StringSpan("42P01")
```

### `UNDEFINED_COLUMN`

```mojo
comptime UNDEFINED_COLUMN = StringSpan("42703")
```

### `SYNTAX_ERROR`

```mojo
comptime SYNTAX_ERROR = StringSpan("42601")
```

### `INSUFFICIENT_PRIVILEGE`

```mojo
comptime INSUFFICIENT_PRIVILEGE = StringSpan("42501")
```

### `DUPLICATE_TABLE`

```mojo
comptime DUPLICATE_TABLE = StringSpan("42P07")
```

### `QUERY_CANCELED`

```mojo
comptime QUERY_CANCELED = StringSpan("57014")
```

### `INVALID_TEXT_REPRESENTATION`

```mojo
comptime INVALID_TEXT_REPRESENTATION = StringSpan("22P02")
```

### `NUMERIC_VALUE_OUT_OF_RANGE`

```mojo
comptime NUMERIC_VALUE_OUT_OF_RANGE = StringSpan("22003")
```

### `DIVISION_BY_ZERO`

```mojo
comptime DIVISION_BY_ZERO = StringSpan("22012")
```

### `LOCK_NOT_AVAILABLE`

```mojo
comptime LOCK_NOT_AVAILABLE = StringSpan("55P03")
```

### `ADMIN_SHUTDOWN`

```mojo
comptime ADMIN_SHUTDOWN = StringSpan("57P01")
```

### `CONNECTION_FAILURE`

```mojo
comptime CONNECTION_FAILURE = StringSpan("08006")
```

### `SQLCLIENT_UNABLE_TO_ESTABLISH`

```mojo
comptime SQLCLIENT_UNABLE_TO_ESTABLISH = StringSpan("08001")
```

### `CONNECTION_DOES_NOT_EXIST`

```mojo
comptime CONNECTION_DOES_NOT_EXIST = StringSpan("08003")
```

Raised by `pool.ConnectionPool.lease` for a lease taken after `pool.ConnectionPool.close`.

### `TOO_MANY_CONNECTIONS`

```mojo
comptime TOO_MANY_CONNECTIONS = StringSpan("53300")
```

Raised by `pool.ConnectionPool.lease` when `pool.PoolConfig.acquire_timeout_ms` elapses with every connection checked out.  The server uses this same code when it runs out of backends, which is the same condition one layer down.
