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.
@@ -37,140 +37,6 @@ $$ LANGUAGE plpgsql;
37
37
  CREATE OR REPLACE FUNCTION _delta_bump_and_notify(p_doc TEXT, p_ops JSONB)
38
38
  RETURNS BIGINT LANGUAGE sql AS $$ SELECT _delta_bump_and_notify(p_doc, p_ops, NULL); $$;
39
39
 
40
- -- ---------------------------------------------------------------------------
41
- -- _delta_load_collection: recursively load a collection's rows as a JSONB map.
42
- -- When p_at is NULL, loads current state. When set, loads at that point in time.
43
- -- ---------------------------------------------------------------------------
44
-
45
- CREATE OR REPLACE FUNCTION _delta_load_collection(
46
- p_collection_key TEXT,
47
- p_root_collection TEXT,
48
- p_root_id BIGINT,
49
- p_at TIMESTAMPTZ DEFAULT NULL
50
- ) RETURNS JSONB AS $$
51
- DECLARE
52
- v_coll RECORD;
53
- v_source TEXT;
54
- v_strip BOOLEAN;
55
- v_where TEXT;
56
- v_result JSONB;
57
- v_parent_map JSONB;
58
- v_parent_ids BIGINT[];
59
- BEGIN
60
- SELECT * INTO v_coll FROM _delta_collections
61
- WHERE collection_key = p_collection_key;
62
- IF NOT FOUND THEN RETURN '{}'::jsonb; END IF;
63
-
64
- v_source := _delta_source_view(v_coll.table_name, v_coll.temporal, p_at);
65
- v_strip := v_coll.temporal;
66
- v_where := CASE WHEN v_coll.temporal AND p_at IS NOT NULL
67
- THEN _delta_temporal_where(p_at)
68
- ELSE 'TRUE' END;
69
-
70
- -- No parent
71
- IF v_coll.parent_collection IS NULL THEN
72
- IF v_strip THEN
73
- EXECUTE format(
74
- 'SELECT COALESCE(jsonb_object_agg(t.id, _delta_strip_temporal(to_jsonb(t.*))), ''{}''::jsonb) FROM %I t WHERE %s',
75
- v_source, v_where
76
- ) INTO v_result;
77
- ELSE
78
- EXECUTE format(
79
- 'SELECT COALESCE(jsonb_object_agg(t.id, to_jsonb(t.*)), ''{}''::jsonb) FROM %I t',
80
- v_source
81
- ) INTO v_result;
82
- END IF;
83
- RETURN v_result;
84
- END IF;
85
-
86
- -- Direct child of root
87
- IF v_coll.parent_collection = p_root_collection THEN
88
- IF v_strip THEN
89
- EXECUTE format(
90
- 'SELECT COALESCE(jsonb_object_agg(t.id, _delta_strip_temporal(to_jsonb(t.*))), ''{}''::jsonb) FROM %I t WHERE t.%I = $1 AND %s',
91
- v_source, v_coll.parent_fk, v_where
92
- ) INTO v_result USING p_root_id;
93
- ELSE
94
- EXECUTE format(
95
- 'SELECT COALESCE(jsonb_object_agg(t.id, to_jsonb(t.*)), ''{}''::jsonb) FROM %I t WHERE t.%I = $1',
96
- v_source, v_coll.parent_fk
97
- ) INTO v_result USING p_root_id;
98
- END IF;
99
- RETURN v_result;
100
- END IF;
101
-
102
- -- Grandchild+: load parent rows recursively, then filter by their IDs. The
103
- -- parent is walked whether or not the document includes it; one with no
104
- -- parent of its own ends the chain short of the root, so nothing under it is
105
- -- in the document -- as _delta_chain_root and _delta_row_in_scope judge it,
106
- -- and the SQLite backend reads it (todo #32).
107
- IF NOT EXISTS (SELECT 1 FROM _delta_collections
108
- WHERE collection_key = v_coll.parent_collection
109
- AND parent_collection IS NOT NULL) THEN
110
- RETURN '{}'::jsonb;
111
- END IF;
112
- v_parent_map := _delta_load_collection(
113
- v_coll.parent_collection, p_root_collection, p_root_id, p_at
114
- );
115
- SELECT array_agg(k::BIGINT) INTO v_parent_ids
116
- FROM jsonb_object_keys(v_parent_map) AS k;
117
-
118
- IF v_parent_ids IS NULL THEN RETURN '{}'::jsonb; END IF;
119
-
120
- IF v_strip THEN
121
- EXECUTE format(
122
- 'SELECT COALESCE(jsonb_object_agg(t.id, _delta_strip_temporal(to_jsonb(t.*))), ''{}''::jsonb) FROM %I t WHERE t.%I = ANY($1) AND %s',
123
- v_source, v_coll.parent_fk, v_where
124
- ) INTO v_result USING v_parent_ids;
125
- ELSE
126
- EXECUTE format(
127
- 'SELECT COALESCE(jsonb_object_agg(t.id, to_jsonb(t.*)), ''{}''::jsonb) FROM %I t WHERE t.%I = ANY($1)',
128
- v_source, v_coll.parent_fk
129
- ) INTO v_result USING v_parent_ids;
130
- END IF;
131
-
132
- RETURN v_result;
133
- END;
134
- $$ LANGUAGE plpgsql STABLE;
135
-
136
- -- ---------------------------------------------------------------------------
137
- -- _delta_load_collection_all: load every row of a collection as a JSONB map.
138
- -- Used by list-mode docs' `include` traversal (no FK filter — list mode has
139
- -- no single root id to filter against, so "include" means "load it all").
140
- -- ---------------------------------------------------------------------------
141
-
142
- CREATE OR REPLACE FUNCTION _delta_load_collection_all(
143
- p_collection_key TEXT,
144
- p_at TIMESTAMPTZ DEFAULT NULL
145
- ) RETURNS JSONB AS $$
146
- DECLARE
147
- v_coll RECORD;
148
- v_source TEXT;
149
- v_result JSONB;
150
- BEGIN
151
- SELECT * INTO v_coll FROM _delta_collections
152
- WHERE collection_key = p_collection_key;
153
- IF NOT FOUND THEN RETURN '{}'::jsonb; END IF;
154
-
155
- v_source := _delta_source_view(v_coll.table_name, v_coll.temporal, p_at);
156
-
157
- IF v_coll.temporal THEN
158
- EXECUTE format(
159
- 'SELECT COALESCE(jsonb_object_agg(t.id, _delta_strip_temporal(to_jsonb(t.*))), ''{}''::jsonb) FROM %I t WHERE %s',
160
- v_source,
161
- CASE WHEN p_at IS NOT NULL THEN _delta_temporal_where(p_at) ELSE 'TRUE' END
162
- ) INTO v_result;
163
- ELSE
164
- EXECUTE format(
165
- 'SELECT COALESCE(jsonb_object_agg(t.id, to_jsonb(t.*)), ''{}''::jsonb) FROM %I t',
166
- v_source
167
- ) INTO v_result;
168
- END IF;
169
-
170
- RETURN v_result;
171
- END;
172
- $$ LANGUAGE plpgsql STABLE;
173
-
174
40
  -- ---------------------------------------------------------------------------
175
41
  -- Implied documents (todo #34): there before their root row is, as on SQLite.
176
42
  --
@@ -193,14 +59,14 @@ RETURNS JSONB LANGUAGE sql STABLE AS $$
193
59
  WHEN d->>'type' IN ('integer', 'real') THEN '0'::jsonb
194
60
  WHEN d->>'type' = 'boolean' THEN 'false'::jsonb
195
61
  ELSE 'null'::jsonb END)
196
- FROM _delta_collections c, jsonb_each(_delta_cols(c.collection_key, c.columns_def)) AS x(k, d)
197
- WHERE c.collection_key = p_def.root_collection), '{}'::jsonb);
62
+ FROM (SELECT _delta_collection(p_def.root_collection) AS c) AS r,
63
+ jsonb_each(_delta_cols((r.c).collection_key, (r.c).columns_def)) AS x(k, d)), '{}'::jsonb);
198
64
  $$;
199
65
 
200
66
  CREATE OR REPLACE FUNCTION _delta_make_implied(p_def _delta_docs, p_id BIGINT, p_ops JSONB, p_ts TIMESTAMPTZ)
201
67
  RETURNS void AS $$
202
68
  DECLARE
203
- v_coll RECORD;
69
+ v_coll _delta_collections;
204
70
  v_exists BOOLEAN;
205
71
  v_row JSONB;
206
72
  BEGIN
@@ -210,8 +76,8 @@ BEGIN
210
76
  AND (_delta_split_path(o->>'path'))[1] = p_def.root_collection) THEN
211
77
  RETURN;
212
78
  END IF;
213
- SELECT * INTO v_coll FROM _delta_collections WHERE collection_key = p_def.root_collection;
214
- IF NOT FOUND THEN RETURN; END IF;
79
+ v_coll := _delta_collection(p_def.root_collection);
80
+ IF v_coll.collection_key IS NULL THEN RETURN; END IF;
215
81
  v_exists := _delta_row_there(p_def.root_collection, p_id);
216
82
  IF v_exists THEN RETURN; END IF;
217
83
  -- Not there, as the writer reads it: made under the add rule (todo #71) -- an
@@ -244,43 +110,10 @@ BEGIN
244
110
  END;
245
111
  $$ LANGUAGE plpgsql;
246
112
 
247
- -- ---------------------------------------------------------------------------
248
- -- _delta_read_list: a list document -- the root rows `p_where` admits, and
249
- -- each included collection in full (list mode has no single root id to filter
250
- -- against, so "include" means "load it all") -- as they stand (p_at NULL), or
251
- -- a temporal table's as they stood at p_at. delta_open's and delta_open_at's.
252
- -- ---------------------------------------------------------------------------
253
-
254
- CREATE OR REPLACE FUNCTION _delta_read_list(p_def _delta_docs, p_where TEXT, p_at TIMESTAMPTZ)
255
- RETURNS JSONB AS $$
256
- DECLARE
257
- v_root RECORD;
258
- v_rows JSONB;
259
- v_result JSONB;
260
- v_coll_key TEXT;
261
- BEGIN
262
- SELECT * INTO v_root FROM _delta_collections WHERE collection_key = p_def.root_collection;
263
- EXECUTE format(
264
- 'SELECT COALESCE(jsonb_object_agg(t.id, %s), ''{}''::jsonb) FROM %I t WHERE %s AND %s',
265
- CASE WHEN v_root.temporal THEN '_delta_strip_temporal(to_jsonb(t.*))' ELSE 'to_jsonb(t.*)' END,
266
- _delta_source_view(v_root.table_name, v_root.temporal, p_at),
267
- CASE WHEN v_root.temporal AND p_at IS NOT NULL THEN _delta_temporal_where(p_at) ELSE 'TRUE' END,
268
- p_where
269
- ) INTO v_rows;
270
- v_result := jsonb_build_object(p_def.root_collection, v_rows);
271
- FOREACH v_coll_key IN ARRAY p_def.include
272
- LOOP
273
- v_result := v_result || jsonb_build_object(v_coll_key, _delta_load_collection_all(v_coll_key, p_at));
274
- END LOOP;
275
- RETURN v_result;
276
- END;
277
- $$ LANGUAGE plpgsql STABLE;
278
-
279
113
  -- ---------------------------------------------------------------------------
280
114
  -- _delta_show: a row of `p_coll` as a document shows it -- where it names that
281
115
  -- collection's columns (`held`, todo #70), those, the row's id and its parent
282
- -- key; else whole. _delta_show_doc: each row of a document so (`p_single`: its
283
- -- root is one row, not a map). showRow / showDoc in schema.ts.
116
+ -- key; else whole. showRow in schema.ts.
284
117
  -- ---------------------------------------------------------------------------
285
118
 
286
119
  CREATE OR REPLACE FUNCTION _delta_show(p_def _delta_docs, p_coll TEXT, p_row JSONB)
@@ -288,24 +121,9 @@ RETURNS JSONB AS $$
288
121
  SELECT CASE WHEN NOT COALESCE(p_def.held, '{}'::jsonb) ? p_coll OR jsonb_typeof(p_row) IS DISTINCT FROM 'object' THEN p_row
289
122
  ELSE (SELECT COALESCE(jsonb_object_agg(f, v), '{}'::jsonb) FROM jsonb_each(p_row) AS e(f, v)
290
123
  WHERE f = 'id' OR (p_def.held->p_coll) ? f
291
- OR f = (SELECT parent_fk FROM _delta_collections WHERE collection_key = p_coll)) END;
124
+ OR f = (_delta_collection(p_coll)).parent_fk) END;
292
125
  $$ LANGUAGE sql STABLE;
293
126
 
294
- CREATE OR REPLACE FUNCTION _delta_show_doc(p_def _delta_docs, p_doc JSONB, p_single BOOLEAN)
295
- RETURNS JSONB AS $$
296
- DECLARE
297
- v_out JSONB := p_doc;
298
- v_coll TEXT;
299
- BEGIN
300
- FOR v_coll IN SELECT jsonb_object_keys(COALESCE(p_def.held, '{}'::jsonb)) LOOP
301
- CONTINUE WHEN jsonb_typeof(p_doc->v_coll) IS DISTINCT FROM 'object';
302
- v_out := v_out || jsonb_build_object(v_coll, CASE WHEN p_single AND v_coll = p_def.root_collection THEN _delta_show(p_def, v_coll, p_doc->v_coll)
303
- ELSE (SELECT COALESCE(jsonb_object_agg(id, _delta_show(p_def, v_coll, r)), '{}'::jsonb) FROM jsonb_each(p_doc->v_coll) AS e(id, r)) END);
304
- END LOOP;
305
- RETURN v_out;
306
- END;
307
- $$ LANGUAGE plpgsql STABLE;
308
-
309
127
  -- _delta_show_ops: ops as the document `p_doc_name` shows them -- what its
310
128
  -- writer is answered (a write's ops and inverse, a walk's, its history): a
311
129
  -- row's value, its columns (_delta_show); a field it does not hold
@@ -319,13 +137,13 @@ DECLARE
319
137
  v_out JSONB := '[]'::jsonb;
320
138
  v_op JSONB;
321
139
  v_parts TEXT[];
322
- v_coll RECORD;
140
+ v_coll _delta_collections;
323
141
  v_field TEXT;
324
142
  BEGIN
325
143
  IF v_def.prefix IS NULL OR COALESCE(v_def.held, '{}'::jsonb) = '{}'::jsonb OR jsonb_typeof(p_ops) <> 'array' THEN RETURN p_ops; END IF;
326
144
  FOR v_op IN SELECT jsonb_array_elements(p_ops) LOOP
327
145
  v_parts := _delta_split_path(v_op->>'path');
328
- SELECT * INTO v_coll FROM _delta_collections WHERE collection_key = v_parts[1];
146
+ v_coll := _delta_collection(v_parts[1]);
329
147
  v_field := CASE WHEN array_length(v_parts, 1) >= 3 THEN v_parts[3]
330
148
  WHEN array_length(v_parts, 1) = 2 AND (_delta_cols(v_coll.collection_key, v_coll.columns_def) ? v_parts[2] OR v_parts[2] = v_coll.parent_fk) THEN v_parts[2] END;
331
149
  IF v_field IS NOT NULL THEN
@@ -340,109 +158,14 @@ END;
340
158
  $$ LANGUAGE plpgsql STABLE;
341
159
 
342
160
  -- ---------------------------------------------------------------------------
343
- -- delta_open: load a doc from relational tables, return JSONB + version -- its
344
- -- rows as it shows them (_delta_show_doc). _delta_open(name, TRUE) reads them
345
- -- whole: what the ledger's inverse and a walk's guard read.
161
+ -- delta_open: a document as it stands, with its version -- its rows as it
162
+ -- shows them. _delta_open(name, TRUE) reads them whole: what the ledger's
163
+ -- inverse and a walk's guard read. generateSql sends each document to its own
164
+ -- (compileLedger, compileOpens); before it is applied there is no document.
346
165
  -- ---------------------------------------------------------------------------
347
166
 
348
- CREATE OR REPLACE FUNCTION _delta_open_framework(p_doc_name TEXT, p_whole BOOLEAN)
349
- RETURNS JSONB AS $$
350
- DECLARE
351
- v_def _delta_docs;
352
- v_resolved JSONB;
353
- v_where TEXT;
354
- v_mode TEXT;
355
- v_at TIMESTAMPTZ;
356
- v_doc_id BIGINT;
357
- v_root_coll RECORD;
358
- v_view TEXT;
359
- v_root_row JSONB;
360
- v_result JSONB;
361
- v_coll_key TEXT;
362
- v_version BIGINT;
363
- BEGIN
364
- v_def := _delta_find_doc(p_doc_name);
365
- IF v_def.prefix IS NULL THEN
366
- -- Fail-fast: caller asked for a doc whose prefix isn't registered.
367
- -- NULL-as-config-error used to mask typos and missing migrations.
368
- RAISE EXCEPTION 'no doc def for: %', p_doc_name
369
- USING ERRCODE = 'P0001';
370
- END IF;
371
-
372
- v_resolved := _delta_resolve_scope(v_def, p_doc_name);
373
- v_where := v_resolved->>'where';
374
- v_mode := v_resolved->>'mode';
375
- IF v_resolved->>'at' IS NOT NULL THEN
376
- v_at := (v_resolved->>'at')::TIMESTAMPTZ;
377
- END IF;
378
-
379
- SELECT * INTO v_root_coll FROM _delta_collections
380
- WHERE collection_key = v_def.root_collection;
381
- IF NOT FOUND THEN
382
- RAISE EXCEPTION 'doc "%" references unknown root collection: %',
383
- p_doc_name, v_def.root_collection
384
- USING ERRCODE = 'P0001';
385
- END IF;
386
-
387
- -- A time in the name (`at:`): the whole document as it stood then, its root
388
- -- as its included collections, as SQLite reads it.
389
- v_view := _delta_source_view(v_root_coll.table_name, v_root_coll.temporal, v_at);
390
- IF v_root_coll.temporal AND v_at IS NOT NULL THEN
391
- v_where := v_where || ' AND ' || _delta_temporal_where(v_at);
392
- END IF;
393
-
394
- IF v_mode = 'list' THEN
395
- v_result := _delta_read_list(v_def, v_resolved->>'where', v_at);
396
- ELSE
397
- -- Single mode: one root row + included collections
398
- v_doc_id := (v_resolved->'values'->>'id')::BIGINT;
399
-
400
- -- the row its name names, where it meets the rest of the name's scope too
401
- -- (fo-guest:<id>:<email>: that id with that email), as SQLite reads it
402
- EXECUTE format('SELECT to_jsonb(t.*) FROM %I t WHERE t.id = $1 AND %s', v_view, v_where)
403
- INTO v_root_row USING v_doc_id;
404
-
405
- IF v_root_row IS NULL AND NOT COALESCE(v_def.implied, FALSE) THEN RETURN NULL; END IF;
406
-
407
- IF v_root_row IS NULL THEN
408
- -- Implied: there before its root row is -- the root its first write will
409
- -- make, each included collection empty, as on SQLite. No row is made.
410
- v_result := jsonb_build_object(v_def.root_collection, _delta_implied_root(v_def, v_doc_id));
411
- FOREACH v_coll_key IN ARRAY v_def.include
412
- LOOP
413
- v_result := v_result || jsonb_build_object(v_coll_key, '{}'::jsonb);
414
- END LOOP;
415
- ELSE
416
- IF v_root_coll.temporal THEN
417
- v_root_row := _delta_strip_temporal(v_root_row);
418
- END IF;
419
-
420
- v_result := jsonb_build_object(v_def.root_collection, v_root_row);
421
-
422
- FOREACH v_coll_key IN ARRAY v_def.include
423
- LOOP
424
- v_result := v_result || jsonb_build_object(
425
- v_coll_key,
426
- _delta_load_collection(v_coll_key, v_def.root_collection, v_doc_id, v_at)
427
- );
428
- END LOOP;
429
- END IF;
430
- END IF;
431
-
432
- -- Track version
433
- INSERT INTO _delta_versions (doc_name, version) VALUES (p_doc_name, 0)
434
- ON CONFLICT (doc_name) DO NOTHING;
435
- SELECT version INTO v_version FROM _delta_versions WHERE doc_name = p_doc_name;
436
-
437
- IF NOT p_whole THEN v_result := _delta_show_doc(v_def, v_result, v_mode <> 'list'); END IF;
438
- RETURN v_result || jsonb_build_object('_version', v_version);
439
- END;
440
- $$ LANGUAGE plpgsql;
441
-
442
- -- the framework's, until dzql's compiler (`compileLedger`) sends each document
443
- -- it compiled to its own open, shown or whole
444
167
  CREATE OR REPLACE FUNCTION _delta_open(p_doc_name TEXT, p_whole BOOLEAN)
445
- RETURNS JSONB LANGUAGE sql AS $$ SELECT _delta_open_framework(p_doc_name, p_whole); $$;
168
+ RETURNS JSONB LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'no doc def for: %', p_doc_name USING ERRCODE = 'P0001'; END $$;
446
169
 
447
170
  CREATE OR REPLACE FUNCTION delta_open(p_doc_name TEXT)
448
171
  RETURNS JSONB LANGUAGE sql AS $$ SELECT _delta_open(p_doc_name, FALSE); $$;