dzql 0.10.0 → 0.12.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 +6 -6
- package/package.json +4 -3
- package/src/cli/index.ts +2 -2
- package/src/compile/cascades.ts +6 -6
- package/src/compile/columns.ts +7 -8
- package/src/compile/holders.ts +5 -11
- package/src/compile/holds.ts +4 -5
- package/src/compile/ledger.ts +7 -8
- package/src/compile/lookups.ts +118 -0
- package/src/compile/opens.ts +6 -0
- package/src/compile/resolve.ts +4 -4
- package/src/compile/root.ts +4 -4
- package/src/compile/rows.ts +27 -18
- package/src/compile/scope.ts +5 -4
- package/src/compile/told.ts +5 -4
- package/src/postgres/auth-link.ts +53 -0
- package/src/postgres/cli.ts +44 -4
- package/src/postgres/codegen.ts +2 -1
- package/src/postgres/listener.ts +50 -35
- package/src/postgres/pglite.ts +4 -1
- package/src/sql/001a-delta-meta.sql +52 -19
- package/src/sql/001b-delta-scope.sql +18 -477
- package/src/sql/001c-delta-read.sql +14 -291
- package/src/sql/001d-delta-write.sql +64 -379
- package/src/sql/001e-delta-ops.sql +4 -62
- package/src/sql/001g-delta-ledger.sql +6 -29
|
@@ -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
|
|
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
|
-
|
|
79
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
287
|
-
--
|
|
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
|
|
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
|
-
|
|
389
|
-
|
|
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
|
|
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).
|
|
456
|
-
--
|
|
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
|
|
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
|
-
-- (
|
|
476
|
-
-- (
|
|
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
|
|
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:
|
|
486
|
-
--
|
|
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
|
|
499
|
-
RETURNS JSONB AS $$
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
--
|
|
549
|
-
|
|
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
|
-
--
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
753
|
-
|
|
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
|
|
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
|