Current section
Files
Jump to
Current section
Files
CHANGELOG.md
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## v0.2.0 — 2026-08-23
### Added
- `db_prepared_statements` (PostgREST `db-prepared-statements`,
`PGRST_DB_PREPARED_STATEMENTS`, default `true`): the hot-path statements —
the auth preamble, reads, mutations, and RPC — are cached as named prepared
statements on each pool connection, skipping the parse step when a query
shape repeats. Set it to `false` behind a transaction-mode pooler such as
PgBouncer (#127).
### Changed
- Typed filter values and RPC scalar arguments are now bound as parameters
(`($n::text)::<type>`) instead of being inlined as escaped literals
(`'<v>'::<type>`). The server-side coercion — and every error it can
raise — is identical (PostgreSQL I/O-conversion casts), the conformance
suite is byte-for-byte unchanged, and it matches the SQL PostgREST
executes (`"id" = $1`). This is what makes the statement cache effective:
requests differing only in their values now share one SQL text (#127).
### Fixed
- PGRST205/PGRST202 not-found errors (and their "Perhaps you meant" hints)
now qualify the missing table/function with the request's active schema,
matching real PostgREST (`Error.hs` builds `qi <> "." <> name` from the
resolved profile). Previously area-mirror schemas were reported as
`test.<name>` — an assumption the conformance suite's oracle disproved.
Conformance `spec/` bumped to `v16.0.0-suite.3`, which pins the corrected
behavior (cases 1360/1368/1373).
## v0.1.0 — 2026-08-18
First release. Bier serves a RESTful API generated at boot from PostgreSQL
introspection, reproducing the request/response behavior of
[PostgREST](https://postgrest.org) v16.0.
### The API surface
- Reads with the full PostgREST query grammar: `select` (columns, aliases,
casts, JSON paths, computed columns, aggregates), horizontal filters and
the operator set, logical trees (`and`/`or`, negation, nesting),
quantifiers, ordering, `limit`/`offset` and `Range` pagination, and
resource embedding (many-to-one, one-to-many, many-to-many, `!inner`,
spread, aliases, disambiguation).
- Mutations — `POST` insert, `PATCH` update, `PUT` single-row upsert,
`DELETE` — with `Prefer: return=`, `resolution=`, `missing=default`,
`handling=strict` and `max-affected=`.
- `/rpc/<function>` calls over `GET`/`HEAD`/`POST`, rendering every routine
return kind (scalar, composite, `SETOF`, `TABLE(...)`, `void`).
- Every request compiled into a **single parameterized SQL statement** whose
response bodies are byte-identical to PostgREST's, row separators and
embedded-JSON internals included.
- Content negotiation across `application/json`, `text/csv`,
`application/geo+json` (relations, mutations, RPC and embedded reads,
wherever PostGIS is installed), the `vnd.pgrst.object`/`array` variants
with `nulls=stripped`, and `vnd.pgrst.plan`.
- `Prefer: timezone=<tz>` for per-request `timestamptz` rendering, including
numeric UTC offsets.
- A generated OpenAPI document at the root, with per-role privilege
filtering, an opt-in OpenAPI 3.0.3 emitter (`openapi_version: "3.0"`, a
Bier extension), and `db_root_spec` to replace it wholesale.
### Authentication
- JWT verification through JOSE: HS256/384/512, plus RS/ES/PS/EdDSA from a
JWK or JWK Set, with the algorithm family decided by the key's shape rather
than the token's `alg` header.
- Role switching and request-scoped GUCs (`request.jwt.claims`, headers,
cookies, `app.settings.*`), applied as a single batched
`SELECT set_config(…)` statement per request — one database round trip
for the whole preamble, the same shape PostgREST executes.
- `jwt_role_claim_key` as an [RFC 9535][] JSON Path into the claims,
`jwt_secret_is_base64`, `jwt_aud`, and a per-instance verification cache.
- `db_pre_request`, run inside the request transaction before the main query.
### Operations
- Multiple named instances per BEAM node, each with its own configuration,
connection pool, runtime-built router, and Bandit server.
- Schema-cache reload over `LISTEN`/`NOTIFY` and `Bier.reload_schema_cache/1`;
a failed reload leaves the previous snapshot serving.
- Standalone operation with no host application: PostgREST-compatible
`PGRST_*` environment variables, a config-file parser, the in-database
(`ALTER ROLE … SET pgrst.*`) configuration source, a `bier` escript with
`--dump-config`/`--example`, a `mix release` target, and a Dockerfile.
- Observability: `:telemetry` events for requests, schema-cache loads, pool
status, JWT cache and SSE; an Apache-combined access log gated by
`log_level`, with optional `log_query`; `Server-Timing`; a trace-header
passthrough; and `/live` + `/ready` on an optional admin listener.
- Query cancellation at the PostgreSQL backend when the HTTP client
disconnects (`cancel_on_disconnect`, on by default) — something PostgREST
cannot do.
### Beyond PostgREST
- **Realtime events**: a config-gated SSE endpoint bridging Postgres
`LISTEN`/`NOTIFY` to browsers (`events_channels`, `events_path`,
`events_heartbeat_interval`). PostgREST has no equivalent.
- **`Vary: Origin`** on CORS responses that echo the request's `Origin`, which
upstream omits.
- **RFC 4180 CSV quoting**, where upstream emits malformed CSV for values
containing quotes or newlines.
- **`Server: bier/<version>`** and an OpenAPI `info.version` reporting Bier's
own version. The PostgREST dialect is advertised through the document's
`externalDocs` instead.
The README's "Deliberate divergences from PostgREST" section is the
authoritative list; everything else is intended to match upstream byte for
byte.
[RFC 9535]: https://www.rfc-editor.org/rfc/rfc9535