dzql 0.5.33 → 0.6.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/.env.sample +28 -0
- package/compose.yml +28 -0
- package/dist/client/index.ts +1 -0
- package/dist/client/stores/useMyProfileStore.ts +114 -0
- package/dist/client/stores/useOrgDashboardStore.ts +131 -0
- package/dist/client/stores/useVenueDetailStore.ts +117 -0
- package/dist/client/ws.ts +716 -0
- package/dist/db/migrations/000_core.sql +92 -0
- package/dist/db/migrations/20251229T212912022Z_schema.sql +3020 -0
- package/dist/db/migrations/20251229T212912022Z_subscribables.sql +371 -0
- package/dist/runtime/manifest.json +1562 -0
- package/docs/README.md +293 -36
- package/docs/feature-requests/applyPatch-bug-report.md +85 -0
- package/docs/feature-requests/connection-ready-profile.md +57 -0
- package/docs/feature-requests/hidden-bug-report.md +111 -0
- package/docs/feature-requests/hidden-fields-subscribables.md +34 -0
- package/docs/feature-requests/subscribable-param-key-bug.md +38 -0
- package/docs/feature-requests/todo.md +146 -0
- package/docs/for_ai.md +641 -0
- package/docs/project-setup.md +432 -0
- package/examples/blog.ts +50 -0
- package/examples/invalid.ts +18 -0
- package/examples/venues.js +485 -0
- package/package.json +23 -60
- package/src/cli/codegen/client.ts +99 -0
- package/src/cli/codegen/manifest.ts +95 -0
- package/src/cli/codegen/pinia.ts +174 -0
- package/src/cli/codegen/realtime.ts +58 -0
- package/src/cli/codegen/sql.ts +698 -0
- package/src/cli/codegen/subscribable_sql.ts +547 -0
- package/src/cli/codegen/subscribable_store.ts +184 -0
- package/src/cli/codegen/types.ts +142 -0
- package/src/cli/compiler/analyzer.ts +52 -0
- package/src/cli/compiler/graph_rules.ts +251 -0
- package/src/cli/compiler/ir.ts +233 -0
- package/src/cli/compiler/loader.ts +132 -0
- package/src/cli/compiler/permissions.ts +227 -0
- package/src/cli/index.ts +164 -0
- package/src/client/index.ts +1 -0
- package/src/client/ws.ts +286 -0
- package/src/create/.env.example +8 -0
- package/src/create/README.md +101 -0
- package/src/create/compose.yml +14 -0
- package/src/create/domain.ts +153 -0
- package/src/create/package.json +24 -0
- package/src/create/server.ts +18 -0
- package/src/create/setup.sh +11 -0
- package/src/create/tsconfig.json +15 -0
- package/src/runtime/auth.ts +39 -0
- package/src/runtime/db.ts +33 -0
- package/src/runtime/errors.ts +51 -0
- package/src/runtime/index.ts +98 -0
- package/src/runtime/js_functions.ts +63 -0
- package/src/runtime/manifest_loader.ts +29 -0
- package/src/runtime/namespace.ts +483 -0
- package/src/runtime/server.ts +87 -0
- package/src/runtime/ws.ts +197 -0
- package/src/shared/ir.ts +197 -0
- package/tests/client.test.ts +38 -0
- package/tests/codegen.test.ts +71 -0
- package/tests/compiler.test.ts +45 -0
- package/tests/graph_rules.test.ts +173 -0
- package/tests/integration/db.test.ts +174 -0
- package/tests/integration/e2e.test.ts +65 -0
- package/tests/integration/features.test.ts +922 -0
- package/tests/integration/full_stack.test.ts +262 -0
- package/tests/integration/setup.ts +45 -0
- package/tests/ir.test.ts +32 -0
- package/tests/namespace.test.ts +395 -0
- package/tests/permissions.test.ts +55 -0
- package/tests/pinia.test.ts +48 -0
- package/tests/realtime.test.ts +22 -0
- package/tests/runtime.test.ts +80 -0
- package/tests/subscribable_gen.test.ts +72 -0
- package/tests/subscribable_reactivity.test.ts +258 -0
- package/tests/venues_gen.test.ts +25 -0
- package/tsconfig.json +20 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/README.md +0 -90
- package/bin/cli.js +0 -727
- package/docs/compiler/ADVANCED_FILTERS.md +0 -183
- package/docs/compiler/CODING_STANDARDS.md +0 -415
- package/docs/compiler/COMPARISON.md +0 -673
- package/docs/compiler/QUICKSTART.md +0 -326
- package/docs/compiler/README.md +0 -134
- package/docs/examples/README.md +0 -38
- package/docs/examples/blog.sql +0 -160
- package/docs/examples/venue-detail-simple.sql +0 -8
- package/docs/examples/venue-detail-subscribable.sql +0 -45
- package/docs/for-ai/claude-guide.md +0 -1210
- package/docs/getting-started/quickstart.md +0 -125
- package/docs/getting-started/subscriptions-quick-start.md +0 -203
- package/docs/getting-started/tutorial.md +0 -1104
- package/docs/guides/atomic-updates.md +0 -299
- package/docs/guides/client-stores.md +0 -730
- package/docs/guides/composite-primary-keys.md +0 -158
- package/docs/guides/custom-functions.md +0 -362
- package/docs/guides/drop-semantics.md +0 -554
- package/docs/guides/field-defaults.md +0 -240
- package/docs/guides/interpreter-vs-compiler.md +0 -237
- package/docs/guides/many-to-many.md +0 -929
- package/docs/guides/subscriptions.md +0 -537
- package/docs/reference/api.md +0 -1373
- package/docs/reference/client.md +0 -224
- package/src/client/stores/index.js +0 -8
- package/src/client/stores/useAppStore.js +0 -285
- package/src/client/stores/useWsStore.js +0 -289
- package/src/client/ws.js +0 -762
- package/src/compiler/cli/compile-example.js +0 -33
- package/src/compiler/cli/compile-subscribable.js +0 -43
- package/src/compiler/cli/debug-compile.js +0 -44
- package/src/compiler/cli/debug-parse.js +0 -26
- package/src/compiler/cli/debug-path-parser.js +0 -18
- package/src/compiler/cli/debug-subscribable-parser.js +0 -21
- package/src/compiler/cli/index.js +0 -174
- package/src/compiler/codegen/auth-codegen.js +0 -153
- package/src/compiler/codegen/drop-semantics-codegen.js +0 -553
- package/src/compiler/codegen/graph-rules-codegen.js +0 -450
- package/src/compiler/codegen/notification-codegen.js +0 -232
- package/src/compiler/codegen/operation-codegen.js +0 -1382
- package/src/compiler/codegen/permission-codegen.js +0 -318
- package/src/compiler/codegen/subscribable-codegen.js +0 -827
- package/src/compiler/compiler.js +0 -371
- package/src/compiler/index.js +0 -11
- package/src/compiler/parser/entity-parser.js +0 -440
- package/src/compiler/parser/path-parser.js +0 -290
- package/src/compiler/parser/subscribable-parser.js +0 -244
- package/src/database/dzql-core.sql +0 -161
- package/src/database/migrations/001_schema.sql +0 -60
- package/src/database/migrations/002_functions.sql +0 -890
- package/src/database/migrations/003_operations.sql +0 -1135
- package/src/database/migrations/004_search.sql +0 -581
- package/src/database/migrations/005_entities.sql +0 -730
- package/src/database/migrations/006_auth.sql +0 -94
- package/src/database/migrations/007_events.sql +0 -133
- package/src/database/migrations/008_hello.sql +0 -18
- package/src/database/migrations/008a_meta.sql +0 -172
- package/src/database/migrations/009_subscriptions.sql +0 -240
- package/src/database/migrations/010_atomic_updates.sql +0 -157
- package/src/database/migrations/010_fix_m2m_events.sql +0 -94
- package/src/index.js +0 -40
- package/src/server/api.js +0 -9
- package/src/server/db.js +0 -442
- package/src/server/index.js +0 -317
- package/src/server/logger.js +0 -259
- package/src/server/mcp.js +0 -594
- package/src/server/meta-route.js +0 -251
- package/src/server/namespace.js +0 -292
- package/src/server/subscriptions.js +0 -351
- package/src/server/ws.js +0 -573
|
@@ -1,673 +0,0 @@
|
|
|
1
|
-
# Runtime vs Compiled DZQL - Side-by-Side Comparison
|
|
2
|
-
|
|
3
|
-
This document shows the architectural differences between the current runtime-interpreted DZQL and the new compiled approach.
|
|
4
|
-
|
|
5
|
-
## Entity Registration
|
|
6
|
-
|
|
7
|
-
### Runtime (Current)
|
|
8
|
-
|
|
9
|
-
**Entity definition is stored as JSON in database:**
|
|
10
|
-
|
|
11
|
-
```sql
|
|
12
|
-
-- Stored in dzql.entities table
|
|
13
|
-
INSERT INTO dzql.entities (
|
|
14
|
-
table_name,
|
|
15
|
-
label_field,
|
|
16
|
-
searchable_fields,
|
|
17
|
-
fk_includes,
|
|
18
|
-
permission_paths,
|
|
19
|
-
notification_paths,
|
|
20
|
-
graph_rules
|
|
21
|
-
) VALUES (
|
|
22
|
-
'venues',
|
|
23
|
-
'name',
|
|
24
|
-
ARRAY['name', 'address'],
|
|
25
|
-
'{"org": "organisations"}'::jsonb,
|
|
26
|
-
'{"update": ["@org_id->acts_for[org_id=$]{active}.user_id"]}'::jsonb,
|
|
27
|
-
'{"ownership": ["@org_id->acts_for[org_id=$]{active}.user_id"]}'::jsonb,
|
|
28
|
-
'{"on_create": {...}}'::jsonb
|
|
29
|
-
);
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
**Every operation parses this JSON at runtime:**
|
|
33
|
-
|
|
34
|
-
```sql
|
|
35
|
-
SELECT dzql.generic_exec('save', 'venues', '{"name": "MSG"}'::jsonb, 42);
|
|
36
|
-
↓
|
|
37
|
-
dzql.generic_save('venues', ...)
|
|
38
|
-
↓
|
|
39
|
-
1. Fetch entity config from dzql.entities (JSONB parse)
|
|
40
|
-
2. Parse permission_paths JSON
|
|
41
|
-
3. Evaluate paths dynamically
|
|
42
|
-
4. Parse graph_rules JSON
|
|
43
|
-
5. Execute rules via dynamic SQL
|
|
44
|
-
6. Parse notification_paths JSON
|
|
45
|
-
7. Resolve paths dynamically
|
|
46
|
-
8. Finally execute the save
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
**Cost per request:**
|
|
50
|
-
- 1 table lookup (`dzql.entities`)
|
|
51
|
-
- 3-5 JSONB parse operations
|
|
52
|
-
- Dynamic SQL generation
|
|
53
|
-
- Path resolution interpretation
|
|
54
|
-
- Graph rule interpretation
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
### Compiled (New)
|
|
59
|
-
|
|
60
|
-
**Entity definition compiles to native functions:**
|
|
61
|
-
|
|
62
|
-
```sql
|
|
63
|
-
-- Permission check (pre-compiled)
|
|
64
|
-
CREATE OR REPLACE FUNCTION can_update_venues(p_user_id INT, p_record JSONB)
|
|
65
|
-
RETURNS BOOLEAN AS $$
|
|
66
|
-
BEGIN
|
|
67
|
-
RETURN EXISTS (
|
|
68
|
-
SELECT 1 FROM acts_for
|
|
69
|
-
WHERE org_id = (p_record->>'org_id')::int
|
|
70
|
-
AND user_id = p_user_id
|
|
71
|
-
AND valid_to IS NULL -- {active}
|
|
72
|
-
);
|
|
73
|
-
END;
|
|
74
|
-
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
|
|
75
|
-
|
|
76
|
-
-- Save operation (pre-compiled)
|
|
77
|
-
CREATE OR REPLACE FUNCTION save_venues(p_data JSONB, p_user_id INT)
|
|
78
|
-
RETURNS JSONB AS $$
|
|
79
|
-
DECLARE
|
|
80
|
-
v_result venues%ROWTYPE;
|
|
81
|
-
BEGIN
|
|
82
|
-
-- Check permission (direct function call)
|
|
83
|
-
IF NOT can_update_venues(p_user_id, p_data) THEN
|
|
84
|
-
RAISE EXCEPTION 'Permission denied';
|
|
85
|
-
END IF;
|
|
86
|
-
|
|
87
|
-
-- Perform save
|
|
88
|
-
INSERT INTO venues (...) VALUES (...)
|
|
89
|
-
ON CONFLICT (id) DO UPDATE SET ...
|
|
90
|
-
RETURNING * INTO v_result;
|
|
91
|
-
|
|
92
|
-
-- Execute graph rules (pre-compiled)
|
|
93
|
-
-- ...
|
|
94
|
-
|
|
95
|
-
-- Resolve notifications (pre-compiled)
|
|
96
|
-
-- ...
|
|
97
|
-
|
|
98
|
-
RETURN to_jsonb(v_result);
|
|
99
|
-
END;
|
|
100
|
-
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
**Direct function call:**
|
|
104
|
-
|
|
105
|
-
```sql
|
|
106
|
-
SELECT save_venues('{"name": "MSG"}'::jsonb, 42);
|
|
107
|
-
↓
|
|
108
|
-
save_venues(...)
|
|
109
|
-
↓
|
|
110
|
-
1. can_update_venues() - optimized SQL
|
|
111
|
-
2. INSERT/UPDATE - direct
|
|
112
|
-
3. Graph rules - pre-compiled
|
|
113
|
-
4. Notifications - pre-compiled
|
|
114
|
-
5. Done
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
**Cost per request:**
|
|
118
|
-
- 0 JSON config lookups
|
|
119
|
-
- 0 dynamic SQL generation
|
|
120
|
-
- 0 path interpretation
|
|
121
|
-
- Just the actual work
|
|
122
|
-
|
|
123
|
-
---
|
|
124
|
-
|
|
125
|
-
## Performance Comparison
|
|
126
|
-
|
|
127
|
-
### GET Operation
|
|
128
|
-
|
|
129
|
-
#### Runtime (Current)
|
|
130
|
-
|
|
131
|
-
```sql
|
|
132
|
-
SELECT dzql.generic_exec('get', 'venues', '{"id": 1}'::jsonb, 42);
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
**Execution path:**
|
|
136
|
-
1. Call `dzql.generic_exec()`
|
|
137
|
-
2. Route to `dzql.generic_get()`
|
|
138
|
-
3. `SELECT * FROM dzql.entities WHERE table_name = 'venues'` ← **lookup**
|
|
139
|
-
4. Parse `fk_includes` JSONB ← **parse**
|
|
140
|
-
5. Parse `permission_paths` JSONB ← **parse**
|
|
141
|
-
6. Build permission query dynamically ← **generate SQL**
|
|
142
|
-
7. Check permission via `dzql.check_permission()` ← **interpret path**
|
|
143
|
-
8. Fetch record: `SELECT * FROM venues WHERE id = 1`
|
|
144
|
-
9. Loop through `fk_includes` JSONB ← **interpret**
|
|
145
|
-
10. For each FK, call `dzql.resolve_direct_fk()` ← **dynamic SQL**
|
|
146
|
-
11. Return result
|
|
147
|
-
|
|
148
|
-
**Total:** ~11 steps, 3 JSONB parses, 2 dynamic SQL generations
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
#### Compiled (New)
|
|
153
|
-
|
|
154
|
-
```sql
|
|
155
|
-
SELECT get_venues(1, 42);
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
**Execution path:**
|
|
159
|
-
1. Call `get_venues()`
|
|
160
|
-
2. Check permission via `can_view_venues()` ← **pre-compiled**
|
|
161
|
-
3. Fetch record: `SELECT * FROM venues WHERE id = 1`
|
|
162
|
-
4. Expand FK: `SELECT * FROM organisations WHERE id = v.org_id` ← **pre-compiled**
|
|
163
|
-
5. Expand children: `SELECT * FROM sites WHERE venue_id = 1` ← **pre-compiled**
|
|
164
|
-
6. Return result
|
|
165
|
-
|
|
166
|
-
**Total:** ~6 steps, 0 JSONB parses, 0 dynamic SQL
|
|
167
|
-
|
|
168
|
-
**Speedup:** ~2-3x faster, more consistent query plans
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
## Permission Checking
|
|
173
|
-
|
|
174
|
-
### Runtime (Current)
|
|
175
|
-
|
|
176
|
-
```sql
|
|
177
|
-
-- Permission path stored as JSON string
|
|
178
|
-
permission_paths: {
|
|
179
|
-
"update": ["@org_id->acts_for[org_id=$]{active}.user_id"]
|
|
180
|
-
}
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
**At runtime:**
|
|
184
|
-
|
|
185
|
-
```plpgsql
|
|
186
|
-
-- In dzql.check_permission()
|
|
187
|
-
l_paths := entity_config.permission_paths->operation; -- JSONB parse
|
|
188
|
-
|
|
189
|
-
FOR l_path IN SELECT * FROM jsonb_array_elements_text(l_paths)
|
|
190
|
-
LOOP
|
|
191
|
-
-- Parse path string: "@org_id->acts_for[org_id=$]{active}.user_id"
|
|
192
|
-
l_result := dzql.resolve_path(table_name, record, l_path);
|
|
193
|
-
-- Dynamically build and execute SQL:
|
|
194
|
-
-- SELECT user_id FROM acts_for WHERE org_id = ... AND valid_to IS NULL
|
|
195
|
-
|
|
196
|
-
IF l_result @> to_jsonb(user_id) THEN
|
|
197
|
-
RETURN true;
|
|
198
|
-
END IF;
|
|
199
|
-
END LOOP;
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
**Problems:**
|
|
203
|
-
- Path parsed every time
|
|
204
|
-
- SQL built dynamically
|
|
205
|
-
- Hard to optimize
|
|
206
|
-
- No query plan caching
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
### Compiled (New)
|
|
211
|
-
|
|
212
|
-
```sql
|
|
213
|
-
CREATE OR REPLACE FUNCTION can_update_venues(p_user_id INT, p_record JSONB)
|
|
214
|
-
RETURNS BOOLEAN AS $$
|
|
215
|
-
BEGIN
|
|
216
|
-
RETURN EXISTS (
|
|
217
|
-
SELECT 1 FROM acts_for
|
|
218
|
-
WHERE org_id = (p_record->>'org_id')::int
|
|
219
|
-
AND user_id = p_user_id
|
|
220
|
-
AND valid_to IS NULL
|
|
221
|
-
);
|
|
222
|
-
END;
|
|
223
|
-
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
**Benefits:**
|
|
227
|
-
- Path pre-compiled to SQL
|
|
228
|
-
- PostgreSQL can plan and cache it
|
|
229
|
-
- Can use indexes effectively
|
|
230
|
-
- Easy to debug with EXPLAIN
|
|
231
|
-
- Consistent performance
|
|
232
|
-
|
|
233
|
-
---
|
|
234
|
-
|
|
235
|
-
## Notification Resolution
|
|
236
|
-
|
|
237
|
-
### Runtime (Current)
|
|
238
|
-
|
|
239
|
-
```sql
|
|
240
|
-
-- Notification path stored as JSON
|
|
241
|
-
notification_paths: {
|
|
242
|
-
"ownership": ["@org_id->acts_for[org_id=$]{active}.user_id"]
|
|
243
|
-
}
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
**At runtime:**
|
|
247
|
-
|
|
248
|
-
```plpgsql
|
|
249
|
-
-- In dzql.resolve_notification_paths()
|
|
250
|
-
l_paths := entity_config.notification_paths; -- JSONB parse
|
|
251
|
-
|
|
252
|
-
FOR l_path_name, l_path IN SELECT * FROM jsonb_each(l_paths)
|
|
253
|
-
LOOP
|
|
254
|
-
FOR l_path_str IN SELECT * FROM jsonb_array_elements_text(l_path)
|
|
255
|
-
LOOP
|
|
256
|
-
-- Parse and resolve each path dynamically
|
|
257
|
-
l_users := l_users || dzql.resolve_notification_path(table_name, record, l_path_str);
|
|
258
|
-
END LOOP;
|
|
259
|
-
END LOOP;
|
|
260
|
-
|
|
261
|
-
-- Insert into events table with dynamic user list
|
|
262
|
-
INSERT INTO dzql.events (notify_users) VALUES (l_users);
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
**Problems:**
|
|
266
|
-
- Multiple JSONB parses
|
|
267
|
-
- Multiple path resolutions
|
|
268
|
-
- Hard to optimize
|
|
269
|
-
- Can't use indexes well
|
|
270
|
-
|
|
271
|
-
---
|
|
272
|
-
|
|
273
|
-
### Compiled (New)
|
|
274
|
-
|
|
275
|
-
```sql
|
|
276
|
-
CREATE OR REPLACE FUNCTION resolve_notification_paths_venues(p_record JSONB)
|
|
277
|
-
RETURNS INT[] AS $$
|
|
278
|
-
BEGIN
|
|
279
|
-
RETURN ARRAY(
|
|
280
|
-
SELECT DISTINCT user_id
|
|
281
|
-
FROM acts_for
|
|
282
|
-
WHERE org_id = (p_record->>'org_id')::int
|
|
283
|
-
AND valid_to IS NULL
|
|
284
|
-
);
|
|
285
|
-
END;
|
|
286
|
-
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
**Called in save_venues:**
|
|
290
|
-
|
|
291
|
-
```plpgsql
|
|
292
|
-
-- Resolve notifications (direct function call)
|
|
293
|
-
v_notify_users := resolve_notification_paths_venues(to_jsonb(v_result));
|
|
294
|
-
|
|
295
|
-
-- Insert event
|
|
296
|
-
INSERT INTO dzql.events (notify_users) VALUES (v_notify_users);
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
**Benefits:**
|
|
300
|
-
- Single function call
|
|
301
|
-
- Pre-optimized SQL
|
|
302
|
-
- Can use indexes
|
|
303
|
-
- Consistent query plan
|
|
304
|
-
|
|
305
|
-
---
|
|
306
|
-
|
|
307
|
-
## Graph Rules Execution
|
|
308
|
-
|
|
309
|
-
### Runtime (Current)
|
|
310
|
-
|
|
311
|
-
```sql
|
|
312
|
-
-- Graph rules stored as nested JSON
|
|
313
|
-
graph_rules: {
|
|
314
|
-
"on_create": {
|
|
315
|
-
"establish_ownership": {
|
|
316
|
-
"actions": [
|
|
317
|
-
{
|
|
318
|
-
"type": "create",
|
|
319
|
-
"entity": "acts_for",
|
|
320
|
-
"data": {
|
|
321
|
-
"user_id": "@user_id",
|
|
322
|
-
"org_id": "@id"
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
]
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
**At runtime:**
|
|
332
|
-
|
|
333
|
-
```plpgsql
|
|
334
|
-
-- In dzql.execute_graph_rules()
|
|
335
|
-
l_rules := entity_config.graph_rules->trigger; -- JSONB parse
|
|
336
|
-
|
|
337
|
-
FOR l_rule_name, l_rule_config IN SELECT * FROM jsonb_each(l_rules)
|
|
338
|
-
LOOP
|
|
339
|
-
FOR l_action IN SELECT * FROM jsonb_array_elements(l_rule_config->'actions')
|
|
340
|
-
LOOP
|
|
341
|
-
l_action_type := l_action->>'type'; -- JSONB parse
|
|
342
|
-
|
|
343
|
-
CASE l_action_type
|
|
344
|
-
WHEN 'create' THEN
|
|
345
|
-
l_entity := l_action->>'entity'; -- JSONB parse
|
|
346
|
-
l_data := dzql.resolve_graph_data(l_action->'data', record, user_id); -- JSONB parse + resolution
|
|
347
|
-
PERFORM dzql.execute_graph_insert(l_entity, l_data, user_id); -- Dynamic INSERT
|
|
348
|
-
WHEN 'update' THEN
|
|
349
|
-
-- Similar dynamic execution
|
|
350
|
-
END CASE;
|
|
351
|
-
END LOOP;
|
|
352
|
-
END LOOP;
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
**Problems:**
|
|
356
|
-
- Multiple levels of JSONB parsing
|
|
357
|
-
- Dynamic SQL for every action
|
|
358
|
-
- Hard to debug
|
|
359
|
-
- Performance unpredictable
|
|
360
|
-
|
|
361
|
-
---
|
|
362
|
-
|
|
363
|
-
### Compiled (New - Planned)
|
|
364
|
-
|
|
365
|
-
```sql
|
|
366
|
-
-- Generated for graph rule
|
|
367
|
-
CREATE OR REPLACE FUNCTION graph_venues_on_create(
|
|
368
|
-
p_record JSONB,
|
|
369
|
-
p_user_id INT
|
|
370
|
-
) RETURNS VOID AS $$
|
|
371
|
-
BEGIN
|
|
372
|
-
-- establish_ownership action (pre-compiled)
|
|
373
|
-
INSERT INTO acts_for (user_id, org_id, valid_from)
|
|
374
|
-
VALUES (
|
|
375
|
-
p_user_id,
|
|
376
|
-
(p_record->>'id')::int,
|
|
377
|
-
CURRENT_DATE
|
|
378
|
-
);
|
|
379
|
-
END;
|
|
380
|
-
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
**Called in save_venues:**
|
|
384
|
-
|
|
385
|
-
```plpgsql
|
|
386
|
-
-- Execute graph rules (direct function call)
|
|
387
|
-
IF v_is_insert THEN
|
|
388
|
-
PERFORM graph_venues_on_create(to_jsonb(v_result), p_user_id);
|
|
389
|
-
END IF;
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
**Benefits:**
|
|
393
|
-
- No JSONB parsing
|
|
394
|
-
- Direct SQL execution
|
|
395
|
-
- Atomic with transaction
|
|
396
|
-
- Easy to debug and optimize
|
|
397
|
-
|
|
398
|
-
---
|
|
399
|
-
|
|
400
|
-
## FK Expansion
|
|
401
|
-
|
|
402
|
-
### Runtime (Current)
|
|
403
|
-
|
|
404
|
-
```sql
|
|
405
|
-
-- FK includes stored as JSON
|
|
406
|
-
fk_includes: {
|
|
407
|
-
"org": "organisations",
|
|
408
|
-
"sites": "sites"
|
|
409
|
-
}
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
**At runtime:**
|
|
413
|
-
|
|
414
|
-
```plpgsql
|
|
415
|
-
-- In dzql.generic_get()
|
|
416
|
-
l_fk_includes := entity_config.fk_includes; -- JSONB parse
|
|
417
|
-
|
|
418
|
-
FOR l_key, l_value IN SELECT * FROM jsonb_each_text(l_fk_includes)
|
|
419
|
-
LOOP
|
|
420
|
-
-- Determine if direct or reverse FK
|
|
421
|
-
IF l_key = l_value THEN
|
|
422
|
-
-- Reverse FK (child array)
|
|
423
|
-
l_fk_result := dzql.resolve_reverse_fk(record, l_key, l_value || '.' || entity || '_id');
|
|
424
|
-
ELSE
|
|
425
|
-
-- Direct FK
|
|
426
|
-
l_fk_result := dzql.resolve_direct_fk(record, l_key, l_value);
|
|
427
|
-
END IF;
|
|
428
|
-
|
|
429
|
-
-- Merge into result
|
|
430
|
-
l_result := l_result || jsonb_build_object(l_key, l_fk_result);
|
|
431
|
-
END LOOP;
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
**Problems:**
|
|
435
|
-
- JSONB parsing
|
|
436
|
-
- Loop overhead
|
|
437
|
-
- Dynamic SQL in resolve functions
|
|
438
|
-
- Multiple separate queries
|
|
439
|
-
|
|
440
|
-
---
|
|
441
|
-
|
|
442
|
-
### Compiled (New)
|
|
443
|
-
|
|
444
|
-
```sql
|
|
445
|
-
-- In get_venues()
|
|
446
|
-
BEGIN
|
|
447
|
-
-- Fetch record
|
|
448
|
-
SELECT * INTO v_record FROM venues WHERE id = p_id;
|
|
449
|
-
v_result := to_jsonb(v_record);
|
|
450
|
-
|
|
451
|
-
-- Expand org (direct FK - pre-compiled)
|
|
452
|
-
IF v_record.org_id IS NOT NULL THEN
|
|
453
|
-
v_result := v_result || jsonb_build_object(
|
|
454
|
-
'org',
|
|
455
|
-
(SELECT to_jsonb(t.*) FROM organisations t WHERE t.id = v_record.org_id)
|
|
456
|
-
);
|
|
457
|
-
END IF;
|
|
458
|
-
|
|
459
|
-
-- Expand sites (reverse FK - pre-compiled)
|
|
460
|
-
v_result := v_result || jsonb_build_object(
|
|
461
|
-
'sites',
|
|
462
|
-
(SELECT COALESCE(jsonb_agg(to_jsonb(t.*)), '[]'::jsonb)
|
|
463
|
-
FROM sites t
|
|
464
|
-
WHERE t.venue_id = v_record.id)
|
|
465
|
-
);
|
|
466
|
-
|
|
467
|
-
RETURN v_result;
|
|
468
|
-
END;
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
**Benefits:**
|
|
472
|
-
- No loops or JSONB parsing
|
|
473
|
-
- Direct SQL queries
|
|
474
|
-
- PostgreSQL can optimize joins
|
|
475
|
-
- Predictable performance
|
|
476
|
-
|
|
477
|
-
---
|
|
478
|
-
|
|
479
|
-
## Debugging & Observability
|
|
480
|
-
|
|
481
|
-
### Runtime (Current)
|
|
482
|
-
|
|
483
|
-
**Query Analysis:**
|
|
484
|
-
```sql
|
|
485
|
-
EXPLAIN ANALYZE SELECT dzql.generic_exec('save', 'venues', '{...}'::jsonb, 42);
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
**Result:**
|
|
489
|
-
```
|
|
490
|
-
Function Scan on generic_exec
|
|
491
|
-
-> Function Scan on generic_save
|
|
492
|
-
-> Seq Scan on dzql.entities (filter: table_name = 'venues')
|
|
493
|
-
-> Result (JSONB operations)
|
|
494
|
-
-> Function Scan on check_permission
|
|
495
|
-
-> Result (dynamic SQL from path resolution)
|
|
496
|
-
-> Result (dynamic INSERT/UPDATE)
|
|
497
|
-
-> Function Scan on execute_graph_rules
|
|
498
|
-
-> Result (JSONB parsing and dynamic SQL)
|
|
499
|
-
-> Function Scan on resolve_notification_paths
|
|
500
|
-
-> Result (dynamic SQL from path resolution)
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
**Problems:**
|
|
504
|
-
- Hard to understand
|
|
505
|
-
- Generic query plans
|
|
506
|
-
- Can't see actual operations
|
|
507
|
-
- Hard to optimize
|
|
508
|
-
|
|
509
|
-
---
|
|
510
|
-
|
|
511
|
-
### Compiled (New)
|
|
512
|
-
|
|
513
|
-
**Query Analysis:**
|
|
514
|
-
```sql
|
|
515
|
-
EXPLAIN ANALYZE SELECT save_venues('{...}'::jsonb, 42);
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
**Result:**
|
|
519
|
-
```
|
|
520
|
-
Function Scan on save_venues
|
|
521
|
-
-> Function Scan on can_update_venues
|
|
522
|
-
-> Index Scan on acts_for (org_id, user_id)
|
|
523
|
-
-> Index Scan on venues (id)
|
|
524
|
-
-> Result (INSERT/UPDATE)
|
|
525
|
-
-> Function Scan on graph_venues_on_create
|
|
526
|
-
-> Insert on acts_for
|
|
527
|
-
-> Function Scan on resolve_notification_paths_venues
|
|
528
|
-
-> Index Scan on acts_for (org_id)
|
|
529
|
-
```
|
|
530
|
-
|
|
531
|
-
**Benefits:**
|
|
532
|
-
- Clear execution path
|
|
533
|
-
- Specific query plans
|
|
534
|
-
- Can see index usage
|
|
535
|
-
- Easy to optimize
|
|
536
|
-
|
|
537
|
-
**Standard PostgreSQL Tools Work:**
|
|
538
|
-
```sql
|
|
539
|
-
-- View slow queries
|
|
540
|
-
SELECT * FROM pg_stat_statements
|
|
541
|
-
WHERE query LIKE '%save_venues%'
|
|
542
|
-
ORDER BY total_time DESC;
|
|
543
|
-
|
|
544
|
-
-- Analyze specific function
|
|
545
|
-
EXPLAIN (ANALYZE, BUFFERS) SELECT save_venues('{...}'::jsonb, 42);
|
|
546
|
-
|
|
547
|
-
-- View function source
|
|
548
|
-
\sf save_venues
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
---
|
|
552
|
-
|
|
553
|
-
## Development Workflow
|
|
554
|
-
|
|
555
|
-
### Runtime (Current)
|
|
556
|
-
|
|
557
|
-
1. **Define entity:**
|
|
558
|
-
```javascript
|
|
559
|
-
registerEntity('venues', {
|
|
560
|
-
labelField: 'name',
|
|
561
|
-
permissions: { ... },
|
|
562
|
-
graphRules: { ... }
|
|
563
|
-
});
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
2. **Server restarts** → Entity registered in database
|
|
567
|
-
|
|
568
|
-
3. **Immediate use:**
|
|
569
|
-
```javascript
|
|
570
|
-
await db.api.save.venues({ name: 'MSG' }, userId);
|
|
571
|
-
```
|
|
572
|
-
|
|
573
|
-
4. **Debug issues:**
|
|
574
|
-
- Enable query logging
|
|
575
|
-
- Add console.log in generic_exec
|
|
576
|
-
- Inspect dzql.entities table
|
|
577
|
-
- Hope the path resolution works
|
|
578
|
-
|
|
579
|
-
**Pros:**
|
|
580
|
-
- ✅ Fast iteration
|
|
581
|
-
- ✅ No build step
|
|
582
|
-
- ✅ Dynamic changes
|
|
583
|
-
|
|
584
|
-
**Cons:**
|
|
585
|
-
- ❌ Hard to debug
|
|
586
|
-
- ❌ Performance unpredictable
|
|
587
|
-
- ❌ No type safety
|
|
588
|
-
- ❌ Opaque behavior
|
|
589
|
-
|
|
590
|
-
---
|
|
591
|
-
|
|
592
|
-
### Compiled (New)
|
|
593
|
-
|
|
594
|
-
1. **Define entity:**
|
|
595
|
-
```sql
|
|
596
|
-
select dzql.register_entity('venues', ...);
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
2. **Compile:**
|
|
600
|
-
```bash
|
|
601
|
-
bun dzql-compile entities/venues.sql -o compiled/
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
3. **Review generated SQL:**
|
|
605
|
-
```bash
|
|
606
|
-
cat compiled/venues.sql
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
4. **Deploy:**
|
|
610
|
-
```bash
|
|
611
|
-
psql < compiled/venues.sql
|
|
612
|
-
```
|
|
613
|
-
|
|
614
|
-
5. **Use:**
|
|
615
|
-
```sql
|
|
616
|
-
SELECT save_venues('{"name": "MSG"}'::jsonb, 42);
|
|
617
|
-
```
|
|
618
|
-
|
|
619
|
-
6. **Debug:**
|
|
620
|
-
```sql
|
|
621
|
-
EXPLAIN ANALYZE SELECT save_venues(...);
|
|
622
|
-
\sf save_venues
|
|
623
|
-
```
|
|
624
|
-
|
|
625
|
-
**Pros:**
|
|
626
|
-
- ✅ Clear generated SQL
|
|
627
|
-
- ✅ Standard debugging tools
|
|
628
|
-
- ✅ Predictable performance
|
|
629
|
-
- ✅ Git-trackable output
|
|
630
|
-
|
|
631
|
-
**Cons:**
|
|
632
|
-
- ❌ Build step required
|
|
633
|
-
- ❌ Slower iteration
|
|
634
|
-
- ❌ Less dynamic
|
|
635
|
-
|
|
636
|
-
---
|
|
637
|
-
|
|
638
|
-
## Summary Table
|
|
639
|
-
|
|
640
|
-
| Aspect | Runtime | Compiled | Winner |
|
|
641
|
-
|--------|---------|----------|--------|
|
|
642
|
-
| **Performance** | Variable | Consistent | ✅ Compiled |
|
|
643
|
-
| **Query Planning** | Generic | Specific | ✅ Compiled |
|
|
644
|
-
| **Debugging** | Opaque | Transparent | ✅ Compiled |
|
|
645
|
-
| **Development Speed** | Fast | Slower | ✅ Runtime |
|
|
646
|
-
| **Type Safety** | None | Possible | ✅ Compiled |
|
|
647
|
-
| **Optimization** | Hard | Easy | ✅ Compiled |
|
|
648
|
-
| **Flexibility** | High | Medium | ✅ Runtime |
|
|
649
|
-
| **Learning Curve** | Moderate | Low | ✅ Compiled |
|
|
650
|
-
| **Production Readiness** | Good | Better | ✅ Compiled |
|
|
651
|
-
|
|
652
|
-
## Conclusion
|
|
653
|
-
|
|
654
|
-
The compiled approach trades **development flexibility** for **production performance and clarity**.
|
|
655
|
-
|
|
656
|
-
### When to use Runtime (Current):
|
|
657
|
-
- ✅ Rapid prototyping
|
|
658
|
-
- ✅ Frequent schema changes
|
|
659
|
-
- ✅ Learning DZQL
|
|
660
|
-
- ✅ Small applications
|
|
661
|
-
|
|
662
|
-
### When to use Compiled (New):
|
|
663
|
-
- ✅ Production applications
|
|
664
|
-
- ✅ Performance-critical systems
|
|
665
|
-
- ✅ Large teams (reviewable SQL)
|
|
666
|
-
- ✅ Complex permission rules
|
|
667
|
-
- ✅ Need to debug query plans
|
|
668
|
-
|
|
669
|
-
### Ideal Approach:
|
|
670
|
-
**Use both!**
|
|
671
|
-
- Development: Runtime for fast iteration
|
|
672
|
-
- Staging: Compile and test
|
|
673
|
-
- Production: Deploy compiled functions
|