dzql 0.5.33 → 0.6.1
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 +309 -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 +653 -0
- package/docs/project-setup.md +456 -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 +166 -0
- package/src/client/index.ts +1 -0
- package/src/client/ws.ts +286 -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,183 +0,0 @@
|
|
|
1
|
-
# Advanced SEARCH Filters
|
|
2
|
-
|
|
3
|
-
The compiled SEARCH functions support powerful JSONB-based filtering with multiple operators.
|
|
4
|
-
|
|
5
|
-
## Filter Operators
|
|
6
|
-
|
|
7
|
-
### Comparison Operators
|
|
8
|
-
|
|
9
|
-
**Equal (`eq`)** - Exact match
|
|
10
|
-
```json
|
|
11
|
-
{ "status": { "eq": "active" } }
|
|
12
|
-
```
|
|
13
|
-
or simplified:
|
|
14
|
-
```json
|
|
15
|
-
{ "status": "active" }
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
**Not Equal (`ne`)** - Exclude values
|
|
19
|
-
```json
|
|
20
|
-
{ "status": { "ne": "deleted" } }
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
**Greater Than (`gt`)**
|
|
24
|
-
```json
|
|
25
|
-
{ "age": { "gt": 18 } }
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
**Greater Than or Equal (`gte`)**
|
|
29
|
-
```json
|
|
30
|
-
{ "age": { "gte": 18 } }
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
**Less Than (`lt`)**
|
|
34
|
-
```json
|
|
35
|
-
{ "created_at": { "lt": "2024-01-01" } }
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**Less Than or Equal (`lte`)**
|
|
39
|
-
```json
|
|
40
|
-
{ "price": { "lte": 100 } }
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### Array Membership
|
|
44
|
-
|
|
45
|
-
**In (`in`)** - Match any value in array
|
|
46
|
-
```json
|
|
47
|
-
{ "status": { "in": ["active", "pending", "review"] } }
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### Pattern Matching
|
|
51
|
-
|
|
52
|
-
**Case-Insensitive Like (`ilike`)** - PostgreSQL pattern matching
|
|
53
|
-
```json
|
|
54
|
-
{ "email": { "ilike": "%@gmail.com" } }
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
**Like (`like`)** - Case-sensitive pattern matching
|
|
58
|
-
```json
|
|
59
|
-
{ "code": { "like": "PRD-%" } }
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Combining Multiple Filters
|
|
63
|
-
|
|
64
|
-
You can combine multiple filters on different fields:
|
|
65
|
-
|
|
66
|
-
```json
|
|
67
|
-
{
|
|
68
|
-
"age": { "gte": 18, "lte": 65 },
|
|
69
|
-
"status": { "in": ["active", "pending"] },
|
|
70
|
-
"email": { "ilike": "%@company.com" }
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
You can also apply multiple operators to the same field:
|
|
75
|
-
|
|
76
|
-
```json
|
|
77
|
-
{
|
|
78
|
-
"age": { "gte": 18, "lt": 65 }
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## Example Usage
|
|
83
|
-
|
|
84
|
-
### Simple equality filter
|
|
85
|
-
```javascript
|
|
86
|
-
const result = await sql`
|
|
87
|
-
SELECT search_users(
|
|
88
|
-
p_filters := '{"status": "active"}'::jsonb,
|
|
89
|
-
p_page := 1,
|
|
90
|
-
p_limit := 25
|
|
91
|
-
)
|
|
92
|
-
`;
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Age range filter
|
|
96
|
-
```javascript
|
|
97
|
-
const result = await sql`
|
|
98
|
-
SELECT search_users(
|
|
99
|
-
p_filters := '{"age": {"gte": 18, "lte": 65}}'::jsonb
|
|
100
|
-
)
|
|
101
|
-
`;
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Multiple status filter
|
|
105
|
-
```javascript
|
|
106
|
-
const result = await sql`
|
|
107
|
-
SELECT search_orders(
|
|
108
|
-
p_filters := '{"status": {"in": ["pending", "processing", "shipped"]}}'::jsonb
|
|
109
|
-
)
|
|
110
|
-
`;
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### Email domain filter with text search
|
|
114
|
-
```javascript
|
|
115
|
-
const result = await sql`
|
|
116
|
-
SELECT search_users(
|
|
117
|
-
p_filters := '{"email": {"ilike": "%@company.com"}}'::jsonb,
|
|
118
|
-
p_search := 'john',
|
|
119
|
-
p_sort := '{"field": "created_at", "order": "desc"}'::jsonb
|
|
120
|
-
)
|
|
121
|
-
`;
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Complex combined filters
|
|
125
|
-
```javascript
|
|
126
|
-
const result = await sql`
|
|
127
|
-
SELECT search_products(
|
|
128
|
-
p_filters := '{
|
|
129
|
-
"price": {"gte": 10, "lte": 100},
|
|
130
|
-
"category": {"in": ["electronics", "accessories"]},
|
|
131
|
-
"status": "active",
|
|
132
|
-
"name": {"ilike": "%wireless%"}
|
|
133
|
-
}'::jsonb,
|
|
134
|
-
p_page := 1,
|
|
135
|
-
p_limit := 50
|
|
136
|
-
)
|
|
137
|
-
`;
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
## Generated SQL
|
|
141
|
-
|
|
142
|
-
The filters are converted to optimized SQL WHERE conditions:
|
|
143
|
-
|
|
144
|
-
```sql
|
|
145
|
-
-- Input filters:
|
|
146
|
-
{ "age": {"gte": 18}, "status": {"in": ["active", "pending"]} }
|
|
147
|
-
|
|
148
|
-
-- Generated WHERE clause:
|
|
149
|
-
WHERE TRUE
|
|
150
|
-
AND age >= '18'
|
|
151
|
-
AND status = ANY(ARRAY['active', 'pending']::TEXT[])
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
## Performance Considerations
|
|
155
|
-
|
|
156
|
-
1. **Index your filter fields** - Create indexes on frequently filtered columns:
|
|
157
|
-
```sql
|
|
158
|
-
CREATE INDEX idx_users_status ON users(status);
|
|
159
|
-
CREATE INDEX idx_users_age ON users(age);
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
2. **Use exact matches when possible** - `eq` is faster than `ilike`
|
|
163
|
-
|
|
164
|
-
3. **Limit ILIKE patterns** - Patterns starting with `%` can't use indexes efficiently
|
|
165
|
-
|
|
166
|
-
4. **Combine with pagination** - Always use `p_limit` to avoid loading large result sets
|
|
167
|
-
|
|
168
|
-
## Type Safety
|
|
169
|
-
|
|
170
|
-
The filter system is type-aware and handles:
|
|
171
|
-
- **Strings** - Quoted and escaped properly
|
|
172
|
-
- **Numbers** - Cast appropriately
|
|
173
|
-
- **Booleans** - Converted to SQL boolean values
|
|
174
|
-
- **Dates** - Handled as timestamp comparisons
|
|
175
|
-
- **Arrays** - Expanded for `IN` clauses
|
|
176
|
-
|
|
177
|
-
## Error Handling
|
|
178
|
-
|
|
179
|
-
Unknown operators are silently ignored to prevent SQL injection. Only these operators are supported:
|
|
180
|
-
- `eq`, `ne`
|
|
181
|
-
- `gt`, `gte`, `lt`, `lte`
|
|
182
|
-
- `in`
|
|
183
|
-
- `like`, `ilike`
|
|
@@ -1,415 +0,0 @@
|
|
|
1
|
-
# DZQL Compiler - Coding Standards
|
|
2
|
-
|
|
3
|
-
This document defines the coding standards that the DZQL Compiler enforces when generating PostgreSQL functions.
|
|
4
|
-
|
|
5
|
-
## Function Naming Conventions
|
|
6
|
-
|
|
7
|
-
### 1. Parameter Naming
|
|
8
|
-
|
|
9
|
-
**All parameters MUST use the `p_` prefix:**
|
|
10
|
-
|
|
11
|
-
```sql
|
|
12
|
-
-- ✅ CORRECT
|
|
13
|
-
CREATE FUNCTION get_users(
|
|
14
|
-
p_user_id INT,
|
|
15
|
-
p_id INT,
|
|
16
|
-
p_on_date TIMESTAMPTZ DEFAULT NULL
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
-- ❌ WRONG
|
|
20
|
-
CREATE FUNCTION get_users(
|
|
21
|
-
user_id INT,
|
|
22
|
-
id INT,
|
|
23
|
-
on_date TIMESTAMPTZ DEFAULT NULL
|
|
24
|
-
)
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### 2. Parameter Ordering
|
|
28
|
-
|
|
29
|
-
**`p_user_id INT` MUST be the first parameter in ALL functions:**
|
|
30
|
-
|
|
31
|
-
This is a critical security requirement from the DZQL framework - the authenticated user ID must always be the first parameter.
|
|
32
|
-
|
|
33
|
-
```sql
|
|
34
|
-
-- ✅ CORRECT - p_user_id first
|
|
35
|
-
CREATE FUNCTION get_users(p_user_id INT, p_id INT, ...)
|
|
36
|
-
CREATE FUNCTION save_users(p_user_id INT, p_data JSONB)
|
|
37
|
-
CREATE FUNCTION delete_users(p_user_id INT, p_id INT)
|
|
38
|
-
CREATE FUNCTION lookup_users(p_user_id INT, p_filter TEXT, ...)
|
|
39
|
-
CREATE FUNCTION search_users(p_user_id INT, p_filters JSONB, ...)
|
|
40
|
-
|
|
41
|
-
-- ❌ WRONG - p_user_id not first
|
|
42
|
-
CREATE FUNCTION get_users(p_id INT, p_user_id INT, ...)
|
|
43
|
-
CREATE FUNCTION save_users(p_data JSONB, p_user_id INT)
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### 3. Helper Function Prefixes
|
|
47
|
-
|
|
48
|
-
**Helper functions MUST start with underscore `_` to prevent direct websocket access:**
|
|
49
|
-
|
|
50
|
-
Helper functions are internal implementation details that should not be callable by websocket clients.
|
|
51
|
-
|
|
52
|
-
```sql
|
|
53
|
-
-- ✅ CORRECT - Helper functions with underscore
|
|
54
|
-
CREATE FUNCTION _graph_users_on_create(p_user_id INT, p_record JSONB)
|
|
55
|
-
CREATE FUNCTION _resolve_notification_paths_users(p_user_id INT, p_record JSONB)
|
|
56
|
-
|
|
57
|
-
-- ❌ WRONG - Helper functions without underscore (publicly callable!)
|
|
58
|
-
CREATE FUNCTION graph_users_on_create(p_record JSONB, p_user_id INT)
|
|
59
|
-
CREATE FUNCTION resolve_notification_paths_users(p_record JSONB)
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
**Public API functions do NOT use underscore prefix:**
|
|
63
|
-
|
|
64
|
-
```sql
|
|
65
|
-
-- ✅ CORRECT - Public API functions
|
|
66
|
-
CREATE FUNCTION can_view_users(p_user_id INT, p_record JSONB)
|
|
67
|
-
CREATE FUNCTION can_create_users(p_user_id INT, p_record JSONB)
|
|
68
|
-
CREATE FUNCTION get_users(p_user_id INT, p_id INT, ...)
|
|
69
|
-
CREATE FUNCTION save_users(p_user_id INT, p_data JSONB)
|
|
70
|
-
CREATE FUNCTION delete_users(p_user_id INT, p_id INT)
|
|
71
|
-
CREATE FUNCTION lookup_users(p_user_id INT, p_filter TEXT, ...)
|
|
72
|
-
CREATE FUNCTION search_users(p_user_id INT, p_filters JSONB, ...)
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## JSON vs JSONB for Function Parameters
|
|
76
|
-
|
|
77
|
-
### External API Parameters: Use JSON
|
|
78
|
-
|
|
79
|
-
When defining function parameters that accept JSON from external callers (API boundary), use `JSON` type (text-based) rather than `JSONB`. This allows callers to pass `JSON.stringify(options)` as a plain string without needing special serialization like `sql.json()`.
|
|
80
|
-
|
|
81
|
-
```sql
|
|
82
|
-
-- ✅ CORRECT - JSON for external input parameters
|
|
83
|
-
CREATE FUNCTION register_user(
|
|
84
|
-
p_email TEXT,
|
|
85
|
-
p_password TEXT,
|
|
86
|
-
p_options JSON DEFAULT NULL -- Accepts plain JSON string from API
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
-- ❌ WRONG - JSONB requires special serialization from clients
|
|
90
|
-
CREATE FUNCTION register_user(
|
|
91
|
-
p_email TEXT,
|
|
92
|
-
p_password TEXT,
|
|
93
|
-
p_options JSONB DEFAULT NULL -- Harder to call from JavaScript
|
|
94
|
-
)
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### Internal Operations: Cast to JSONB
|
|
98
|
-
|
|
99
|
-
Inside the function, cast to JSONB if you need JSONB operators (`->`, `->>`, `-`, `||`, `?`, etc.):
|
|
100
|
-
|
|
101
|
-
```sql
|
|
102
|
-
CREATE FUNCTION register_user(p_email TEXT, p_password TEXT, p_options JSON DEFAULT NULL)
|
|
103
|
-
RETURNS JSONB AS $$
|
|
104
|
-
DECLARE
|
|
105
|
-
v_insert_data JSONB;
|
|
106
|
-
BEGIN
|
|
107
|
-
v_insert_data := jsonb_build_object('email', p_email);
|
|
108
|
-
|
|
109
|
-
IF p_options IS NOT NULL THEN
|
|
110
|
-
-- Cast to JSONB for internal operations
|
|
111
|
-
v_insert_data := (p_options::jsonb - 'id' - 'password') || v_insert_data;
|
|
112
|
-
END IF;
|
|
113
|
-
|
|
114
|
-
-- ...
|
|
115
|
-
END;
|
|
116
|
-
$$ LANGUAGE plpgsql;
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
### Table Columns: Use JSONB
|
|
120
|
-
|
|
121
|
-
Table columns should still use `JSONB` for efficient storage and indexing:
|
|
122
|
-
|
|
123
|
-
```sql
|
|
124
|
-
-- ✅ CORRECT - JSONB for table columns
|
|
125
|
-
CREATE TABLE users (
|
|
126
|
-
id SERIAL PRIMARY KEY,
|
|
127
|
-
metadata JSONB DEFAULT '{}' -- Efficient storage & indexing
|
|
128
|
-
);
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Summary
|
|
132
|
-
|
|
133
|
-
| Context | Type | Reason |
|
|
134
|
-
|---------|------|--------|
|
|
135
|
-
| Function input parameters | `JSON` | Easy to pass from JavaScript (`JSON.stringify()`) |
|
|
136
|
-
| Internal function operations | `::jsonb` cast | Access to JSONB operators |
|
|
137
|
-
| Table columns | `JSONB` | Efficient storage and indexing |
|
|
138
|
-
|
|
139
|
-
This pattern - **JSON for input parameters, JSONB for storage** - eliminates serialization confusion at the API boundary.
|
|
140
|
-
|
|
141
|
-
## Function Categories
|
|
142
|
-
|
|
143
|
-
### Public API Functions (No underscore)
|
|
144
|
-
|
|
145
|
-
These are directly callable via the DZQL websocket API:
|
|
146
|
-
|
|
147
|
-
1. **Permission Check Functions** - `can_{operation}_{table}`
|
|
148
|
-
- `can_view_{table}(p_user_id INT, p_record JSONB)`
|
|
149
|
-
- `can_create_{table}(p_user_id INT, p_record JSONB)`
|
|
150
|
-
- `can_update_{table}(p_user_id INT, p_record JSONB)`
|
|
151
|
-
- `can_delete_{table}(p_user_id INT, p_record JSONB)`
|
|
152
|
-
|
|
153
|
-
2. **CRUD Operation Functions**
|
|
154
|
-
- `get_{table}(p_user_id INT, p_id INT, p_on_date TIMESTAMPTZ DEFAULT NULL)`
|
|
155
|
-
- `save_{table}(p_user_id INT, p_data JSONB)`
|
|
156
|
-
- `delete_{table}(p_user_id INT, p_id INT)`
|
|
157
|
-
- `lookup_{table}(p_user_id INT, p_filter TEXT DEFAULT NULL, p_limit INT DEFAULT 50)`
|
|
158
|
-
- `search_{table}(p_user_id INT, p_filters JSONB DEFAULT '{}', ...)`
|
|
159
|
-
|
|
160
|
-
### Helper Functions (With underscore)
|
|
161
|
-
|
|
162
|
-
These are internal and NOT directly callable via websocket:
|
|
163
|
-
|
|
164
|
-
1. **Graph Rule Functions** - `_graph_{table}_{trigger}`
|
|
165
|
-
- `_graph_{table}_on_create(p_user_id INT, p_record JSONB)`
|
|
166
|
-
- `_graph_{table}_on_update(p_user_id INT, p_old_record JSONB, p_new_record JSONB)`
|
|
167
|
-
- `_graph_{table}_on_delete(p_user_id INT, p_old_record JSONB)`
|
|
168
|
-
|
|
169
|
-
2. **Notification Resolution Functions** - `_resolve_notification_paths_{table}`
|
|
170
|
-
- `_resolve_notification_paths_{table}(p_user_id INT, p_record JSONB)`
|
|
171
|
-
|
|
172
|
-
## Standard Permission Functions
|
|
173
|
-
|
|
174
|
-
**ALL entities MUST have all 4 permission check functions generated:**
|
|
175
|
-
|
|
176
|
-
Even if an entity has no permission restrictions (public access), all 4 functions must exist:
|
|
177
|
-
|
|
178
|
-
```sql
|
|
179
|
-
-- Public access - returns true
|
|
180
|
-
CREATE FUNCTION can_view_users(p_user_id INT, p_record JSONB)
|
|
181
|
-
RETURNS BOOLEAN AS $$
|
|
182
|
-
BEGIN
|
|
183
|
-
RETURN true; -- Public access
|
|
184
|
-
END;
|
|
185
|
-
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
|
|
186
|
-
|
|
187
|
-
-- Restricted access - checks permissions
|
|
188
|
-
CREATE FUNCTION can_update_users(p_user_id INT, p_record JSONB)
|
|
189
|
-
RETURNS BOOLEAN AS $$
|
|
190
|
-
BEGIN
|
|
191
|
-
RETURN EXISTS (
|
|
192
|
-
SELECT 1 FROM acts_for
|
|
193
|
-
WHERE acts_for.org_id = (p_record->>'org_id')::int
|
|
194
|
-
AND acts_for.user_id = p_user_id
|
|
195
|
-
AND acts_for.valid_to IS NULL
|
|
196
|
-
);
|
|
197
|
-
END;
|
|
198
|
-
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
## Variable Naming
|
|
202
|
-
|
|
203
|
-
### Local Variables
|
|
204
|
-
|
|
205
|
-
**Use `v_` prefix for all local variables:**
|
|
206
|
-
|
|
207
|
-
```sql
|
|
208
|
-
DECLARE
|
|
209
|
-
v_result users%ROWTYPE; -- ✅ CORRECT
|
|
210
|
-
v_existing users%ROWTYPE; -- ✅ CORRECT
|
|
211
|
-
v_is_insert BOOLEAN := false; -- ✅ CORRECT
|
|
212
|
-
v_notify_users INT[]; -- ✅ CORRECT
|
|
213
|
-
BEGIN
|
|
214
|
-
-- ...
|
|
215
|
-
END;
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
## SQL Style
|
|
219
|
-
|
|
220
|
-
### Keywords
|
|
221
|
-
|
|
222
|
-
**All SQL keywords should be UPPERCASE:**
|
|
223
|
-
|
|
224
|
-
```sql
|
|
225
|
-
-- ✅ CORRECT
|
|
226
|
-
CREATE OR REPLACE FUNCTION get_users(...)
|
|
227
|
-
RETURNS JSONB AS $$
|
|
228
|
-
DECLARE
|
|
229
|
-
v_result JSONB;
|
|
230
|
-
BEGIN
|
|
231
|
-
SELECT * INTO v_result FROM users WHERE id = p_id;
|
|
232
|
-
RETURN v_result;
|
|
233
|
-
END;
|
|
234
|
-
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
235
|
-
|
|
236
|
-
-- ❌ WRONG
|
|
237
|
-
create or replace function get_users(...)
|
|
238
|
-
returns jsonb as $$
|
|
239
|
-
declare
|
|
240
|
-
v_result jsonb;
|
|
241
|
-
begin
|
|
242
|
-
select * into v_result from users where id = p_id;
|
|
243
|
-
return v_result;
|
|
244
|
-
end;
|
|
245
|
-
$$ language plpgsql security definer;
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
### Function Attributes
|
|
249
|
-
|
|
250
|
-
**All functions MUST have:**
|
|
251
|
-
- `LANGUAGE plpgsql` (or `LANGUAGE sql`)
|
|
252
|
-
- `SECURITY DEFINER` - Runs with privileges of the function owner
|
|
253
|
-
|
|
254
|
-
**Permission functions should additionally have:**
|
|
255
|
-
- `STABLE` - Indicates function doesn't modify the database
|
|
256
|
-
|
|
257
|
-
```sql
|
|
258
|
-
-- Permission function
|
|
259
|
-
CREATE FUNCTION can_view_users(...)
|
|
260
|
-
RETURNS BOOLEAN AS $$
|
|
261
|
-
-- ...
|
|
262
|
-
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
|
|
263
|
-
|
|
264
|
-
-- Operation function
|
|
265
|
-
CREATE FUNCTION save_users(...)
|
|
266
|
-
RETURNS JSONB AS $$
|
|
267
|
-
-- ...
|
|
268
|
-
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
## Complete Example
|
|
272
|
-
|
|
273
|
-
Here's a complete example showing all coding standards:
|
|
274
|
-
|
|
275
|
-
```sql
|
|
276
|
-
-- ============================================================================
|
|
277
|
-
-- Permission Functions (Public API - no underscore)
|
|
278
|
-
-- ============================================================================
|
|
279
|
-
|
|
280
|
-
-- Permission check: view on organisations
|
|
281
|
-
CREATE OR REPLACE FUNCTION can_view_organisations(
|
|
282
|
-
p_user_id INT,
|
|
283
|
-
p_record JSONB
|
|
284
|
-
) RETURNS BOOLEAN AS $$
|
|
285
|
-
BEGIN
|
|
286
|
-
RETURN true; -- Public access
|
|
287
|
-
END;
|
|
288
|
-
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
|
|
289
|
-
|
|
290
|
-
-- ============================================================================
|
|
291
|
-
-- CRUD Operations (Public API - no underscore)
|
|
292
|
-
-- ============================================================================
|
|
293
|
-
|
|
294
|
-
-- GET operation for organisations
|
|
295
|
-
CREATE OR REPLACE FUNCTION get_organisations(
|
|
296
|
-
p_user_id INT,
|
|
297
|
-
p_id INT,
|
|
298
|
-
p_on_date TIMESTAMPTZ DEFAULT NULL
|
|
299
|
-
) RETURNS JSONB AS $$
|
|
300
|
-
DECLARE
|
|
301
|
-
v_result JSONB;
|
|
302
|
-
v_record organisations%ROWTYPE;
|
|
303
|
-
BEGIN
|
|
304
|
-
-- Fetch the record
|
|
305
|
-
SELECT * INTO v_record FROM organisations WHERE id = p_id;
|
|
306
|
-
|
|
307
|
-
IF NOT FOUND THEN
|
|
308
|
-
RAISE EXCEPTION 'Record not found: % with id=%', 'organisations', p_id;
|
|
309
|
-
END IF;
|
|
310
|
-
|
|
311
|
-
-- Convert to JSONB
|
|
312
|
-
v_result := to_jsonb(v_record);
|
|
313
|
-
|
|
314
|
-
-- Check view permission
|
|
315
|
-
IF NOT can_view_organisations(p_user_id, v_result) THEN
|
|
316
|
-
RAISE EXCEPTION 'Permission denied: view on organisations';
|
|
317
|
-
END IF;
|
|
318
|
-
|
|
319
|
-
RETURN v_result;
|
|
320
|
-
END;
|
|
321
|
-
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
322
|
-
|
|
323
|
-
-- SAVE operation for organisations
|
|
324
|
-
CREATE OR REPLACE FUNCTION save_organisations(
|
|
325
|
-
p_user_id INT,
|
|
326
|
-
p_data JSONB
|
|
327
|
-
) RETURNS JSONB AS $$
|
|
328
|
-
DECLARE
|
|
329
|
-
v_result organisations%ROWTYPE;
|
|
330
|
-
v_is_insert BOOLEAN;
|
|
331
|
-
BEGIN
|
|
332
|
-
-- Perform UPSERT
|
|
333
|
-
-- ... implementation ...
|
|
334
|
-
|
|
335
|
-
-- Call graph rules helper
|
|
336
|
-
IF v_is_insert THEN
|
|
337
|
-
PERFORM _graph_organisations_on_create(p_user_id, to_jsonb(v_result));
|
|
338
|
-
END IF;
|
|
339
|
-
|
|
340
|
-
RETURN to_jsonb(v_result);
|
|
341
|
-
END;
|
|
342
|
-
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
343
|
-
|
|
344
|
-
-- ============================================================================
|
|
345
|
-
-- Helper Functions (Internal - with underscore)
|
|
346
|
-
-- ============================================================================
|
|
347
|
-
|
|
348
|
-
-- Graph rules: on_create on organisations
|
|
349
|
-
CREATE OR REPLACE FUNCTION _graph_organisations_on_create(
|
|
350
|
-
p_user_id INT,
|
|
351
|
-
p_record JSONB
|
|
352
|
-
) RETURNS VOID AS $$
|
|
353
|
-
BEGIN
|
|
354
|
-
-- Creator becomes owner
|
|
355
|
-
INSERT INTO acts_for (user_id, org_id, valid_from)
|
|
356
|
-
VALUES (p_user_id, (p_record->>'id'), CURRENT_DATE);
|
|
357
|
-
END;
|
|
358
|
-
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
359
|
-
|
|
360
|
-
-- Notification path resolution for organisations
|
|
361
|
-
CREATE OR REPLACE FUNCTION _resolve_notification_paths_organisations(
|
|
362
|
-
p_user_id INT,
|
|
363
|
-
p_record JSONB
|
|
364
|
-
) RETURNS INT[] AS $$
|
|
365
|
-
DECLARE
|
|
366
|
-
v_users INT[] := ARRAY[]::INT[];
|
|
367
|
-
BEGIN
|
|
368
|
-
-- Resolve notification recipients
|
|
369
|
-
-- ... implementation ...
|
|
370
|
-
|
|
371
|
-
RETURN ARRAY(SELECT DISTINCT unnest(v_users));
|
|
372
|
-
END;
|
|
373
|
-
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
## Why These Standards Matter
|
|
377
|
-
|
|
378
|
-
### Security
|
|
379
|
-
|
|
380
|
-
1. **`p_user_id` first** - Ensures user context is always explicit and hard to forget
|
|
381
|
-
2. **Helper functions with `_`** - Prevents direct websocket access to internal functions
|
|
382
|
-
3. **SECURITY DEFINER** - Ensures proper privilege execution
|
|
383
|
-
|
|
384
|
-
### Consistency
|
|
385
|
-
|
|
386
|
-
1. **Predictable parameter order** - Makes all functions follow the same pattern
|
|
387
|
-
2. **Standard naming** - Makes generated code easy to read and maintain
|
|
388
|
-
3. **All 4 permission functions** - Ensures complete access control coverage
|
|
389
|
-
|
|
390
|
-
### WebSocket Safety
|
|
391
|
-
|
|
392
|
-
The underscore prefix prevents clients from directly calling helper functions:
|
|
393
|
-
|
|
394
|
-
```javascript
|
|
395
|
-
// ✅ These work - public API
|
|
396
|
-
await ws.api.get.users({id: 1});
|
|
397
|
-
await ws.api.save.users({name: 'John'});
|
|
398
|
-
|
|
399
|
-
// ❌ These DON'T work - helper functions blocked
|
|
400
|
-
await ws.api._graph_users_on_create({...}); // Blocked!
|
|
401
|
-
await ws.api._resolve_notification_paths_users({...}); // Blocked!
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
## Validation
|
|
405
|
-
|
|
406
|
-
The DZQL Compiler enforces these standards automatically. All generated code is validated by comprehensive tests that verify:
|
|
407
|
-
|
|
408
|
-
- ✅ Parameter naming and ordering
|
|
409
|
-
- ✅ Function prefixes (helper vs public)
|
|
410
|
-
- ✅ All 4 permission functions exist
|
|
411
|
-
- ✅ SQL syntax correctness
|
|
412
|
-
- ✅ SECURITY DEFINER attribute presence
|
|
413
|
-
- ✅ Uppercase SQL keywords
|
|
414
|
-
|
|
415
|
-
See `tests/sql-validation.test.js` for complete validation suite.
|