Jelajahi Sumber

Merge pull request #4 from database-pizza/docs/contract-audit

docs: fix remaining stale contract claims (RETURNING, ON CONFLICT, CHECK/FK, TLS, JSON)
Danilo Fragoso 2 hari lalu
induk
melakukan
140c1edefe

+ 1 - 1
src/content/docs/clients/api-keys.md

@@ -78,7 +78,7 @@ Revoking a key takes effect immediately; any application using it loses access o
 
 - Keys are stored hashed; only the prefix is visible after creation.
 - `DB_ACCESS` and `REST_API` keys are bound to a database. Reserved platform keys are organization-level.
-- A key used for the Postgres protocol is sent as a cleartext password to the proxy, and the proxy currently declines TLS — factor that into how you handle the connection (see [Connect](/getting-started/connect/)).
+- A key used for the Postgres protocol is sent as the password to the proxy. TLS terminates at the proxy when the deployment installs a certificate (`sslmode=require`); without one the key crosses the client-to-proxy hop in cleartext. The proxy-to-engine loopback hop is always plaintext (see [Connect](/getting-started/connect/)).
 
 ## Next
 

+ 2 - 2
src/content/docs/clients/javascript.md

@@ -13,7 +13,7 @@ export PZ_API_KEY='pz_live_REPLACE_ME'
 
 ## With `pg` (Node.js)
 
-The `pg` driver (node-postgres) gives you a real connection pool and transaction support. The database name includes the `/`, and TLS is disabled.
+The `pg` driver (node-postgres) gives you a real connection pool and transaction support. The database name includes the `/`. node-postgres has `ssl: false` by default, which works in `optional` TLS mode; when the proxy requires TLS, set `ssl: true` (or provide a CA) and expect `08P01` until you do.
 
 ```bash
 npm install pg
@@ -28,7 +28,7 @@ const client = new Client({
   user: 'u',                       // ignored; any value
   password: process.env.PZ_API_KEY,
   database: 'acme/production',     // discrete field — no %2F encoding
-  ssl: false,                      // TLS is declined by the proxy
+  ssl: false,                      // set true when the proxy requires TLS
 });
 
 await client.connect();

+ 1 - 1
src/content/docs/clients/postgresql.md

@@ -148,7 +148,7 @@ The SQL you send is **SQLite-compatible**, not PostgreSQL. That means:
 - `INTEGER PRIMARY KEY`, not `SERIAL`; values are assigned when omitted.
 - `TEXT`, not `VARCHAR(n)` with enforced length.
 - No schemas, roles, `ARRAY`, `JSONB`, or `ENUM` types.
-- No `RETURNING`-heavy Postgres-specific features; check [Compatibility](/sql-reference/compatibility/) first.
+- `RETURNING` works for `INSERT`, `UPDATE`, and `DELETE`; PostgreSQL-specific features such as `JSONB` and arrays do not — check [Compatibility](/sql-reference/compatibility/) first.
 
 The [SQL reference](/sql-reference/overview/) is the source of truth for what's supported.
 

+ 2 - 1
src/content/docs/engine/transactions.md

@@ -53,7 +53,8 @@ COMMIT;                -- keeps the first insert
 On the PostgreSQL protocol, the connection tracks transaction state. If a statement inside a transaction block errors:
 
 - The connection enters a "failed transaction" state and rejects further commands with SQLSTATE `25P02` until the client sends `ROLLBACK` (or `ROLLBACK TO SAVEPOINT`).
-- This mirrors PostgreSQL's aborted-transaction behaviour.
+- A failed **`COMMIT`** is different: storage has already rolled the transaction back, so the session returns to idle and the next statement starts a new implicit transaction. Do not keep issuing `ROLLBACK` after a failed commit.
+- This mirrors PostgreSQL's aborted-statement behaviour.
 
 ## Managed service restrictions
 

+ 1 - 1
src/content/docs/guides/errors.md

@@ -83,7 +83,7 @@ Common causes:
 
 - **`no such table`** — the table doesn't exist yet, or you're connected to the wrong database.
 - **`syntax error`** — a typo, or Postgres-only syntax. PizzaSQL uses SQLite-compatible SQL; check [Compatibility](/sql-reference/compatibility/).
-- **`duplicate primary key`** — an insert reused an existing primary-key value. Other `UNIQUE` constraints are not enforced.
+- **`duplicate primary key`** — an insert reused an existing primary-key value. `UNIQUE` constraints and unique indexes are enforced too and return SQLSTATE `23505` on the PostgreSQL path.
 - **`NOT NULL constraint failed`** — a `NULL` value in a `NOT NULL` column.
 
 ## Transactions over HTTP

+ 1 - 1
src/content/docs/internals/catalog.md

@@ -41,7 +41,7 @@ This means concurrent DDL and DML from different connections eventually converge
 
 The catalog is deliberately minimal:
 
-- It does **not** store `CHECK` or `FOREIGN KEY` constraints — those are discarded at `CREATE TABLE`. `UNIQUE` constraints are materialized into unique index definitions (see [Constraints](/sql-reference/constraints/)).
+- It does **not** store `CHECK` or `FOREIGN KEY` constraints — those DDL clauses are rejected at `CREATE TABLE` with SQLSTATE `0A000`. `UNIQUE` constraints are materialized into unique index definitions (see [Constraints](/sql-reference/constraints/)).
 - It does not store index definitions (those live in the `SchemaManager`/storage, not the analyzer catalog).
 - It does not store statistics, histograms, or anything a cost-based planner would use — there is no planner.
 

+ 1 - 1
src/content/docs/internals/query-lifecycle.md

@@ -7,7 +7,7 @@ This page traces a single statement from bytes to result. It explains *why* cert
 
 ## 1. Lex and parse
 
-The SQL string is tokenized and parsed into an AST. Both steps are pure and fail fast with a syntax error for anything the grammar doesn't cover (`RETURNING`, `WITHOUT ROWID`, triggers, etc.). A statement that parses is guaranteed to be one the engine at least *recognizes*, even if it later turns out to be only partially implemented.
+The SQL string is tokenized and parsed into an AST. Both steps are pure and fail fast with a syntax error for anything the grammar doesn't cover (`WITHOUT ROWID`, triggers, etc.). A statement that parses is guaranteed to be one the engine at least *recognizes*, even if it later turns out to be only partially implemented.
 
 Over the PostgreSQL protocol, a multi-statement batch is parsed *in full before execution begins*, so an unsupported trailing statement doesn't leave earlier writes half-committed.
 

+ 2 - 2
src/content/docs/sql-reference/compatibility.md

@@ -99,7 +99,7 @@ Unsupported syntax normally returns an error. The items above are called out sep
 | `INSERT OR IGNORE`, `REPLACE`, and related SQLite conflict modes | Supported | Supported | Different syntax |
 | `ON CONFLICT DO NOTHING` | Supported for primary-key and unique-index targets | Supported for unique constraints | Supported for unique constraints |
 | `ON CONFLICT DO UPDATE` | Supported for primary-key and unique-index targets, including `excluded` | Supported for unique constraints | Supported for unique constraints |
-| `RETURNING` | Unsupported | Supported since 3.35 | Supported |
+| `RETURNING` | Supported for `INSERT`, `UPDATE`, and `DELETE` | Supported since 3.35 | Supported |
 | `TRUNCATE` | Unsupported | Unsupported | Supported |
 
 ## Schema and constraints
@@ -168,7 +168,7 @@ PizzaSQL accepts many familiar type names but does not implement PostgreSQL's st
 | SQLite date and time functions | Supported | Supported | Different function set |
 | `last_insert_rowid()`, `changes()`, `total_changes()` | Supported; session-local | Supported | Unsupported |
 | PostgreSQL date and time functions | Unsupported | Unsupported | Supported |
-| JSON functions | Unsupported | Available with SQLite JSON support | Supported |
+| JSON functions | Supported: TEXT storage plus JSON1 scalar functions and `json_group_array`; no `JSONB` or `->`/`->>` | Available with SQLite JSON support | Supported |
 | Window functions | Partial; `ROW_NUMBER()` only | Supported | Supported |
 
 ## Transactions

+ 2 - 2
src/content/docs/sql-reference/constraints.md

@@ -1,6 +1,6 @@
 ---
 title: Constraints
-description: What PRIMARY KEY, NOT NULL, DEFAULT, UNIQUE, CHECK, and FOREIGN KEY actually do in PizzaSQL — and which ones are not enforced.
+description: What PRIMARY KEY, NOT NULL, DEFAULT, UNIQUE, CHECK, and FOREIGN KEY actually do in PizzaSQL — and which ones are rejected.
 ---
 
 Constraints in PizzaSQL fall into two camps: the ones that are actually implemented, and the ones that merely parse. This page is deliberately blunt about the difference, because silently assuming a `CHECK` or `FOREIGN KEY` constraint is enforced will corrupt data. `UNIQUE` is now enforced on the core write paths, with caveats described below.
@@ -13,7 +13,7 @@ Constraints in PizzaSQL fall into two camps: the ones that are actually implemen
 - A composite primary key (`PRIMARY KEY (a, b)`) is rejected at `CREATE TABLE` with SQLSTATE `0A000`; it is never silently reduced to the first column.
 - An **`INTEGER PRIMARY KEY`** column becomes an alias for the implicit rowid. Inserting without a value auto-assigns the next rowid; inserting with a value sets the rowid and advances the counter past it.
 - A **non-integer primary key** (e.g. `TEXT PRIMARY KEY`) is a normal column with a separate hidden rowid counter.
-- The primary key drives **duplicate detection** on `INSERT`, and therefore `INSERT OR REPLACE`/`OR IGNORE` and `ON CONFLICT DO NOTHING`/`DO UPDATE` — all of which are keyed on the primary key only.
+- The primary key and unique indexes drive **duplicate detection** on `INSERT`, and therefore `INSERT OR REPLACE`/`OR IGNORE` and `ON CONFLICT DO NOTHING`/`DO UPDATE`. `ON CONFLICT` targets a primary key or unique-index column list.
 - Without any declared primary key, the engine assigns a synthetic `_rowid_` primary key, so duplicate and NULL values are allowed in user columns.
 
 ### NOT NULL

+ 2 - 3
src/content/docs/sql-reference/overview.md

@@ -29,7 +29,7 @@ SELECT [DISTINCT] cols FROM table [WHERE ...] [GROUP BY ...] [HAVING ...]
 INSERT INTO t (cols) VALUES (...), (...);
 INSERT INTO t SELECT ...;
 INSERT OR REPLACE/IGNORE/FAIL/ABORT INTO t ...;
-INSERT INTO t ... ON CONFLICT (pk) DO NOTHING | DO UPDATE SET c = v, ...;
+INSERT INTO t ... ON CONFLICT (col, ...) DO NOTHING | DO UPDATE SET c = v, ...;
 UPDATE t SET c = v, ... WHERE ...;
 DELETE FROM t WHERE ...;
 
@@ -57,7 +57,6 @@ EXPLAIN ...; EXPLAIN QUERY PLAN ...;
 PizzaSQL is intentionally small. These features are not implemented at all — they will be rejected by the parser, not silently mishandled:
 
 - **Window functions** — only `ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)`; other window functions and frame clauses are not implemented.
-- **`RETURNING`** — `INSERT`/`UPDATE`/`DELETE` do not return rows.
 - **`WITHOUT ROWID`** tables.
 - **Triggers**, **stored procedures**, **prepared SQL in the engine** (the PG driver handles parameters client-side).
 - **`GLOB`** — the keyword exists but is not wired up as an operator; `x GLOB 'a*'` is a parse error.
@@ -66,7 +65,7 @@ A second group of features *parses* but has no effect or is only partially imple
 
 - `RIGHT JOIN` and `FULL [OUTER] JOIN` parse but return an empty result — they are not executed. Use `LEFT JOIN` and reorder.
 - `NATURAL JOIN` and `USING (cols)` parse but the condition is ignored.
-- `UNIQUE`, `CHECK`, and `FOREIGN KEY` constraints parse but are **not enforced** (see [Constraints](/sql-reference/constraints/)).
+- `CHECK` and `FOREIGN KEY` constraints are **rejected at DDL** with SQLSTATE `0A000`; `UNIQUE` constraints and unique indexes are enforced (see [Constraints](/sql-reference/constraints/)).
 - `AUTOINCREMENT` parses but has **no behaviour** beyond ordinary `INTEGER PRIMARY KEY` rowid generation.
 - `LIKE ... ESCAPE 'x'` — the `ESCAPE` clause is parsed and ignored.
 

+ 13 - 11
src/content/docs/sql-reference/statements.md

@@ -51,29 +51,31 @@ INSERT OR REPLACE INTO table ...;
 INSERT OR IGNORE INTO table ...;
 INSERT OR FAIL INTO table ...;
 INSERT OR ABORT INTO table ...;
-INSERT INTO table ... ON CONFLICT [(pk)] DO NOTHING;
-INSERT INTO table ... ON CONFLICT [(pk)] DO UPDATE SET col = expr, ...;
+INSERT INTO table ... ON CONFLICT [(col, ...)] DO NOTHING;
+INSERT INTO table ... ON CONFLICT [(col, ...)] DO UPDATE SET col = expr, ...;
+INSERT INTO table ... RETURNING expr, ...;
 ```
 
 - Omitting the column list targets all columns in declaration order.
 - `INSERT ... SELECT` bulk-inserts the materialized result.
-- **Conflict handling** is driven by the primary key only:
-  - `INSERT OR IGNORE` silently skips duplicate-PK rows.
+- **Conflict handling** applies to the primary key and to unique indexes:
+  - `INSERT OR IGNORE` silently skips rows that would violate a primary key or unique index.
   - `INSERT OR REPLACE` deletes the conflicting row and inserts the new one.
-  - `INSERT OR FAIL`/`OR ABORT` abort on the first duplicate.
-  - `ON CONFLICT (target) DO NOTHING` / `DO UPDATE SET ...` work only when the conflict is on the primary key; the `(target)` list must name the PK column (or be omitted).
-- Auto-generated integer primary keys (and the implicit rowid) are assigned when no PK value is supplied. There is **no `RETURNING`**, but `SELECT last_insert_rowid()` returns the rowid of the most recent successful `INSERT` on this connection (see [Functions](/sql-reference/functions/)).
+  - `INSERT OR FAIL`/`OR ABORT` abort on the first conflict.
+  - `ON CONFLICT (target) DO NOTHING` / `DO UPDATE SET ...` accept a primary-key or unique-index column list; omit the list for any conflict. `excluded.column` refers to the value proposed by the INSERT.
+- Auto-generated integer primary keys (and the implicit rowid) are assigned when no PK value is supplied. `SELECT last_insert_rowid()` returns the rowid of the most recent successful `INSERT` on this connection (see [Functions](/sql-reference/functions/)).
+- `RETURNING expr, ...` returns the inserted rows (including generated keys and defaults) instead of a bare command tag.
 
 ## UPDATE and DELETE
 
 ```sql
-UPDATE table SET col = expr, ... WHERE condition;
-DELETE FROM table WHERE condition;
+UPDATE table SET col = expr, ... [WHERE condition] [RETURNING expr, ...];
+DELETE FROM table [WHERE condition] [RETURNING expr, ...];
 ```
 
 - Both evaluate the `WHERE` condition per row. `UPDATE` evaluates `SET` expressions against the row's current values, so `SET balance = balance + 100` works.
 - Omitting `WHERE` affects every row.
-- No `RETURNING`, no `ORDER BY`/`LIMIT` on `UPDATE`/`DELETE`.
+- `RETURNING` returns the affected rows. `ORDER BY`/`LIMIT` on `UPDATE`/`DELETE` are not supported.
 
 ## CREATE TABLE
 
@@ -176,4 +178,4 @@ DETACH DATABASE alias;
 
 ## Not supported
 
-These are rejected at parse time: `RETURNING`, `WITHOUT ROWID`, `TRUNCATE`, `UPSERT` (beyond the `OR`/`ON CONFLICT` forms), triggers, and `CREATE SCHEMA`/roles. Window functions other than `ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)` are also rejected.
+These are rejected at parse time: `WITHOUT ROWID`, `TRUNCATE`, `UPSERT` (beyond the `OR`/`ON CONFLICT` forms), triggers, and `CREATE SCHEMA`/roles. Window functions other than `ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)` are also rejected.