dzql 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -13
- package/package.json +2 -2
- package/src/compile/cascades.ts +151 -0
- package/src/compile/columns.ts +143 -0
- package/src/compile/holders.ts +66 -0
- package/src/compile/holds.ts +90 -0
- package/src/compile/ledger.ts +72 -0
- package/src/compile/lookups.ts +118 -0
- package/src/compile/opens.ts +69 -23
- package/src/compile/resolve.ts +75 -0
- package/src/compile/root.ts +61 -0
- package/src/compile/rows.ts +123 -0
- package/src/compile/scope.ts +88 -0
- package/src/compile/told.ts +95 -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 +13 -2
- package/src/postgres/index.ts +2 -2
- package/src/postgres/listener.ts +50 -35
- package/src/postgres/pglite.ts +5 -2
- package/src/sql/001a-delta-meta.sql +55 -15
- package/src/sql/001b-delta-scope.sql +19 -451
- package/src/sql/001c-delta-read.sql +18 -296
- package/src/sql/001d-delta-write.sql +104 -330
- package/src/sql/001e-delta-ops.sql +4 -62
- package/src/sql/001g-delta-ledger.sql +51 -30
- package/src/sql/auth-jwt.sql +1 -1
|
@@ -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,85 +49,10 @@ END $$;
|
|
|
75
49
|
-- ---------------------------------------------------------------------------
|
|
76
50
|
|
|
77
51
|
DROP FUNCTION IF EXISTS _delta_cascade_remove(TEXT, BIGINT, TEXT[]);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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;
|
|
52
|
+
-- generateSql writes one for each collection (compileCascades); a collection
|
|
53
|
+
-- there is not has nothing to take
|
|
54
|
+
CREATE OR REPLACE FUNCTION _delta_cascade_remove(p_collection_key TEXT, p_id BIGINT)
|
|
55
|
+
RETURNS JSONB LANGUAGE sql AS $$ SELECT '[]'::jsonb; $$;
|
|
157
56
|
|
|
158
57
|
-- ---------------------------------------------------------------------------
|
|
159
58
|
-- _delta_cascade_rows: the rows _delta_cascade_remove would take, read before
|
|
@@ -162,52 +61,8 @@ $$ LANGUAGE plpgsql;
|
|
|
162
61
|
-- ---------------------------------------------------------------------------
|
|
163
62
|
|
|
164
63
|
DROP FUNCTION IF EXISTS _delta_cascade_rows(TEXT, BIGINT, TEXT[]);
|
|
165
|
-
CREATE OR REPLACE FUNCTION _delta_cascade_rows(
|
|
166
|
-
|
|
167
|
-
p_id BIGINT
|
|
168
|
-
) RETURNS JSONB AS $$
|
|
169
|
-
DECLARE
|
|
170
|
-
v_coll RECORD;
|
|
171
|
-
v_view TEXT;
|
|
172
|
-
v_row JSONB;
|
|
173
|
-
v_child RECORD;
|
|
174
|
-
v_ref RECORD;
|
|
175
|
-
v_id BIGINT;
|
|
176
|
-
v_rows JSONB := '[]'::jsonb;
|
|
177
|
-
BEGIN
|
|
178
|
-
SELECT * INTO v_coll FROM _delta_collections WHERE collection_key = p_collection_key;
|
|
179
|
-
IF NOT FOUND THEN RETURN v_rows; END IF;
|
|
180
|
-
v_view := _delta_source_view(v_coll.table_name, v_coll.temporal);
|
|
181
|
-
EXECUTE format('SELECT to_jsonb(t.*) FROM %I t WHERE t.id = $1', v_view) INTO v_row USING p_id;
|
|
182
|
-
IF v_row IS NULL THEN RETURN v_rows; END IF;
|
|
183
|
-
v_rows := jsonb_build_array(jsonb_build_object('coll', p_collection_key, 'id', p_id, 'row', _delta_strip_temporal(v_row)));
|
|
184
|
-
|
|
185
|
-
FOR v_child IN
|
|
186
|
-
SELECT * FROM _delta_collections
|
|
187
|
-
WHERE parent_collection = p_collection_key
|
|
188
|
-
ORDER BY collection_key COLLATE "C"
|
|
189
|
-
LOOP
|
|
190
|
-
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
|
|
191
|
-
LOOP
|
|
192
|
-
v_rows := v_rows || _delta_cascade_rows(v_child.collection_key, v_id);
|
|
193
|
-
END LOOP;
|
|
194
|
-
END LOOP;
|
|
195
|
-
|
|
196
|
-
FOR v_ref IN
|
|
197
|
-
SELECT c.collection_key, c.table_name, c.temporal, elem->>'fk' AS fk_column
|
|
198
|
-
FROM _delta_collections c, jsonb_array_elements(c.cascade_on) AS elem
|
|
199
|
-
WHERE elem->>'collection' = p_collection_key
|
|
200
|
-
ORDER BY c.collection_key COLLATE "C", elem->>'fk' COLLATE "C"
|
|
201
|
-
LOOP
|
|
202
|
-
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
|
|
203
|
-
LOOP
|
|
204
|
-
v_rows := v_rows || _delta_cascade_rows(v_ref.collection_key, v_id);
|
|
205
|
-
END LOOP;
|
|
206
|
-
END LOOP;
|
|
207
|
-
|
|
208
|
-
RETURN v_rows;
|
|
209
|
-
END;
|
|
210
|
-
$$ LANGUAGE plpgsql STABLE;
|
|
64
|
+
CREATE OR REPLACE FUNCTION _delta_cascade_rows(p_collection_key TEXT, p_id BIGINT)
|
|
65
|
+
RETURNS JSONB LANGUAGE sql STABLE AS $$ SELECT '[]'::jsonb; $$;
|
|
211
66
|
|
|
212
67
|
-- ---------------------------------------------------------------------------
|
|
213
68
|
-- _delta_hanging_rows: the rows that hang from a row -- its children, theirs,
|
|
@@ -217,26 +72,7 @@ $$ LANGUAGE plpgsql STABLE;
|
|
|
217
72
|
-- ---------------------------------------------------------------------------
|
|
218
73
|
|
|
219
74
|
CREATE OR REPLACE FUNCTION _delta_hanging_rows(p_collection_key TEXT, p_id BIGINT)
|
|
220
|
-
RETURNS JSONB AS $$
|
|
221
|
-
DECLARE
|
|
222
|
-
v_child RECORD;
|
|
223
|
-
v_part JSONB;
|
|
224
|
-
v_rows JSONB := '[]'::jsonb;
|
|
225
|
-
BEGIN
|
|
226
|
-
-- a child collection at a time, in one aggregate: each row by id, the rows under it after it
|
|
227
|
-
FOR v_child IN SELECT * FROM _delta_collections WHERE parent_collection = p_collection_key ORDER BY collection_key COLLATE "C" LOOP
|
|
228
|
-
EXECUTE format(
|
|
229
|
-
'SELECT COALESCE(jsonb_agg(e ORDER BY t.id, n), ''[]''::jsonb) FROM %I t,
|
|
230
|
-
jsonb_array_elements(jsonb_build_array(jsonb_build_object(''coll'', %L, ''id'', t.id, ''row'', _delta_strip_temporal(to_jsonb(t.*))))
|
|
231
|
-
|| _delta_hanging_rows(%L, t.id)) WITH ORDINALITY AS x(e, n)
|
|
232
|
-
WHERE t.%I = $1',
|
|
233
|
-
_delta_source_view(v_child.table_name, v_child.temporal), v_child.collection_key, v_child.collection_key, v_child.parent_fk
|
|
234
|
-
) INTO v_part USING p_id;
|
|
235
|
-
v_rows := v_rows || v_part;
|
|
236
|
-
END LOOP;
|
|
237
|
-
RETURN v_rows;
|
|
238
|
-
END;
|
|
239
|
-
$$ LANGUAGE plpgsql STABLE;
|
|
75
|
+
RETURNS JSONB LANGUAGE sql STABLE AS $$ SELECT '[]'::jsonb; $$;
|
|
240
76
|
|
|
241
77
|
-- ---------------------------------------------------------------------------
|
|
242
78
|
-- _delta_tell: tell every document that holds a row a write changed what
|
|
@@ -268,6 +104,14 @@ $$ LANGUAGE plpgsql STABLE;
|
|
|
268
104
|
-- two-argument form logs none.
|
|
269
105
|
-- ---------------------------------------------------------------------------
|
|
270
106
|
|
|
107
|
+
-- _delta_told: what one document is told of a write -- `p_rows`, the write's
|
|
108
|
+
-- touches with their holders, as _delta_tell gathers them -- by its name
|
|
109
|
+
-- `p_target`: an add, a replace of each field that changed, a remove; its
|
|
110
|
+
-- root's arrival or leaving as a replace. generateSql sends each document to
|
|
111
|
+
-- its own (compileTold).
|
|
112
|
+
CREATE OR REPLACE FUNCTION _delta_told(p_def _delta_docs, p_target TEXT, p_rows JSONB)
|
|
113
|
+
RETURNS JSONB LANGUAGE plpgsql STABLE AS $$ BEGIN RAISE EXCEPTION 'no doc def for: %', p_target USING ERRCODE = 'P0001'; END $$;
|
|
114
|
+
|
|
271
115
|
CREATE OR REPLACE FUNCTION _delta_tell(p_writer TEXT, p_touched JSONB, p_applied JSONB)
|
|
272
116
|
RETURNS BIGINT AS $$
|
|
273
117
|
DECLARE
|
|
@@ -277,12 +121,7 @@ DECLARE
|
|
|
277
121
|
v_targets TEXT[] := ARRAY[]::text[];
|
|
278
122
|
v_target TEXT;
|
|
279
123
|
v_def _delta_docs;
|
|
280
|
-
v_single BOOLEAN;
|
|
281
124
|
v_told JSONB;
|
|
282
|
-
v_was BOOLEAN;
|
|
283
|
-
v_is BOOLEAN;
|
|
284
|
-
v_root BOOLEAN;
|
|
285
|
-
v_path TEXT;
|
|
286
125
|
v_version BIGINT;
|
|
287
126
|
v_next JSONB := '{}'::jsonb; -- a row's holders, as its next touch found them
|
|
288
127
|
v_key TEXT;
|
|
@@ -309,29 +148,7 @@ BEGIN
|
|
|
309
148
|
LOOP
|
|
310
149
|
v_def := _delta_find_doc(v_target);
|
|
311
150
|
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;
|
|
151
|
+
v_told := _delta_told(v_def, v_target, v_rows);
|
|
335
152
|
IF v_target = p_writer THEN
|
|
336
153
|
v_version := _delta_bump_and_notify(p_writer, v_told, CASE WHEN p_applied IS DISTINCT FROM v_told THEN p_applied END);
|
|
337
154
|
ELSIF jsonb_array_length(v_told) > 0 THEN
|
|
@@ -353,19 +170,10 @@ RETURNS BIGINT LANGUAGE sql AS $$ SELECT _delta_tell(p_writer, p_touched, NULL);
|
|
|
353
170
|
-- not yet written opens empty). `there` in sqlite.ts.
|
|
354
171
|
-- ---------------------------------------------------------------------------
|
|
355
172
|
|
|
173
|
+
-- generateSql writes one for each collection (compileRows); a collection there
|
|
174
|
+
-- is not has no row there
|
|
356
175
|
CREATE OR REPLACE FUNCTION _delta_row_there(p_collection_key TEXT, p_id BIGINT)
|
|
357
|
-
RETURNS BOOLEAN AS $$
|
|
358
|
-
DECLARE
|
|
359
|
-
v_coll RECORD;
|
|
360
|
-
v_exists BOOLEAN;
|
|
361
|
-
BEGIN
|
|
362
|
-
SELECT * INTO v_coll FROM _delta_collections WHERE collection_key = p_collection_key;
|
|
363
|
-
IF NOT FOUND OR p_id IS NULL THEN RETURN FALSE; END IF;
|
|
364
|
-
EXECUTE format('SELECT EXISTS (SELECT 1 FROM %I WHERE id = $1)', _delta_source_view(v_coll.table_name, v_coll.temporal))
|
|
365
|
-
INTO v_exists USING p_id;
|
|
366
|
-
RETURN v_exists;
|
|
367
|
-
END;
|
|
368
|
-
$$ LANGUAGE plpgsql STABLE;
|
|
176
|
+
RETURNS BOOLEAN LANGUAGE sql STABLE AS $$ SELECT FALSE; $$;
|
|
369
177
|
|
|
370
178
|
-- ---------------------------------------------------------------------------
|
|
371
179
|
-- _delta_assert_parent_held: a parent key written (in `p_value`, over the row
|
|
@@ -391,6 +199,63 @@ BEGIN
|
|
|
391
199
|
END;
|
|
392
200
|
$$ LANGUAGE plpgsql STABLE;
|
|
393
201
|
|
|
202
|
+
-- _delta_root_there: is a single document's root row `p_id` there now, and
|
|
203
|
+
-- does it meet the whole of its name's scope (a column beside its id), as
|
|
204
|
+
-- delta_open reads it? Asked before each op of a write (delta_apply).
|
|
205
|
+
-- generateSql sends each document to its own (compileRootThere).
|
|
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)
|
|
207
|
+
RETURNS BOOLEAN LANGUAGE plpgsql STABLE AS $$ BEGIN RAISE EXCEPTION 'no doc def for: %', p_doc_name USING ERRCODE = 'P0001'; END $$;
|
|
208
|
+
|
|
209
|
+
-- _delta_write_columns: the columns of `p_coll` a document writes -- as the
|
|
210
|
+
-- process writing declares them (_delta_cols), those the document holds
|
|
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).
|
|
214
|
+
CREATE OR REPLACE FUNCTION _delta_write_columns(p_def _delta_docs, p_coll TEXT, p_columns_def JSONB)
|
|
215
|
+
RETURNS JSONB LANGUAGE plpgsql STABLE AS $$ BEGIN RAISE EXCEPTION 'no doc def for: %', p_def.prefix USING ERRCODE = 'P0001'; END $$;
|
|
216
|
+
|
|
217
|
+
-- ---------------------------------------------------------------------------
|
|
218
|
+
-- A row's SQL, as delta_apply runs it: generateSql writes each for each
|
|
219
|
+
-- collection (compileRows).
|
|
220
|
+
-- _delta_row_lock: a row as it stands, locked for the write (NULL: not there)
|
|
221
|
+
-- _delta_row_put: a replace's row stored -- a temporal one a new version (one
|
|
222
|
+
-- this write made gives way to it; the standing one closes at `p_ts`), else
|
|
223
|
+
-- the row again -- answered as stored
|
|
224
|
+
-- _delta_next_id: an add's id, minted
|
|
225
|
+
-- _delta_row_clear_moment: a temporal version this write made and took back
|
|
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)
|
|
229
|
+
-- (_delta_row_there, above, is an add's "is the id there now".)
|
|
230
|
+
-- ---------------------------------------------------------------------------
|
|
231
|
+
|
|
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 $$;
|
|
244
|
+
|
|
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)
|
|
250
|
+
RETURNS JSONB AS $$
|
|
251
|
+
DECLARE
|
|
252
|
+
v_row JSONB;
|
|
253
|
+
BEGIN
|
|
254
|
+
EXECUTE _delta_insert_sql(p_table, p_row) || ' RETURNING to_jsonb(t.*)' INTO v_row USING p_row;
|
|
255
|
+
RETURN v_row;
|
|
256
|
+
END;
|
|
257
|
+
$$ LANGUAGE plpgsql;
|
|
258
|
+
|
|
394
259
|
-- ---------------------------------------------------------------------------
|
|
395
260
|
-- _delta_whole: a replace of a row itself (`/<coll>/<id>`, a single
|
|
396
261
|
-- document's `/<root>`) is the row, as RFC 6902 says: each column
|
|
@@ -402,18 +267,6 @@ $$ LANGUAGE plpgsql STABLE;
|
|
|
402
267
|
-- and asked with the value's fields, before whether the row is there (todo
|
|
403
268
|
-- #3; replaceRow in sqlite.ts).
|
|
404
269
|
-- ---------------------------------------------------------------------------
|
|
405
|
-
|
|
406
|
-
-- _delta_held_columns: the columns of `p_coll` (`p_columns`, its columns_def)
|
|
407
|
-
-- a document holds -- those it names (`held`, todo #70), else all. A write
|
|
408
|
-
-- through it names those alone (_delta_assert_fields: 400 for another, as for
|
|
409
|
-
-- a column there is not), and replacing a row whole sets those alone.
|
|
410
|
-
|
|
411
|
-
CREATE OR REPLACE FUNCTION _delta_held_columns(p_def _delta_docs, p_coll TEXT, p_columns JSONB)
|
|
412
|
-
RETURNS JSONB AS $$
|
|
413
|
-
SELECT CASE WHEN NOT COALESCE(p_def.held, '{}'::jsonb) ? p_coll THEN p_columns
|
|
414
|
-
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
|
-
$$ LANGUAGE sql STABLE;
|
|
416
|
-
|
|
417
270
|
CREATE OR REPLACE FUNCTION _delta_whole(p_columns JSONB, p_value JSONB)
|
|
418
271
|
RETURNS JSONB AS $$
|
|
419
272
|
DECLARE
|
|
@@ -456,11 +309,11 @@ DECLARE
|
|
|
456
309
|
v_scope JSONB;
|
|
457
310
|
v_is_list BOOLEAN;
|
|
458
311
|
v_doc_id BIGINT;
|
|
459
|
-
v_root_coll
|
|
312
|
+
v_root_coll _delta_collections;
|
|
460
313
|
v_op JSONB;
|
|
461
314
|
v_parts TEXT[];
|
|
462
315
|
v_coll_key TEXT;
|
|
463
|
-
v_coll
|
|
316
|
+
v_coll _delta_collections;
|
|
464
317
|
v_view TEXT;
|
|
465
318
|
v_root_view TEXT;
|
|
466
319
|
v_opened BOOLEAN; -- a single document's root was there as the write began
|
|
@@ -472,7 +325,7 @@ DECLARE
|
|
|
472
325
|
v_row JSONB;
|
|
473
326
|
v_new_row JSONB;
|
|
474
327
|
v_blank JSONB; -- a replace of the row whole: each column as an add leaves it (_delta_whole); of a field, nothing
|
|
475
|
-
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
|
|
476
329
|
v_exists BOOLEAN;
|
|
477
330
|
v_missing TEXT;
|
|
478
331
|
v_ts TIMESTAMPTZ := NOW();
|
|
@@ -515,8 +368,7 @@ BEGIN
|
|
|
515
368
|
v_doc_id := (v_scope->'values'->>'id')::BIGINT;
|
|
516
369
|
END IF;
|
|
517
370
|
|
|
518
|
-
|
|
519
|
-
WHERE collection_key = v_def.root_collection;
|
|
371
|
+
v_root_coll := _delta_collection(v_def.root_collection);
|
|
520
372
|
v_root_view := _delta_source_view(v_root_coll.table_name, v_root_coll.temporal);
|
|
521
373
|
|
|
522
374
|
-- an implied document's first write makes its root row (todo #34), told as a
|
|
@@ -524,11 +376,11 @@ BEGIN
|
|
|
524
376
|
-- documents, which showed it already (ensureImpliedRoot in sqlite.ts); an
|
|
525
377
|
-- empty write makes nothing
|
|
526
378
|
IF NOT v_is_list AND COALESCE(v_def.implied, FALSE) AND jsonb_array_length(p_ops) > 0 THEN
|
|
527
|
-
|
|
379
|
+
v_exists := _delta_row_there(v_def.root_collection, v_doc_id);
|
|
528
380
|
PERFORM _delta_make_implied(v_def, v_doc_id, p_ops, v_ts);
|
|
529
381
|
IF NOT v_exists THEN
|
|
530
382
|
v_passed := jsonb_build_object(v_root_coll.table_name, v_doc_id);
|
|
531
|
-
|
|
383
|
+
v_row := _delta_row_read(v_def.root_collection, v_root_view, v_doc_id);
|
|
532
384
|
IF v_row IS NOT NULL THEN
|
|
533
385
|
v_touched := jsonb_build_array(jsonb_build_object('coll', v_def.root_collection, 'id', v_doc_id, 'after', v_row, 'carried', TRUE,
|
|
534
386
|
'before', COALESCE((SELECT jsonb_agg(n) FROM unnest(_delta_holders(v_def.root_collection, v_row, p_doc_name)) AS n
|
|
@@ -541,7 +393,7 @@ BEGIN
|
|
|
541
393
|
-- An empty write is asked what each op is (below): a single document whose
|
|
542
394
|
-- root is not there is not found, as on SQLite -- unless implied, or a walk.
|
|
543
395
|
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
|
-
|
|
396
|
+
v_exists := _delta_root_there(v_def, p_doc_name, v_scope, v_root_view, v_doc_id);
|
|
545
397
|
IF NOT v_exists THEN
|
|
546
398
|
RAISE EXCEPTION 'document not found: % (its root, %, is not there)',
|
|
547
399
|
p_doc_name, _delta_build_path(v_def.root_collection, v_doc_id::text)
|
|
@@ -565,7 +417,7 @@ BEGIN
|
|
|
565
417
|
-- (_delta_walk_plan), as SQLite's and ./json's are.
|
|
566
418
|
IF NOT v_is_list AND NOT COALESCE(p_walk, FALSE) THEN -- a walk not said (null) is none
|
|
567
419
|
-- there, and meeting the whole of its name's scope (a column beside its id), as delta_open reads it
|
|
568
|
-
|
|
420
|
+
v_exists := _delta_root_there(v_def, p_doc_name, v_scope, v_root_view, v_doc_id);
|
|
569
421
|
v_opened := COALESCE(v_opened, v_exists OR COALESCE(v_def.implied, FALSE));
|
|
570
422
|
v_back := FALSE;
|
|
571
423
|
IF NOT v_exists AND v_opened AND v_op->>'op' = 'add' THEN
|
|
@@ -583,9 +435,8 @@ BEGIN
|
|
|
583
435
|
v_parts := _delta_split_path(v_op->>'path');
|
|
584
436
|
v_coll_key := v_parts[1];
|
|
585
437
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
IF NOT FOUND THEN
|
|
438
|
+
v_coll := _delta_collection(v_coll_key);
|
|
439
|
+
IF v_coll.collection_key IS NULL THEN
|
|
589
440
|
RAISE EXCEPTION 'unknown collection: %', v_coll_key USING ERRCODE = '22023';
|
|
590
441
|
END IF;
|
|
591
442
|
-- a walk puts back what the ledger recorded, whole
|
|
@@ -594,7 +445,7 @@ BEGIN
|
|
|
594
445
|
THEN COALESCE((SELECT jsonb_object_agg(c, '{"nullable": true}'::jsonb) FROM unnest(_delta_stored_columns(v_coll.table_name)) AS c
|
|
595
446
|
WHERE c NOT IN ('id', 'valid_from', 'valid_to') AND c IS DISTINCT FROM v_coll.parent_fk), '{}'::jsonb)
|
|
596
447
|
|| _delta_cols(v_coll_key, v_coll.columns_def)
|
|
597
|
-
ELSE
|
|
448
|
+
ELSE _delta_write_columns(v_def, v_coll_key, v_coll.columns_def) END;
|
|
598
449
|
|
|
599
450
|
-- Scope guard: an op may only target the doc's root or an included
|
|
600
451
|
-- collection. Defence-in-depth — a client that opened doc A must not write
|
|
@@ -637,17 +488,7 @@ BEGIN
|
|
|
637
488
|
v_blank := CASE WHEN array_length(v_parts, 1) = 1 THEN _delta_whole(v_cols, v_op->'value') ELSE '{}'::jsonb END;
|
|
638
489
|
|
|
639
490
|
-- 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;
|
|
491
|
+
v_row := _delta_row_lock(v_coll_key, v_coll.table_name, v_coll.temporal, v_doc_id); -- read, and lock
|
|
651
492
|
|
|
652
493
|
IF v_row IS NULL THEN
|
|
653
494
|
RAISE EXCEPTION 'root row not found: %', v_def.root_collection USING ERRCODE = 'P0002';
|
|
@@ -660,34 +501,8 @@ BEGIN
|
|
|
660
501
|
-- is the path's (it renumbered the row).
|
|
661
502
|
v_new_row := v_row || v_blank || (v_op->'value') || jsonb_build_object('id', v_doc_id);
|
|
662
503
|
|
|
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;
|
|
504
|
+
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
|
|
505
|
+
IF v_coll.temporal THEN v_new_row := _delta_strip_temporal(v_new_row); END IF;
|
|
691
506
|
|
|
692
507
|
-- The root written through its document stays in its scope, as a list's
|
|
693
508
|
-- rows do (below): one written out of the rest of its name's scope -- a
|
|
@@ -737,7 +552,7 @@ BEGIN
|
|
|
737
552
|
v_id_text := v_parts[2];
|
|
738
553
|
-- Auto-generate ID from sequence if path ends with '-'
|
|
739
554
|
IF v_id_text = '-' THEN
|
|
740
|
-
|
|
555
|
+
v_id := _delta_next_id(v_coll_key, v_coll.table_name);
|
|
741
556
|
ELSE
|
|
742
557
|
v_id := _delta_row_id(v_coll_key, v_id_text);
|
|
743
558
|
END IF;
|
|
@@ -767,7 +582,7 @@ BEGIN
|
|
|
767
582
|
v_exists := NOT _delta_serial_new(v_coll.table_name, v_id);
|
|
768
583
|
END IF;
|
|
769
584
|
IF NOT v_exists AND (v_id_text <> '-' OR v_coll.temporal) THEN
|
|
770
|
-
|
|
585
|
+
v_exists := _delta_row_there(v_coll_key, v_id);
|
|
771
586
|
END IF;
|
|
772
587
|
IF v_exists THEN
|
|
773
588
|
RAISE EXCEPTION 'id already taken: % -- add to % for a new id',
|
|
@@ -847,10 +662,7 @@ BEGIN
|
|
|
847
662
|
-- valid_from and valid_to both the write's NOW()) never was outside it,
|
|
848
663
|
-- so an add of the same id takes its place rather than colliding with it
|
|
849
664
|
-- 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;
|
|
665
|
+
PERFORM _delta_row_clear_moment(v_coll_key, v_coll.table_name, v_id, v_ts);
|
|
854
666
|
v_new_row := v_new_row || jsonb_build_object('valid_from', v_ts, 'valid_to', NULL);
|
|
855
667
|
END IF;
|
|
856
668
|
|
|
@@ -858,7 +670,7 @@ BEGIN
|
|
|
858
670
|
-- value its column casts (a scope's '1' into an integer, a date into a
|
|
859
671
|
-- timestamptz) is told as a later open reads it, so the broadcast, the
|
|
860
672
|
-- ledger's entry and undo's guard agree with the table.
|
|
861
|
-
|
|
673
|
+
v_new_row := _delta_row_add(v_coll_key, v_coll.table_name, v_new_row);
|
|
862
674
|
|
|
863
675
|
-- An id the add named moves the serial past it, so a later /- does not meet it.
|
|
864
676
|
IF v_id_text <> '-' AND v_id > COALESCE((v_passed->>v_coll.table_name)::BIGINT, 0) THEN
|
|
@@ -955,17 +767,7 @@ BEGIN
|
|
|
955
767
|
USING ERRCODE = 'P0002';
|
|
956
768
|
END IF;
|
|
957
769
|
|
|
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;
|
|
770
|
+
v_row := _delta_row_lock(v_coll_key, v_coll.table_name, v_coll.temporal, v_id); -- read, and lock
|
|
969
771
|
|
|
970
772
|
IF v_row IS NULL THEN
|
|
971
773
|
RAISE EXCEPTION 'row not found: %/%', v_coll_key, v_id USING ERRCODE = 'P0002';
|
|
@@ -1000,35 +802,7 @@ BEGIN
|
|
|
1000
802
|
-- an add's is (it renumbered the row).
|
|
1001
803
|
v_new_row := v_row || v_blank || (v_op->'value') || jsonb_build_object('id', v_id);
|
|
1002
804
|
|
|
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;
|
|
805
|
+
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
806
|
|
|
1033
807
|
-- A list's root row written through the list stays in its scope, as an
|
|
1034
808
|
-- add through it is given its bindings: a field, a row or a merge that
|