docs: propose ADR 0050, Module Database ABI v1 #68
@@ -168,8 +168,13 @@ query({ text, values, rowMode }) → { command, rowCount, rows, fields }
|
|||||||
transaction(callback, options) → the callback's result
|
transaction(callback, options) → the callback's result
|
||||||
```
|
```
|
||||||
|
|
||||||
- **`text`** is exactly one PostgreSQL statement, using `$1 … $n` placeholders. Statement text that
|
- **`text`** is exactly one PostgreSQL statement, using `$1 … $n` placeholders. **One `query` call
|
||||||
holds several statements is not part of the ABI, and Core may reject it.
|
runs one statement.** This is a correctness and API rule, not a security boundary.
|
||||||
|
- The implementation upholds it with a reliable driver or protocol mechanism where one exists,
|
||||||
|
such as PostgreSQL's extended query protocol, which accepts a single statement per request.
|
||||||
|
- It does not require, and must not grow, an Ekklesia SQL parser.
|
||||||
|
- If an implementation cannot reliably uphold this rule, the implementing change stops and reports
|
||||||
|
the conflict. It does not weaken this ADR silently.
|
||||||
- **`values`** is the ordered list of bound parameters (see
|
- **`values`** is the ordered list of bound parameters (see
|
||||||
[Parameters](#parameters)). Values are always bound and never interpolated into the text.
|
[Parameters](#parameters)). Values are always bound and never interpolated into the text.
|
||||||
- **`rowMode`** is `"object"` or `"array"`.
|
- **`rowMode`** is `"object"` or `"array"`.
|
||||||
@@ -280,16 +285,24 @@ Conceptually:
|
|||||||
ModuleDatabaseError { code, sqlState? }
|
ModuleDatabaseError { code, sqlState? }
|
||||||
```
|
```
|
||||||
|
|
||||||
- **`code`** is a small, stable, Ekklesia-defined set. It covers at least:
|
The two fields mean different things:
|
||||||
- the statement failed in PostgreSQL;
|
|
||||||
- a parameter or option was refused;
|
- **`code`** is always present. It is a stable, Ekklesia-owned error **category**, meant for
|
||||||
- the scope is closed;
|
programmatic handling. Its categories cover at least:
|
||||||
|
- PostgreSQL rejected the statement;
|
||||||
|
- a parameter value was refused;
|
||||||
|
- a transaction option was refused;
|
||||||
|
- the scope was closed or used incorrectly;
|
||||||
- the transaction could not be completed.
|
- the transaction could not be completed.
|
||||||
|
|
||||||
The exact names are an implementation detail.
|
This ADR does not freeze the complete set or the exact names. Once published, a code keeps its
|
||||||
- **`sqlState`** is PostgreSQL's five-character SQLSTATE, present when PostgreSQL reported one.
|
meaning.
|
||||||
Modules legitimately branch on stable conditions such as `23505` (unique violation), `23503`
|
- **`sqlState`** is optional. It is the five-character SQLSTATE that PostgreSQL itself reported, such
|
||||||
(foreign-key violation), and `40001` (serialization failure).
|
as `23505` (unique violation), `23503` (foreign-key violation), or `40001` (serialization failure).
|
||||||
|
It is present only when the database supplied one, and Core never invents one.
|
||||||
|
- Failures that the ABI detects itself have a `code` and no `sqlState`: a closed scope, an
|
||||||
|
invalid parameter type, or an invalid transaction option.
|
||||||
|
- Modules branch on `code` for ABI failures and on `sqlState` for PostgreSQL conditions.
|
||||||
- **It never carries:**
|
- **It never carries:**
|
||||||
- SQL text or parameter values;
|
- SQL text or parameter values;
|
||||||
- the driver's message, detail, hint, constraint, table, or column names;
|
- the driver's message, detail, hint, constraint, table, or column names;
|
||||||
@@ -313,13 +326,22 @@ transaction(async (tx) => { … }, { isolation, readOnly })
|
|||||||
- **Starting.** Core takes a connection from its pool and begins a transaction with the requested
|
- **Starting.** Core takes a connection from its pool and begins a transaction with the requested
|
||||||
options. It then calls the callback with `tx`, a Module Database ABI v1 executor scoped to that
|
options. It then calls the callback with `tx`, a Module Database ABI v1 executor scoped to that
|
||||||
transaction alone. No `pg` client and no Core ORM transaction is ever exposed.
|
transaction alone. No `pg` client and no Core ORM transaction is ever exposed.
|
||||||
- **Commit and rollback.** Core commits when the callback's promise resolves, and rolls back when it
|
- **Commit and rollback.** Core commits only when the callback's promise resolves and no operation
|
||||||
rejects. The transaction call then rejects with the callback's error. There is no `rollback()`
|
launched through `tx` is still outstanding. When the callback rejects, Core rolls back, and the
|
||||||
method: to roll back, the module throws.
|
transaction call rejects with the callback's error. There is no `rollback()` method: to roll back,
|
||||||
- **Unfinished queries.** Before committing or rolling back, Core waits for every query the scope
|
the module throws.
|
||||||
has already started. If one of them fails after the callback resolved, Core rolls back and reports
|
- **All work is awaited inside the callback.** Module code must await every database operation it
|
||||||
the failure.
|
starts through `tx` before its callback returns.
|
||||||
- **Revocation.** When the callback settles, `tx` is revoked. Any later call on it fails with the
|
- If the callback settles while an operation launched through that scope is still outstanding, the
|
||||||
|
module has used the scope incorrectly.
|
||||||
|
- The transaction is then never committed: Core rolls it back, and the transaction call rejects
|
||||||
|
with the scope-usage `ModuleDatabaseError`.
|
||||||
|
- A forgotten `await` therefore fails. It never quietly changes what the transaction contains.
|
||||||
|
- Core may let operations that already started finish, or settle, before it rolls back and releases
|
||||||
|
the connection, so that a pooled connection is never handed on mid-operation. Waiting for them
|
||||||
|
never turns into a commit.
|
||||||
|
- **Revocation.** When the callback settles, `tx` is revoked, and it stays revoked once the scope has
|
||||||
|
finished. Any later call on it fails with the
|
||||||
closed-scope `ModuleDatabaseError` and sends nothing to PostgreSQL. A `tx` that escapes its
|
closed-scope `ModuleDatabaseError` and sends nothing to PostgreSQL. A `tx` that escapes its
|
||||||
callback can never run a statement on a connection that has gone back to the pool.
|
callback can never run a statement on a connection that has gone back to the pool.
|
||||||
- **Options.** `isolation` is `read committed`, `repeatable read`, or `serializable`. `readOnly` is a
|
- **Options.** `isolation` is `read committed`, `repeatable read`, or `serializable`. `readOnly` is a
|
||||||
@@ -340,6 +362,9 @@ tx.savepoint(async (sp) => { … })
|
|||||||
- **Naming.** Core generates every savepoint identifier. Modules never provide one.
|
- **Naming.** Core generates every savepoint identifier. Modules never provide one.
|
||||||
- **Release and rollback.** A resolved callback releases the savepoint. A rejected one rolls back
|
- **Release and rollback.** A resolved callback releases the savepoint. A rejected one rolls back
|
||||||
to it, releases it, and rejects with the callback's error.
|
to it, releases it, and rejects with the callback's error.
|
||||||
|
- **Outstanding work.** The awaiting rule for transactions applies unchanged. A savepoint callback
|
||||||
|
that settles with operations still outstanding is a scope-usage error. The savepoint is rolled
|
||||||
|
back, never released, and the call rejects with that error.
|
||||||
- **One active scope.** Only the innermost open scope may run statements. While a savepoint is
|
- **One active scope.** Only the innermost open scope may run statements. While a savepoint is
|
||||||
open, its parent executor refuses statements and new savepoints, so savepoint scopes can never
|
open, its parent executor refuses statements and new savepoints, so savepoint scopes can never
|
||||||
overlap.
|
overlap.
|
||||||
|
|||||||
Reference in New Issue
Block a user