dzql 0.10.0 → 0.11.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/README.md CHANGED
@@ -20,12 +20,12 @@ import { openPglite } from "dzql/pglite"; // Postgres in this process
20
20
  import { jwtAuth } from "dzql/auth-jwt";
21
21
  ```
22
22
 
23
- Its tests run delta's own cases (`@blueshed/delta/testing`) against it. Compiled SQL is replacing
24
- the framework SQL a piece at a time: `generateSql` writes functions for each of your documents
25
- (opening it, reading its name, who holds a row, a write's scope, what each document is told, the
26
- fan-out), each held equal to the framework's; documents it did not compile go to the framework. Apply `generateSql`
27
- after the framework, every time (`docs/delta/postgres.md`, "Compiled SQL"). The plan is in
28
- Workshop's `docs/postgres-split-plan.md`.
23
+ Its tests run delta's own cases (`@blueshed/delta/testing`) against it. `generateSql` writes the
24
+ SQL for each of your documents and collections, compiled from their definitions (opening a
25
+ document, reading its name, who holds a row, a write's scope and rows, what each document is told,
26
+ the fan-out, the ledger's reads): nothing interprets a definition at run time. Apply it after the
27
+ framework, every time, one app per database (`docs/delta/postgres.md`, "Compiled SQL"). The plan
28
+ is in Workshop's `docs/postgres-split-plan.md`.
29
29
 
30
30
  ## The whole-stack dzql (retiring)
31
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dzql",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "delta's compiled Postgres step: an app's delta definitions compiled to SQL",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,8 +13,8 @@
13
13
  * - `dzql_hanging_rows__<slug>(id)`: the rows that hang from it, and on down,
14
14
  * parent first.
15
15
  * And `_delta_cascade_remove`, `_delta_cascade_rows` and `_delta_hanging_rows`,
16
- * sending each collection to its own and any other to the framework's
17
- * (`_framework`). The order is the framework's: child collections by key, then
16
+ * sending each collection to its own; a collection there is not has nothing
17
+ * to take. The order is the one the framework's had: child collections by key, then
18
18
  * the collections that cascade on it by key and key column, each in the
19
19
  * database's byte order (`COLLATE "C"`), and each one's rows by id.
20
20
  */
@@ -118,15 +118,15 @@ END;
118
118
  $$ LANGUAGE plpgsql STABLE;`;
119
119
  }
120
120
 
121
- /** A dispatcher: each collection of `schema` to its own, any other to the framework's. */
121
+ /** A dispatcher: each collection of `schema` to its own; any other has nothing to take. */
122
122
  function dispatch(schema: Schema, name: string, what: string, volatility: string): string {
123
123
  const colls = Object.keys(schema.tables);
124
124
  const body = colls.length
125
125
  ? ` CASE p_collection_key
126
126
  ${colls.map((c) => ` WHEN ${lit(c)} THEN RETURN ${fn(what, c)}(p_id);`).join("\n")}
127
- ELSE RETURN ${name}_framework(p_collection_key, p_id);
127
+ ELSE RETURN '[]'::jsonb; -- a collection there is not: nothing to take, nothing hangs from it
128
128
  END CASE;`
129
- : ` RETURN ${name}_framework(p_collection_key, p_id);`;
129
+ : ` RETURN '[]'::jsonb;`;
130
130
  return `CREATE OR REPLACE FUNCTION ${name}(p_collection_key TEXT, p_id BIGINT)
131
131
  RETURNS JSONB AS $$
132
132
  BEGIN
@@ -135,7 +135,7 @@ END;
135
135
  $$ LANGUAGE plpgsql${volatility};`;
136
136
  }
137
137
 
138
- /** The cascades and hanging rows of every collection of `schema`, and the dispatch to the framework's for any other. */
138
+ /** The cascades and hanging rows of every collection of `schema`, and their dispatch. */
139
139
  export function compileCascades(schema: Schema): string {
140
140
  assertSlugs(schema);
141
141
  const colls = Object.keys(schema.tables);
@@ -10,19 +10,18 @@
10
10
  *
11
11
  * - `dzql_columns__<slug>(collection, columns_def)` for each document: the
12
12
  * columns it writes, the ones it names (`columns`) where it names some,
13
- * from the list written in. `_delta_write_columns` sends each compiled
14
- * document to its own, any other to the framework
15
- * (`_delta_write_columns_framework`).
13
+ * from the list written in. `_delta_write_columns` sends each document to
14
+ * its own, and refuses a name of none.
16
15
  * - `_delta_assert_fields` itself: for each collection, each set of columns
17
16
  * the definitions give it (all of them, and each document's own), checked
18
17
  * by written statements -- the fields there are, the parent key, each
19
18
  * column's type -- with the framework's messages, put together in the
20
19
  * framework's order (`ORDER BY` in SQL, so the database's collation orders
21
20
  * both alike). Columns of another set (a walk's, another version's) are
22
- * checked by the framework's (`_delta_assert_fields_framework`).
21
+ * checked by the columns given (`_delta_assert_fields_declared`).
23
22
  */
24
23
  import type { ColumnDef, DocDef, ResolvedTable, Schema } from "@blueshed/delta/backend";
25
- import { lit, named, need } from "./opens";
24
+ import { lit, named, need, noDoc } from "./opens";
26
25
 
27
26
  /** A document's columns function: `dzql_columns__<slug>`. */
28
27
  export const columnsFunction = (prefix: string): string => named("columns", prefix);
@@ -105,7 +104,7 @@ RETURNS JSONB AS $$
105
104
  BEGIN
106
105
  CASE p_def.prefix
107
106
  ${docs.map((d) => ` WHEN ${lit(d.prefix)} THEN RETURN ${columnsFunction(d.prefix)}(p_coll, p_columns_def);`).join("\n")}
108
- ELSE RETURN _delta_write_columns_framework(p_def, p_coll, p_columns_def);
107
+ ELSE ${noDoc("p_def.prefix")}
109
108
  END CASE;
110
109
  END;
111
110
  $$ LANGUAGE plpgsql STABLE;`;
@@ -136,8 +135,8 @@ BEGIN
136
135
  ${branches.join("\n")}
137
136
  ELSE NULL;
138
137
  END CASE;
139
- -- another set of columns (a walk's, another version's): the framework's
140
- PERFORM _delta_assert_fields_framework(p_collection, p_columns, p_parent_fk, p_value);
138
+ -- another set of columns (a walk's, another version's): by the columns given
139
+ PERFORM _delta_assert_fields_declared(p_collection, p_columns, p_parent_fk, p_value);
141
140
  END;
142
141
  $$ LANGUAGE plpgsql IMMUTABLE;`;
143
142
  return [...docs.map((d) => columnsOf(schema, d)), dispatch, checks, ""].join("\n\n");
@@ -10,10 +10,9 @@
10
10
  * row hangs from, its chain written as lookups, where the document is one row
11
11
  * of an unscoped definition; else every open name of its prefix), and the
12
12
  * writer's; each name its own prefix owns and whose document holds the row
13
- * (`dzql_holds__<slug>`). Any other document (registered by hand, or by another
14
- * `generateSql`) is asked by the framework's (`_delta_holders_framework`), told
15
- * which prefixes were asked already. SECURITY DEFINER, with the search path it
16
- * was applied with and pg_temp last, as the framework's.
13
+ * (`dzql_holds__<slug>`). There is no other document: the framework that asked
14
+ * the rest retired (todo #26). SECURITY DEFINER, with the search path it was
15
+ * applied with and pg_temp last, as the framework's was.
17
16
  */
18
17
  import type { DocDef, Schema } from "@blueshed/delta/backend";
19
18
  import { chainRoot, holdsFunction } from "./holds";
@@ -39,13 +38,12 @@ ${all ? "" : ` v_rid := ${coll === doc.root ? "(p_row->>'id')::BIGINT" : ch
39
38
  END LOOP;`;
40
39
  }
41
40
 
42
- /** The SQL that answers who holds a row: `_delta_holders`, each of `docs` asked by its own lookups, any other by the framework's. */
41
+ /** The SQL that answers who holds a row: `_delta_holders`, each of `docs` asked by its own lookups. */
43
42
  export function compileHolders(schema: Schema, docs: DocDef[]): string {
44
43
  const branches = Object.keys(schema.tables).flatMap((coll) => {
45
44
  const holding = docs.filter((d) => d.root === coll || d.include.includes(coll));
46
45
  return holding.length ? [` WHEN ${lit(coll)} THEN\n${holding.map((d) => askOf(schema, d, coll)).join("\n")}`] : [];
47
46
  });
48
- const asked = `ARRAY[${docs.map((d) => lit(d.prefix)).join(", ")}]::text[]`;
49
47
  return `CREATE OR REPLACE FUNCTION _delta_holders(p_coll TEXT, p_row JSONB, p_writer TEXT)
50
48
  RETURNS TEXT[] AS $$
51
49
  DECLARE
@@ -58,11 +56,7 @@ BEGIN
58
56
  upper(current_setting('transaction_isolation')) USING ERRCODE = '0A000';
59
57
  END IF;
60
58
  IF p_row IS NULL THEN RETURN v_names; END IF;
61
- ${branches.length ? ` CASE p_coll\n${branches.join("\n")}\n ELSE NULL;\n END CASE;\n` : ""} -- the documents not compiled here, as the framework asks them
62
- IF EXISTS (SELECT 1 FROM _delta_docs WHERE (root_collection = p_coll OR p_coll = ANY(include)) AND NOT (prefix = ANY(${asked}))) THEN
63
- v_names := v_names || _delta_holders_framework(p_coll, p_row, p_writer, ${asked});
64
- END IF;
65
- RETURN v_names;
59
+ ${branches.length ? ` CASE p_coll\n${branches.join("\n")}\n ELSE NULL;\n END CASE;\n` : ""} RETURN v_names;
66
60
  END;
67
61
  $$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
68
62
  DO $$ BEGIN
@@ -10,12 +10,11 @@
10
10
  * (`scopeOf`, a name its columns cannot take holding nothing), a temporal row
11
11
  * not told to a document named with a time past, and an included row's chain
12
12
  * of parents up to the root, as written lookups. And `_delta_doc_holds`
13
- * itself, which sends each compiled document to its function and any other
14
- * (registered by hand, or by another `generateSql`) to the framework's
15
- * (`_delta_doc_holds_framework`).
13
+ * itself, which sends each document to its function and refuses a name of none (`no doc def for`: the framework that read the rest
14
+ * retired, todo #26).
16
15
  */
17
16
  import { q, type DocDef, type ResolvedTable, type Schema } from "@blueshed/delta/backend";
18
- import { lit, named, need, scopeOf , currentOnly } from "./opens";
17
+ import { lit, named, need, noDoc, scopeOf, currentOnly } from "./opens";
19
18
 
20
19
  /** A document's holds function: `dzql_holds__<slug>`. */
21
20
  export const holdsFunction = (prefix: string): string => named("holds", prefix);
@@ -83,7 +82,7 @@ RETURNS BOOLEAN AS $$
83
82
  BEGIN
84
83
  CASE p_def.prefix
85
84
  ${docs.map((d) => ` WHEN ${lit(d.prefix)} THEN RETURN ${holdsFunction(d.prefix)}(p_doc_name, p_coll, p_row);`).join("\n")}
86
- ELSE RETURN _delta_doc_holds_framework(p_def, p_doc_name, p_coll, p_row);
85
+ ELSE ${noDoc("p_doc_name")}
87
86
  END CASE;
88
87
  END;
89
88
  $$ LANGUAGE plpgsql STABLE;`;
@@ -8,17 +8,16 @@
8
8
  * - `dzql_open_whole__<slug>(name)` for each document: its open with every
9
9
  * column of every row, whatever columns it names (`readOf`, whole), and
10
10
  * `_delta_open` itself, sending each compiled document to its own open, shown
11
- * (`dzql_open__<slug>`) or whole, and any other to the framework's
12
- * (`_delta_open_framework`);
11
+ * (`dzql_open__<slug>`) or whole, and refusing a name of none;
13
12
  * - `_delta_same(collection, field, a, b)`: two values of a field the same, a
14
13
  * time by the instant it names. The field's type is the writing process's
15
14
  * (`delta.declared`), else the schema's, written in for each collection;
16
- * any other collection is the framework's (`_delta_same_framework`);
15
+ * a collection there is not, the same as JSON;
17
16
  * - `_delta_row_standing(collection, id)`: a row as it stands, as `{row}`, for
18
- * every collection of the schema; any other the framework's.
17
+ * every collection of the schema; NULL for one there is not.
19
18
  */
20
19
  import type { DocDef, Schema } from "@blueshed/delta/backend";
21
- import { lit, openFunction, openWholeFunction, readOf, standingRows } from "./opens";
20
+ import { lit, noDoc, openFunction, openWholeFunction, readOf, standingRows } from "./opens";
22
21
 
23
22
  /** The SQL the ledger reads by, for `schema` and each of `docs`. */
24
23
  export function compileLedger(schema: Schema, docs: DocDef[]): string {
@@ -31,7 +30,7 @@ DECLARE
31
30
  BEGIN
32
31
  CASE v_def.prefix
33
32
  ${docs.map((d) => ` WHEN ${lit(d.prefix)} THEN RETURN CASE WHEN p_whole THEN ${openWholeFunction(d.prefix)}(p_doc_name) ELSE ${openFunction(d.prefix)}(p_doc_name) END;`).join("\n")}
34
- ELSE RETURN _delta_open_framework(p_doc_name, p_whole); -- a name no document compiled here holds (none at all: the framework refuses it)
33
+ ELSE ${noDoc("p_doc_name")}
35
34
  END CASE;
36
35
  END;
37
36
  $$ LANGUAGE plpgsql;`;
@@ -51,7 +50,7 @@ ${colls.map(([coll, t]) => {
51
50
  v_declared := NULLIF(current_setting('delta.declared', true), '')::jsonb -> ${lit(coll)};
52
51
  v_type := CASE WHEN v_declared IS NOT NULL THEN v_declared -> p_field ->> 'type'${times.length ? ` WHEN p_field IN (${times.map(lit).join(", ")}) THEN 'timestamptz'` : ""} END;`;
53
52
  }).join("\n")}
54
- ELSE RETURN _delta_same_framework(p_coll, p_field, a, b);
53
+ ELSE NULL; -- a collection there is not: its values the same as JSON
55
54
  END CASE;
56
55
  IF v_type = 'timestamptz' THEN RETURN (a #>> '{}')::timestamptz = (b #>> '{}')::timestamptz; END IF;
57
56
  END IF;
@@ -64,7 +63,7 @@ RETURNS JSONB AS $$
64
63
  BEGIN
65
64
  CASE p_coll
66
65
  ${colls.map(([coll, t]) => ` WHEN ${lit(coll)} THEN RETURN jsonb_build_object('row', (SELECT _delta_strip_temporal(to_jsonb(t.*)) FROM ${standingRows(t)} t WHERE t.id = p_id));`).join("\n")}
67
- ELSE RETURN _delta_row_standing_framework(p_coll, p_id);
66
+ ELSE RETURN NULL; -- a collection there is not
68
67
  END CASE;
69
68
  END;
70
69
  $$ LANGUAGE plpgsql STABLE;`;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Compiled lookups: a collection's definition, its table's serial, and a
3
+ * document read as it stood. What the write, the reads and the ledger still
4
+ * looked up at run time -- `_delta_collections` for a collection's table,
5
+ * parent and columns, a table's sequence by its name, the framework's read as
6
+ * it stood -- written from the definitions.
7
+ *
8
+ * `compileLookups(schema, docs)` writes:
9
+ * - `_delta_collection(key)`: each collection's definition, as generateSql
10
+ * records it in `_delta_collections`, written in; NULL fields for a key of
11
+ * none;
12
+ * - `_delta_serial_new(table, id)` and `_delta_serial_past(table, id)`: each
13
+ * table's sequence named, read or moved as the framework's do (as the
14
+ * functions' owner, for a caller with the table's privilege); for a table
15
+ * there is not, no id is new and nothing moves;
16
+ * - `_delta_find_doc(name)`: the document a name is of, by the longest prefix
17
+ * it starts with, as generateSql records it in `_delta_docs`, written in;
18
+ * NULL for a name of none;
19
+ * - `delta_open_at(name, at)`: each compiled document to its own read as it
20
+ * stood (`dzql_open_at__<slug>`), refusing a name of none.
21
+ *
22
+ * Each answers what the framework's did (recorded: tests/compile/lookups.test.ts).
23
+ */
24
+ import { q, type DocDef, type Schema } from "@blueshed/delta/backend";
25
+ import { findCascadeOn } from "../postgres/sql";
26
+ import { lit, noDoc, openAtFunction } from "./opens";
27
+
28
+ /** The caller, as the framework's serials ask: the role set, else the session's. */
29
+ const CALLER = "COALESCE(NULLIF(current_setting('role'), 'none'), session_user)";
30
+
31
+ /** The search path a definer function runs with: the schemas applied in, and pg_temp last (as the framework's serials). */
32
+ const searchPath = (signature: string) => `DO $$
33
+ DECLARE
34
+ v_path TEXT := COALESCE((SELECT string_agg(quote_ident(s), ', ') FROM unnest(current_schemas(false)) AS s), 'public') || ', pg_temp';
35
+ BEGIN
36
+ EXECUTE format('ALTER FUNCTION ${signature} SET search_path = %s', v_path);
37
+ END $$;`;
38
+
39
+ export function compileLookups(schema: Schema, docs: DocDef[]): string {
40
+ const tables = Object.entries(schema.tables);
41
+
42
+ const collection = `CREATE OR REPLACE FUNCTION _delta_collection(p_key TEXT)
43
+ RETURNS _delta_collections AS $$
44
+ BEGIN
45
+ CASE p_key
46
+ ${tables.map(([key, t]) => {
47
+ const row = {
48
+ collection_key: key,
49
+ table_name: t.name,
50
+ columns_def: t.columns,
51
+ parent_collection: t.parent?.collection ?? null,
52
+ parent_fk: t.parent?.fkColumn ?? null,
53
+ temporal: t.temporal,
54
+ cascade_on: findCascadeOn(key, schema),
55
+ };
56
+ return ` WHEN ${lit(key)} THEN RETURN jsonb_populate_record(null::_delta_collections, ${lit(JSON.stringify(row))}::jsonb);`;
57
+ }).join("\n")}
58
+ ELSE RETURN NULL; -- a collection there is not
59
+ END CASE;
60
+ END;
61
+ $$ LANGUAGE plpgsql STABLE;`;
62
+
63
+ const serialNew = `CREATE OR REPLACE FUNCTION _delta_serial_new(p_table TEXT, p_id BIGINT)
64
+ RETURNS BOOLEAN AS $$
65
+ DECLARE
66
+ v_new BOOLEAN;
67
+ BEGIN
68
+ CASE p_table
69
+ ${tables.map(([, t]) => ` WHEN ${lit(t.name)} THEN
70
+ IF NOT has_table_privilege(${CALLER}, p_table, 'SELECT') THEN RETURN FALSE; END IF;
71
+ SELECT p_id >= last_value + is_called::int INTO v_new FROM ${q(`seq_${t.name}`)};
72
+ RETURN v_new;`).join("\n")}
73
+ ELSE RETURN FALSE; -- a table there is not: no id is new
74
+ END CASE;
75
+ END;
76
+ $$ LANGUAGE plpgsql SECURITY DEFINER;
77
+ ${searchPath("_delta_serial_new(text, bigint)")}`;
78
+
79
+ const serialPast = `CREATE OR REPLACE FUNCTION _delta_serial_past(p_table TEXT, p_id BIGINT)
80
+ RETURNS void AS $$
81
+ BEGIN
82
+ CASE p_table
83
+ ${tables.map(([, t]) => ` WHEN ${lit(t.name)} THEN
84
+ IF p_id > 9007199254740991 OR NOT has_table_privilege(${CALLER}, p_table, 'INSERT') THEN RETURN; END IF;
85
+ PERFORM setval(${lit(`seq_${t.name}`)}, p_id) FROM ${q(`seq_${t.name}`)} WHERE p_id >= last_value + is_called::int;`).join("\n")}
86
+ ELSE NULL; -- a table there is not: nothing to move
87
+ END CASE;
88
+ END;
89
+ $$ LANGUAGE plpgsql SECURITY DEFINER;
90
+ ${searchPath("_delta_serial_past(text, bigint)")}`;
91
+
92
+ // longest first, as the framework's ORDER BY length(prefix) DESC: a name is of the longest prefix it starts with
93
+ const byLength = [...docs].sort((a, b) => b.prefix.length - a.prefix.length || (a.prefix < b.prefix ? -1 : 1));
94
+ const findDoc = `CREATE OR REPLACE FUNCTION _delta_find_doc(p_doc_name TEXT)
95
+ RETURNS _delta_docs AS $$
96
+ BEGIN
97
+ ${byLength.map((d) => {
98
+ const row = { prefix: d.prefix, root_collection: d.root, include: d.include, scope: d.scope, implied: !!d.implied, held: d.columns ?? {} };
99
+ return ` IF starts_with(p_doc_name, ${lit(d.prefix)}) THEN RETURN jsonb_populate_record(null::_delta_docs, ${lit(JSON.stringify(row))}::jsonb); END IF;`;
100
+ }).join("\n")}
101
+ RETURN NULL; -- a name no document holds
102
+ END;
103
+ $$ LANGUAGE plpgsql STABLE;`;
104
+
105
+ const openAt = `CREATE OR REPLACE FUNCTION delta_open_at(p_doc_name TEXT, p_at TIMESTAMPTZ)
106
+ RETURNS JSONB AS $$
107
+ DECLARE
108
+ v_def _delta_docs := _delta_find_doc(p_doc_name);
109
+ BEGIN
110
+ CASE v_def.prefix
111
+ ${docs.map((d) => ` WHEN ${lit(d.prefix)} THEN RETURN ${openAtFunction(d.prefix)}(p_doc_name, p_at);`).join("\n")}
112
+ ELSE ${noDoc("p_doc_name")}
113
+ END CASE;
114
+ END;
115
+ $$ LANGUAGE plpgsql STABLE;`;
116
+
117
+ return [collection, findDoc, serialNew, serialPast, openAt, ""].join("\n\n");
118
+ }
@@ -27,6 +27,12 @@ import { columnSqlType } from "../postgres/sql";
27
27
  /** A string as an SQL literal. */
28
28
  export const lit = (s: string): string => `'${s.replace(/'/g, "''")}'`;
29
29
 
30
+ /** A name no document compiled here holds: there is none (the framework that read the rest retired, todo #26). */
31
+ export const noDoc = (name: string): string => `RAISE EXCEPTION 'no doc def for: %', ${name} USING ERRCODE = 'P0001';`;
32
+
33
+ /** A collection none was compiled for: the write asks only its own, and delta_apply refuses any other first. */
34
+ export const noCollection = (key: string): string => `RAISE EXCEPTION 'unknown collection: %', ${key} USING ERRCODE = '22023';`;
35
+
30
36
  /** A compiled function's name: `dzql_<kind>__` and the document's prefix (or a collection's key), lower case, every other character `_`. */
31
37
  export const named = (kind: string, name: string): string =>
32
38
  `dzql_${kind}__${name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "")}`;
@@ -10,11 +10,11 @@
10
10
  * bindings), `mode` and `at`; a value its column cannot take refused with the
11
11
  * same message (`scopeOf`), raised, or with `refuse` false answered as the
12
12
  * scope that holds nothing. And `_delta_resolve_scope` itself, which sends each
13
- * compiled document to its own and any other to the framework
14
- * (`_delta_resolve_scope_framework`).
13
+ * document to its own and refuses a name of none (`no doc def for`: the framework that read the rest
14
+ * retired, todo #26).
15
15
  */
16
16
  import type { DocDef, Schema } from "@blueshed/delta/backend";
17
- import { lit, named, need, scopeOf } from "./opens";
17
+ import { lit, named, need, noDoc, scopeOf } from "./opens";
18
18
 
19
19
  /** A document's scope function: `dzql_scope__<slug>`. */
20
20
  export const resolveFunction = (prefix: string): string => named("scope", prefix);
@@ -67,7 +67,7 @@ RETURNS JSONB AS $$
67
67
  BEGIN
68
68
  CASE p_def.prefix
69
69
  ${docs.map((d) => ` WHEN ${lit(d.prefix)} THEN RETURN ${resolveFunction(d.prefix)}(p_doc_name, p_refuse);`).join("\n")}
70
- ELSE RETURN _delta_resolve_scope_framework(p_def, p_doc_name, p_refuse);
70
+ ELSE ${noDoc("p_doc_name")}
71
71
  END CASE;
72
72
  END;
73
73
  $$ LANGUAGE plpgsql STABLE;`;
@@ -9,11 +9,11 @@
9
9
  * (`scopeOf`, refused with the same message), and the root's current rows
10
10
  * asked with its conditions written as typed SQL, where the framework runs
11
11
  * the resolver's conditions as text. And `_delta_root_there` itself, which
12
- * sends each compiled document to its own and any other to the framework
13
- * (`_delta_root_there_framework`).
12
+ * sends each document to its own and refuses a name of none (`no doc def for`: the framework that read the rest
13
+ * retired, todo #26).
14
14
  */
15
15
  import type { DocDef, Schema } from "@blueshed/delta/backend";
16
- import { lit, named, need, scopeOf, standingRows } from "./opens";
16
+ import { lit, named, need, noDoc, scopeOf, standingRows } from "./opens";
17
17
 
18
18
  /** A document's root check: `dzql_root_there__<slug>`. */
19
19
  export const rootThereFunction = (prefix: string): string => named("root_there", prefix);
@@ -53,7 +53,7 @@ RETURNS BOOLEAN AS $$
53
53
  BEGIN
54
54
  CASE p_def.prefix
55
55
  ${docs.map((d) => ` WHEN ${lit(d.prefix)} THEN RETURN ${rootThereFunction(d.prefix)}(p_doc_name, p_id);`).join("\n")}
56
- ELSE RETURN _delta_root_there_framework(p_def, p_doc_name, p_scope, p_view, p_id);
56
+ ELSE ${noDoc("p_doc_name")}
57
57
  END CASE;
58
58
  END;
59
59
  $$ LANGUAGE plpgsql STABLE;`;
@@ -15,17 +15,18 @@
15
15
  * back, so an add of its id takes its place;
16
16
  * - `_delta_row_there`: is the row there now (a temporal one's open version)?
17
17
  * - `_delta_row_read`: the row as it stands, without its validity;
18
- * - `_delta_row_add`: an add's row stored. The framework's INSERT names the
19
- * columns the row gives (`_delta_insert_sql`), so one it leaves out takes its
20
- * database default: where the row gives exactly every column the schema
21
- * declares (its id, parent key, columns and validity), that INSERT is one
22
- * statement, written here; a row with any other keys is the framework's.
18
+ * - `_delta_row_add`: an add's row stored. An INSERT names the columns the row
19
+ * gives (`_delta_insert_sql`), so one it leaves out takes its database
20
+ * default: where the row gives exactly every column the schema declares (its
21
+ * id, parent key, columns and validity), that INSERT is one statement,
22
+ * written here; a row with any other keys is stored by the columns it gives
23
+ * (`_delta_row_add_given`).
23
24
  *
24
- * Each answers what the framework's (`_framework`) answers, and sends any
25
- * collection it was not compiled for there.
25
+ * Each answers what the framework's did. A collection none was compiled for is
26
+ * refused (`unknown collection`), and a row of it is not there.
26
27
  */
27
28
  import { q, type Schema } from "@blueshed/delta/backend";
28
- import { lit, standingRows } from "./opens";
29
+ import { lit, noCollection, standingRows } from "./opens";
29
30
 
30
31
  /** A CASE over the collections, each branch from `branch`, any other collection to `fallback`. */
31
32
  function dispatch(schema: Schema, branch: (coll: string, table: Schema["tables"][string]) => string | null, fallback: string): string {
@@ -36,7 +37,7 @@ function dispatch(schema: Schema, branch: (coll: string, table: Schema["tables"]
36
37
  return branches.length ? ` CASE p_coll\n${branches.join("\n")}\n ELSE ${fallback}\n END CASE;` : ` ${fallback}`;
37
38
  }
38
39
 
39
- /** The row SQL of every collection of `schema`, and the dispatch to the framework's for any other. */
40
+ /** The row SQL of every collection of `schema`, and its dispatch. */
40
41
  export function compileRows(schema: Schema): string {
41
42
  const lock = `CREATE OR REPLACE FUNCTION _delta_row_lock(p_coll TEXT, p_table TEXT, p_temporal BOOLEAN, p_id BIGINT)
42
43
  RETURNS JSONB AS $$
@@ -44,7 +45,7 @@ DECLARE
44
45
  v_row JSONB;
45
46
  BEGIN
46
47
  ${dispatch(schema, (_, t) => `SELECT to_jsonb(t.*) INTO v_row FROM ${q(t.name)} t WHERE t.id = p_id${t.temporal ? " AND valid_to IS NULL" : ""} FOR UPDATE;
47
- RETURN v_row;`, "RETURN _delta_row_lock_framework(p_coll, p_table, p_temporal, p_id);")}
48
+ RETURN v_row;`, noCollection("p_coll"))}
48
49
  END;
49
50
  $$ LANGUAGE plpgsql;`;
50
51
 
@@ -61,7 +62,7 @@ ${dispatch(schema, (_, t) => (t.temporal
61
62
  RETURN v_row;`
62
63
  : `DELETE FROM ${q(t.name)} WHERE id = p_id;
63
64
  INSERT INTO ${q(t.name)} AS t SELECT * FROM jsonb_populate_record(null::${q(t.name)}, p_row) RETURNING to_jsonb(t.*) INTO v_row;
64
- RETURN v_row;`), "RETURN _delta_row_put_framework(p_coll, p_table, p_temporal, p_id, p_row, p_ts);")}
65
+ RETURN v_row;`), noCollection("p_coll"))}
65
66
  END;
66
67
  $$ LANGUAGE plpgsql;`;
67
68
 
@@ -69,7 +70,7 @@ $$ LANGUAGE plpgsql;`;
69
70
  const next = `CREATE OR REPLACE FUNCTION _delta_next_id(p_coll TEXT, p_table TEXT)
70
71
  RETURNS BIGINT AS $$
71
72
  BEGIN
72
- ${dispatch(schema, (_, t) => `RETURN nextval(${lit(`seq_${t.name}`)});`, "RETURN _delta_next_id_framework(p_coll, p_table);")}
73
+ ${dispatch(schema, (_, t) => `RETURN nextval(${lit(`seq_${t.name}`)});`, noCollection("p_coll"))}
73
74
  END;
74
75
  $$ LANGUAGE plpgsql;`;
75
76
 
@@ -77,7 +78,7 @@ $$ LANGUAGE plpgsql;`;
77
78
  RETURNS void AS $$
78
79
  BEGIN
79
80
  ${dispatch(schema, (_, t) => (t.temporal ? `DELETE FROM ${q(t.name)} WHERE id = p_id AND valid_from = p_ts AND valid_to = p_ts;
80
- RETURN;` : null), "PERFORM _delta_row_clear_moment_framework(p_coll, p_table, p_id, p_ts);")}
81
+ RETURN;` : null), noCollection("p_coll"))}
81
82
  END;
82
83
  $$ LANGUAGE plpgsql;`;
83
84
 
@@ -87,7 +88,7 @@ DECLARE
87
88
  p_coll TEXT := p_collection_key;
88
89
  BEGIN
89
90
  ${dispatch(schema, (_, t) => `RETURN p_id IS NOT NULL AND EXISTS (SELECT 1 FROM ${standingRows(t)} WHERE id = p_id);`,
90
- "RETURN _delta_row_there_framework(p_collection_key, p_id);")}
91
+ "RETURN FALSE; -- a collection there is not: no row of it is there")}
91
92
  END;
92
93
  $$ LANGUAGE plpgsql STABLE;`;
93
94
 
@@ -97,7 +98,7 @@ DECLARE
97
98
  v_row JSONB;
98
99
  BEGIN
99
100
  ${dispatch(schema, (_, t) => `SELECT _delta_strip_temporal(to_jsonb(t.*)) INTO v_row FROM ${standingRows(t)} t WHERE t.id = p_id;
100
- RETURN v_row;`, "RETURN _delta_row_read_framework(p_coll, p_view, p_id);")}
101
+ RETURN v_row;`, noCollection("p_coll"))}
101
102
  END;
102
103
  $$ LANGUAGE plpgsql STABLE;`;
103
104
 
@@ -113,8 +114,8 @@ ${dispatch(schema, (_, t) => {
113
114
  INSERT INTO ${q(t.name)} AS t (${cols}) SELECT ${cols} FROM jsonb_populate_record(null::${q(t.name)}, p_row) RETURNING to_jsonb(t.*) INTO v_row;
114
115
  RETURN v_row;
115
116
  END IF;
116
- RETURN _delta_row_add_framework(p_coll, p_table, p_row);`;
117
- }, "RETURN _delta_row_add_framework(p_coll, p_table, p_row);")}
117
+ RETURN _delta_row_add_given(p_table, p_row); -- some other set of keys: the INSERT names those it gives`;
118
+ }, noCollection("p_coll"))}
118
119
  END;
119
120
  $$ LANGUAGE plpgsql;`;
120
121
 
@@ -10,11 +10,12 @@
10
10
  * its included collections whole and its root rows by the name's conditions;
11
11
  * a single document holds a row whose parent chain, walked up through the
12
12
  * tables as they stand, reaches its root, which meets the rest of its name's
13
- * scope. And `_delta_row_in_scope` itself, which sends each compiled document
14
- * to its own and any other to the framework (`_delta_row_in_scope_framework`).
13
+ * scope. And `_delta_row_in_scope` itself, which sends each document to its
14
+ * own and refuses a name of none (`no doc def for`: the framework that read the rest
15
+ * retired, todo #26).
15
16
  */
16
17
  import { q, type DocDef, type Schema } from "@blueshed/delta/backend";
17
- import { lit, named, need, scopeOf , currentOnly } from "./opens";
18
+ import { lit, named, need, noDoc, scopeOf, currentOnly } from "./opens";
18
19
 
19
20
  /** A document's write-scope function: `dzql_in_scope__<slug>`. */
20
21
  export const inScopeFunction = (prefix: string): string => named("in_scope", prefix);
@@ -79,7 +80,7 @@ RETURNS BOOLEAN AS $$
79
80
  BEGIN
80
81
  CASE p_def.prefix
81
82
  ${docs.map((d) => ` WHEN ${lit(d.prefix)} THEN RETURN ${inScopeFunction(d.prefix)}(p_doc_name, p_collection_key, p_id);`).join("\n")}
82
- ELSE RETURN _delta_row_in_scope_framework(p_def, p_doc_name, p_collection_key, p_id);
83
+ ELSE ${noDoc("p_doc_name")}
83
84
  END CASE;
84
85
  END;
85
86
  $$ LANGUAGE plpgsql STABLE;`;
@@ -8,11 +8,12 @@
8
8
  * definition: whether the name makes it one row (`scopeOf`), its root, the
9
9
  * columns it shows of each collection it names them for (`columns`, the row's
10
10
  * id and parent key with them), and the empty root an implied one opens with
11
- * when its root leaves. And `_delta_told` itself, which sends each compiled
12
- * document to its own and any other to the framework (`_delta_told_framework`).
11
+ * when its root leaves. And `_delta_told` itself, which sends each document to
12
+ * its own and refuses a name of none (`no doc def for`: the framework that read the rest
13
+ * retired, todo #26).
13
14
  */
14
15
  import type { DocDef, Schema } from "@blueshed/delta/backend";
15
- import { lit, named, need, scopeOf } from "./opens";
16
+ import { lit, named, need, noDoc, scopeOf } from "./opens";
16
17
 
17
18
  /** A document's telling function: `dzql_told__<slug>`. */
18
19
  export const toldFunction = (prefix: string): string => named("told", prefix);
@@ -86,7 +87,7 @@ RETURNS JSONB AS $$
86
87
  BEGIN
87
88
  CASE p_def.prefix
88
89
  ${docs.map((d) => ` WHEN ${lit(d.prefix)} THEN RETURN ${toldFunction(d.prefix)}(p_target, p_rows);`).join("\n")}
89
- ELSE RETURN _delta_told_framework(p_def, p_target, p_rows);
90
+ ELSE ${noDoc("p_target")}
90
91
  END CASE;
91
92
  END;
92
93
  $$ LANGUAGE plpgsql STABLE;`;
@@ -36,6 +36,7 @@ import { compileColumns } from "../compile/columns";
36
36
  import { compileRows } from "../compile/rows";
37
37
  import { compileCascades } from "../compile/cascades";
38
38
  import { compileLedger } from "../compile/ledger";
39
+ import { compileLookups } from "../compile/lookups";
39
40
  import { compileTold } from "../compile/told";
40
41
 
41
42
  export interface GenerateSqlOptions {
@@ -180,6 +181,6 @@ export function generateSql(
180
181
  lines.push("");
181
182
 
182
183
  // each document opened by SQL compiled for it (dzql), which docTypeFromDef calls
183
- lines.push("", compileOpens(schema, docs), "", compileHolds(schema, docs), "", compileInScope(schema, docs), "", compileTold(schema, docs), "", compileHolders(schema, docs), "", compileResolve(schema, docs), "", compileRootThere(schema, docs), "", compileColumns(schema, docs), "", compileRows(schema), "", compileCascades(schema), "", compileLedger(schema, docs));
184
+ lines.push("", compileOpens(schema, docs), "", compileHolds(schema, docs), "", compileInScope(schema, docs), "", compileTold(schema, docs), "", compileHolders(schema, docs), "", compileResolve(schema, docs), "", compileRootThere(schema, docs), "", compileColumns(schema, docs), "", compileRows(schema), "", compileCascades(schema), "", compileLedger(schema, docs), "", compileLookups(schema, docs));
184
185
  return lines.join("\n");
185
186
  }