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.
@@ -13,22 +13,6 @@
13
13
  -- is pinned below, pg_temp last.
14
14
  -- ---------------------------------------------------------------------------
15
15
 
16
- CREATE OR REPLACE FUNCTION _delta_serial_past(p_table TEXT, p_id BIGINT)
17
- RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$
18
- BEGIN
19
- IF p_id > 9007199254740991 OR NOT EXISTS (SELECT 1 FROM _delta_collections WHERE table_name = p_table)
20
- OR NOT has_table_privilege(COALESCE(NULLIF(current_setting('role'), 'none'), session_user), p_table, 'INSERT') THEN
21
- RETURN;
22
- END IF;
23
- EXECUTE format('SELECT setval(%L, $1) FROM %I WHERE $1 >= last_value + is_called::int',
24
- 'seq_' || p_table, 'seq_' || p_table) USING p_id;
25
- END;
26
- $$;
27
- -- the search path it was applied with, and pg_temp last (as _delta_holders, 001b)
28
- DO $$ BEGIN
29
- EXECUTE format('ALTER FUNCTION _delta_serial_past(TEXT, BIGINT) SET search_path = %s, pg_temp', COALESCE(NULLIF(current_setting('search_path'), ''), 'public'));
30
- END $$;
31
-
32
16
  -- ---------------------------------------------------------------------------
33
17
  -- _delta_serial_new: whether an id is past every one a collection's serial has
34
18
  -- handed out (minted, or passed for an add that named it): what an add may
@@ -37,25 +21,15 @@ END $$;
37
21
  -- other every id is taken, so it learns nothing of the table's rows.
38
22
  -- ---------------------------------------------------------------------------
39
23
 
24
+ -- generateSql writes each table's sequence in (compileLookups): as the
25
+ -- functions' owner, with the path they were applied with and pg_temp last, so a
26
+ -- caller's temporary _delta_collections, or a temporary table named as one of
27
+ -- delta's, never stands in for the real one. Before it is applied there is no
28
+ -- table: no id is new, and nothing moves.
29
+ CREATE OR REPLACE FUNCTION _delta_serial_past(p_table TEXT, p_id BIGINT)
30
+ RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ BEGIN NULL; END $$;
40
31
  CREATE OR REPLACE FUNCTION _delta_serial_new(p_table TEXT, p_id BIGINT)
41
- RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $$
42
- DECLARE
43
- v_new BOOLEAN;
44
- BEGIN
45
- IF NOT EXISTS (SELECT 1 FROM _delta_collections WHERE table_name = p_table)
46
- OR NOT has_table_privilege(COALESCE(NULLIF(current_setting('role'), 'none'), session_user), p_table, 'SELECT') THEN
47
- RETURN FALSE;
48
- END IF;
49
- EXECUTE format('SELECT $1 >= last_value + is_called::int FROM %I', 'seq_' || p_table) INTO v_new USING p_id;
50
- RETURN v_new;
51
- END;
52
- $$;
53
-
54
- -- Both run as the framework's owner, so each looks its names up only in the
55
- -- schemas the framework is applied in, and pg_temp last: a caller's temporary
56
- -- _delta_collections, or a temporary table named as one of delta's, never
57
- -- stands in for the real one. (search_path FROM CURRENT took "$user", public,
58
- -- and pg_temp is searched first when it is not named.)
32
+ RETURNS BOOLEAN LANGUAGE sql SECURITY DEFINER AS $$ SELECT FALSE; $$;
59
33
  DO $$
60
34
  DECLARE
61
35
  v_path TEXT := COALESCE((SELECT string_agg(quote_ident(s), ', ') FROM unnest(current_schemas(false)) AS s), 'public') || ', pg_temp';
@@ -75,91 +49,10 @@ END $$;
75
49
  -- ---------------------------------------------------------------------------
76
50
 
77
51
  DROP FUNCTION IF EXISTS _delta_cascade_remove(TEXT, BIGINT, TEXT[]);
78
- CREATE OR REPLACE FUNCTION _delta_cascade_remove_framework(
79
- p_collection_key TEXT,
80
- p_id BIGINT
81
- ) RETURNS JSONB AS $$
82
- DECLARE
83
- v_coll RECORD;
84
- v_view TEXT;
85
- v_child RECORD;
86
- v_child_row RECORD;
87
- v_ref RECORD;
88
- v_ref_row RECORD;
89
- v_affected BIGINT;
90
- v_ops JSONB := '[]'::jsonb;
91
- BEGIN
92
- SELECT * INTO v_coll FROM _delta_collections
93
- WHERE collection_key = p_collection_key;
94
- IF NOT FOUND THEN RETURN v_ops; END IF;
95
-
96
- -- Close or delete the row itself
97
- IF v_coll.temporal THEN
98
- EXECUTE format(
99
- 'UPDATE %I SET valid_to = NOW() WHERE id = $1 AND valid_to IS NULL',
100
- v_coll.table_name
101
- ) USING p_id;
102
- ELSE
103
- EXECUTE format('DELETE FROM %I WHERE id = $1', v_coll.table_name)
104
- USING p_id;
105
- END IF;
106
-
107
- -- Only claim a removal that actually happened. The row may already be closed,
108
- -- or an RLS policy may have silently filtered the UPDATE/DELETE to zero rows —
109
- -- in which case emitting the op would tell every subscriber to drop a row that
110
- -- is still in the table, and cascading from it would compound the lie.
111
- GET DIAGNOSTICS v_affected = ROW_COUNT;
112
- IF v_affected = 0 THEN RETURN v_ops; END IF;
113
-
114
- v_ops := v_ops || jsonb_build_array(
115
- jsonb_build_object('op', 'remove', 'path', _delta_build_path(p_collection_key, p_id::text))
116
- );
117
-
118
- -- Cascade via parent relationship (children of this collection)
119
- FOR v_child IN
120
- SELECT * FROM _delta_collections
121
- WHERE parent_collection = p_collection_key
122
- ORDER BY collection_key COLLATE "C"
123
- LOOP
124
- v_view := _delta_source_view(v_child.table_name, v_child.temporal);
125
-
126
- FOR v_child_row IN
127
- EXECUTE format('SELECT id FROM %I WHERE %I = $1 ORDER BY id', v_view, v_child.parent_fk)
128
- USING p_id
129
- LOOP
130
- v_ops := v_ops || _delta_cascade_remove(v_child.collection_key, v_child_row.id);
131
- END LOOP;
132
- END LOOP;
133
-
134
- -- Cascade via cascadeOn (referencedBy)
135
- -- Find collections whose cascade_on references this collection
136
- FOR v_ref IN
137
- SELECT c.collection_key, c.table_name, c.temporal,
138
- elem->>'fk' AS fk_column
139
- FROM _delta_collections c,
140
- jsonb_array_elements(c.cascade_on) AS elem
141
- WHERE elem->>'collection' = p_collection_key
142
- ORDER BY c.collection_key COLLATE "C", elem->>'fk' COLLATE "C"
143
- LOOP
144
- v_view := _delta_source_view(v_ref.table_name, v_ref.temporal);
145
-
146
- FOR v_ref_row IN
147
- EXECUTE format('SELECT id FROM %I WHERE %I = $1 ORDER BY id', v_view, v_ref.fk_column)
148
- USING p_id
149
- LOOP
150
- v_ops := v_ops || _delta_cascade_remove(v_ref.collection_key, v_ref_row.id);
151
- END LOOP;
152
- END LOOP;
153
-
154
- RETURN v_ops;
155
- END;
156
- $$ LANGUAGE plpgsql;
157
-
158
- -- the framework's, until dzql's compiler (`compileCascades`) writes one per
159
- -- collection; the framework's own recursion goes through this name, so a
160
- -- collection either way cascades into the other
52
+ -- generateSql writes one for each collection (compileCascades); a collection
53
+ -- there is not has nothing to take
161
54
  CREATE OR REPLACE FUNCTION _delta_cascade_remove(p_collection_key TEXT, p_id BIGINT)
162
- RETURNS JSONB LANGUAGE sql AS $$ SELECT _delta_cascade_remove_framework(p_collection_key, p_id); $$;
55
+ RETURNS JSONB LANGUAGE sql AS $$ SELECT '[]'::jsonb; $$;
163
56
 
164
57
  -- ---------------------------------------------------------------------------
165
58
  -- _delta_cascade_rows: the rows _delta_cascade_remove would take, read before
@@ -168,55 +61,8 @@ RETURNS JSONB LANGUAGE sql AS $$ SELECT _delta_cascade_remove_framework(p_collec
168
61
  -- ---------------------------------------------------------------------------
169
62
 
170
63
  DROP FUNCTION IF EXISTS _delta_cascade_rows(TEXT, BIGINT, TEXT[]);
171
- CREATE OR REPLACE FUNCTION _delta_cascade_rows_framework(
172
- p_collection_key TEXT,
173
- p_id BIGINT
174
- ) RETURNS JSONB AS $$
175
- DECLARE
176
- v_coll RECORD;
177
- v_view TEXT;
178
- v_row JSONB;
179
- v_child RECORD;
180
- v_ref RECORD;
181
- v_id BIGINT;
182
- v_rows JSONB := '[]'::jsonb;
183
- BEGIN
184
- SELECT * INTO v_coll FROM _delta_collections WHERE collection_key = p_collection_key;
185
- IF NOT FOUND THEN RETURN v_rows; END IF;
186
- v_view := _delta_source_view(v_coll.table_name, v_coll.temporal);
187
- EXECUTE format('SELECT to_jsonb(t.*) FROM %I t WHERE t.id = $1', v_view) INTO v_row USING p_id;
188
- IF v_row IS NULL THEN RETURN v_rows; END IF;
189
- v_rows := jsonb_build_array(jsonb_build_object('coll', p_collection_key, 'id', p_id, 'row', _delta_strip_temporal(v_row)));
190
-
191
- FOR v_child IN
192
- SELECT * FROM _delta_collections
193
- WHERE parent_collection = p_collection_key
194
- ORDER BY collection_key COLLATE "C"
195
- LOOP
196
- FOR v_id IN EXECUTE format('SELECT id FROM %I WHERE %I = $1 ORDER BY id', _delta_source_view(v_child.table_name, v_child.temporal), v_child.parent_fk) USING p_id
197
- LOOP
198
- v_rows := v_rows || _delta_cascade_rows(v_child.collection_key, v_id);
199
- END LOOP;
200
- END LOOP;
201
-
202
- FOR v_ref IN
203
- SELECT c.collection_key, c.table_name, c.temporal, elem->>'fk' AS fk_column
204
- FROM _delta_collections c, jsonb_array_elements(c.cascade_on) AS elem
205
- WHERE elem->>'collection' = p_collection_key
206
- ORDER BY c.collection_key COLLATE "C", elem->>'fk' COLLATE "C"
207
- LOOP
208
- FOR v_id IN EXECUTE format('SELECT id FROM %I WHERE %I = $1 ORDER BY id', _delta_source_view(v_ref.table_name, v_ref.temporal), v_ref.fk_column) USING p_id
209
- LOOP
210
- v_rows := v_rows || _delta_cascade_rows(v_ref.collection_key, v_id);
211
- END LOOP;
212
- END LOOP;
213
-
214
- RETURN v_rows;
215
- END;
216
- $$ LANGUAGE plpgsql STABLE;
217
-
218
64
  CREATE OR REPLACE FUNCTION _delta_cascade_rows(p_collection_key TEXT, p_id BIGINT)
219
- RETURNS JSONB LANGUAGE sql STABLE AS $$ SELECT _delta_cascade_rows_framework(p_collection_key, p_id); $$;
65
+ RETURNS JSONB LANGUAGE sql STABLE AS $$ SELECT '[]'::jsonb; $$;
220
66
 
221
67
  -- ---------------------------------------------------------------------------
222
68
  -- _delta_hanging_rows: the rows that hang from a row -- its children, theirs,
@@ -225,30 +71,8 @@ RETURNS JSONB LANGUAGE sql STABLE AS $$ SELECT _delta_cascade_rows_framework(p_c
225
71
  -- hanging in sqlite.ts).
226
72
  -- ---------------------------------------------------------------------------
227
73
 
228
- CREATE OR REPLACE FUNCTION _delta_hanging_rows_framework(p_collection_key TEXT, p_id BIGINT)
229
- RETURNS JSONB AS $$
230
- DECLARE
231
- v_child RECORD;
232
- v_part JSONB;
233
- v_rows JSONB := '[]'::jsonb;
234
- BEGIN
235
- -- a child collection at a time, in one aggregate: each row by id, the rows under it after it
236
- FOR v_child IN SELECT * FROM _delta_collections WHERE parent_collection = p_collection_key ORDER BY collection_key COLLATE "C" LOOP
237
- EXECUTE format(
238
- 'SELECT COALESCE(jsonb_agg(e ORDER BY t.id, n), ''[]''::jsonb) FROM %I t,
239
- jsonb_array_elements(jsonb_build_array(jsonb_build_object(''coll'', %L, ''id'', t.id, ''row'', _delta_strip_temporal(to_jsonb(t.*))))
240
- || _delta_hanging_rows(%L, t.id)) WITH ORDINALITY AS x(e, n)
241
- WHERE t.%I = $1',
242
- _delta_source_view(v_child.table_name, v_child.temporal), v_child.collection_key, v_child.collection_key, v_child.parent_fk
243
- ) INTO v_part USING p_id;
244
- v_rows := v_rows || v_part;
245
- END LOOP;
246
- RETURN v_rows;
247
- END;
248
- $$ LANGUAGE plpgsql STABLE;
249
-
250
74
  CREATE OR REPLACE FUNCTION _delta_hanging_rows(p_collection_key TEXT, p_id BIGINT)
251
- RETURNS JSONB LANGUAGE sql STABLE AS $$ SELECT _delta_hanging_rows_framework(p_collection_key, p_id); $$;
75
+ RETURNS JSONB LANGUAGE sql STABLE AS $$ SELECT '[]'::jsonb; $$;
252
76
 
253
77
  -- ---------------------------------------------------------------------------
254
78
  -- _delta_tell: tell every document that holds a row a write changed what
@@ -283,49 +107,10 @@ RETURNS JSONB LANGUAGE sql STABLE AS $$ SELECT _delta_hanging_rows_framework(p_c
283
107
  -- _delta_told: what one document is told of a write -- `p_rows`, the write's
284
108
  -- touches with their holders, as _delta_tell gathers them -- by its name
285
109
  -- `p_target`: an add, a replace of each field that changed, a remove; its
286
- -- root's arrival or leaving as a replace. The framework's answer is
287
- -- `_delta_told_framework`; dzql's compiler (`compileTold`) replaces
288
- -- `_delta_told` with one sending each document it compiled to its own.
289
-
290
- CREATE OR REPLACE FUNCTION _delta_told_framework(p_def _delta_docs, p_target TEXT, p_rows JSONB)
291
- RETURNS JSONB AS $$
292
- DECLARE
293
- v_t JSONB;
294
- v_single BOOLEAN;
295
- v_told JSONB := '[]'::jsonb;
296
- v_was BOOLEAN;
297
- v_is BOOLEAN;
298
- v_root BOOLEAN;
299
- v_path TEXT;
300
- BEGIN
301
- v_single := (_delta_resolve_scope(p_def, p_target)->>'mode') = 'single';
302
- FOR v_t IN SELECT jsonb_array_elements(p_rows) LOOP
303
- v_was := (v_t->'before') ? p_target;
304
- v_is := (v_t->'holders') ? p_target;
305
- CONTINUE WHEN NOT v_was AND NOT v_is;
306
- CONTINUE WHEN v_was AND v_is AND (v_t ? 'carried'); -- carried along, and held either way: unchanged for it
307
- v_root := v_single AND (v_t->>'coll') = p_def.root_collection;
308
- v_path := CASE WHEN v_root THEN _delta_build_path(v_t->>'coll') ELSE _delta_build_path(v_t->>'coll', v_t->>'id') END;
309
- -- each row as this document shows it: the columns it holds (_delta_show, todo #70)
310
- IF v_was AND v_is THEN -- stayed: what changed
311
- v_told := v_told || COALESCE((SELECT jsonb_agg(jsonb_build_object('op', 'replace', 'path', v_path || _delta_build_path(f), 'value', v) ORDER BY f COLLATE "C")
312
- FROM jsonb_each(_delta_show(p_def, v_t->>'coll', v_t->'after')) AS e(f, v) WHERE v IS DISTINCT FROM v_t->'was'->f), '[]'::jsonb);
313
- ELSIF v_is THEN
314
- v_told := v_told || jsonb_build_array(jsonb_build_object(
315
- 'op', CASE WHEN v_root THEN 'replace' ELSE 'add' END, 'path', v_path, 'value', _delta_show(p_def, v_t->>'coll', v_t->'after')));
316
- ELSIF v_root THEN -- gone: null, or the empty root an implied document then opens with
317
- v_told := v_told || jsonb_build_array(jsonb_build_object('op', 'replace', 'path', v_path, 'value',
318
- CASE WHEN COALESCE(p_def.implied, FALSE) THEN _delta_show(p_def, v_t->>'coll', _delta_implied_root(p_def, (v_t->>'id')::BIGINT)) ELSE 'null'::jsonb END));
319
- ELSE
320
- v_told := v_told || jsonb_build_array(jsonb_build_object('op', 'remove', 'path', v_path));
321
- END IF;
322
- END LOOP;
323
- RETURN v_told;
324
- END;
325
- $$ LANGUAGE plpgsql STABLE;
326
-
110
+ -- root's arrival or leaving as a replace. generateSql sends each document to
111
+ -- its own (compileTold).
327
112
  CREATE OR REPLACE FUNCTION _delta_told(p_def _delta_docs, p_target TEXT, p_rows JSONB)
328
- RETURNS JSONB LANGUAGE sql STABLE AS $$ SELECT _delta_told_framework(p_def, p_target, p_rows); $$;
113
+ RETURNS JSONB LANGUAGE plpgsql STABLE AS $$ BEGIN RAISE EXCEPTION 'no doc def for: %', p_target USING ERRCODE = 'P0001'; END $$;
329
114
 
330
115
  CREATE OR REPLACE FUNCTION _delta_tell(p_writer TEXT, p_touched JSONB, p_applied JSONB)
331
116
  RETURNS BIGINT AS $$
@@ -385,23 +170,10 @@ RETURNS BIGINT LANGUAGE sql AS $$ SELECT _delta_tell(p_writer, p_touched, NULL);
385
170
  -- not yet written opens empty). `there` in sqlite.ts.
386
171
  -- ---------------------------------------------------------------------------
387
172
 
388
- CREATE OR REPLACE FUNCTION _delta_row_there_framework(p_collection_key TEXT, p_id BIGINT)
389
- RETURNS BOOLEAN AS $$
390
- DECLARE
391
- v_coll RECORD;
392
- v_exists BOOLEAN;
393
- BEGIN
394
- SELECT * INTO v_coll FROM _delta_collections WHERE collection_key = p_collection_key;
395
- IF NOT FOUND OR p_id IS NULL THEN RETURN FALSE; END IF;
396
- EXECUTE format('SELECT EXISTS (SELECT 1 FROM %I WHERE id = $1)', _delta_source_view(v_coll.table_name, v_coll.temporal))
397
- INTO v_exists USING p_id;
398
- RETURN v_exists;
399
- END;
400
- $$ LANGUAGE plpgsql STABLE;
401
-
402
- -- the framework's, until dzql's compiler (`compileRows`) writes one per collection
173
+ -- generateSql writes one for each collection (compileRows); a collection there
174
+ -- is not has no row there
403
175
  CREATE OR REPLACE FUNCTION _delta_row_there(p_collection_key TEXT, p_id BIGINT)
404
- RETURNS BOOLEAN LANGUAGE sql STABLE AS $$ SELECT _delta_row_there_framework(p_collection_key, p_id); $$;
176
+ RETURNS BOOLEAN LANGUAGE sql STABLE AS $$ SELECT FALSE; $$;
405
177
 
406
178
  -- ---------------------------------------------------------------------------
407
179
  -- _delta_assert_parent_held: a parent key written (in `p_value`, over the row
@@ -427,64 +199,24 @@ BEGIN
427
199
  END;
428
200
  $$ LANGUAGE plpgsql STABLE;
429
201
 
430
- -- ---------------------------------------------------------------------------
431
- -- _delta_whole: a replace of a row itself (`/<coll>/<id>`, a single
432
- -- document's `/<root>`) is the row, as RFC 6902 says: each column
433
- -- (`p_columns`) holds what an add gives one its value leaves out -- its
434
- -- default, or null -- the row as blank as that, for the row as it is to be
435
- -- written over and the value merged over that. Its id and parent key, where
436
- -- it is, stay. A required column the value leaves out (not nullable, no
437
- -- default) is the writer's mistake: 23502, 400 on the wire, as for an add,
438
- -- and asked with the value's fields, before whether the row is there (todo
439
- -- #3; replaceRow in sqlite.ts).
440
- -- ---------------------------------------------------------------------------
441
-
442
- -- _delta_held_columns: the columns of `p_coll` (`p_columns`, its columns_def)
443
- -- a document holds -- those it names (`held`, todo #70), else all. A write
444
- -- through it names those alone (_delta_assert_fields: 400 for another, as for
445
- -- a column there is not), and replacing a row whole sets those alone.
446
-
447
- CREATE OR REPLACE FUNCTION _delta_held_columns(p_def _delta_docs, p_coll TEXT, p_columns JSONB)
448
- RETURNS JSONB AS $$
449
- SELECT CASE WHEN NOT COALESCE(p_def.held, '{}'::jsonb) ? p_coll THEN p_columns
450
- ELSE COALESCE((SELECT jsonb_object_agg(k, v) FROM jsonb_each(p_columns) AS e(k, v) WHERE (p_def.held->p_coll) ? k), '{}'::jsonb) END;
451
- $$ LANGUAGE sql STABLE;
452
-
453
202
  -- _delta_root_there: is a single document's root row `p_id` there now, and
454
203
  -- does it meet the whole of its name's scope (a column beside its id), as
455
- -- delta_open reads it? Asked before each op of a write (delta_apply). The
456
- -- framework's runs the scope's conditions as text (`p_scope`, the resolver's
457
- -- answer) over the root's current rows (`p_view`); dzql's compiler
458
- -- (`compileRootThere`) replaces `_delta_root_there` with one sending each
459
- -- document it compiled to its own.
460
- CREATE OR REPLACE FUNCTION _delta_root_there_framework(p_def _delta_docs, p_doc_name TEXT, p_scope JSONB, p_view TEXT, p_id BIGINT)
461
- RETURNS BOOLEAN AS $$
462
- DECLARE
463
- v_exists BOOLEAN;
464
- BEGIN
465
- EXECUTE format('SELECT EXISTS (SELECT 1 FROM %I t WHERE t.id = $1 AND %s)', p_view, p_scope->>'where') INTO v_exists USING p_id;
466
- RETURN v_exists;
467
- END;
468
- $$ LANGUAGE plpgsql STABLE;
469
-
204
+ -- delta_open reads it? Asked before each op of a write (delta_apply).
205
+ -- generateSql sends each document to its own (compileRootThere).
470
206
  CREATE OR REPLACE FUNCTION _delta_root_there(p_def _delta_docs, p_doc_name TEXT, p_scope JSONB, p_view TEXT, p_id BIGINT)
471
- RETURNS BOOLEAN LANGUAGE sql STABLE AS $$ SELECT _delta_root_there_framework(p_def, p_doc_name, p_scope, p_view, p_id); $$;
207
+ RETURNS BOOLEAN LANGUAGE plpgsql STABLE AS $$ BEGIN RAISE EXCEPTION 'no doc def for: %', p_doc_name USING ERRCODE = 'P0001'; END $$;
472
208
 
473
209
  -- _delta_write_columns: the columns of `p_coll` a document writes -- as the
474
210
  -- process writing declares them (_delta_cols), those the document holds
475
- -- (_delta_held_columns). The framework's, until dzql's compiler
476
- -- (`compileColumns`) replaces it with one sending each document it compiled to
477
- -- its own.
478
- CREATE OR REPLACE FUNCTION _delta_write_columns_framework(p_def _delta_docs, p_coll TEXT, p_columns_def JSONB)
479
- RETURNS JSONB LANGUAGE sql STABLE AS $$ SELECT _delta_held_columns(p_def, p_coll, _delta_cols(p_coll, p_columns_def)); $$;
480
-
211
+ -- (`held`, todo #70), else all: a write through it names those alone
212
+ -- (_delta_assert_fields: 400 for another), and replacing a row whole sets those
213
+ -- alone. generateSql sends each document to its own (compileColumns).
481
214
  CREATE OR REPLACE FUNCTION _delta_write_columns(p_def _delta_docs, p_coll TEXT, p_columns_def JSONB)
482
- RETURNS JSONB LANGUAGE sql STABLE AS $$ SELECT _delta_write_columns_framework(p_def, p_coll, p_columns_def); $$;
215
+ RETURNS JSONB LANGUAGE plpgsql STABLE AS $$ BEGIN RAISE EXCEPTION 'no doc def for: %', p_def.prefix USING ERRCODE = 'P0001'; END $$;
483
216
 
484
217
  -- ---------------------------------------------------------------------------
485
- -- A row's SQL, as delta_apply runs it: the framework's, built from the table's
486
- -- name at run time; dzql's compiler (`compileRows`) replaces each with one
487
- -- written for each collection it compiled, any other sent here.
218
+ -- A row's SQL, as delta_apply runs it: generateSql writes each for each
219
+ -- collection (compileRows).
488
220
  -- _delta_row_lock: a row as it stands, locked for the write (NULL: not there)
489
221
  -- _delta_row_put: a replace's row stored -- a temporal one a new version (one
490
222
  -- this write made gives way to it; the standing one closes at `p_ts`), else
@@ -492,61 +224,29 @@ RETURNS JSONB LANGUAGE sql STABLE AS $$ SELECT _delta_write_columns_framework(p_
492
224
  -- _delta_next_id: an add's id, minted
493
225
  -- _delta_row_clear_moment: a temporal version this write made and took back
494
226
  -- (it never was outside the write), so an add of its id takes its place
227
+ -- _delta_row_add: an add's row stored, answered as stored
228
+ -- _delta_row_read: a row as it stands, without its validity (NULL: not there)
495
229
  -- (_delta_row_there, above, is an add's "is the id there now".)
496
230
  -- ---------------------------------------------------------------------------
497
231
 
498
- CREATE OR REPLACE FUNCTION _delta_row_lock_framework(p_coll TEXT, p_table TEXT, p_temporal BOOLEAN, p_id BIGINT)
499
- RETURNS JSONB AS $$
500
- DECLARE
501
- v_row JSONB;
502
- BEGIN
503
- IF p_temporal THEN
504
- EXECUTE format('SELECT to_jsonb(t.*) FROM %I t WHERE t.id = $1 AND valid_to IS NULL FOR UPDATE', p_table) INTO v_row USING p_id;
505
- ELSE
506
- EXECUTE format('SELECT to_jsonb(t.*) FROM %I t WHERE t.id = $1 FOR UPDATE', p_table) INTO v_row USING p_id;
507
- END IF;
508
- RETURN v_row;
509
- END;
510
- $$ LANGUAGE plpgsql;
511
-
512
- CREATE OR REPLACE FUNCTION _delta_row_put_framework(p_coll TEXT, p_table TEXT, p_temporal BOOLEAN, p_id BIGINT, p_row JSONB, p_ts TIMESTAMPTZ)
513
- RETURNS JSONB AS $$
514
- DECLARE
515
- v_row JSONB;
516
- BEGIN
517
- IF p_temporal THEN
518
- EXECUTE format('DELETE FROM %I WHERE id = $1 AND valid_to IS NULL AND valid_from = $2', p_table) USING p_id, p_ts;
519
- EXECUTE format('UPDATE %I SET valid_to = $2 WHERE id = $1 AND valid_to IS NULL', p_table) USING p_id, p_ts;
520
- EXECUTE format('INSERT INTO %I AS t SELECT * FROM jsonb_populate_record(null::%I, $1) RETURNING to_jsonb(t.*)', p_table, p_table)
521
- INTO v_row USING p_row || jsonb_build_object('valid_from', p_ts, 'valid_to', NULL);
522
- ELSE
523
- EXECUTE format('DELETE FROM %I WHERE id = $1', p_table) USING p_id;
524
- EXECUTE format('INSERT INTO %I AS t SELECT * FROM jsonb_populate_record(null::%I, $1) RETURNING to_jsonb(t.*)', p_table, p_table)
525
- INTO v_row USING p_row;
526
- END IF;
527
- RETURN v_row;
528
- END;
529
- $$ LANGUAGE plpgsql;
530
-
531
- CREATE OR REPLACE FUNCTION _delta_next_id_framework(p_coll TEXT, p_table TEXT)
532
- RETURNS BIGINT AS $$
533
- DECLARE
534
- v_id BIGINT;
535
- BEGIN
536
- EXECUTE format('SELECT nextval(%L)', 'seq_' || p_table) INTO v_id;
537
- RETURN v_id;
538
- END;
539
- $$ LANGUAGE plpgsql;
540
-
541
- CREATE OR REPLACE FUNCTION _delta_row_clear_moment_framework(p_coll TEXT, p_table TEXT, p_id BIGINT, p_ts TIMESTAMPTZ)
542
- RETURNS void AS $$
543
- BEGIN
544
- EXECUTE format('DELETE FROM %I WHERE id = $1 AND valid_from = $2 AND valid_to = $2', p_table) USING p_id, p_ts;
545
- END;
546
- $$ LANGUAGE plpgsql;
232
+ CREATE OR REPLACE FUNCTION _delta_row_lock(p_coll TEXT, p_table TEXT, p_temporal BOOLEAN, p_id BIGINT)
233
+ RETURNS JSONB LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'unknown collection: %', p_coll USING ERRCODE = '22023'; END $$;
234
+ CREATE OR REPLACE FUNCTION _delta_row_put(p_coll TEXT, p_table TEXT, p_temporal BOOLEAN, p_id BIGINT, p_row JSONB, p_ts TIMESTAMPTZ)
235
+ RETURNS JSONB LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'unknown collection: %', p_coll USING ERRCODE = '22023'; END $$;
236
+ CREATE OR REPLACE FUNCTION _delta_next_id(p_coll TEXT, p_table TEXT)
237
+ RETURNS BIGINT LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'unknown collection: %', p_coll USING ERRCODE = '22023'; END $$;
238
+ CREATE OR REPLACE FUNCTION _delta_row_clear_moment(p_coll TEXT, p_table TEXT, p_id BIGINT, p_ts TIMESTAMPTZ)
239
+ RETURNS void LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'unknown collection: %', p_coll USING ERRCODE = '22023'; END $$;
240
+ CREATE OR REPLACE FUNCTION _delta_row_add(p_coll TEXT, p_table TEXT, p_row JSONB)
241
+ RETURNS JSONB LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'unknown collection: %', p_coll USING ERRCODE = '22023'; END $$;
242
+ CREATE OR REPLACE FUNCTION _delta_row_read(p_coll TEXT, p_view TEXT, p_id BIGINT)
243
+ RETURNS JSONB LANGUAGE plpgsql STABLE AS $$ BEGIN RAISE EXCEPTION 'unknown collection: %', p_coll USING ERRCODE = '22023'; END $$;
547
244
 
548
- -- _delta_row_add: an add's row stored, naming the columns the row gives (_delta_insert_sql), answered as stored
549
- CREATE OR REPLACE FUNCTION _delta_row_add_framework(p_coll TEXT, p_table TEXT, p_row JSONB)
245
+ -- _delta_row_add_given: a row stored by the columns it gives (_delta_insert_sql),
246
+ -- answered as stored. A compiled add whose row gives some other set of keys
247
+ -- than its collection's every column (a walk's row, as stored; a column a
248
+ -- migration added, left to its default) stores it so.
249
+ CREATE OR REPLACE FUNCTION _delta_row_add_given(p_table TEXT, p_row JSONB)
550
250
  RETURNS JSONB AS $$
551
251
  DECLARE
552
252
  v_row JSONB;
@@ -556,30 +256,17 @@ BEGIN
556
256
  END;
557
257
  $$ LANGUAGE plpgsql;
558
258
 
559
- -- _delta_row_read: a row as it stands, without its validity (NULL: not there)
560
- CREATE OR REPLACE FUNCTION _delta_row_read_framework(p_coll TEXT, p_view TEXT, p_id BIGINT)
561
- RETURNS JSONB AS $$
562
- DECLARE
563
- v_row JSONB;
564
- BEGIN
565
- EXECUTE format('SELECT _delta_strip_temporal(to_jsonb(t.*)) FROM %I t WHERE t.id = $1', p_view) INTO v_row USING p_id;
566
- RETURN v_row;
567
- END;
568
- $$ LANGUAGE plpgsql STABLE;
569
-
570
- CREATE OR REPLACE FUNCTION _delta_row_add(p_coll TEXT, p_table TEXT, p_row JSONB)
571
- RETURNS JSONB LANGUAGE sql AS $$ SELECT _delta_row_add_framework(p_coll, p_table, p_row); $$;
572
- CREATE OR REPLACE FUNCTION _delta_row_read(p_coll TEXT, p_view TEXT, p_id BIGINT)
573
- RETURNS JSONB LANGUAGE sql STABLE AS $$ SELECT _delta_row_read_framework(p_coll, p_view, p_id); $$;
574
- CREATE OR REPLACE FUNCTION _delta_row_lock(p_coll TEXT, p_table TEXT, p_temporal BOOLEAN, p_id BIGINT)
575
- RETURNS JSONB LANGUAGE sql AS $$ SELECT _delta_row_lock_framework(p_coll, p_table, p_temporal, p_id); $$;
576
- CREATE OR REPLACE FUNCTION _delta_row_put(p_coll TEXT, p_table TEXT, p_temporal BOOLEAN, p_id BIGINT, p_row JSONB, p_ts TIMESTAMPTZ)
577
- RETURNS JSONB LANGUAGE sql AS $$ SELECT _delta_row_put_framework(p_coll, p_table, p_temporal, p_id, p_row, p_ts); $$;
578
- CREATE OR REPLACE FUNCTION _delta_next_id(p_coll TEXT, p_table TEXT)
579
- RETURNS BIGINT LANGUAGE sql AS $$ SELECT _delta_next_id_framework(p_coll, p_table); $$;
580
- CREATE OR REPLACE FUNCTION _delta_row_clear_moment(p_coll TEXT, p_table TEXT, p_id BIGINT, p_ts TIMESTAMPTZ)
581
- RETURNS void LANGUAGE sql AS $$ SELECT _delta_row_clear_moment_framework(p_coll, p_table, p_id, p_ts); $$;
582
-
259
+ -- ---------------------------------------------------------------------------
260
+ -- _delta_whole: a replace of a row itself (`/<coll>/<id>`, a single
261
+ -- document's `/<root>`) is the row, as RFC 6902 says: each column
262
+ -- (`p_columns`) holds what an add gives one its value leaves out -- its
263
+ -- default, or null -- the row as blank as that, for the row as it is to be
264
+ -- written over and the value merged over that. Its id and parent key, where
265
+ -- it is, stay. A required column the value leaves out (not nullable, no
266
+ -- default) is the writer's mistake: 23502, 400 on the wire, as for an add,
267
+ -- and asked with the value's fields, before whether the row is there (todo
268
+ -- #3; replaceRow in sqlite.ts).
269
+ -- ---------------------------------------------------------------------------
583
270
  CREATE OR REPLACE FUNCTION _delta_whole(p_columns JSONB, p_value JSONB)
584
271
  RETURNS JSONB AS $$
585
272
  DECLARE
@@ -622,11 +309,11 @@ DECLARE
622
309
  v_scope JSONB;
623
310
  v_is_list BOOLEAN;
624
311
  v_doc_id BIGINT;
625
- v_root_coll RECORD;
312
+ v_root_coll _delta_collections;
626
313
  v_op JSONB;
627
314
  v_parts TEXT[];
628
315
  v_coll_key TEXT;
629
- v_coll RECORD;
316
+ v_coll _delta_collections;
630
317
  v_view TEXT;
631
318
  v_root_view TEXT;
632
319
  v_opened BOOLEAN; -- a single document's root was there as the write began
@@ -638,7 +325,7 @@ DECLARE
638
325
  v_row JSONB;
639
326
  v_new_row JSONB;
640
327
  v_blank JSONB; -- a replace of the row whole: each column as an add leaves it (_delta_whole); of a field, nothing
641
- v_cols JSONB; -- the op's collection's columns the document holds (_delta_held_columns); a walk's, all
328
+ v_cols JSONB; -- the op's collection's columns the document holds (_delta_write_columns); a walk's, all
642
329
  v_exists BOOLEAN;
643
330
  v_missing TEXT;
644
331
  v_ts TIMESTAMPTZ := NOW();
@@ -681,8 +368,7 @@ BEGIN
681
368
  v_doc_id := (v_scope->'values'->>'id')::BIGINT;
682
369
  END IF;
683
370
 
684
- SELECT * INTO v_root_coll FROM _delta_collections
685
- WHERE collection_key = v_def.root_collection;
371
+ v_root_coll := _delta_collection(v_def.root_collection);
686
372
  v_root_view := _delta_source_view(v_root_coll.table_name, v_root_coll.temporal);
687
373
 
688
374
  -- an implied document's first write makes its root row (todo #34), told as a
@@ -749,9 +435,8 @@ BEGIN
749
435
  v_parts := _delta_split_path(v_op->>'path');
750
436
  v_coll_key := v_parts[1];
751
437
 
752
- SELECT * INTO v_coll FROM _delta_collections
753
- WHERE collection_key = v_coll_key;
754
- IF NOT FOUND THEN
438
+ v_coll := _delta_collection(v_coll_key);
439
+ IF v_coll.collection_key IS NULL THEN
755
440
  RAISE EXCEPTION 'unknown collection: %', v_coll_key USING ERRCODE = '22023';
756
441
  END IF;
757
442
  -- a walk puts back what the ledger recorded, whole
@@ -27,71 +27,13 @@ RETURNS TABLE(version BIGINT, ops JSONB, applied JSONB) AS $$
27
27
  $$ LANGUAGE sql STABLE;
28
28
 
29
29
  -- ---------------------------------------------------------------------------
30
- -- delta_open_at: time-travel — load a doc as it existed at a point in time
30
+ -- delta_open_at: time-travel -- a document as it stood at a point in time.
31
+ -- generateSql sends each document to its own read as it stood
32
+ -- (compileLookups, dzql_open_at__<slug>); before it is applied there is none.
31
33
  -- ---------------------------------------------------------------------------
32
34
 
33
35
  CREATE OR REPLACE FUNCTION delta_open_at(p_doc_name TEXT, p_at TIMESTAMPTZ)
34
- RETURNS JSONB AS $$
35
- DECLARE
36
- v_def _delta_docs;
37
- v_resolved JSONB;
38
- v_mode TEXT;
39
- v_doc_id BIGINT;
40
- v_root_coll RECORD;
41
- v_root_row JSONB;
42
- v_result JSONB;
43
- v_coll_key TEXT;
44
- BEGIN
45
- v_def := _delta_find_doc(p_doc_name);
46
- IF v_def.prefix IS NULL THEN
47
- RAISE EXCEPTION 'no doc def for: %', p_doc_name
48
- USING ERRCODE = 'P0001';
49
- END IF;
50
-
51
- v_resolved := _delta_resolve_scope(v_def, p_doc_name);
52
- v_mode := v_resolved->>'mode';
53
-
54
- SELECT * INTO v_root_coll FROM _delta_collections
55
- WHERE collection_key = v_def.root_collection;
56
- IF NOT FOUND THEN
57
- RAISE EXCEPTION 'doc "%" references unknown root collection: %',
58
- p_doc_name, v_def.root_collection
59
- USING ERRCODE = 'P0001';
60
- END IF;
61
-
62
- -- List mode (todo #74): as delta_open reads a list, at p_at -- each row as it shows (todo #70).
63
- IF v_mode = 'list' THEN RETURN _delta_show_doc(v_def, _delta_read_list(v_def, v_resolved->>'where', p_at), FALSE); END IF;
64
-
65
- v_doc_id := (v_resolved->'values'->>'id')::BIGINT;
66
-
67
- -- the root as it stood, where it met the whole of its name's scope then
68
- -- (a column beside its id), as delta_open reads it now
69
- IF v_root_coll.temporal THEN
70
- EXECUTE format(
71
- 'SELECT to_jsonb(t.*) FROM %I t WHERE t.id = $1 AND %s AND %s',
72
- v_root_coll.table_name, _delta_temporal_where(p_at), v_resolved->>'where'
73
- ) INTO v_root_row USING v_doc_id;
74
- v_root_row := _delta_strip_temporal(v_root_row);
75
- ELSE
76
- EXECUTE format('SELECT to_jsonb(t.*) FROM %I t WHERE t.id = $1 AND %s', v_root_coll.table_name, v_resolved->>'where')
77
- INTO v_root_row USING v_doc_id;
78
- END IF;
79
-
80
- IF v_root_row IS NULL THEN RETURN NULL; END IF;
81
-
82
- v_result := jsonb_build_object(v_def.root_collection, v_root_row);
83
-
84
- FOREACH v_coll_key IN ARRAY v_def.include
85
- LOOP
86
- v_result := v_result || jsonb_build_object(
87
- v_coll_key,
88
- _delta_load_collection(v_coll_key, v_def.root_collection, v_doc_id, p_at)
89
- );
90
- END LOOP;
91
-
92
- RETURN _delta_show_doc(v_def, v_result, TRUE);
93
- END;
94
- $$ LANGUAGE plpgsql STABLE;
36
+ RETURNS JSONB LANGUAGE plpgsql STABLE AS $$ BEGIN RAISE EXCEPTION 'no doc def for: %', p_doc_name USING ERRCODE = 'P0001'; END $$;
95
37
 
96
38
  -- ---------------------------------------------------------------------------
97
39
  -- delta_prune_ops: clean up old log entries -- and the names nobody holds