dzql 0.9.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,10 +76,9 @@ 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;
215
- EXECUTE format('SELECT EXISTS (SELECT 1 FROM %I WHERE id = $1)', _delta_source_view(v_coll.table_name, v_coll.temporal))
216
- INTO v_exists USING p_id;
79
+ v_coll := _delta_collection(p_def.root_collection);
80
+ IF v_coll.collection_key IS NULL THEN RETURN; END IF;
81
+ v_exists := _delta_row_there(p_def.root_collection, p_id);
217
82
  IF v_exists THEN RETURN; END IF;
218
83
  -- Not there, as the writer reads it: made under the add rule (todo #71) -- an
219
84
  -- id the serial has handed out is taken, whether a row RLS hides holds it or
@@ -225,7 +90,7 @@ BEGIN
225
90
  END IF;
226
91
  v_row := _delta_implied_root(p_def, p_id);
227
92
  IF v_coll.temporal THEN v_row := v_row || jsonb_build_object('valid_from', p_ts, 'valid_to', NULL); END IF;
228
- EXECUTE _delta_insert_sql(v_coll.table_name, v_row) USING v_row;
93
+ PERFORM _delta_row_add(p_def.root_collection, v_coll.table_name, v_row);
229
94
  END;
230
95
  $$ LANGUAGE plpgsql;
231
96
 
@@ -235,58 +100,20 @@ DECLARE
235
100
  v_doc JSONB := COALESCE(_delta_open(p_doc_name, TRUE), '{}'::jsonb);
236
101
  v_def _delta_docs := _delta_find_doc(p_doc_name);
237
102
  v_scope JSONB;
238
- v_coll RECORD;
239
- v_exists BOOLEAN;
240
103
  BEGIN
241
104
  IF NOT COALESCE(v_def.implied, FALSE) THEN RETURN v_doc; END IF;
242
105
  v_scope := _delta_resolve_scope(v_def, p_doc_name);
243
106
  IF (v_scope->>'mode') <> 'single' THEN RETURN v_doc; END IF;
244
- SELECT * INTO v_coll FROM _delta_collections WHERE collection_key = v_def.root_collection;
245
- IF NOT FOUND THEN RETURN v_doc; END IF;
246
- EXECUTE format('SELECT EXISTS (SELECT 1 FROM %I WHERE id = $1)', _delta_source_view(v_coll.table_name, v_coll.temporal))
247
- INTO v_exists USING (v_scope->'values'->>'id')::BIGINT;
248
- IF v_exists THEN RETURN v_doc; END IF;
107
+ -- (its root collection is there: _delta_open raised otherwise)
108
+ IF _delta_row_there(v_def.root_collection, (v_scope->'values'->>'id')::BIGINT) THEN RETURN v_doc; END IF;
249
109
  RETURN v_doc || jsonb_build_object(v_def.root_collection, 'null'::jsonb);
250
110
  END;
251
111
  $$ LANGUAGE plpgsql;
252
112
 
253
- -- ---------------------------------------------------------------------------
254
- -- _delta_read_list: a list document -- the root rows `p_where` admits, and
255
- -- each included collection in full (list mode has no single root id to filter
256
- -- against, so "include" means "load it all") -- as they stand (p_at NULL), or
257
- -- a temporal table's as they stood at p_at. delta_open's and delta_open_at's.
258
- -- ---------------------------------------------------------------------------
259
-
260
- CREATE OR REPLACE FUNCTION _delta_read_list(p_def _delta_docs, p_where TEXT, p_at TIMESTAMPTZ)
261
- RETURNS JSONB AS $$
262
- DECLARE
263
- v_root RECORD;
264
- v_rows JSONB;
265
- v_result JSONB;
266
- v_coll_key TEXT;
267
- BEGIN
268
- SELECT * INTO v_root FROM _delta_collections WHERE collection_key = p_def.root_collection;
269
- EXECUTE format(
270
- 'SELECT COALESCE(jsonb_object_agg(t.id, %s), ''{}''::jsonb) FROM %I t WHERE %s AND %s',
271
- CASE WHEN v_root.temporal THEN '_delta_strip_temporal(to_jsonb(t.*))' ELSE 'to_jsonb(t.*)' END,
272
- _delta_source_view(v_root.table_name, v_root.temporal, p_at),
273
- CASE WHEN v_root.temporal AND p_at IS NOT NULL THEN _delta_temporal_where(p_at) ELSE 'TRUE' END,
274
- p_where
275
- ) INTO v_rows;
276
- v_result := jsonb_build_object(p_def.root_collection, v_rows);
277
- FOREACH v_coll_key IN ARRAY p_def.include
278
- LOOP
279
- v_result := v_result || jsonb_build_object(v_coll_key, _delta_load_collection_all(v_coll_key, p_at));
280
- END LOOP;
281
- RETURN v_result;
282
- END;
283
- $$ LANGUAGE plpgsql STABLE;
284
-
285
113
  -- ---------------------------------------------------------------------------
286
114
  -- _delta_show: a row of `p_coll` as a document shows it -- where it names that
287
115
  -- collection's columns (`held`, todo #70), those, the row's id and its parent
288
- -- key; else whole. _delta_show_doc: each row of a document so (`p_single`: its
289
- -- root is one row, not a map). showRow / showDoc in schema.ts.
116
+ -- key; else whole. showRow in schema.ts.
290
117
  -- ---------------------------------------------------------------------------
291
118
 
292
119
  CREATE OR REPLACE FUNCTION _delta_show(p_def _delta_docs, p_coll TEXT, p_row JSONB)
@@ -294,24 +121,9 @@ RETURNS JSONB AS $$
294
121
  SELECT CASE WHEN NOT COALESCE(p_def.held, '{}'::jsonb) ? p_coll OR jsonb_typeof(p_row) IS DISTINCT FROM 'object' THEN p_row
295
122
  ELSE (SELECT COALESCE(jsonb_object_agg(f, v), '{}'::jsonb) FROM jsonb_each(p_row) AS e(f, v)
296
123
  WHERE f = 'id' OR (p_def.held->p_coll) ? f
297
- OR f = (SELECT parent_fk FROM _delta_collections WHERE collection_key = p_coll)) END;
124
+ OR f = (_delta_collection(p_coll)).parent_fk) END;
298
125
  $$ LANGUAGE sql STABLE;
299
126
 
300
- CREATE OR REPLACE FUNCTION _delta_show_doc(p_def _delta_docs, p_doc JSONB, p_single BOOLEAN)
301
- RETURNS JSONB AS $$
302
- DECLARE
303
- v_out JSONB := p_doc;
304
- v_coll TEXT;
305
- BEGIN
306
- FOR v_coll IN SELECT jsonb_object_keys(COALESCE(p_def.held, '{}'::jsonb)) LOOP
307
- CONTINUE WHEN jsonb_typeof(p_doc->v_coll) IS DISTINCT FROM 'object';
308
- 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)
309
- 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);
310
- END LOOP;
311
- RETURN v_out;
312
- END;
313
- $$ LANGUAGE plpgsql STABLE;
314
-
315
127
  -- _delta_show_ops: ops as the document `p_doc_name` shows them -- what its
316
128
  -- writer is answered (a write's ops and inverse, a walk's, its history): a
317
129
  -- row's value, its columns (_delta_show); a field it does not hold
@@ -325,13 +137,13 @@ DECLARE
325
137
  v_out JSONB := '[]'::jsonb;
326
138
  v_op JSONB;
327
139
  v_parts TEXT[];
328
- v_coll RECORD;
140
+ v_coll _delta_collections;
329
141
  v_field TEXT;
330
142
  BEGIN
331
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;
332
144
  FOR v_op IN SELECT jsonb_array_elements(p_ops) LOOP
333
145
  v_parts := _delta_split_path(v_op->>'path');
334
- SELECT * INTO v_coll FROM _delta_collections WHERE collection_key = v_parts[1];
146
+ v_coll := _delta_collection(v_parts[1]);
335
147
  v_field := CASE WHEN array_length(v_parts, 1) >= 3 THEN v_parts[3]
336
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;
337
149
  IF v_field IS NOT NULL THEN
@@ -346,104 +158,14 @@ END;
346
158
  $$ LANGUAGE plpgsql STABLE;
347
159
 
348
160
  -- ---------------------------------------------------------------------------
349
- -- delta_open: load a doc from relational tables, return JSONB + version -- its
350
- -- rows as it shows them (_delta_show_doc). _delta_open(name, TRUE) reads them
351
- -- 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.
352
165
  -- ---------------------------------------------------------------------------
353
166
 
354
167
  CREATE OR REPLACE FUNCTION _delta_open(p_doc_name TEXT, p_whole BOOLEAN)
355
- RETURNS JSONB AS $$
356
- DECLARE
357
- v_def _delta_docs;
358
- v_resolved JSONB;
359
- v_where TEXT;
360
- v_mode TEXT;
361
- v_at TIMESTAMPTZ;
362
- v_doc_id BIGINT;
363
- v_root_coll RECORD;
364
- v_view TEXT;
365
- v_root_row JSONB;
366
- v_result JSONB;
367
- v_coll_key TEXT;
368
- v_version BIGINT;
369
- BEGIN
370
- v_def := _delta_find_doc(p_doc_name);
371
- IF v_def.prefix IS NULL THEN
372
- -- Fail-fast: caller asked for a doc whose prefix isn't registered.
373
- -- NULL-as-config-error used to mask typos and missing migrations.
374
- RAISE EXCEPTION 'no doc def for: %', p_doc_name
375
- USING ERRCODE = 'P0001';
376
- END IF;
377
-
378
- v_resolved := _delta_resolve_scope(v_def, p_doc_name);
379
- v_where := v_resolved->>'where';
380
- v_mode := v_resolved->>'mode';
381
- IF v_resolved->>'at' IS NOT NULL THEN
382
- v_at := (v_resolved->>'at')::TIMESTAMPTZ;
383
- END IF;
384
-
385
- SELECT * INTO v_root_coll FROM _delta_collections
386
- WHERE collection_key = v_def.root_collection;
387
- IF NOT FOUND THEN
388
- RAISE EXCEPTION 'doc "%" references unknown root collection: %',
389
- p_doc_name, v_def.root_collection
390
- USING ERRCODE = 'P0001';
391
- END IF;
392
-
393
- -- A time in the name (`at:`): the whole document as it stood then, its root
394
- -- as its included collections, as SQLite reads it.
395
- v_view := _delta_source_view(v_root_coll.table_name, v_root_coll.temporal, v_at);
396
- IF v_root_coll.temporal AND v_at IS NOT NULL THEN
397
- v_where := v_where || ' AND ' || _delta_temporal_where(v_at);
398
- END IF;
399
-
400
- IF v_mode = 'list' THEN
401
- v_result := _delta_read_list(v_def, v_resolved->>'where', v_at);
402
- ELSE
403
- -- Single mode: one root row + included collections
404
- v_doc_id := (v_resolved->'values'->>'id')::BIGINT;
405
-
406
- -- the row its name names, where it meets the rest of the name's scope too
407
- -- (fo-guest:<id>:<email>: that id with that email), as SQLite reads it
408
- EXECUTE format('SELECT to_jsonb(t.*) FROM %I t WHERE t.id = $1 AND %s', v_view, v_where)
409
- INTO v_root_row USING v_doc_id;
410
-
411
- IF v_root_row IS NULL AND NOT COALESCE(v_def.implied, FALSE) THEN RETURN NULL; END IF;
412
-
413
- IF v_root_row IS NULL THEN
414
- -- Implied: there before its root row is -- the root its first write will
415
- -- make, each included collection empty, as on SQLite. No row is made.
416
- v_result := jsonb_build_object(v_def.root_collection, _delta_implied_root(v_def, v_doc_id));
417
- FOREACH v_coll_key IN ARRAY v_def.include
418
- LOOP
419
- v_result := v_result || jsonb_build_object(v_coll_key, '{}'::jsonb);
420
- END LOOP;
421
- ELSE
422
- IF v_root_coll.temporal THEN
423
- v_root_row := _delta_strip_temporal(v_root_row);
424
- END IF;
425
-
426
- v_result := jsonb_build_object(v_def.root_collection, v_root_row);
427
-
428
- FOREACH v_coll_key IN ARRAY v_def.include
429
- LOOP
430
- v_result := v_result || jsonb_build_object(
431
- v_coll_key,
432
- _delta_load_collection(v_coll_key, v_def.root_collection, v_doc_id, v_at)
433
- );
434
- END LOOP;
435
- END IF;
436
- END IF;
437
-
438
- -- Track version
439
- INSERT INTO _delta_versions (doc_name, version) VALUES (p_doc_name, 0)
440
- ON CONFLICT (doc_name) DO NOTHING;
441
- SELECT version INTO v_version FROM _delta_versions WHERE doc_name = p_doc_name;
442
-
443
- IF NOT p_whole THEN v_result := _delta_show_doc(v_def, v_result, v_mode <> 'list'); END IF;
444
- RETURN v_result || jsonb_build_object('_version', v_version);
445
- END;
446
- $$ LANGUAGE plpgsql;
168
+ RETURNS JSONB LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'no doc def for: %', p_doc_name USING ERRCODE = 'P0001'; END $$;
447
169
 
448
170
  CREATE OR REPLACE FUNCTION delta_open(p_doc_name TEXT)
449
171
  RETURNS JSONB LANGUAGE sql AS $$ SELECT _delta_open(p_doc_name, FALSE); $$;