@crvouga/mockingbird-service-postgres 3.1.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/COMPATIBILITY.md +35 -5
- package/README.md +147 -4
- package/compat/coverage.json +8 -7
- package/compat/divergences.json +37 -2
- package/compat/requirements.json +1 -1
- package/compat/sections/cat.ts +49 -0
- package/compat/sections/ddl.ts +11 -0
- package/compat/unsupported-register.json +0 -3
- package/dist/admin.js +3330 -668
- package/dist/admin.js.map +4 -4
- package/dist/api/database.d.ts +11 -0
- package/dist/ast/nodes.d.ts +43 -1
- package/dist/constraints/enforce.d.ts +2 -0
- package/dist/executor/alter-column.d.ts +27 -0
- package/dist/executor/comment.d.ts +4 -0
- package/dist/executor/ddl.d.ts +1 -2
- package/dist/executor/dml.d.ts +2 -0
- package/dist/executor/extensions.d.ts +11 -0
- package/dist/executor/maintenance.d.ts +25 -0
- package/dist/expressions/context.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3249 -549
- package/dist/index.js.map +4 -4
- package/dist/parser/parser.d.ts +9 -0
- package/dist/schema/catalog-extensions.d.ts +5 -0
- package/dist/schema/extensions.d.ts +33 -0
- package/dist/schema/index-catalog.d.ts +40 -0
- package/dist/schema/persistence.d.ts +32 -0
- package/dist/schema/storage.d.ts +22 -0
- package/dist/server.js +5384 -743
- package/dist/server.js.map +4 -4
- package/dist/sql/deparse-default.d.ts +12 -0
- package/dist/sql/deparse-index.d.ts +12 -2
- package/dist/sql/keywords.d.ts +6 -0
- package/dist/sql/ruleutils.d.ts +27 -0
- package/dist/storage/database-state.d.ts +51 -0
- package/dist/types/regnamespace.d.ts +23 -0
- package/dist/types/value.d.ts +2 -0
- package/dist/unstable.js +11520 -8875
- package/dist/unstable.js.map +4 -4
- package/dist/wire/cli.js +5396 -750
- package/dist/wire/cli.js.map +4 -4
- package/dist/wire/cluster.d.ts +8 -0
- package/dist/wire/connection.d.ts +38 -0
- package/dist/wire/durable.d.ts +82 -0
- package/dist/wire/file-storage.d.ts +3 -0
- package/dist/wire/fleet.d.ts +2 -0
- package/dist/wire/index.d.ts +21 -1
- package/dist/wire/index.js +5383 -741
- package/dist/wire/index.js.map +4 -4
- package/dist/wire/startup.d.ts +36 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-postgres
|
|
2
2
|
|
|
3
|
+
## 3.3.0 (2026-10-11)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- resolve reported emulator coverage gaps ([6edc643](https://github.com/crvouga/mockingbird/commit/6edc6438444853a51aca27849c4c8c0449109ea0))
|
|
8
|
+
|
|
9
|
+
### Fixes and improvements
|
|
10
|
+
|
|
11
|
+
- make README examples pass consumer smoke ([9f5bf6a](https://github.com/crvouga/mockingbird/commit/9f5bf6a212fffb03b1f70fb67bce7fcb49f08c40))
|
|
12
|
+
|
|
13
|
+
## 3.2.0 (2026-10-08)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
- match Emulate provider coverage ([16b65ce](https://github.com/crvouga/mockingbird/commit/16b65ce791fa216f28ec1521df636f57771d9c5e))
|
|
18
|
+
|
|
3
19
|
## 3.1.0 (2026-10-08)
|
|
4
20
|
|
|
5
21
|
### Features
|
package/COMPATIBILITY.md
CHANGED
|
@@ -18,7 +18,7 @@ clock controls. This supervisor surface does not change the SQL dialect compatib
|
|
|
18
18
|
|
|
19
19
|
Differential tests compare a **tuple** per statement: rows (normalized to canonical PostgreSQL text where typed), column names and type names where requested, error SQLSTATE class + normalized message, command tag / `rowCount`, and transaction status, plus a **logical state dump** (catalog names, column definitions, row payloads, sequence values) after write sequences.
|
|
20
20
|
|
|
21
|
-
A catalog ID appearing in a test file is **not** proof by itself. Trivial probes are tracked in [`compat/smoke-baseline.json`](compat/smoke-baseline.json) and ratcheted downward (currently **0 smoke stubs** across
|
|
21
|
+
A catalog ID appearing in a test file is **not** proof by itself. Trivial probes are tracked in [`compat/smoke-baseline.json`](compat/smoke-baseline.json) and ratcheted downward (currently **0 smoke stubs** across 998 catalog scenarios). Generated operator/cast matrices live under [`tests/contract/matrices/`](tests/contract/matrices/). Observed mem≠oracle diffs must bind to a `compat/divergences.json` entry or be a **FAILURE** — unexplained diffs are not allowed.
|
|
22
22
|
|
|
23
23
|
Intentional differences are finite and machine-readable in `compat/divergences.json` (PGMM snapshots, seeded `random()`/`now()`, sync single-session API, no aborted-transaction state, EXPLAIN stubs, trigger-order/`UPDATE OF` edges, float8 rounding/overflow edges, …). The wire server supports isolated database catalogs, startup selection, database lifecycle DDL, and copy-on-write template cloning. Human-readable: [DIVERGENCES.md](DIVERGENCES.md).
|
|
24
24
|
|
|
@@ -26,6 +26,25 @@ The wire server supports protocol-v3 text and CSV `COPY FROM STDIN` / `COPY TO S
|
|
|
26
26
|
incremental `CopyData`, client aborts, and statement-atomic rollback. Binary COPY remains outside
|
|
27
27
|
the compatibility surface.
|
|
28
28
|
|
|
29
|
+
The wire server keeps settings per connection and applies the startup packet's run-time parameters
|
|
30
|
+
(libpq `options=-c name=value` / `--name=value`, split like `pg_split_opts`, then parameters sent by
|
|
31
|
+
name) as session defaults that `RESET` returns to. Malformed options, unknown parameters, bad
|
|
32
|
+
values and read-only parameters end the connection with the `FATAL` SQLSTATEs PostgreSQL 18.3
|
|
33
|
+
reports (`42601`, `42704`, `22023`, `55P02`), pinned by `tests/wire/startup-options.test.ts`.
|
|
34
|
+
Known gaps: parameters the engine has no setting for are `42704`, `postgres` switches other than
|
|
35
|
+
`-c` / `--` are `0A000`, `ParameterStatus` is not re-sent when a reported setting changes,
|
|
36
|
+
`SET LOCAL` does not carry across the statements of a wire transaction block, `DISCARD ALL` is a
|
|
37
|
+
no-op, and with no schema of `search_path` present an unqualified `CREATE` goes to `public` where
|
|
38
|
+
PostgreSQL raises `3F000`.
|
|
39
|
+
|
|
40
|
+
The wire server's opt-in durable mode (`serve({ durable })`, `--durable <dir>`) persists the
|
|
41
|
+
cluster before a commit is acknowledged, so acknowledged commits survive a killed process. It is
|
|
42
|
+
snapshot persistence of PGMM bytes behind an injectable storage port, with an atomic file-backed
|
|
43
|
+
implementation. It is not a write-ahead log and not PostgreSQL's on-disk format: there is no
|
|
44
|
+
`pg_wal`, no data directory another PostgreSQL could open, no physical replication or
|
|
45
|
+
point-in-time recovery, and each commit costs an encode of the database it changed. See the README
|
|
46
|
+
for the guarantees and their limits (`tests/wire/durable*.test.ts`, `tests/wire/file-storage.test.ts`).
|
|
47
|
+
|
|
29
48
|
## Status vocabulary
|
|
30
49
|
|
|
31
50
|
| Status | Meaning |
|
|
@@ -44,7 +63,7 @@ Anything a PostgreSQL application can invoke through SQL against the PGlite **18
|
|
|
44
63
|
3. **Single-session sync API** — no concurrent sessions or `25P02` aborted-transaction state. The optional wire server separately provides per-session `READ COMMITTED` workspaces and row locks.
|
|
45
64
|
4. **NOT APPLICABLE** rows in `compat/coverage.json` (roles, replication, VACUUM internals, LISTEN/NOTIFY, cursors, full PL/pgSQL, extensions other than `pgcrypto`'s `digest()` and `pg_trgm`'s `similarity` / `<%` / `gin_trgm_ops`).
|
|
46
65
|
|
|
47
|
-
The oracle exposes **2787 builtin functions** and **74 operators** in `pg_catalog`; postgres-mem implements **
|
|
66
|
+
The oracle exposes **2787 builtin functions** and **74 operators** in `pg_catalog`; postgres-mem implements **306 functions** and **41 operators**, and every remaining item is an explicit entry in [`compat/unsupported-register.json`](compat/unsupported-register.json) with a reason (trigger/internal plumbing, admin/monitoring, unsupported type families, …). The gate fails closed on silence.
|
|
48
67
|
|
|
49
68
|
## Requirements coverage (PostgreSQL 18 SQL commands)
|
|
50
69
|
|
|
@@ -67,10 +86,21 @@ The oracle exposes **2787 builtin functions** and **74 operators** in `pg_catalo
|
|
|
67
86
|
| Sequences / serial / identity | VERIFIED | Includes pg_dump identity sequence names and options |
|
|
68
87
|
| Schemas + search_path + pg_catalog / information_schema | VERIFIED | Catalog columns are the commonly-queried subset |
|
|
69
88
|
| Enums, domains, generated columns | VERIFIED | |
|
|
89
|
+
| `ALTER COLUMN SET / DROP NOT NULL`, `SET / DROP DEFAULT` | VERIFIED | PostgreSQL's errors for existing NULLs (`23502`), primary-key and identity columns, column references and subqueries in a default, and literals that are not valid input for the column type. Every action of one `ALTER TABLE` applies or none does. An undefined function in a default is reported when a row first uses it, not by `ALTER TABLE` (`tests/contract/alter-table/set-not-null.test.ts`, `set-default.test.ts`) |
|
|
90
|
+
| `information_schema.columns.column_default` | PARTIALLY VERIFIED | Rendered the way `pg_get_expr` prints the analyzed default: constants of every type, casts, SQL value functions, `nextval`, the built-in functions with a signature on record, operators, `ARRAY[...]`, `CASE`, `IN`, `BETWEEN`, `LIKE`. Functions outside that table print an untyped literal argument as `text`; `CURRENT_TIMESTAMP(n)`, `current_schema()` and unary `+` lose the detail the parser drops; a nested `CASE` is printed on one line. `pg_attrdef` and `pg_get_expr` are not exposed |
|
|
91
|
+
| `ALTER COLUMN SET STORAGE`, `STORAGE` column clause, `pg_attribute.attstorage` | VERIFIED | SQL and catalog contract only: `PLAIN` / `EXTERNAL` / `EXTENDED` / `MAIN` / `DEFAULT`, per-type defaults, `LIKE ... INCLUDING STORAGE`, PostgreSQL's errors for an unknown mode (`22023`), a non-toastable type (`0A000`) and a missing column. **No TOAST**: values are never compressed or moved out of line, so the mode changes nothing about how rows are held. `SET STORAGE` on a materialized view fails loud (`0A000`); `SET COMPRESSION` is unsupported (`tests/contract/alter-table/set-storage.test.ts`) |
|
|
92
|
+
| `CREATE UNLOGGED TABLE / SEQUENCE`, `SET LOGGED / UNLOGGED`, `pg_class.relpersistence` | VERIFIED | SQL and catalog contract only: `p` / `u` / `t` for tables, their indexes and owned sequences; contents, indexes and constraints are kept across a conversion; PostgreSQL's errors for temporary tables, for logged / unlogged / temporary foreign-key combinations (`42P16`) and for a second change in one statement. **No WAL and no crash recovery**: an unlogged table is stored exactly like a logged one, is not truncated after a crash, and is included in snapshots (`tests/contract/alter-table/set-logged.test.ts`) |
|
|
93
|
+
| Index identity: `pg_class` / `pg_index` / `pg_indexes` / `pg_am` / `pg_constraint.conindid` | VERIFIED | Every index is a relation with its own oid, distinct from its table, kept through `ALTER INDEX / TABLE / SCHEMA ... RENAME`, `SET SCHEMA`, transactions and snapshots; `DROP` + `CREATE` and `REINDEX CONCURRENTLY` make a new one. The index behind a `PRIMARY KEY` or `UNIQUE` constraint appears in all of them and follows `ADD / RENAME / DROP CONSTRAINT`; dropping it directly is `2BP01`. Snapshots written before indexes had oids load with distinct ones. `pg_index` has the flag columns only: `indexprs` / `indpred` are the text `pg_get_expr()` returns, and `indkey`, `indclass`, `indoption`, `indcollation` are absent. A constraint's index name must be free among the schema's relations (`42P07`), and a generated name steps past one that is taken (`t_pkey1`) (`tests/contract/catalogs/index-definitions.test.ts`) |
|
|
94
|
+
| `pg_get_indexdef(oid)`, `pg_get_indexdef(oid, column, pretty)`, `pg_indexes.indexdef` | VERIFIED | `UNIQUE`, `USING` method, `DESC` / `NULLS FIRST / LAST`, `INCLUDE`, non-default operator classes, `COLLATE`, `NULLS NOT DISTINCT`, `WITH (...)`, quoted identifiers (every non-unreserved keyword), and expressions and predicates printed as PostgreSQL prints the analyzed tree: typed constants, the implicit casts operator resolution adds, `= ANY (ARRAY[...])` for `IN`, `~~` for `LIKE`, expanded `BETWEEN`, flattened `AND` / `OR`, `CASE` layout, SQL-syntax functions. Column positions past the end or negative are `''`, an oid that is not an index is NULL, and pretty-printing drops the schema and redundant parentheses. Differential over a 127-index construct matrix; expressions outside it (subqueries, row constructors, `SIMILAR TO`, array slices) are best-effort. Access methods other than btree are recorded and rendered, not implemented differently (`tests/contract/catalogs/index-definitions.test.ts`) |
|
|
95
|
+
| `pg_index.indisvalid`, `CREATE / DROP INDEX CONCURRENTLY`, `REINDEX` | VERIFIED | Completed indexes are valid, ready and live. A concurrent unique build that hits duplicates fails (`23505`) and leaves the index in the catalog, invalid and not enforced; `DROP INDEX` removes it and `REINDEX INDEX / TABLE / SCHEMA / DATABASE` repairs it once the data allows; a failed `REINDEX INDEX CONCURRENTLY` leaves `name_ccnew`. The commands PostgreSQL refuses in a transaction block are `25001`. An *interrupted* build (cancel, deadlock) cannot be provoked in the oracle: `Database.fault({ concurrentIndexBuild: "57014" })` and `server.fault({ failConcurrentIndexBuild })` arm one for tests, in the build-phase state (`indisready = false`); the state a failure in the later validation scan leaves (`indisready = true`, still enforcing) is not modelled (divergence `concurrent-index-build-fault`). `REINDEX` has no physical index to rebuild (`tests/contract/catalogs/index-validity.test.ts`, `tests/wire/index-catalog.test.ts`) |
|
|
96
|
+
| `COMMENT ON INDEX`, `pg_description`, `obj_description` | VERIFIED | Stored on the index: replace, clear with `NULL` or `''`, removed with the index / table / schema, kept through renames, transactional, in snapshots, isolated per wire transaction; `42P01` / `3F000` / `42809` for a missing index, a missing schema, a relation that is not an index. Comments on every other kind of object are still accepted and not stored (divergence `comment-on-not-stored`) (`tests/contract/catalogs/index-comments.test.ts`) |
|
|
97
|
+
| `regnamespace`, `to_regnamespace` | VERIFIED | Input (`-`, an all-digit oid, or exactly one identifier, case-folded unless quoted), output (quoted like `quote_ident`, numeric when no schema has the oid, `-` for zero), casts from text / name / oid / integers, implicit use as `oid` in comparisons, `pg_toast` / `pg_catalog` / `information_schema`; `3F000`, `42602` and `22003` as PostgreSQL raises them. `public` has a generated oid, not 2200 (`tests/contract/catalogs/regnamespace.test.ts`) |
|
|
98
|
+
| `pg_stat_user_tables`, `pg_stat_user_indexes`, `ANALYZE` | PARTIALLY VERIFIED | PostgreSQL 18's columns and types; one row per user table and materialized view (and per index), `relid` joins `pg_class.oid`, follows create / drop / rename / schema moves, restored with snapshots. `n_live_tup` is the exact row count, immediately; `ANALYZE` validates its targets (`42P01`, `42703`, `42701`, `42601`) and records `analyze_count` / `last_analyze` from the database clock. Not tracked (read zero / NULL): scans, tuples inserted / updated / deleted, dead tuples, vacuum; `idx_scan` counts only the unique-key lookups made for `SELECT`. `VACUUM ANALYZE` does not count. Temporary tables are listed under the schema they were created in, not `pg_temp` (divergence `pg-stat-counters-immediate`) (`tests/contract/catalogs/table-statistics.test.ts`) |
|
|
70
99
|
| Triggers (row-level, LANGUAGE sql-expressible) | PARTIALLY VERIFIED | Creation-order firing, `UPDATE OF` ignored, no INSTEAD OF (documented) |
|
|
71
100
|
| CREATE FUNCTION LANGUAGE sql / plpgsql-lite | PARTIALLY VERIFIED | Scalar + set-returning; plpgsql subset: DECLARE, EXCEPTION WHEN others, CASE, FOR-IN-SELECT, RETURN NEXT |
|
|
72
101
|
| Text search (tsvector / tsquery / @@ / ts_rank) | PARTIALLY VERIFIED | `simple`-style config; no ispell/synonym dictionaries |
|
|
73
102
|
| `CREATE EXTENSION pgcrypto` / `digest()` | PARTIALLY VERIFIED | `digest(bytea, text)` and `digest(text, text)` for md5, sha1, sha224, sha256, sha384, sha512. `crypt`, `hmac`, `gen_salt`, and PGP functions are not installed. OpenSSL-only names (sha3, blake2, ripemd160, sm3) are not available. Extensions other than `pgcrypto` and `pg_trgm` still fail loud (`0A000`) |
|
|
103
|
+
| `pg_available_extensions`, `pg_available_extension_versions`, `pg_extension` | VERIFIED | List exactly what `CREATE EXTENSION` can install here: `pgcrypto` (1.3, 1.4), `pg_trgm` (1.3 to 1.6) and the built-in `plpgsql` (1.0), with `installed_version`, schema and version following `CREATE` / `DROP EXTENSION`, rollback and snapshots. A stock PostgreSQL server lists every contrib module it ships; those are absent here (zero rows, and `CREATE EXTENSION` is `0A000`). An older version installs the same partial function set as the default one. `DROP EXTENSION` reports dependents (`2BP01`) and `CASCADE` removes dependent defaults, generated columns, CHECK constraints, views and indexes; `DROP EXTENSION plpgsql` and `ALTER EXTENSION` fail loud (`0A000`) (`tests/contract/extensions/catalogs.test.ts`) |
|
|
74
104
|
| `CREATE EXTENSION pg_trgm` | PARTIALLY VERIFIED | `similarity(text, text)`, `<%` at the default word-similarity threshold 0.6, and a single-column `gin_trgm_ops` index on `text` or `varchar`. `gist_trgm_ops`, multicolumn `gin_trgm_ops`, and non-default thresholds are not installed. |
|
|
75
105
|
| COPY FROM/TO (text, csv) | VERIFIED | Via `copyFrom` API hook / rows out |
|
|
76
106
|
| PREPARE / EXECUTE / DEALLOCATE, SET / SHOW / RESET | VERIFIED | GUC subset, including PostgreSQL dump and migration-client preambles |
|
|
@@ -79,9 +109,9 @@ The oracle exposes **2787 builtin functions** and **74 operators** in `pg_catalo
|
|
|
79
109
|
| Regex (`~`, `~*`, POSIX functions) | PARTIALLY VERIFIED | JS regex flavor mapped to POSIX ERE; documented edges |
|
|
80
110
|
| EXPLAIN | PARTIALLY VERIFIED | Stub plan shapes |
|
|
81
111
|
| MERGE / CALL / cursors / LISTEN / full PL/pgSQL | UNSUPPORTED | Fail loud `0A000`, registered |
|
|
82
|
-
| Roles / GRANT / VACUUM /
|
|
83
|
-
| Wire protocol / multi-session concurrency | PARTIALLY VERIFIED | `READ COMMITTED` workspaces; `FOR UPDATE`, `NOWAIT`, `SKIP LOCKED`, and deadlock detection |
|
|
84
|
-
| On-disk PostgreSQL format | NOT APPLICABLE | PGMM snapshots are the persistence format |
|
|
112
|
+
| Roles / GRANT / VACUUM / LOCK | NOT APPLICABLE | Parsed no-ops where harmless |
|
|
113
|
+
| Wire protocol / multi-session concurrency | PARTIALLY VERIFIED | `READ COMMITTED` workspaces; `FOR UPDATE`, `NOWAIT`, `SKIP LOCKED`, and deadlock detection; per-connection settings and startup `options` / run-time parameters |
|
|
114
|
+
| On-disk PostgreSQL format | NOT APPLICABLE | PGMM snapshots are the persistence format, also for the wire server's opt-in durable mode (snapshot persistence, not WAL) |
|
|
85
115
|
|
|
86
116
|
## How to verify
|
|
87
117
|
|
package/README.md
CHANGED
|
@@ -185,6 +185,7 @@ Or from the command line (installs a `mockingbird-postgres` bin):
|
|
|
185
185
|
```bash
|
|
186
186
|
mockingbird-postgres serve --port 55432 # trust auth
|
|
187
187
|
mockingbird-postgres serve postgres://postgres:secret@0.0.0.0:55432/app --log
|
|
188
|
+
mockingbird-postgres serve --port 55432 --durable ./.postgres # commits survive a restart
|
|
188
189
|
```
|
|
189
190
|
|
|
190
191
|
`serve({ database })` shares an existing `Database`, and `serve({ database: snapshot })` boots every
|
|
@@ -202,7 +203,10 @@ return a conflict until closed.
|
|
|
202
203
|
|
|
203
204
|
`server.snapshot()` freezes the live state; `server.fault({ dropConnection | delayStatementMs |
|
|
204
205
|
failCommit })` arms the next statement or connection for a drop, a delay, or a `40001` commit
|
|
205
|
-
failure.
|
|
206
|
+
failure. `server.fault({ failConcurrentIndexBuild: "57014" })` (and `database.fault({
|
|
207
|
+
concurrentIndexBuild: "57014" })` on the sync API) interrupts the next `CREATE INDEX CONCURRENTLY`
|
|
208
|
+
or `REINDEX ... CONCURRENTLY` with that SQLSTATE and, as PostgreSQL does, leaves the index it was
|
|
209
|
+
building in the catalog with `pg_index.indisvalid = false` until it is dropped or reindexed.
|
|
206
210
|
|
|
207
211
|
One committed engine is shared by every connection. Explicit transactions execute in per-session
|
|
208
212
|
copy-on-write workspaces that rebase each statement on the latest committed state, providing
|
|
@@ -222,20 +226,155 @@ the wire. `COPY … FROM STDIN` and `COPY … TO STDOUT` use protocol-v3 `CopyIn
|
|
|
222
226
|
incrementally across arbitrary frame and quoted-record boundaries; failed imports are atomic and
|
|
223
227
|
leave the connection in PostgreSQL's transaction state.
|
|
224
228
|
|
|
229
|
+
**Session settings and startup parameters.** Every connection has its own settings: a `SET`,
|
|
230
|
+
`RESET` or `set_config()` on one never reaches another, a `SET` inside a transaction block is
|
|
231
|
+
undone by `ROLLBACK`, and a new connection starts from the database's settings, not a neighbour's.
|
|
232
|
+
The startup packet's run-time parameters are applied before the first query, the way a PostgreSQL
|
|
233
|
+
backend applies them: libpq's `options` string first, then every parameter sent by name (which
|
|
234
|
+
therefore wins).
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import pg from "pg"
|
|
238
|
+
import { serve } from "@crvouga/mockingbird-service-postgres/wire"
|
|
239
|
+
|
|
240
|
+
const server = await serve({ port: 0 })
|
|
241
|
+
const client = new pg.Client({
|
|
242
|
+
connectionString: `${server.connectionString}?options=-c%20search_path%3Dapp`,
|
|
243
|
+
})
|
|
244
|
+
await client.connect()
|
|
245
|
+
await client.query("CREATE SCHEMA app")
|
|
246
|
+
await client.query("CREATE TABLE app.notes (id integer)")
|
|
247
|
+
await client.query("SET search_path TO app")
|
|
248
|
+
await client.end()
|
|
249
|
+
await server.close()
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Other clients set the same startup option with their own configuration (for example, JDBC's
|
|
253
|
+
`currentSchema` or postgres.js / Bun SQL's `connection.search_path`).
|
|
254
|
+
|
|
255
|
+
`options` is split like `pg_split_opts` (whitespace separates arguments, `\ ` is a literal space,
|
|
256
|
+
`\\` a backslash) and accepts `-c name=value`, `-cname=value` and `--name=value` (dashes in a
|
|
257
|
+
`--name` are underscores). A schema named in `search_path` need not exist yet: it starts resolving
|
|
258
|
+
once it is created. Startup values are the session's defaults, so `RESET search_path`,
|
|
259
|
+
`SET search_path TO DEFAULT` and `RESET ALL` return to them. Invalid input ends the connection
|
|
260
|
+
with a `FATAL` ErrorResponse, as PostgreSQL 18.3 does: `42601` for a malformed `options` string
|
|
261
|
+
(`-c name` without a value, a stray argument, an unknown switch), `42704` for an unknown
|
|
262
|
+
parameter, `22023` for a bad value (`search_path` list syntax, booleans, integers and timeouts with
|
|
263
|
+
units, enum parameters, `DateStyle`, `TimeZone`) and `55P02` for a read-only parameter such as
|
|
264
|
+
`server_version`. The session-aware functions (`pg_advisory_lock`, `pg_backend_pid`, `pg_notify`,
|
|
265
|
+
…) live in `pg_catalog`, so they keep working when `search_path` leaves out `public`.
|
|
266
|
+
|
|
267
|
+
Boundaries: a parameter is known when the engine has a setting for it (the same rule as `SET`), so
|
|
268
|
+
a real PostgreSQL parameter the engine does not model, such as `work_mem` or `jit`, is `42704`
|
|
269
|
+
at startup too; names with a dot are custom parameters and always accepted. The other `postgres`
|
|
270
|
+
switches (`-e`, `-S`, `-d`, …) fail loud with `0A000`. `client_encoding`, `lc_*` and
|
|
271
|
+
`application_name` values are stored as sent, an unquoted upper-case schema in a startup
|
|
272
|
+
`search_path` is shown folded to lower case, and `ParameterStatus` is sent at startup only, not
|
|
273
|
+
again when a reported setting changes. `SET LOCAL` and `DISCARD ALL` keep their engine behaviour
|
|
274
|
+
(see [COMPATIBILITY.md](COMPATIBILITY.md)).
|
|
275
|
+
|
|
225
276
|
**Not modelled by the server:** transaction ID and snapshot-inspection functions, predicate and
|
|
226
277
|
table locks, isolation levels beyond `READ COMMITTED`, and the binary parameter formats beyond the
|
|
227
278
|
common scalar types (a client that sends another binary type gets `0A000`, and can switch that
|
|
228
279
|
parameter to text). Binary `COPY` is not supported.
|
|
229
280
|
|
|
281
|
+
#### Durable mode (opt-in)
|
|
282
|
+
|
|
283
|
+
The server keeps everything in memory, and that stays the default: no option, no filesystem, the
|
|
284
|
+
same bytes on every run. For local development, where the server replaces a native database and
|
|
285
|
+
a killed process must not lose acknowledged work, `durable` persists the cluster before each
|
|
286
|
+
commit is acknowledged:
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
import { serve } from "@crvouga/mockingbird-service-postgres/wire"
|
|
290
|
+
|
|
291
|
+
const server = await serve({ port: 55432, durable: "./.postgres" }) // a directory
|
|
292
|
+
// … the process is SIGKILLed …
|
|
293
|
+
const again = await serve({ port: 55432, durable: "./.postgres" }) // resumes from the last acknowledged commit
|
|
294
|
+
await again.close()
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
| | Guarantee |
|
|
298
|
+
| --- | --- |
|
|
299
|
+
| Acknowledged commit | An autocommit write, a `COMMIT`, the implicit commit of a multi-statement query, a `COPY … FROM STDIN` and `CREATE` / `DROP` / `ALTER DATABASE` are in storage before `CommandComplete` (for an implicit commit, before `ReadyForQuery`) is sent. They survive `SIGKILL`. |
|
|
300
|
+
| Uncommitted work | A transaction that was open or rolled back is never restored: it only ever existed in its session's workspace. |
|
|
301
|
+
| Storage failure | The commit is not acknowledged. The statement fails with `58030` (`io_error`), or `53100` (`disk_full`) when the storage reports `ENOSPC` / `EDQUOT`; the change is taken back out of memory, the transaction is gone (as after any failed `COMMIT`: the session is idle, not in an aborted block), its `NOTIFY`s are dropped, and the next statement runs normally. |
|
|
302
|
+
| Atomic | The file-backed store writes each file under a temporary name, fsyncs it, renames it into place and fsyncs the directory; the manifest swap is the commit point. A crash leaves the previous state or the new one. Leftovers of an interrupted write are removed on the next start, and a snapshot whose length or SHA-256 does not match its manifest entry is refused, never decoded. |
|
|
303
|
+
| Whole cluster | Every database with its catalog, data, sequences and deterministic random position. A restart restores all of them; separate directories (or storages) are separate clusters. |
|
|
304
|
+
| Visibility | A commit being persisted holds the statement turn, so no other connection reads it before it is durable. |
|
|
305
|
+
|
|
306
|
+
It is **snapshot persistence, not a write-ahead log, and not PostgreSQL's on-disk format**: the
|
|
307
|
+
payload is the [PGMM snapshot](#snapshots) of each database (`<sha256>.pgmm`)
|
|
308
|
+
plus a `MANIFEST.json` naming the current one per database. Nothing reads a PostgreSQL data
|
|
309
|
+
directory, and `pg_dump`, physical replication and point-in-time recovery do not apply.
|
|
310
|
+
|
|
311
|
+
The trade-off is cost per commit: every commit re-encodes the database it changed (other
|
|
312
|
+
databases are not rewritten) and then waits for two file fsyncs and two directory fsyncs. That is
|
|
313
|
+
proportional to the size of that database, not to the size of the change: about 10 ms for a
|
|
314
|
+
200 KB snapshot and several hundred milliseconds for 2 MB (100 000 short rows) on a laptop. It
|
|
315
|
+
suits development datasets; batch bulk writes into one transaction (one `COMMIT`, one encode), and
|
|
316
|
+
keep the in-memory default for tests. Reads, `SET` and statements inside an open transaction
|
|
317
|
+
never touch storage; a statement that could have written (a `SELECT` calling a user-defined
|
|
318
|
+
function, a view or `nextval`) is encoded and compared, and written only if the snapshot differs.
|
|
319
|
+
`fsync` is as strong as the platform makes it: surviving a killed process does not depend on it,
|
|
320
|
+
surviving power loss does.
|
|
321
|
+
|
|
322
|
+
Boundaries of durable mode:
|
|
323
|
+
|
|
324
|
+
- One server process per directory; nothing locks it.
|
|
325
|
+
- `database` seeds an empty storage (the seed is persisted at startup). Once storage holds a
|
|
326
|
+
cluster, that cluster is served and `database` only supplies the runtime: clock, seed, entropy
|
|
327
|
+
mode and `int8` mode. Read state through `server.database`, not the object you passed.
|
|
328
|
+
- What a PGMM snapshot does not hold is not restored: functions added with `registerFunction`,
|
|
329
|
+
session state (settings, prepared statements, advisory locks, `LISTEN`s), and faults.
|
|
330
|
+
- Writes made from the host process (`server.database.exec(...)`, `restoreAll()`) are not commits
|
|
331
|
+
of a connection: call `await server.persist()` to store them. A host-side read during a commit
|
|
332
|
+
can see it before it is durable.
|
|
333
|
+
- `nextval()` and `setval()` are persisted at once. The random position consumed by a statement
|
|
334
|
+
that wrote nothing (`SELECT gen_random_uuid()`) is stored with the next commit of that database.
|
|
335
|
+
- `mockingbird-postgres serve --config` (the fleet) has no durable option.
|
|
336
|
+
|
|
337
|
+
The storage is a port, so a test can model failure and crashes without a filesystem:
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
import { type ClusterImage, type DurableStorage, fileStorage, serve } from "@crvouga/mockingbird-service-postgres/wire"
|
|
341
|
+
|
|
342
|
+
let image: ClusterImage | null = null
|
|
343
|
+
let failing = false
|
|
344
|
+
const paused = Promise.resolve()
|
|
345
|
+
const storage: DurableStorage = {
|
|
346
|
+
read: async () => image,
|
|
347
|
+
write: async (next) => {
|
|
348
|
+
if (failing) throw Object.assign(new Error("disk full"), { code: "ENOSPC" }) // → 53100, not acknowledged
|
|
349
|
+
image = new Map(next)
|
|
350
|
+
},
|
|
351
|
+
}
|
|
352
|
+
const server = await serve({
|
|
353
|
+
durable: {
|
|
354
|
+
storage, // or a directory, or fileStorage(directory)
|
|
355
|
+
beforeAcknowledge: (commit) => paused, // persisted, not acknowledged yet: crash here
|
|
356
|
+
afterAcknowledge: (commit) => paused, // the acknowledgement is on the socket: crash here
|
|
357
|
+
},
|
|
358
|
+
})
|
|
359
|
+
const restarted = await serve({ durable: storage }) // "restart" from what storage holds
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
`write` receives the whole cluster as PGMM bytes per database and must be atomic: resolve once the
|
|
363
|
+
image would survive a kill, or reject and leave the previous image in place. The server never
|
|
364
|
+
calls it concurrently and passes the same `Uint8Array` for a database that did not change.
|
|
365
|
+
`beforeAcknowledge` / `afterAcknowledge` receive `{ pid, database, sql }` and are awaited, which
|
|
366
|
+
is how a crash-regression test stops the server on either side of the acknowledgement.
|
|
367
|
+
|
|
230
368
|
### Method semantics
|
|
231
369
|
|
|
232
370
|
| Method | Behaviour |
|
|
233
371
|
| --- | --- |
|
|
234
372
|
| `exec(sql)` | Runs all semicolon-separated statements; **discards** row results (`void`). Does **not** accept bind parameters. Read `db.changes` afterwards if needed (reflects the **most recent** completed DML statement). Dump-only `DO` blocks and `ALTER TABLE ... SET (` storage parameters are no-ops. |
|
|
235
|
-
| `registerFunction(spec)` | Install a JavaScript scalar. Not stored in PGMM snapshots; `open()` of a live snapshot copies the implementation by reference. |
|
|
373
|
+
| `registerFunction(spec)` | Install a JavaScript scalar. Not stored in PGMM snapshots; `open()` of a live snapshot copies the implementation by reference. A `pg_catalog.`-qualified name is found ahead of `search_path`, as PostgreSQL's own functions are. |
|
|
236
374
|
| `query(sql, params?, { at? }?)` | **Single statement only** (trailing `;` is fine). Returns all rows. `at` is a `Snapshot` or a timeline checkpoint id and queries that checkpoint without changing live state. Multi-statement scripts throw `misuse`. |
|
|
237
375
|
| `prepare(sql)` | **Single statement only**. Parses immediately; the AST is reused. Pass binds as rest args to `run` / `all` / `get` / `result` / `textResult` on each call. |
|
|
238
376
|
| `transaction(fn)` | If idle: `BEGIN`, `fn()`, `COMMIT`, or `ROLLBACK` + rethrow. If already in a transaction: nested savepoint. A nested SQL `BEGIN` inside is a no-op warning like PostgreSQL. `close()` inside `fn` throws `misuse`. |
|
|
377
|
+
| `fault({ concurrentIndexBuild })` | Test control: arm the next `CREATE INDEX CONCURRENTLY` / `REINDEX ... CONCURRENTLY` to fail with the given SQLSTATE (`"57014"` canceled, `"40P01"` deadlock) and leave its index invalid (`pg_index.indisvalid = false`), as an interrupted build does on PostgreSQL. One shot; not part of snapshots. |
|
|
239
378
|
| `copyFrom(sql, data)` | Executes `COPY table [(cols)] FROM STDIN` with `data` as the copy-in payload (text or csv per the COPY options). Returns rows copied. `COPY ... TO STDOUT` output is returned as result rows by `query`. |
|
|
240
379
|
| `snapshot()` | Freeze a reusable `Snapshot` template (no encode) and commit it on the followed timeline branch. Illegal inside a transaction (`25P01`). |
|
|
241
380
|
| `checkpoint()` / `branch(at?)` | `checkpoint` records the same timeline point as `snapshot`. `branch` opens an isolated database from a snapshot (or the current state). |
|
|
@@ -335,12 +474,16 @@ Intentional differences: custom `PGMM` snapshots; seeded `random()` / fixed `now
|
|
|
335
474
|
- `EXPLAIN`: stub plan shapes, not real planner output
|
|
336
475
|
- The in-process API does **not** poison a transaction after a failed statement (no `25P02`); the wire-protocol server does, per connection
|
|
337
476
|
- Triggers fire in **creation order** (PostgreSQL: name order); `UPDATE OF` column lists are ignored; `INSTEAD OF` is unsupported
|
|
338
|
-
- `COMMENT ON`
|
|
477
|
+
- `COMMENT ON INDEX` is stored (`pg_description`, `obj_description`); comments on every other kind of object parse but are not stored
|
|
339
478
|
- `round(float8)` rounds ties away from zero (PostgreSQL: half-to-even); numeric `round()` has full parity
|
|
340
479
|
- `'1e400'::float8` saturates to `Infinity` instead of raising `22003`
|
|
341
480
|
- In the engine `MERGE`, `CALL`/procedures, cursors (`DECLARE`/`FETCH`), `LISTEN`/`NOTIFY`, and full PL/pgSQL (packages, NOTICE, cursors) fail loud (`0A000`); the wire-protocol server implements `LISTEN`/`NOTIFY` itself
|
|
342
|
-
- `VACUUM` / `
|
|
481
|
+
- `VACUUM` / `CLUSTER` / `CHECKPOINT` / `GRANT` / `REVOKE` / `LOCK` are parsed no-ops. `ANALYZE` validates its targets and records `analyze_count` / `last_analyze`, and `REINDEX` validates its targets and repairs an invalid index; neither has planner statistics or a physical index behind it
|
|
482
|
+
- `pg_stat_user_tables` / `pg_stat_user_indexes` have PostgreSQL 18's columns, but only `n_live_tup` (the exact row count, immediately), the `ANALYZE` bookkeeping and `idx_scan` for unique-key lookups are real; the other activity counters read zero
|
|
483
|
+
- Index access methods other than btree (`USING hash` / `gin` / …) are recorded and shown by `pg_get_indexdef` and `pg_am`, not implemented differently; `pg_index` has no `indkey` (use `pg_get_indexdef(oid, n, pretty)`)
|
|
343
484
|
- Collation is `C` semantics (byte order); locale/ICU-dependent ordering is out of scope
|
|
485
|
+
- Column `STORAGE` modes (`pg_attribute.attstorage`) and `UNLOGGED` persistence (`pg_class.relpersistence`) follow PostgreSQL's SQL and catalog contract but are metadata only: there is no TOAST and no WAL, so an unlogged table is stored, snapshotted and kept like any other
|
|
486
|
+
- `pg_available_extensions` / `pg_extension` list only what `CREATE EXTENSION` can install here (`pgcrypto`, `pg_trgm`) plus the built-in `plpgsql`; any other extension is absent from the views and `CREATE EXTENSION` fails with `0A000`
|
|
344
487
|
|
|
345
488
|
**Also supported (oracle parity):** schemas + `search_path`, `pg_catalog` / `information_schema`
|
|
346
489
|
introspection, sequences (`serial`, identity, `nextval`/`currval`/`setval`), enums, domains,
|
package/compat/coverage.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-
|
|
2
|
+
"generatedAt": "2026-10-11T00:19:02.759Z",
|
|
3
3
|
"referenceServerVersion": "18.3",
|
|
4
4
|
"counts": {
|
|
5
5
|
"total": 183,
|
|
@@ -251,9 +251,9 @@
|
|
|
251
251
|
"sql-analyze": {
|
|
252
252
|
"status": "PARTIALLY_VERIFIED",
|
|
253
253
|
"evidence": [
|
|
254
|
-
"tests/contract/
|
|
254
|
+
"tests/contract/catalogs/"
|
|
255
255
|
],
|
|
256
|
-
"notes": "
|
|
256
|
+
"notes": "targets, columns and options are validated and pg_stat_user_tables records analyze_count / last_analyze; no planner statistics are gathered (divergence pg-stat-counters-immediate)"
|
|
257
257
|
},
|
|
258
258
|
"sql-begin": {
|
|
259
259
|
"status": "VERIFIED",
|
|
@@ -289,9 +289,10 @@
|
|
|
289
289
|
"sql-comment": {
|
|
290
290
|
"status": "PARTIALLY_VERIFIED",
|
|
291
291
|
"evidence": [
|
|
292
|
-
"tests/contract/catalog/"
|
|
292
|
+
"tests/contract/catalog/",
|
|
293
|
+
"tests/contract/catalogs/"
|
|
293
294
|
],
|
|
294
|
-
"notes": "
|
|
295
|
+
"notes": "COMMENT ON INDEX is stored (pg_description, obj_description); comments on other objects are accepted and not stored (divergence comment-on-not-stored)"
|
|
295
296
|
},
|
|
296
297
|
"sql-commit": {
|
|
297
298
|
"status": "VERIFIED",
|
|
@@ -924,9 +925,9 @@
|
|
|
924
925
|
"sql-reindex": {
|
|
925
926
|
"status": "PARTIALLY_VERIFIED",
|
|
926
927
|
"evidence": [
|
|
927
|
-
"tests/contract/
|
|
928
|
+
"tests/contract/catalogs/"
|
|
928
929
|
],
|
|
929
|
-
"notes": "
|
|
930
|
+
"notes": "INDEX / TABLE / SCHEMA / DATABASE / SYSTEM with CONCURRENTLY: targets are validated, a unique index is re-checked, an invalid index is repaired and a failed concurrent rebuild leaves its _ccnew index; there is no physical index to rebuild and TABLESPACE is ignored"
|
|
930
931
|
},
|
|
931
932
|
"sql-release-savepoint": {
|
|
932
933
|
"status": "VERIFIED",
|
package/compat/divergences.json
CHANGED
|
@@ -145,8 +145,8 @@
|
|
|
145
145
|
{
|
|
146
146
|
"id": "comment-on-not-stored",
|
|
147
147
|
"scope": "sql",
|
|
148
|
-
"predicate": "COMMENT ON parses and succeeds but the comment is not stored; obj_description()
|
|
149
|
-
"specifiedBehavior": "COMMENT ON is accepted for compatibility;
|
|
148
|
+
"predicate": "COMMENT ON for any object other than an index parses and succeeds but the comment is not stored; obj_description() / col_description() return NULL for it and pg_description has no row",
|
|
149
|
+
"specifiedBehavior": "COMMENT ON INDEX is persisted (pg_description, obj_description) and differential-tested. For tables, columns and every other object kind COMMENT ON is accepted for compatibility and the description is not persisted; a missing target of those kinds is not an error either.",
|
|
150
150
|
"pinnedBy": ["DDL-comment-01"]
|
|
151
151
|
},
|
|
152
152
|
{
|
|
@@ -190,6 +190,41 @@
|
|
|
190
190
|
"predicate": "pg_get_viewdef() is not implemented and raises 42883",
|
|
191
191
|
"specifiedBehavior": "View definitions are visible via information_schema.views.view_definition (stored SQL text).",
|
|
192
192
|
"pinnedBy": ["CAT-viewdef-01"]
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"id": "storage-persistence-catalog-only",
|
|
196
|
+
"scope": "sql",
|
|
197
|
+
"predicate": "Column storage modes (STORAGE / SET STORAGE, pg_attribute.attstorage) and relation persistence (UNLOGGED, SET LOGGED / UNLOGGED, pg_class.relpersistence) are catalog metadata only: there is no TOAST and no write-ahead log",
|
|
198
|
+
"specifiedBehavior": "The SQL and catalog contract matches PostgreSQL, including its errors for invalid modes, targets and conversions, and is differential-tested. Physically nothing changes: values are never compressed or moved out of line, an unlogged table is stored like a logged one, is not truncated by a crash and is part of every snapshot. PostgreSQL also keeps a storage mode for materialized-view columns; here ALTER COLUMN ... SET STORAGE on a materialized view fails loud with 0A000.",
|
|
199
|
+
"pinnedBy": ["tests/contract/alter-table/set-storage.test.ts", "tests/contract/alter-table/set-logged.test.ts"]
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"id": "extension-catalog-subset",
|
|
203
|
+
"scope": "catalog",
|
|
204
|
+
"predicate": "pg_available_extensions, pg_available_extension_versions and pg_extension list only the extensions the engine can install (pgcrypto, pg_trgm) plus the built-in plpgsql; DROP EXTENSION plpgsql is refused with 0A000",
|
|
205
|
+
"specifiedBehavior": "A stock PostgreSQL server lists every contrib module it ships; here an extension that is not implemented is absent from the views (zero rows) and CREATE EXTENSION fails with 0A000, as it does on a server without that control file. The PGlite oracle is built with exactly the same three extensions, so the views are compared in full. plpgsql cannot be dropped because the language is part of the engine; PostgreSQL allows dropping and recreating it.",
|
|
206
|
+
"pinnedBy": ["tests/contract/extensions/catalogs.test.ts"]
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"id": "concurrent-index-build-fault",
|
|
210
|
+
"scope": "engine",
|
|
211
|
+
"predicate": "Database.fault({ concurrentIndexBuild: sqlstate }) and, on the wire server, server.fault({ failConcurrentIndexBuild: sqlstate }) make the next CREATE INDEX CONCURRENTLY or REINDEX ... CONCURRENTLY fail with that SQLSTATE and leave the index it was building in the catalog with indisvalid = false, indisready = false, indislive = true",
|
|
212
|
+
"specifiedBehavior": "PostgreSQL leaves such an invalid index when a concurrent build is canceled, deadlocks or otherwise fails partway (sql-createindex, 'Building Indexes Concurrently'); an interruption cannot be provoked deterministically in the oracle, so this one-shot test control stands in for it. The failure PostgreSQL can be made to produce, duplicate keys in a concurrent unique build, is differential-tested, as are DROP INDEX and REINDEX as the repair. The preset models an interruption in the build phase, the state PostgreSQL shows for the duplicate-key failure (not ready, so not maintained and not enforcing). A failure in the later validation scan, where PostgreSQL leaves indisready = true and a unique index keeps enforcing, is not modeled. The fault is not part of snapshots and is not undone by a rollback.",
|
|
213
|
+
"pinnedBy": ["CAT-index-valid-07", "tests/contract/catalogs/index-validity.test.ts", "tests/wire/index-catalog.test.ts"]
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"id": "pg-stat-counters-immediate",
|
|
217
|
+
"scope": "catalog",
|
|
218
|
+
"predicate": "pg_stat_user_tables.n_live_tup is the exact current row count, current as soon as a statement finishes; seq_scan, seq_tup_read, idx_tup_fetch, n_tup_ins / n_tup_upd / n_tup_del, n_tup_hot_upd, n_dead_tup, n_mod_since_analyze and the vacuum columns are not tracked and read zero or NULL; idx_scan (also in pg_stat_user_indexes) counts only the unique-key equality lookups the engine performs for SELECT; VACUUM, including VACUUM ANALYZE, is a no-op and does not count",
|
|
219
|
+
"specifiedBehavior": "PostgreSQL reports these counters asynchronously (after pg_stat_force_next_flush() or a delay) and n_live_tup is an estimate that ANALYZE resets. The differential tests read after pg_stat_force_next_flush() and ANALYZE, where PostgreSQL's value is the row count too, and compare relid, schemaname, relname, n_live_tup, analyze_count, last_analyze and the column list and types of both views. There are no dead tuples and no planner, so the activity counters have nothing to count; treat them as absent, not as zero activity.",
|
|
220
|
+
"pinnedBy": ["CAT-table-stat-04", "tests/contract/catalogs/table-statistics.test.ts"]
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"id": "pg-index-catalog-subset",
|
|
224
|
+
"scope": "catalog",
|
|
225
|
+
"predicate": "pg_index exposes indexrelid, indrelid, indnatts, indnkeyatts and the boolean flags; indexprs and indpred are the deparsed text that pg_get_expr() returns rather than pg_node_tree, and indkey, indcollation, indclass and indoption are absent. Index access methods other than btree are recorded and rendered (USING hash / gin / gist / spgist / brin) but every index is maintained the same way",
|
|
226
|
+
"specifiedBehavior": "pg_get_indexdef(), pg_indexes.indexdef and pg_get_expr(indpred | indexprs, indrelid) are differential-tested against PostgreSQL across methods, ordering, NULLS placement, INCLUDE, operator classes, collations, storage parameters, quoted identifiers and an expression / predicate matrix. Read index columns through pg_get_indexdef(oid, n, pretty) instead of indkey. Expressions outside that matrix (subqueries, row constructors, SIMILAR TO, array slices, window or aggregate calls) are rendered best-effort.",
|
|
227
|
+
"pinnedBy": ["tests/contract/catalogs/index-definitions.test.ts"]
|
|
193
228
|
}
|
|
194
229
|
]
|
|
195
230
|
}
|
package/compat/requirements.json
CHANGED
package/compat/sections/cat.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { type CatalogSection, section } from "../scenario-types.ts";
|
|
2
2
|
|
|
3
3
|
const D = "documented_divergence" as const;
|
|
4
|
+
/** Cases for the ext rows live next to the catalog test, in their own file. */
|
|
5
|
+
const EXTENSIONS = ["tests/contract/catalog/extension-cases.ts"];
|
|
6
|
+
/** Cases for the index / namespace / statistics rows, with the full proof under tests/contract/catalogs/. */
|
|
7
|
+
const INDEXES = ["tests/contract/catalog/index-reconciliation-cases.ts", "tests/contract/catalogs/"];
|
|
4
8
|
|
|
5
9
|
export const CAT_SECTION: CatalogSection = section("CAT", "pg_catalog and information_schema", true, [
|
|
6
10
|
["class-01", "user table appears in pg_class with relkind r"],
|
|
@@ -27,6 +31,51 @@ export const CAT_SECTION: CatalogSection = section("CAT", "pg_catalog and inform
|
|
|
27
31
|
["cons-01", "information_schema.table_constraints for PK, UNIQUE, CHECK"],
|
|
28
32
|
["cons-02", "information_schema.key_column_usage lists key columns"],
|
|
29
33
|
["cons-03", "foreign key appears in table_constraints"],
|
|
34
|
+
["ext-01", "pg_available_extensions lists the installable extensions", undefined, undefined, EXTENSIONS],
|
|
35
|
+
["ext-02", "an unavailable extension is zero rows in pg_available_extensions", undefined, undefined, EXTENSIONS],
|
|
36
|
+
["ext-03", "pg_extension follows CREATE / DROP EXTENSION", undefined, undefined, EXTENSIONS],
|
|
37
|
+
["ext-04", "CREATE EXTENSION of an unavailable extension errors", undefined, undefined, EXTENSIONS],
|
|
38
|
+
["index-valid-01", "invalid-index filter is empty on a fresh database", undefined, undefined, INDEXES],
|
|
39
|
+
["index-valid-02", "the pg_index.indisvalid query does not poison a transaction", undefined, undefined, INDEXES],
|
|
40
|
+
["index-valid-03", "completed indexes are valid, ready and live", undefined, undefined, INDEXES],
|
|
41
|
+
["index-valid-04", "a failed CREATE UNIQUE INDEX CONCURRENTLY leaves an invalid index", undefined, undefined, INDEXES],
|
|
42
|
+
["index-valid-05", "CREATE INDEX CONCURRENTLY in a transaction block is 25001", undefined, undefined, INDEXES],
|
|
43
|
+
["index-valid-06", "REINDEX repairs an invalid index once its duplicates are gone", undefined, undefined, INDEXES],
|
|
44
|
+
[
|
|
45
|
+
"index-valid-07",
|
|
46
|
+
"Database.fault interrupts a concurrent index build",
|
|
47
|
+
D,
|
|
48
|
+
"memory: a test control arms the next concurrent build to fail and leave its index invalid; PostgreSQL does this when a build is canceled or deadlocks, which the oracle cannot be made to do",
|
|
49
|
+
INDEXES,
|
|
50
|
+
"concurrent-index-build-fault",
|
|
51
|
+
],
|
|
52
|
+
["index-def-01", "pg_get_indexdef through the pg_index joins", undefined, undefined, INDEXES],
|
|
53
|
+
["index-def-02", "pg_indexes.indexdef", undefined, undefined, INDEXES],
|
|
54
|
+
["index-def-03", "constraint-backed indexes appear in every index catalog", undefined, undefined, INDEXES],
|
|
55
|
+
["index-def-04", "indexdef renders method, ordering, expressions, predicate, quoting", undefined, undefined, INDEXES],
|
|
56
|
+
["index-def-05", "pg_get_indexdef column and pretty-print overloads", undefined, undefined, INDEXES],
|
|
57
|
+
["index-comment-01", "COMMENT ON INDEX is stored in pg_description", undefined, undefined, INDEXES],
|
|
58
|
+
["index-comment-02", "COMMENT ON INDEX of a missing index is 42P01", undefined, undefined, INDEXES],
|
|
59
|
+
["namespace-01", "relnamespace = current_schema()::regnamespace", undefined, undefined, INDEXES],
|
|
60
|
+
["namespace-02", "regnamespace output quotes a mixed-case name", undefined, undefined, INDEXES],
|
|
61
|
+
["namespace-03", "builtin namespace resolves to its catalog oid", undefined, undefined, INDEXES],
|
|
62
|
+
["namespace-04", "an unknown numeric oid prints numerically", undefined, undefined, INDEXES],
|
|
63
|
+
["namespace-05", "a missing schema is 3F000", undefined, undefined, INDEXES],
|
|
64
|
+
["namespace-06", "an oid beyond 2^32-1 is 22003", undefined, undefined, INDEXES],
|
|
65
|
+
["namespace-07", "a qualified name is invalid name syntax", undefined, undefined, INDEXES],
|
|
66
|
+
["table-stat-01", "pg_stat_user_tables join on an empty database", undefined, undefined, INDEXES],
|
|
67
|
+
["table-stat-02", "pg_stat_user_tables lists a new table", undefined, undefined, INDEXES],
|
|
68
|
+
["table-stat-03", "n_live_tup reflects the rows after ANALYZE; DROP removes the row", undefined, undefined, INDEXES],
|
|
69
|
+
[
|
|
70
|
+
"table-stat-04",
|
|
71
|
+
"statistics are current immediately; activity counters are not tracked",
|
|
72
|
+
D,
|
|
73
|
+
"memory: n_live_tup is the exact row count at once and seq_scan / n_tup_* read zero; PostgreSQL reports its counters asynchronously",
|
|
74
|
+
INDEXES,
|
|
75
|
+
"pg-stat-counters-immediate",
|
|
76
|
+
],
|
|
77
|
+
["index-stat-01", "pg_stat_user_indexes on an empty database", undefined, undefined, INDEXES],
|
|
78
|
+
["index-stat-02", "pg_stat_user_indexes lists an index", undefined, undefined, INDEXES],
|
|
30
79
|
["fn-01", "current_database() and current_schema() shapes"],
|
|
31
80
|
["fn-02", "version() reports a PostgreSQL banner"],
|
|
32
81
|
[
|
package/compat/sections/ddl.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type CatalogSection, section } from "../scenario-types.ts";
|
|
2
2
|
|
|
3
3
|
const D = "documented_divergence" as const;
|
|
4
|
+
/** Cases for the alter-set rows live next to the catalog test, in their own file. */
|
|
5
|
+
const ALTER_SET = ["tests/contract/catalog/alter-set-cases.ts"];
|
|
4
6
|
|
|
5
7
|
export const DDL_SECTION: CatalogSection = section("DDL", "Schema definition", true, [
|
|
6
8
|
["ct-01", "CREATE TABLE with common column types"],
|
|
@@ -25,6 +27,15 @@ export const DDL_SECTION: CatalogSection = section("DDL", "Schema definition", t
|
|
|
25
27
|
["alter-02", "DROP COLUMN removes data and star output"],
|
|
26
28
|
["alter-03", "RENAME COLUMN"],
|
|
27
29
|
["alter-04", "RENAME TABLE"],
|
|
30
|
+
["alter-set-01", "ALTER COLUMN SET DEFAULT applies to later inserts", undefined, undefined, ALTER_SET],
|
|
31
|
+
["alter-set-02", "ALTER COLUMN SET NOT NULL flips is_nullable", undefined, undefined, ALTER_SET],
|
|
32
|
+
["alter-set-03", "ALTER COLUMN SET NOT NULL errors while NULLs exist", undefined, undefined, ALTER_SET],
|
|
33
|
+
["alter-set-04", "ALTER COLUMN DROP NOT NULL lifts the constraint", undefined, undefined, ALTER_SET],
|
|
34
|
+
["alter-set-05", "column_default renders SET DEFAULT expressions", undefined, undefined, ALTER_SET],
|
|
35
|
+
["alter-set-06", "ALTER COLUMN SET STORAGE shows in pg_attribute.attstorage", undefined, undefined, ALTER_SET],
|
|
36
|
+
["alter-set-07", "SET STORAGE other than PLAIN on a fixed-length column errors", undefined, undefined, ALTER_SET],
|
|
37
|
+
["alter-set-08", "SET UNLOGGED / SET LOGGED update pg_class.relpersistence", undefined, undefined, ALTER_SET],
|
|
38
|
+
["alter-set-09", "SET UNLOGGED on a table a logged table references errors", undefined, undefined, ALTER_SET],
|
|
28
39
|
["view-01", "CREATE VIEW and query through it"],
|
|
29
40
|
["view-02", "view column-alias list"],
|
|
30
41
|
["view-03", "view on view"],
|
|
@@ -1542,7 +1542,6 @@
|
|
|
1542
1542
|
"pg_get_function_result": "internal operator implementation function; the operator itself is the SQL surface",
|
|
1543
1543
|
"pg_get_function_sqlbody": "internal operator implementation function; the operator itself is the SQL surface",
|
|
1544
1544
|
"pg_get_functiondef": "internal operator implementation function; the operator itself is the SQL surface",
|
|
1545
|
-
"pg_get_indexdef": "internal operator implementation function; the operator itself is the SQL surface",
|
|
1546
1545
|
"pg_get_keywords": "internal operator implementation function; the operator itself is the SQL surface",
|
|
1547
1546
|
"pg_get_loaded_modules": "internal operator implementation function; the operator itself is the SQL surface",
|
|
1548
1547
|
"pg_get_multixact_members": "internal operator implementation function; the operator itself is the SQL surface",
|
|
@@ -1691,7 +1690,6 @@
|
|
|
1691
1690
|
"pg_split_walfile_name": "server administration / storage / replication introspection; NOT APPLICABLE to an in-memory engine",
|
|
1692
1691
|
"pg_stat_clear_snapshot": "server administration / storage / replication introspection; NOT APPLICABLE to an in-memory engine",
|
|
1693
1692
|
"pg_stat_file": "server administration / storage / replication introspection; NOT APPLICABLE to an in-memory engine",
|
|
1694
|
-
"pg_stat_force_next_flush": "internal operator implementation function; the operator itself is the SQL surface",
|
|
1695
1693
|
"pg_stat_get_activity": "internal operator implementation function; the operator itself is the SQL surface",
|
|
1696
1694
|
"pg_stat_get_analyze_count": "internal operator implementation function; the operator itself is the SQL surface",
|
|
1697
1695
|
"pg_stat_get_archiver": "internal operator implementation function; the operator itself is the SQL surface",
|
|
@@ -2283,7 +2281,6 @@
|
|
|
2283
2281
|
"to_oct": "not implemented in this milestone; calls fail loud with SQLSTATE 42883",
|
|
2284
2282
|
"to_regclass": "not implemented in this milestone; calls fail loud with SQLSTATE 42883",
|
|
2285
2283
|
"to_regcollation": "not implemented in this milestone; calls fail loud with SQLSTATE 42883",
|
|
2286
|
-
"to_regnamespace": "not implemented in this milestone; calls fail loud with SQLSTATE 42883",
|
|
2287
2284
|
"to_regoper": "not implemented in this milestone; calls fail loud with SQLSTATE 42883",
|
|
2288
2285
|
"to_regoperator": "not implemented in this milestone; calls fail loud with SQLSTATE 42883",
|
|
2289
2286
|
"to_regproc": "not implemented in this milestone; calls fail loud with SQLSTATE 42883",
|