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
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { DomainIR, EntityIR, SubscribableIR } from "../../shared/ir.js";
|
|
2
|
+
|
|
3
|
+
export interface Manifest {
|
|
4
|
+
version: string;
|
|
5
|
+
functions: Record<string, FunctionDef>;
|
|
6
|
+
entities: Record<string, EntityIR>;
|
|
7
|
+
subscribables: Record<string, SubscribableIR>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface FunctionDef {
|
|
11
|
+
oid?: number; // Resolved at runtime
|
|
12
|
+
schema: string;
|
|
13
|
+
name: string;
|
|
14
|
+
args: string[];
|
|
15
|
+
returnType: string;
|
|
16
|
+
isSubscription?: boolean; // Marks subscribe_* functions for client generation
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function generateManifest(ir: DomainIR): Manifest {
|
|
20
|
+
const functions: Record<string, FunctionDef> = {
|
|
21
|
+
// Built-in Auth Functions
|
|
22
|
+
login_user: { schema: 'dzql_v2', name: 'login_user', args: ['p_params'], returnType: 'jsonb' },
|
|
23
|
+
register_user: { schema: 'dzql_v2', name: 'register_user', args: ['p_params'], returnType: 'jsonb' }
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
for (const [name, entity] of Object.entries(ir.entities)) {
|
|
27
|
+
// Skip unmanaged entities (junction tables, etc.)
|
|
28
|
+
if (entity.managed === false) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Generate standard CRUD functions
|
|
33
|
+
const operations = ['get', 'save', 'delete', 'search', 'lookup'];
|
|
34
|
+
|
|
35
|
+
for (const op of operations) {
|
|
36
|
+
const funcName = `${op}_${name}`;
|
|
37
|
+
functions[funcName] = {
|
|
38
|
+
schema: 'dzql_v2',
|
|
39
|
+
name: funcName,
|
|
40
|
+
args: op === 'get' || op === 'delete'
|
|
41
|
+
? ['p_user_id', 'p_pk']
|
|
42
|
+
: op === 'save'
|
|
43
|
+
? ['p_user_id', 'p_data']
|
|
44
|
+
: ['p_user_id', 'p_query'],
|
|
45
|
+
returnType: 'jsonb'
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const [subName, subIR] of Object.entries(ir.subscribables)) {
|
|
51
|
+
console.log(`[Manifest] Adding subscribable: ${subName}`);
|
|
52
|
+
|
|
53
|
+
// get_<name> - snapshot function
|
|
54
|
+
functions[`get_${subName}`] = {
|
|
55
|
+
schema: 'dzql_v2',
|
|
56
|
+
name: `get_${subName}`,
|
|
57
|
+
args: ['p_params', 'p_user_id'],
|
|
58
|
+
returnType: 'jsonb'
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// <name>_can_subscribe - access control
|
|
62
|
+
functions[`${subName}_can_subscribe`] = {
|
|
63
|
+
schema: 'dzql_v2',
|
|
64
|
+
name: `${subName}_can_subscribe`,
|
|
65
|
+
args: ['p_user_id', 'p_params'],
|
|
66
|
+
returnType: 'boolean'
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// subscribe_<name> - virtual method handled by runtime
|
|
70
|
+
functions[`subscribe_${subName}`] = {
|
|
71
|
+
schema: 'dzql_v2',
|
|
72
|
+
name: `subscribe_${subName}`,
|
|
73
|
+
args: ['p_params'],
|
|
74
|
+
returnType: 'jsonb',
|
|
75
|
+
isSubscription: true // Mark as subscription for special handling
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Add custom functions to allowlist
|
|
80
|
+
for (const fn of ir.customFunctions || []) {
|
|
81
|
+
functions[fn.name] = {
|
|
82
|
+
schema: 'dzql_v2',
|
|
83
|
+
name: fn.name,
|
|
84
|
+
args: fn.args || ['p_user_id', 'p_params'],
|
|
85
|
+
returnType: 'jsonb'
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
version: "2.0.0",
|
|
91
|
+
functions,
|
|
92
|
+
entities: ir.entities, // Pass through for client generator
|
|
93
|
+
subscribables: ir.subscribables
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { Manifest } from "./manifest.js";
|
|
2
|
+
|
|
3
|
+
function toPascalCase(str: string): string {
|
|
4
|
+
return str.replace(/(^|_)([a-z])/g, (g) => g[g.length - 1].toUpperCase());
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function generatePiniaStore(manifest: Manifest, entityName: string): string {
|
|
8
|
+
const entity = manifest.entities[entityName];
|
|
9
|
+
if (!entity) {
|
|
10
|
+
throw new Error(`Entity '${entityName}' not found in manifest.`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const pkField = entity.primaryKey[0] || 'id';
|
|
14
|
+
const pascalName = toPascalCase(entityName);
|
|
15
|
+
|
|
16
|
+
// Generate column types for TypeScript interface
|
|
17
|
+
const columnTypes = entity.columns.map(col => {
|
|
18
|
+
let tsType = 'unknown';
|
|
19
|
+
const pgType = col.type.toLowerCase();
|
|
20
|
+
|
|
21
|
+
if (pgType.includes('serial') || pgType.includes('int')) {
|
|
22
|
+
tsType = 'number';
|
|
23
|
+
} else if (pgType.includes('text') || pgType.includes('varchar') || pgType.includes('char')) {
|
|
24
|
+
tsType = 'string';
|
|
25
|
+
} else if (pgType.includes('bool')) {
|
|
26
|
+
tsType = 'boolean';
|
|
27
|
+
} else if (pgType.includes('timestamp') || pgType.includes('date')) {
|
|
28
|
+
tsType = 'string'; // ISO date string
|
|
29
|
+
} else if (pgType.includes('json')) {
|
|
30
|
+
tsType = 'Record<string, unknown>';
|
|
31
|
+
} else if (pgType.includes('decimal') || pgType.includes('numeric') || pgType.includes('float') || pgType.includes('real')) {
|
|
32
|
+
tsType = 'number';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Handle arrays
|
|
36
|
+
if (col.isArray) {
|
|
37
|
+
tsType = `${tsType}[]`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Make optional if not primary key and not NOT NULL
|
|
41
|
+
const isRequired = pgType.includes('not null') || pgType.includes('primary key');
|
|
42
|
+
return ` ${col.name}${isRequired ? '' : '?'}: ${tsType};`;
|
|
43
|
+
}).join('\n');
|
|
44
|
+
|
|
45
|
+
// Primary key type
|
|
46
|
+
const pkCol = entity.columns.find(c => c.name === pkField);
|
|
47
|
+
const pkType = pkCol && (pkCol.type.toLowerCase().includes('serial') || pkCol.type.toLowerCase().includes('int'))
|
|
48
|
+
? 'number'
|
|
49
|
+
: 'string';
|
|
50
|
+
|
|
51
|
+
return `// Generated by TZQL Compiler v${manifest.version}
|
|
52
|
+
// Do not edit this file directly.
|
|
53
|
+
|
|
54
|
+
import { defineStore } from 'pinia';
|
|
55
|
+
import { ref, type Ref } from 'vue';
|
|
56
|
+
import { ws } from '../../client.js';
|
|
57
|
+
|
|
58
|
+
/** ${pascalName} entity type */
|
|
59
|
+
export interface ${pascalName} {
|
|
60
|
+
${columnTypes}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Event payload for table changes */
|
|
64
|
+
export interface TableChangedPayload {
|
|
65
|
+
table: string;
|
|
66
|
+
operation: 'insert' | 'update' | 'delete';
|
|
67
|
+
pk: { ${pkField}: ${pkType} };
|
|
68
|
+
data: ${pascalName} | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const use${pascalName}Store = defineStore('${entityName}-store', () => {
|
|
72
|
+
const records: Ref<${pascalName}[]> = ref([]);
|
|
73
|
+
const loading: Ref<boolean> = ref(false);
|
|
74
|
+
const error: Ref<Error | null> = ref(null);
|
|
75
|
+
|
|
76
|
+
async function get(id: ${pkType}): Promise<${pascalName} | null> {
|
|
77
|
+
loading.value = true;
|
|
78
|
+
try {
|
|
79
|
+
const result = await ws.api.get_${entityName}({ ${pkField}: id });
|
|
80
|
+
return result as ${pascalName} | null;
|
|
81
|
+
} catch (e) {
|
|
82
|
+
error.value = e as Error;
|
|
83
|
+
throw e;
|
|
84
|
+
} finally {
|
|
85
|
+
loading.value = false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function save(data: Partial<${pascalName}>): Promise<${pascalName}> {
|
|
90
|
+
loading.value = true;
|
|
91
|
+
try {
|
|
92
|
+
const result = await ws.api.save_${entityName}(data);
|
|
93
|
+
return result as ${pascalName};
|
|
94
|
+
} catch (e) {
|
|
95
|
+
error.value = e as Error;
|
|
96
|
+
throw e;
|
|
97
|
+
} finally {
|
|
98
|
+
loading.value = false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function remove(id: ${pkType}): Promise<${pascalName}> {
|
|
103
|
+
loading.value = true;
|
|
104
|
+
try {
|
|
105
|
+
const result = await ws.api.delete_${entityName}({ ${pkField}: id });
|
|
106
|
+
return result as ${pascalName};
|
|
107
|
+
} catch (e) {
|
|
108
|
+
error.value = e as Error;
|
|
109
|
+
throw e;
|
|
110
|
+
} finally {
|
|
111
|
+
loading.value = false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function search(query: {
|
|
116
|
+
filters?: Record<string, unknown>;
|
|
117
|
+
sort_field?: string;
|
|
118
|
+
sort_order?: 'asc' | 'desc';
|
|
119
|
+
limit?: number;
|
|
120
|
+
offset?: number;
|
|
121
|
+
} = {}): Promise<${pascalName}[]> {
|
|
122
|
+
loading.value = true;
|
|
123
|
+
try {
|
|
124
|
+
const result = await ws.api.search_${entityName}(query);
|
|
125
|
+
records.value = result as ${pascalName}[];
|
|
126
|
+
return records.value;
|
|
127
|
+
} catch (e) {
|
|
128
|
+
error.value = e as Error;
|
|
129
|
+
throw e;
|
|
130
|
+
} finally {
|
|
131
|
+
loading.value = false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function table_changed(payload: TableChangedPayload): void {
|
|
136
|
+
if (payload.table === '${entityName}') {
|
|
137
|
+
const pkValue = payload.pk?.${pkField};
|
|
138
|
+
const existingIndex = records.value.findIndex((r: ${pascalName}) => r.${pkField} === pkValue);
|
|
139
|
+
|
|
140
|
+
switch (payload.operation) {
|
|
141
|
+
case 'insert':
|
|
142
|
+
if (payload.data && existingIndex === -1) {
|
|
143
|
+
records.value.push(payload.data);
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
case 'update':
|
|
147
|
+
if (payload.data && existingIndex !== -1) {
|
|
148
|
+
Object.assign(records.value[existingIndex], payload.data);
|
|
149
|
+
} else if (payload.data) {
|
|
150
|
+
records.value.push(payload.data);
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
case 'delete':
|
|
154
|
+
if (existingIndex !== -1) {
|
|
155
|
+
records.value.splice(existingIndex, 1);
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
records,
|
|
164
|
+
loading,
|
|
165
|
+
error,
|
|
166
|
+
get,
|
|
167
|
+
save,
|
|
168
|
+
remove,
|
|
169
|
+
search,
|
|
170
|
+
table_changed,
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
`;
|
|
174
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { SubscribableIR } from "../../shared/ir.js";
|
|
2
|
+
|
|
3
|
+
export function generateAffectedKeysFunction(name: string, subIR: SubscribableIR): string {
|
|
4
|
+
// Logic to determine keys based on table
|
|
5
|
+
// For 'post_detail' (param: post_id), if 'posts' table updates, key is 'post_detail:' + post.id
|
|
6
|
+
|
|
7
|
+
// This is a simplified generator. In reality, it needs to traverse the 'includes' graph
|
|
8
|
+
// to find the path from the updated table back to the root parameter.
|
|
9
|
+
|
|
10
|
+
// Example for post_detail:
|
|
11
|
+
// Root: posts (key: post_id)
|
|
12
|
+
// -> includes comments (filter: post_id=@id)
|
|
13
|
+
|
|
14
|
+
// If posts updates: key = 'post_detail:' + new.id
|
|
15
|
+
// If comments updates: key = 'post_detail:' + new.post_id
|
|
16
|
+
|
|
17
|
+
// We need to generate a CASE statement for p_table
|
|
18
|
+
|
|
19
|
+
const cases: string[] = [];
|
|
20
|
+
|
|
21
|
+
// 1. Root Entity Case
|
|
22
|
+
const rootEntity = subIR.root.entity;
|
|
23
|
+
// Assumption: the param key matches the root entity's PK or is mapped
|
|
24
|
+
// Here, param is 'post_id'. Root key is 'post_id'.
|
|
25
|
+
// If root key starts with @, it's a variable. If not, it's a param name.
|
|
26
|
+
// Wait, in blog.ts: root: { entity: 'posts', key: 'post_id' }
|
|
27
|
+
// This means the subscription param 'post_id' maps to the 'posts' entity.
|
|
28
|
+
// So if 'posts' updates, we look at its ID (assuming ID is the join key? No, 'key' is the param name).
|
|
29
|
+
|
|
30
|
+
// Simpler assumption for V2.0:
|
|
31
|
+
// subscription key = "sub_name:param_value"
|
|
32
|
+
|
|
33
|
+
// If root table updates, the param value is usually the PK of the root table.
|
|
34
|
+
cases.push(
|
|
35
|
+
`
|
|
36
|
+
WHEN '${rootEntity}' THEN
|
|
37
|
+
RETURN ARRAY['${name}:' || COALESCE(p_new->>'id', p_old->>'id')];
|
|
38
|
+
`);
|
|
39
|
+
|
|
40
|
+
// TODO: Handle related tables via traversal
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
`
|
|
44
|
+
CREATE OR REPLACE FUNCTION dzql_v2.${name}_affected_keys(p_table text, p_op text, p_old jsonb, p_new jsonb)
|
|
45
|
+
RETURNS text[]
|
|
46
|
+
LANGUAGE plpgsql
|
|
47
|
+
IMMUTABLE
|
|
48
|
+
AS $$
|
|
49
|
+
BEGIN
|
|
50
|
+
CASE p_table
|
|
51
|
+
${cases.join('\n')}
|
|
52
|
+
ELSE
|
|
53
|
+
RETURN ARRAY[]::text[];
|
|
54
|
+
END CASE;
|
|
55
|
+
END;
|
|
56
|
+
$$;
|
|
57
|
+
`);
|
|
58
|
+
}
|