dzql 0.9.0 → 0.10.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 +29 -13
- package/package.json +2 -2
- package/src/compile/cascades.ts +151 -0
- package/src/compile/columns.ts +144 -0
- package/src/compile/holders.ts +72 -0
- package/src/compile/holds.ts +91 -0
- package/src/compile/ledger.ts +73 -0
- package/src/compile/opens.ts +63 -23
- package/src/compile/resolve.ts +75 -0
- package/src/compile/root.ts +61 -0
- package/src/compile/rows.ts +122 -0
- package/src/compile/scope.ts +87 -0
- package/src/compile/told.ts +94 -0
- package/src/postgres/auth-jwt-sql.ts +1 -1
- package/src/postgres/auth-jwt.ts +2 -2
- package/src/postgres/bootstrap.ts +2 -2
- package/src/postgres/codegen.ts +12 -2
- package/src/postgres/index.ts +2 -2
- package/src/postgres/pglite.ts +1 -1
- package/src/sql/001a-delta-meta.sql +8 -1
- package/src/sql/001b-delta-scope.sql +32 -5
- package/src/sql/001c-delta-read.sql +10 -11
- package/src/sql/001d-delta-write.sql +212 -123
- package/src/sql/001g-delta-ledger.sql +67 -23
- package/src/sql/auth-jwt.sql +1 -1
|
@@ -75,7 +75,7 @@ END $$;
|
|
|
75
75
|
-- ---------------------------------------------------------------------------
|
|
76
76
|
|
|
77
77
|
DROP FUNCTION IF EXISTS _delta_cascade_remove(TEXT, BIGINT, TEXT[]);
|
|
78
|
-
CREATE OR REPLACE FUNCTION
|
|
78
|
+
CREATE OR REPLACE FUNCTION _delta_cascade_remove_framework(
|
|
79
79
|
p_collection_key TEXT,
|
|
80
80
|
p_id BIGINT
|
|
81
81
|
) RETURNS JSONB AS $$
|
|
@@ -155,6 +155,12 @@ BEGIN
|
|
|
155
155
|
END;
|
|
156
156
|
$$ LANGUAGE plpgsql;
|
|
157
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
|
|
161
|
+
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); $$;
|
|
163
|
+
|
|
158
164
|
-- ---------------------------------------------------------------------------
|
|
159
165
|
-- _delta_cascade_rows: the rows _delta_cascade_remove would take, read before
|
|
160
166
|
-- it takes them, in the order it emits their removes -- so who holds each can
|
|
@@ -162,7 +168,7 @@ $$ LANGUAGE plpgsql;
|
|
|
162
168
|
-- ---------------------------------------------------------------------------
|
|
163
169
|
|
|
164
170
|
DROP FUNCTION IF EXISTS _delta_cascade_rows(TEXT, BIGINT, TEXT[]);
|
|
165
|
-
CREATE OR REPLACE FUNCTION
|
|
171
|
+
CREATE OR REPLACE FUNCTION _delta_cascade_rows_framework(
|
|
166
172
|
p_collection_key TEXT,
|
|
167
173
|
p_id BIGINT
|
|
168
174
|
) RETURNS JSONB AS $$
|
|
@@ -209,6 +215,9 @@ BEGIN
|
|
|
209
215
|
END;
|
|
210
216
|
$$ LANGUAGE plpgsql STABLE;
|
|
211
217
|
|
|
218
|
+
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); $$;
|
|
220
|
+
|
|
212
221
|
-- ---------------------------------------------------------------------------
|
|
213
222
|
-- _delta_hanging_rows: the rows that hang from a row -- its children, theirs,
|
|
214
223
|
-- and on down, whichever documents include them -- parent first, each
|
|
@@ -216,7 +225,7 @@ $$ LANGUAGE plpgsql STABLE;
|
|
|
216
225
|
-- hanging in sqlite.ts).
|
|
217
226
|
-- ---------------------------------------------------------------------------
|
|
218
227
|
|
|
219
|
-
CREATE OR REPLACE FUNCTION
|
|
228
|
+
CREATE OR REPLACE FUNCTION _delta_hanging_rows_framework(p_collection_key TEXT, p_id BIGINT)
|
|
220
229
|
RETURNS JSONB AS $$
|
|
221
230
|
DECLARE
|
|
222
231
|
v_child RECORD;
|
|
@@ -238,6 +247,9 @@ BEGIN
|
|
|
238
247
|
END;
|
|
239
248
|
$$ LANGUAGE plpgsql STABLE;
|
|
240
249
|
|
|
250
|
+
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); $$;
|
|
252
|
+
|
|
241
253
|
-- ---------------------------------------------------------------------------
|
|
242
254
|
-- _delta_tell: tell every document that holds a row a write changed what
|
|
243
255
|
-- changed for it (todo #28), and answer the writer's new version.
|
|
@@ -268,6 +280,53 @@ $$ LANGUAGE plpgsql STABLE;
|
|
|
268
280
|
-- two-argument form logs none.
|
|
269
281
|
-- ---------------------------------------------------------------------------
|
|
270
282
|
|
|
283
|
+
-- _delta_told: what one document is told of a write -- `p_rows`, the write's
|
|
284
|
+
-- touches with their holders, as _delta_tell gathers them -- by its name
|
|
285
|
+
-- `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
|
+
|
|
327
|
+
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); $$;
|
|
329
|
+
|
|
271
330
|
CREATE OR REPLACE FUNCTION _delta_tell(p_writer TEXT, p_touched JSONB, p_applied JSONB)
|
|
272
331
|
RETURNS BIGINT AS $$
|
|
273
332
|
DECLARE
|
|
@@ -277,12 +336,7 @@ DECLARE
|
|
|
277
336
|
v_targets TEXT[] := ARRAY[]::text[];
|
|
278
337
|
v_target TEXT;
|
|
279
338
|
v_def _delta_docs;
|
|
280
|
-
v_single BOOLEAN;
|
|
281
339
|
v_told JSONB;
|
|
282
|
-
v_was BOOLEAN;
|
|
283
|
-
v_is BOOLEAN;
|
|
284
|
-
v_root BOOLEAN;
|
|
285
|
-
v_path TEXT;
|
|
286
340
|
v_version BIGINT;
|
|
287
341
|
v_next JSONB := '{}'::jsonb; -- a row's holders, as its next touch found them
|
|
288
342
|
v_key TEXT;
|
|
@@ -309,29 +363,7 @@ BEGIN
|
|
|
309
363
|
LOOP
|
|
310
364
|
v_def := _delta_find_doc(v_target);
|
|
311
365
|
CONTINUE WHEN v_def.prefix IS NULL;
|
|
312
|
-
|
|
313
|
-
v_told := '[]'::jsonb;
|
|
314
|
-
FOR v_t IN SELECT jsonb_array_elements(v_rows) LOOP
|
|
315
|
-
v_was := (v_t->'before') ? v_target;
|
|
316
|
-
v_is := (v_t->'holders') ? v_target;
|
|
317
|
-
CONTINUE WHEN NOT v_was AND NOT v_is;
|
|
318
|
-
CONTINUE WHEN v_was AND v_is AND (v_t ? 'carried'); -- carried along, and held either way: unchanged for it
|
|
319
|
-
v_root := v_single AND (v_t->>'coll') = v_def.root_collection;
|
|
320
|
-
v_path := CASE WHEN v_root THEN _delta_build_path(v_t->>'coll') ELSE _delta_build_path(v_t->>'coll', v_t->>'id') END;
|
|
321
|
-
-- each row as this document shows it: the columns it holds (_delta_show, todo #70)
|
|
322
|
-
IF v_was AND v_is THEN -- stayed: what changed
|
|
323
|
-
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")
|
|
324
|
-
FROM jsonb_each(_delta_show(v_def, v_t->>'coll', v_t->'after')) AS e(f, v) WHERE v IS DISTINCT FROM v_t->'was'->f), '[]'::jsonb);
|
|
325
|
-
ELSIF v_is THEN
|
|
326
|
-
v_told := v_told || jsonb_build_array(jsonb_build_object(
|
|
327
|
-
'op', CASE WHEN v_root THEN 'replace' ELSE 'add' END, 'path', v_path, 'value', _delta_show(v_def, v_t->>'coll', v_t->'after')));
|
|
328
|
-
ELSIF v_root THEN -- gone: null, or the empty root an implied document then opens with
|
|
329
|
-
v_told := v_told || jsonb_build_array(jsonb_build_object('op', 'replace', 'path', v_path, 'value',
|
|
330
|
-
CASE WHEN COALESCE(v_def.implied, FALSE) THEN _delta_show(v_def, v_t->>'coll', _delta_implied_root(v_def, (v_t->>'id')::BIGINT)) ELSE 'null'::jsonb END));
|
|
331
|
-
ELSE
|
|
332
|
-
v_told := v_told || jsonb_build_array(jsonb_build_object('op', 'remove', 'path', v_path));
|
|
333
|
-
END IF;
|
|
334
|
-
END LOOP;
|
|
366
|
+
v_told := _delta_told(v_def, v_target, v_rows);
|
|
335
367
|
IF v_target = p_writer THEN
|
|
336
368
|
v_version := _delta_bump_and_notify(p_writer, v_told, CASE WHEN p_applied IS DISTINCT FROM v_told THEN p_applied END);
|
|
337
369
|
ELSIF jsonb_array_length(v_told) > 0 THEN
|
|
@@ -353,7 +385,7 @@ RETURNS BIGINT LANGUAGE sql AS $$ SELECT _delta_tell(p_writer, p_touched, NULL);
|
|
|
353
385
|
-- not yet written opens empty). `there` in sqlite.ts.
|
|
354
386
|
-- ---------------------------------------------------------------------------
|
|
355
387
|
|
|
356
|
-
CREATE OR REPLACE FUNCTION
|
|
388
|
+
CREATE OR REPLACE FUNCTION _delta_row_there_framework(p_collection_key TEXT, p_id BIGINT)
|
|
357
389
|
RETURNS BOOLEAN AS $$
|
|
358
390
|
DECLARE
|
|
359
391
|
v_coll RECORD;
|
|
@@ -367,6 +399,10 @@ BEGIN
|
|
|
367
399
|
END;
|
|
368
400
|
$$ LANGUAGE plpgsql STABLE;
|
|
369
401
|
|
|
402
|
+
-- the framework's, until dzql's compiler (`compileRows`) writes one per collection
|
|
403
|
+
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); $$;
|
|
405
|
+
|
|
370
406
|
-- ---------------------------------------------------------------------------
|
|
371
407
|
-- _delta_assert_parent_held: a parent key written (in `p_value`, over the row
|
|
372
408
|
-- as it is, `p_row`) moves the row, and only under a parent the document holds,
|
|
@@ -414,6 +450,136 @@ RETURNS JSONB AS $$
|
|
|
414
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;
|
|
415
451
|
$$ LANGUAGE sql STABLE;
|
|
416
452
|
|
|
453
|
+
-- _delta_root_there: is a single document's root row `p_id` there now, and
|
|
454
|
+
-- 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
|
+
|
|
470
|
+
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); $$;
|
|
472
|
+
|
|
473
|
+
-- _delta_write_columns: the columns of `p_coll` a document writes -- as the
|
|
474
|
+
-- 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
|
+
|
|
481
|
+
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); $$;
|
|
483
|
+
|
|
484
|
+
-- ---------------------------------------------------------------------------
|
|
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.
|
|
488
|
+
-- _delta_row_lock: a row as it stands, locked for the write (NULL: not there)
|
|
489
|
+
-- _delta_row_put: a replace's row stored -- a temporal one a new version (one
|
|
490
|
+
-- this write made gives way to it; the standing one closes at `p_ts`), else
|
|
491
|
+
-- the row again -- answered as stored
|
|
492
|
+
-- _delta_next_id: an add's id, minted
|
|
493
|
+
-- _delta_row_clear_moment: a temporal version this write made and took back
|
|
494
|
+
-- (it never was outside the write), so an add of its id takes its place
|
|
495
|
+
-- (_delta_row_there, above, is an add's "is the id there now".)
|
|
496
|
+
-- ---------------------------------------------------------------------------
|
|
497
|
+
|
|
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;
|
|
547
|
+
|
|
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)
|
|
550
|
+
RETURNS JSONB AS $$
|
|
551
|
+
DECLARE
|
|
552
|
+
v_row JSONB;
|
|
553
|
+
BEGIN
|
|
554
|
+
EXECUTE _delta_insert_sql(p_table, p_row) || ' RETURNING to_jsonb(t.*)' INTO v_row USING p_row;
|
|
555
|
+
RETURN v_row;
|
|
556
|
+
END;
|
|
557
|
+
$$ LANGUAGE plpgsql;
|
|
558
|
+
|
|
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
|
+
|
|
417
583
|
CREATE OR REPLACE FUNCTION _delta_whole(p_columns JSONB, p_value JSONB)
|
|
418
584
|
RETURNS JSONB AS $$
|
|
419
585
|
DECLARE
|
|
@@ -524,11 +690,11 @@ BEGIN
|
|
|
524
690
|
-- documents, which showed it already (ensureImpliedRoot in sqlite.ts); an
|
|
525
691
|
-- empty write makes nothing
|
|
526
692
|
IF NOT v_is_list AND COALESCE(v_def.implied, FALSE) AND jsonb_array_length(p_ops) > 0 THEN
|
|
527
|
-
|
|
693
|
+
v_exists := _delta_row_there(v_def.root_collection, v_doc_id);
|
|
528
694
|
PERFORM _delta_make_implied(v_def, v_doc_id, p_ops, v_ts);
|
|
529
695
|
IF NOT v_exists THEN
|
|
530
696
|
v_passed := jsonb_build_object(v_root_coll.table_name, v_doc_id);
|
|
531
|
-
|
|
697
|
+
v_row := _delta_row_read(v_def.root_collection, v_root_view, v_doc_id);
|
|
532
698
|
IF v_row IS NOT NULL THEN
|
|
533
699
|
v_touched := jsonb_build_array(jsonb_build_object('coll', v_def.root_collection, 'id', v_doc_id, 'after', v_row, 'carried', TRUE,
|
|
534
700
|
'before', COALESCE((SELECT jsonb_agg(n) FROM unnest(_delta_holders(v_def.root_collection, v_row, p_doc_name)) AS n
|
|
@@ -541,7 +707,7 @@ BEGIN
|
|
|
541
707
|
-- An empty write is asked what each op is (below): a single document whose
|
|
542
708
|
-- root is not there is not found, as on SQLite -- unless implied, or a walk.
|
|
543
709
|
IF jsonb_array_length(p_ops) = 0 AND NOT v_is_list AND NOT COALESCE(p_walk, FALSE) AND NOT COALESCE(v_def.implied, FALSE) THEN
|
|
544
|
-
|
|
710
|
+
v_exists := _delta_root_there(v_def, p_doc_name, v_scope, v_root_view, v_doc_id);
|
|
545
711
|
IF NOT v_exists THEN
|
|
546
712
|
RAISE EXCEPTION 'document not found: % (its root, %, is not there)',
|
|
547
713
|
p_doc_name, _delta_build_path(v_def.root_collection, v_doc_id::text)
|
|
@@ -565,7 +731,7 @@ BEGIN
|
|
|
565
731
|
-- (_delta_walk_plan), as SQLite's and ./json's are.
|
|
566
732
|
IF NOT v_is_list AND NOT COALESCE(p_walk, FALSE) THEN -- a walk not said (null) is none
|
|
567
733
|
-- there, and meeting the whole of its name's scope (a column beside its id), as delta_open reads it
|
|
568
|
-
|
|
734
|
+
v_exists := _delta_root_there(v_def, p_doc_name, v_scope, v_root_view, v_doc_id);
|
|
569
735
|
v_opened := COALESCE(v_opened, v_exists OR COALESCE(v_def.implied, FALSE));
|
|
570
736
|
v_back := FALSE;
|
|
571
737
|
IF NOT v_exists AND v_opened AND v_op->>'op' = 'add' THEN
|
|
@@ -594,7 +760,7 @@ BEGIN
|
|
|
594
760
|
THEN COALESCE((SELECT jsonb_object_agg(c, '{"nullable": true}'::jsonb) FROM unnest(_delta_stored_columns(v_coll.table_name)) AS c
|
|
595
761
|
WHERE c NOT IN ('id', 'valid_from', 'valid_to') AND c IS DISTINCT FROM v_coll.parent_fk), '{}'::jsonb)
|
|
596
762
|
|| _delta_cols(v_coll_key, v_coll.columns_def)
|
|
597
|
-
ELSE
|
|
763
|
+
ELSE _delta_write_columns(v_def, v_coll_key, v_coll.columns_def) END;
|
|
598
764
|
|
|
599
765
|
-- Scope guard: an op may only target the doc's root or an included
|
|
600
766
|
-- collection. Defence-in-depth — a client that opened doc A must not write
|
|
@@ -637,17 +803,7 @@ BEGIN
|
|
|
637
803
|
v_blank := CASE WHEN array_length(v_parts, 1) = 1 THEN _delta_whole(v_cols, v_op->'value') ELSE '{}'::jsonb END;
|
|
638
804
|
|
|
639
805
|
-- Read + lock
|
|
640
|
-
|
|
641
|
-
EXECUTE format(
|
|
642
|
-
'SELECT to_jsonb(t.*) FROM %I t WHERE t.id = $1 AND valid_to IS NULL FOR UPDATE',
|
|
643
|
-
v_coll.table_name
|
|
644
|
-
) INTO v_row USING v_doc_id;
|
|
645
|
-
ELSE
|
|
646
|
-
EXECUTE format(
|
|
647
|
-
'SELECT to_jsonb(t.*) FROM %I t WHERE t.id = $1 FOR UPDATE',
|
|
648
|
-
v_coll.table_name
|
|
649
|
-
) INTO v_row USING v_doc_id;
|
|
650
|
-
END IF;
|
|
806
|
+
v_row := _delta_row_lock(v_coll_key, v_coll.table_name, v_coll.temporal, v_doc_id); -- read, and lock
|
|
651
807
|
|
|
652
808
|
IF v_row IS NULL THEN
|
|
653
809
|
RAISE EXCEPTION 'root row not found: %', v_def.root_collection USING ERRCODE = 'P0002';
|
|
@@ -660,34 +816,8 @@ BEGIN
|
|
|
660
816
|
-- is the path's (it renumbered the row).
|
|
661
817
|
v_new_row := v_row || v_blank || (v_op->'value') || jsonb_build_object('id', v_doc_id);
|
|
662
818
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
-- write's NOW()) gives way to the next, rather than closing at the same
|
|
666
|
-
-- instant and colliding with it on (id, valid_from).
|
|
667
|
-
EXECUTE format(
|
|
668
|
-
'DELETE FROM %I WHERE id = $1 AND valid_to IS NULL AND valid_from = $2',
|
|
669
|
-
v_coll.table_name
|
|
670
|
-
) USING v_doc_id, v_ts;
|
|
671
|
-
EXECUTE format(
|
|
672
|
-
'UPDATE %I SET valid_to = $2 WHERE id = $1 AND valid_to IS NULL',
|
|
673
|
-
v_coll.table_name
|
|
674
|
-
) USING v_doc_id, v_ts;
|
|
675
|
-
|
|
676
|
-
v_new_row := v_new_row || jsonb_build_object('valid_from', v_ts, 'valid_to', NULL);
|
|
677
|
-
|
|
678
|
-
EXECUTE format(
|
|
679
|
-
'INSERT INTO %I AS t SELECT * FROM jsonb_populate_record(null::%I, $1) RETURNING to_jsonb(t.*)',
|
|
680
|
-
v_coll.table_name, v_coll.table_name
|
|
681
|
-
) INTO v_new_row USING v_new_row;
|
|
682
|
-
|
|
683
|
-
v_new_row := _delta_strip_temporal(v_new_row);
|
|
684
|
-
ELSE
|
|
685
|
-
EXECUTE format('DELETE FROM %I WHERE id = $1', v_coll.table_name) USING v_doc_id;
|
|
686
|
-
EXECUTE format(
|
|
687
|
-
'INSERT INTO %I AS t SELECT * FROM jsonb_populate_record(null::%I, $1) RETURNING to_jsonb(t.*)',
|
|
688
|
-
v_coll.table_name, v_coll.table_name
|
|
689
|
-
) INTO v_new_row USING v_new_row;
|
|
690
|
-
END IF;
|
|
819
|
+
v_new_row := _delta_row_put(v_coll_key, v_coll.table_name, v_coll.temporal, v_doc_id, v_new_row, v_ts); -- the new version, or the row again
|
|
820
|
+
IF v_coll.temporal THEN v_new_row := _delta_strip_temporal(v_new_row); END IF;
|
|
691
821
|
|
|
692
822
|
-- The root written through its document stays in its scope, as a list's
|
|
693
823
|
-- rows do (below): one written out of the rest of its name's scope -- a
|
|
@@ -737,7 +867,7 @@ BEGIN
|
|
|
737
867
|
v_id_text := v_parts[2];
|
|
738
868
|
-- Auto-generate ID from sequence if path ends with '-'
|
|
739
869
|
IF v_id_text = '-' THEN
|
|
740
|
-
|
|
870
|
+
v_id := _delta_next_id(v_coll_key, v_coll.table_name);
|
|
741
871
|
ELSE
|
|
742
872
|
v_id := _delta_row_id(v_coll_key, v_id_text);
|
|
743
873
|
END IF;
|
|
@@ -767,7 +897,7 @@ BEGIN
|
|
|
767
897
|
v_exists := NOT _delta_serial_new(v_coll.table_name, v_id);
|
|
768
898
|
END IF;
|
|
769
899
|
IF NOT v_exists AND (v_id_text <> '-' OR v_coll.temporal) THEN
|
|
770
|
-
|
|
900
|
+
v_exists := _delta_row_there(v_coll_key, v_id);
|
|
771
901
|
END IF;
|
|
772
902
|
IF v_exists THEN
|
|
773
903
|
RAISE EXCEPTION 'id already taken: % -- add to % for a new id',
|
|
@@ -847,10 +977,7 @@ BEGIN
|
|
|
847
977
|
-- valid_from and valid_to both the write's NOW()) never was outside it,
|
|
848
978
|
-- so an add of the same id takes its place rather than colliding with it
|
|
849
979
|
-- on (id, valid_from). SQLite's insertCollectionRow keeps the same rule.
|
|
850
|
-
|
|
851
|
-
'DELETE FROM %I WHERE id = $1 AND valid_from = $2 AND valid_to = $2',
|
|
852
|
-
v_coll.table_name
|
|
853
|
-
) USING v_id, v_ts;
|
|
980
|
+
PERFORM _delta_row_clear_moment(v_coll_key, v_coll.table_name, v_id, v_ts);
|
|
854
981
|
v_new_row := v_new_row || jsonb_build_object('valid_from', v_ts, 'valid_to', NULL);
|
|
855
982
|
END IF;
|
|
856
983
|
|
|
@@ -858,7 +985,7 @@ BEGIN
|
|
|
858
985
|
-- value its column casts (a scope's '1' into an integer, a date into a
|
|
859
986
|
-- timestamptz) is told as a later open reads it, so the broadcast, the
|
|
860
987
|
-- ledger's entry and undo's guard agree with the table.
|
|
861
|
-
|
|
988
|
+
v_new_row := _delta_row_add(v_coll_key, v_coll.table_name, v_new_row);
|
|
862
989
|
|
|
863
990
|
-- An id the add named moves the serial past it, so a later /- does not meet it.
|
|
864
991
|
IF v_id_text <> '-' AND v_id > COALESCE((v_passed->>v_coll.table_name)::BIGINT, 0) THEN
|
|
@@ -955,17 +1082,7 @@ BEGIN
|
|
|
955
1082
|
USING ERRCODE = 'P0002';
|
|
956
1083
|
END IF;
|
|
957
1084
|
|
|
958
|
-
|
|
959
|
-
EXECUTE format(
|
|
960
|
-
'SELECT to_jsonb(t.*) FROM %I t WHERE t.id = $1 AND valid_to IS NULL FOR UPDATE',
|
|
961
|
-
v_coll.table_name
|
|
962
|
-
) INTO v_row USING v_id;
|
|
963
|
-
ELSE
|
|
964
|
-
EXECUTE format(
|
|
965
|
-
'SELECT to_jsonb(t.*) FROM %I t WHERE t.id = $1 FOR UPDATE',
|
|
966
|
-
v_coll.table_name
|
|
967
|
-
) INTO v_row USING v_id;
|
|
968
|
-
END IF;
|
|
1085
|
+
v_row := _delta_row_lock(v_coll_key, v_coll.table_name, v_coll.temporal, v_id); -- read, and lock
|
|
969
1086
|
|
|
970
1087
|
IF v_row IS NULL THEN
|
|
971
1088
|
RAISE EXCEPTION 'row not found: %/%', v_coll_key, v_id USING ERRCODE = 'P0002';
|
|
@@ -1000,35 +1117,7 @@ BEGIN
|
|
|
1000
1117
|
-- an add's is (it renumbered the row).
|
|
1001
1118
|
v_new_row := v_row || v_blank || (v_op->'value') || jsonb_build_object('id', v_id);
|
|
1002
1119
|
|
|
1003
|
-
|
|
1004
|
-
-- One version per write: a version this write made (its valid_from is the
|
|
1005
|
-
-- write's NOW()) gives way to the next, rather than closing at the same
|
|
1006
|
-
-- instant and colliding with it on (id, valid_from).
|
|
1007
|
-
EXECUTE format(
|
|
1008
|
-
'DELETE FROM %I WHERE id = $1 AND valid_to IS NULL AND valid_from = $2',
|
|
1009
|
-
v_coll.table_name
|
|
1010
|
-
) USING v_id, v_ts;
|
|
1011
|
-
EXECUTE format(
|
|
1012
|
-
'UPDATE %I SET valid_to = $2 WHERE id = $1 AND valid_to IS NULL',
|
|
1013
|
-
v_coll.table_name
|
|
1014
|
-
) USING v_id, v_ts;
|
|
1015
|
-
|
|
1016
|
-
v_new_row := v_new_row || jsonb_build_object('valid_from', v_ts, 'valid_to', NULL);
|
|
1017
|
-
|
|
1018
|
-
EXECUTE format(
|
|
1019
|
-
'INSERT INTO %I AS t SELECT * FROM jsonb_populate_record(null::%I, $1) RETURNING to_jsonb(t.*)',
|
|
1020
|
-
v_coll.table_name, v_coll.table_name
|
|
1021
|
-
) INTO v_new_row USING v_new_row;
|
|
1022
|
-
ELSE
|
|
1023
|
-
-- Non-temporal: UPDATE with merged row
|
|
1024
|
-
EXECUTE format(
|
|
1025
|
-
'DELETE FROM %I WHERE id = $1', v_coll.table_name
|
|
1026
|
-
) USING v_id;
|
|
1027
|
-
EXECUTE format(
|
|
1028
|
-
'INSERT INTO %I AS t SELECT * FROM jsonb_populate_record(null::%I, $1) RETURNING to_jsonb(t.*)',
|
|
1029
|
-
v_coll.table_name, v_coll.table_name
|
|
1030
|
-
) INTO v_new_row USING v_new_row;
|
|
1031
|
-
END IF;
|
|
1120
|
+
v_new_row := _delta_row_put(v_coll_key, v_coll.table_name, v_coll.temporal, v_id, v_new_row, v_ts); -- the new version, or the row again
|
|
1032
1121
|
|
|
1033
1122
|
-- A list's root row written through the list stays in its scope, as an
|
|
1034
1123
|
-- add through it is given its bindings: a field, a row or a merge that
|
|
@@ -29,6 +29,8 @@ CREATE TABLE IF NOT EXISTS _delta_ledger (
|
|
|
29
29
|
CREATE INDEX IF NOT EXISTS idx_delta_ledger_doc ON _delta_ledger (doc_name, version);
|
|
30
30
|
CREATE INDEX IF NOT EXISTS idx_delta_ledger_cursor ON _delta_ledger (cursor);
|
|
31
31
|
CREATE INDEX IF NOT EXISTS idx_delta_ledger_undoes ON _delta_ledger (undoes);
|
|
32
|
+
-- a cursor's entries newest first: where an undo or a redo looks for its tip (_delta_ledger_tip)
|
|
33
|
+
CREATE INDEX IF NOT EXISTS idx_delta_ledger_cursor_id ON _delta_ledger (cursor, id);
|
|
32
34
|
|
|
33
35
|
-- ---------------------------------------------------------------------------
|
|
34
36
|
-- _delta_row_at: the row a document holds at a row's path, split -- the twin
|
|
@@ -204,25 +206,44 @@ $$ LANGUAGE plpgsql;
|
|
|
204
206
|
-- what could be redone; an entry not undoable (a fact) starts no chain.
|
|
205
207
|
-- ---------------------------------------------------------------------------
|
|
206
208
|
|
|
209
|
+
-- _delta_ledger_tip: the entry an undo (p_back) or a redo of a cursor takes --
|
|
210
|
+
-- the newest tip of the cursor's chains that goes the way asked: back, a tip at
|
|
211
|
+
-- an even depth (a write, or a redo); forward, one at an odd depth (an undo that
|
|
212
|
+
-- changed something) newer than the cursor's last write. Found newest first,
|
|
213
|
+
-- among the cursor's own entries, so it costs what was written since the
|
|
214
|
+
-- answer, not the cursor's whole history (todo #28): every entry of a chain
|
|
215
|
+
-- carries its write's cursor, as delta_walk records it (an entry recorded by
|
|
216
|
+
-- hand as walking another cursor's chain is not one of this cursor's tips).
|
|
207
217
|
CREATE OR REPLACE FUNCTION _delta_ledger_tip(p_cursor TEXT, p_back BOOLEAN)
|
|
208
218
|
RETURNS _delta_ledger AS $$
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
219
|
+
DECLARE
|
|
220
|
+
v_entry _delta_ledger;
|
|
221
|
+
v_up _delta_ledger;
|
|
222
|
+
v_depth INT;
|
|
223
|
+
BEGIN
|
|
224
|
+
-- a step at a time, each the next older entry (LIMIT 1 on the cursor's index): it reads as far as it goes
|
|
225
|
+
SELECT * INTO v_entry FROM _delta_ledger WHERE cursor = p_cursor ORDER BY id DESC LIMIT 1;
|
|
226
|
+
WHILE v_entry.id IS NOT NULL LOOP
|
|
227
|
+
-- the cursor's last write, met newest first: a redo is of nothing older (a fresh write ends what could be redone)
|
|
228
|
+
IF NOT p_back AND v_entry.undoes IS NULL AND v_entry.undoable THEN RETURN NULL; END IF;
|
|
229
|
+
IF NOT EXISTS (SELECT 1 FROM _delta_ledger w WHERE w.undoes = v_entry.id) THEN -- a tip: nothing has walked it
|
|
230
|
+
-- its depth along its chain, up to the write that began it
|
|
231
|
+
v_depth := 0;
|
|
232
|
+
v_up := v_entry;
|
|
233
|
+
WHILE v_up.undoes IS NOT NULL LOOP
|
|
234
|
+
SELECT * INTO v_up FROM _delta_ledger WHERE id = v_up.undoes;
|
|
235
|
+
v_depth := v_depth + 1;
|
|
236
|
+
END LOOP;
|
|
237
|
+
IF v_up.cursor IS NOT DISTINCT FROM p_cursor AND v_up.undoable THEN -- a chain this cursor's undoable write began
|
|
238
|
+
IF p_back AND v_depth % 2 = 0 THEN RETURN v_entry; END IF;
|
|
239
|
+
IF NOT p_back AND v_depth % 2 = 1 AND v_entry.undoable THEN RETURN v_entry; END IF; -- a walk recorded as changing nothing is never redone
|
|
240
|
+
END IF;
|
|
241
|
+
END IF;
|
|
242
|
+
SELECT * INTO v_entry FROM _delta_ledger WHERE cursor = p_cursor AND id < v_entry.id ORDER BY id DESC LIMIT 1;
|
|
243
|
+
END LOOP;
|
|
244
|
+
RETURN NULL;
|
|
245
|
+
END;
|
|
246
|
+
$$ LANGUAGE plpgsql STABLE;
|
|
226
247
|
|
|
227
248
|
-- JSON values the same, with JSON null and a missing value the same.
|
|
228
249
|
CREATE OR REPLACE FUNCTION _delta_same(a JSONB, b JSONB)
|
|
@@ -233,7 +254,7 @@ $$;
|
|
|
233
254
|
-- A field of a collection's row the same in two values: a timestamptz by the
|
|
234
255
|
-- instant it names, whatever zone its text was written in (a ledger's entry
|
|
235
256
|
-- keeps the session's TimeZone of its write), anything else as _delta_same.
|
|
236
|
-
CREATE OR REPLACE FUNCTION
|
|
257
|
+
CREATE OR REPLACE FUNCTION _delta_same_framework(p_coll TEXT, p_field TEXT, a JSONB, b JSONB)
|
|
237
258
|
RETURNS BOOLEAN LANGUAGE sql STABLE AS $$
|
|
238
259
|
SELECT CASE
|
|
239
260
|
WHEN jsonb_typeof(a) = 'string' AND jsonb_typeof(b) = 'string'
|
|
@@ -243,6 +264,31 @@ RETURNS BOOLEAN LANGUAGE sql STABLE AS $$
|
|
|
243
264
|
END;
|
|
244
265
|
$$;
|
|
245
266
|
|
|
267
|
+
-- the framework's, until dzql's compiler (`compileLedger`) writes each
|
|
268
|
+
-- collection's time fields in
|
|
269
|
+
CREATE OR REPLACE FUNCTION _delta_same(p_coll TEXT, p_field TEXT, a JSONB, b JSONB)
|
|
270
|
+
RETURNS BOOLEAN LANGUAGE sql STABLE AS $$ SELECT _delta_same_framework(p_coll, p_field, a, b); $$;
|
|
271
|
+
|
|
272
|
+
-- _delta_row_standing: row `p_id` of a collection as it stands, without its
|
|
273
|
+
-- validity, as {row} (row null: not there); NULL for a collection there is not.
|
|
274
|
+
-- What a walk guards a row beyond its document by.
|
|
275
|
+
CREATE OR REPLACE FUNCTION _delta_row_standing_framework(p_coll TEXT, p_id BIGINT)
|
|
276
|
+
RETURNS JSONB AS $$
|
|
277
|
+
DECLARE
|
|
278
|
+
v_coll RECORD;
|
|
279
|
+
v_row JSONB;
|
|
280
|
+
BEGIN
|
|
281
|
+
SELECT * INTO v_coll FROM _delta_collections WHERE collection_key = p_coll;
|
|
282
|
+
IF NOT FOUND THEN RETURN NULL; END IF;
|
|
283
|
+
EXECUTE format('SELECT _delta_strip_temporal(to_jsonb(t.*)) FROM %I t WHERE t.id = $1', _delta_source_view(v_coll.table_name, v_coll.temporal))
|
|
284
|
+
INTO v_row USING p_id;
|
|
285
|
+
RETURN jsonb_build_object('row', v_row);
|
|
286
|
+
END;
|
|
287
|
+
$$ LANGUAGE plpgsql STABLE;
|
|
288
|
+
|
|
289
|
+
CREATE OR REPLACE FUNCTION _delta_row_standing(p_coll TEXT, p_id BIGINT)
|
|
290
|
+
RETURNS JSONB LANGUAGE sql STABLE AS $$ SELECT _delta_row_standing_framework(p_coll, p_id); $$;
|
|
291
|
+
|
|
246
292
|
-- _delta_walk_key: a row's key in a walk -- the keyOf of planWalk in
|
|
247
293
|
-- src/server/ledger.ts. A single document's root is one row at two paths,
|
|
248
294
|
-- /root (a replace of it) and /root/id (its add, its remove): both are keyed
|
|
@@ -306,11 +352,9 @@ BEGIN
|
|
|
306
352
|
FOR v_parts IN SELECT DISTINCT _delta_split_path(o->>'path') FROM jsonb_array_elements(p_entry.ops || p_entry.inverse) AS o LOOP
|
|
307
353
|
CONTINUE WHEN array_length(v_parts, 1) <> 2 OR v_parts[2] !~ '^[0-9]+$' OR v_parts[2]::numeric > 9007199254740991
|
|
308
354
|
OR v_parts[1] = v_def.root_collection OR v_parts[1] = ANY(COALESCE(v_def.include, ARRAY[]::text[]));
|
|
309
|
-
|
|
310
|
-
CONTINUE WHEN
|
|
311
|
-
|
|
312
|
-
INTO v_row USING v_parts[2]::bigint;
|
|
313
|
-
v_beyond := v_beyond || jsonb_build_array(jsonb_build_object('coll', v_parts[1], 'id', v_parts[2]::bigint, 'row', v_row));
|
|
355
|
+
v_row := _delta_row_standing(v_parts[1], v_parts[2]::bigint);
|
|
356
|
+
CONTINUE WHEN v_row IS NULL; -- a collection there is not
|
|
357
|
+
v_beyond := v_beyond || jsonb_build_array(jsonb_build_object('coll', v_parts[1], 'id', v_parts[2]::bigint, 'row', v_row->'row'));
|
|
314
358
|
END LOOP;
|
|
315
359
|
v_doc := _delta_beside(v_doc, v_def, v_beyond);
|
|
316
360
|
|
package/src/sql/auth-jwt.sql
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
-- auth-jwt — reference users schema + login / register stored functions.
|
|
3
3
|
-- =========================================================================
|
|
4
4
|
--
|
|
5
|
-
-- Apply this alongside the
|
|
5
|
+
-- Apply this alongside the dzql/auth-jwt module. Consumers who
|
|
6
6
|
-- want a different identity schema (sessions, OAuth, custom tables) can
|
|
7
7
|
-- substitute their own `login` / `register` functions and pass the SQL
|
|
8
8
|
-- strings via `jwtAuth({ loginSql, registerSql })`.
|