dzql 0.7.0 → 0.9.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.
@@ -0,0 +1,142 @@
1
+ -- =========================================================================
2
+ -- Delta-doc: catch-up, time-travel, pruning, snapshots
3
+ -- =========================================================================
4
+
5
+ -- ---------------------------------------------------------------------------
6
+ -- delta_fetch_ops: catch-up — get ops since a given version
7
+ -- ---------------------------------------------------------------------------
8
+
9
+ CREATE OR REPLACE FUNCTION delta_fetch_ops(p_doc_name TEXT, p_since BIGINT)
10
+ RETURNS TABLE(version BIGINT, ops JSONB) AS $$
11
+ SELECT l.version, l.ops
12
+ FROM _delta_ops_log l
13
+ WHERE l.doc_name = p_doc_name AND l.version > p_since
14
+ ORDER BY l.version
15
+ LIMIT 1000;
16
+ $$ LANGUAGE sql STABLE;
17
+
18
+ -- _delta_fetch_log: the listener's catch-up -- delta_fetch_ops' rows, each with
19
+ -- the write it carries for the custom documents (`applied`, 001a).
20
+ CREATE OR REPLACE FUNCTION _delta_fetch_log(p_doc_name TEXT, p_since BIGINT)
21
+ RETURNS TABLE(version BIGINT, ops JSONB, applied JSONB) AS $$
22
+ SELECT l.version, l.ops, l.applied
23
+ FROM _delta_ops_log l
24
+ WHERE l.doc_name = p_doc_name AND l.version > p_since
25
+ ORDER BY l.version
26
+ LIMIT 1000;
27
+ $$ LANGUAGE sql STABLE;
28
+
29
+ -- ---------------------------------------------------------------------------
30
+ -- delta_open_at: time-travel — load a doc as it existed at a point in time
31
+ -- ---------------------------------------------------------------------------
32
+
33
+ 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;
95
+
96
+ -- ---------------------------------------------------------------------------
97
+ -- delta_prune_ops: clean up old log entries -- and the names nobody holds
98
+ -- open (todo #73): the rows of listeners that are gone, and the version of a
99
+ -- name no listener holds, with nothing left of it in the log or the ledger.
100
+ -- Opened again, it starts a fresh copy at 0. The table lock keeps an open from
101
+ -- holding a name between the question and the delete. Answers the log
102
+ -- entries cleared.
103
+ -- ---------------------------------------------------------------------------
104
+
105
+ CREATE OR REPLACE FUNCTION delta_prune_ops(p_keep INTERVAL DEFAULT '1 hour')
106
+ RETURNS BIGINT AS $$
107
+ DECLARE v_count BIGINT;
108
+ BEGIN
109
+ DELETE FROM _delta_ops_log WHERE created_at < NOW() - p_keep;
110
+ GET DIAGNOSTICS v_count = ROW_COUNT;
111
+ LOCK TABLE _delta_held IN SHARE ROW EXCLUSIVE MODE;
112
+ PERFORM _delta_clear_dead_holders();
113
+ DELETE FROM _delta_versions v
114
+ WHERE NOT EXISTS (SELECT 1 FROM _delta_held h WHERE h.doc_name = v.doc_name)
115
+ AND NOT EXISTS (SELECT 1 FROM _delta_ops_log l WHERE l.doc_name = v.doc_name)
116
+ AND NOT EXISTS (SELECT 1 FROM _delta_ledger g WHERE g.doc_name = v.doc_name);
117
+ RETURN v_count;
118
+ END;
119
+ $$ LANGUAGE plpgsql;
120
+
121
+ -- ---------------------------------------------------------------------------
122
+ -- Snapshots
123
+ -- ---------------------------------------------------------------------------
124
+
125
+ CREATE TABLE IF NOT EXISTS _delta_snapshots (
126
+ name TEXT PRIMARY KEY,
127
+ created_at TIMESTAMPTZ NOT NULL
128
+ );
129
+
130
+ CREATE OR REPLACE FUNCTION delta_snapshot(p_name TEXT, p_at TIMESTAMPTZ DEFAULT NOW())
131
+ RETURNS TIMESTAMPTZ AS $$
132
+ BEGIN
133
+ INSERT INTO _delta_snapshots (name, created_at) VALUES (p_name, p_at)
134
+ ON CONFLICT (name) DO UPDATE SET created_at = p_at;
135
+ RETURN p_at;
136
+ END;
137
+ $$ LANGUAGE plpgsql;
138
+
139
+ CREATE OR REPLACE FUNCTION delta_resolve_snapshot(p_name TEXT)
140
+ RETURNS TIMESTAMPTZ AS $$
141
+ SELECT created_at FROM _delta_snapshots WHERE name = p_name;
142
+ $$ LANGUAGE sql STABLE;
@@ -0,0 +1,41 @@
1
+ -- =========================================================================
2
+ -- Delta-doc: identity-wrapped call variants (1-RTT hot path)
3
+ -- =========================================================================
4
+ --
5
+ -- Every authenticated doc operation is logically two things:
6
+ -- 1. Scope the session: `SELECT set_config('app.user_id', <id>, true)`
7
+ -- 2. Call the op: `SELECT delta_apply(...)` / `delta_open(...)`
8
+ --
9
+ -- The naive client does this as two separate round-trips inside an explicit
10
+ -- BEGIN / COMMIT — four RTTs per op. These wrappers collapse it to one:
11
+ -- the SELECT's implicit transaction scopes `set_config(..., true)`, which
12
+ -- RLS policies read via `current_setting('app.user_id', true)`.
13
+ --
14
+ -- SELECT delta_apply_as('42', 'todos:42', '[{...}]'::jsonb);
15
+ --
16
+ -- No explicit transaction, no client-side set_config, no client checkout.
17
+ -- =========================================================================
18
+
19
+ CREATE OR REPLACE FUNCTION delta_open_as(p_user_id TEXT, p_doc_name TEXT)
20
+ RETURNS jsonb LANGUAGE plpgsql AS $$
21
+ BEGIN
22
+ PERFORM set_config('app.user_id', p_user_id, true);
23
+ RETURN delta_open(p_doc_name);
24
+ END
25
+ $$;
26
+
27
+ CREATE OR REPLACE FUNCTION delta_apply_as(p_user_id TEXT, p_doc_name TEXT, p_ops jsonb)
28
+ RETURNS jsonb LANGUAGE plpgsql AS $$
29
+ BEGIN
30
+ PERFORM set_config('app.user_id', p_user_id, true);
31
+ RETURN delta_apply(p_doc_name, p_ops);
32
+ END
33
+ $$;
34
+
35
+ CREATE OR REPLACE FUNCTION delta_open_at_as(p_user_id TEXT, p_doc_name TEXT, p_at TIMESTAMPTZ)
36
+ RETURNS jsonb LANGUAGE plpgsql AS $$
37
+ BEGIN
38
+ PERFORM set_config('app.user_id', p_user_id, true);
39
+ RETURN delta_open_at(p_doc_name, p_at);
40
+ END
41
+ $$;