dzql 0.5.7 → 0.5.9
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/bin/cli.js +1 -4
- package/package.json +1 -1
- package/src/compiler/codegen/subscribable-codegen.js +28 -12
- package/src/server/ws.js +20 -10
package/bin/cli.js
CHANGED
|
@@ -404,13 +404,12 @@ async function runMigrateNew(args) {
|
|
|
404
404
|
}
|
|
405
405
|
|
|
406
406
|
// Generate migration template
|
|
407
|
+
// Note: No BEGIN/COMMIT - postgres.js handles transactions automatically
|
|
407
408
|
const template = `-- ============================================================================
|
|
408
409
|
-- Migration ${paddedNumber}: ${migrationName.replace(/_/g, ' ')}
|
|
409
410
|
-- Generated: ${new Date().toISOString().split('T')[0]}
|
|
410
411
|
-- ============================================================================
|
|
411
412
|
|
|
412
|
-
BEGIN;
|
|
413
|
-
|
|
414
413
|
-- Part 1: Schema Changes
|
|
415
414
|
-- ALTER TABLE example ADD COLUMN IF NOT EXISTS new_field TEXT;
|
|
416
415
|
|
|
@@ -440,8 +439,6 @@ BEGIN;
|
|
|
440
439
|
-- ('my_custom_function'::regproc, 'Description of function')
|
|
441
440
|
-- ON CONFLICT DO NOTHING;
|
|
442
441
|
|
|
443
|
-
COMMIT;
|
|
444
|
-
|
|
445
442
|
-- ============================================================================
|
|
446
443
|
-- Rollback (for migrate:down support)
|
|
447
444
|
-- ============================================================================
|
package/package.json
CHANGED
|
@@ -167,12 +167,9 @@ $$ LANGUAGE plpgsql STABLE SECURITY DEFINER;`;
|
|
|
167
167
|
return 'FALSE';
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
const startField = fieldRef.field;
|
|
171
170
|
const targetTable = tableRef.table;
|
|
172
171
|
const targetField = tableRef.targetField;
|
|
173
172
|
|
|
174
|
-
const startValue = `(${recordVar}->>'${startField}')::int`;
|
|
175
|
-
|
|
176
173
|
// Build WHERE clause
|
|
177
174
|
const whereClauses = [];
|
|
178
175
|
|
|
@@ -181,8 +178,10 @@ $$ LANGUAGE plpgsql STABLE SECURITY DEFINER;`;
|
|
|
181
178
|
for (const filterCondition of tableRef.filter) {
|
|
182
179
|
const field = filterCondition.field;
|
|
183
180
|
if (filterCondition.value.type === 'param') {
|
|
184
|
-
// Parameter reference: org_id=$
|
|
185
|
-
|
|
181
|
+
// Parameter reference: org_id=$ means use the filter field name as the param key
|
|
182
|
+
// e.g., acts_for[organisation_id=$] -> (p_params->>'organisation_id')::int
|
|
183
|
+
const paramValue = `(${recordVar}->>'${field}')::int`;
|
|
184
|
+
whereClauses.push(`${targetTable}.${field} = ${paramValue}`);
|
|
186
185
|
} else {
|
|
187
186
|
// Literal value
|
|
188
187
|
whereClauses.push(`${targetTable}.${field} = '${filterCondition.value}'`);
|
|
@@ -239,13 +238,20 @@ $$ LANGUAGE plpgsql STABLE SECURITY DEFINER;`;
|
|
|
239
238
|
// Build relation subqueries
|
|
240
239
|
const relationSelects = this._generateRelationSelects();
|
|
241
240
|
|
|
241
|
+
// Build schema with path mapping (baked in at compile time)
|
|
242
|
+
const pathMapping = this.buildPathMapping();
|
|
243
|
+
const schemaJson = JSON.stringify({
|
|
244
|
+
root: this.rootEntity,
|
|
245
|
+
paths: pathMapping
|
|
246
|
+
});
|
|
247
|
+
|
|
242
248
|
return `CREATE OR REPLACE FUNCTION get_${this.name}(
|
|
243
249
|
p_params JSONB,
|
|
244
250
|
p_user_id INT
|
|
245
251
|
) RETURNS JSONB AS $$
|
|
246
252
|
DECLARE
|
|
247
253
|
${paramDeclarations}
|
|
248
|
-
|
|
254
|
+
v_data JSONB;
|
|
249
255
|
BEGIN
|
|
250
256
|
-- Extract parameters
|
|
251
257
|
${paramExtractions}
|
|
@@ -259,11 +265,15 @@ ${paramExtractions}
|
|
|
259
265
|
SELECT jsonb_build_object(
|
|
260
266
|
'${this.rootEntity}', row_to_json(root.*)${relationSelects}
|
|
261
267
|
)
|
|
262
|
-
INTO
|
|
268
|
+
INTO v_data
|
|
263
269
|
FROM ${this.rootEntity} root
|
|
264
270
|
WHERE ${rootFilter};
|
|
265
271
|
|
|
266
|
-
|
|
272
|
+
-- Return data with embedded schema for atomic updates
|
|
273
|
+
RETURN jsonb_build_object(
|
|
274
|
+
'data', v_data,
|
|
275
|
+
'schema', '${schemaJson}'::jsonb
|
|
276
|
+
);
|
|
267
277
|
END;
|
|
268
278
|
$$ LANGUAGE plpgsql SECURITY DEFINER;`;
|
|
269
279
|
}
|
|
@@ -301,7 +311,7 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
|
|
|
301
311
|
const relIncludes = typeof relConfig === 'object' ? relConfig.include : null;
|
|
302
312
|
|
|
303
313
|
// Build filter condition
|
|
304
|
-
let filterSQL = this._generateRelationFilter(relFilter, relEntity);
|
|
314
|
+
let filterSQL = this._generateRelationFilter(relFilter, relEntity, relConfig);
|
|
305
315
|
|
|
306
316
|
// Build nested includes if any
|
|
307
317
|
let nestedSelect = 'row_to_json(rel.*)';
|
|
@@ -334,11 +344,17 @@ $$ LANGUAGE plpgsql SECURITY DEFINER;`;
|
|
|
334
344
|
/**
|
|
335
345
|
* Generate filter for relation subquery
|
|
336
346
|
* @private
|
|
347
|
+
* @param {string|null} filter - Optional filter expression
|
|
348
|
+
* @param {string} relEntity - Related entity table name
|
|
349
|
+
* @param {Object} relConfig - Full relation config (may contain foreignKey)
|
|
337
350
|
*/
|
|
338
|
-
_generateRelationFilter(filter, relEntity) {
|
|
351
|
+
_generateRelationFilter(filter, relEntity, relConfig) {
|
|
339
352
|
if (!filter) {
|
|
340
|
-
//
|
|
341
|
-
|
|
353
|
+
// Use explicit foreignKey from config, or default to root_id
|
|
354
|
+
const fk = (typeof relConfig === 'object' && relConfig.foreignKey)
|
|
355
|
+
? relConfig.foreignKey
|
|
356
|
+
: `${this.rootEntity}_id`;
|
|
357
|
+
return `rel.${fk} = root.id`;
|
|
342
358
|
}
|
|
343
359
|
|
|
344
360
|
// Parse filter expression like "venue_id=$venue_id"
|
package/src/server/ws.js
CHANGED
|
@@ -322,14 +322,28 @@ export function createRPCHandler(customHandlers = {}) {
|
|
|
322
322
|
try {
|
|
323
323
|
// Execute initial query (this also checks permissions)
|
|
324
324
|
const queryResult = await sql.unsafe(
|
|
325
|
-
`SELECT get_${subscribableName}($1, $2) as
|
|
325
|
+
`SELECT get_${subscribableName}($1, $2) as result`,
|
|
326
326
|
[params, ws.data.user_id]
|
|
327
327
|
);
|
|
328
328
|
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
//
|
|
332
|
-
|
|
329
|
+
const queryData = queryResult[0]?.result;
|
|
330
|
+
|
|
331
|
+
// Check if compiled function returned embedded schema
|
|
332
|
+
// Compiled: { data, schema } | Interpreted: just the data object
|
|
333
|
+
let data, schema;
|
|
334
|
+
if (queryData && queryData.schema && queryData.data !== undefined) {
|
|
335
|
+
// Compiled mode - schema is embedded
|
|
336
|
+
data = queryData.data;
|
|
337
|
+
schema = queryData.schema;
|
|
338
|
+
} else {
|
|
339
|
+
// Interpreted mode - fetch schema from metadata table
|
|
340
|
+
data = queryData;
|
|
341
|
+
const metadata = await getSubscribableMetadata(subscribableName, sql);
|
|
342
|
+
schema = {
|
|
343
|
+
root: metadata.rootEntity,
|
|
344
|
+
paths: metadata.pathMapping
|
|
345
|
+
};
|
|
346
|
+
}
|
|
333
347
|
|
|
334
348
|
// Register subscription in memory
|
|
335
349
|
const subscriptionId = registerSubscription(
|
|
@@ -343,11 +357,7 @@ export function createRPCHandler(customHandlers = {}) {
|
|
|
343
357
|
const result = {
|
|
344
358
|
subscription_id: subscriptionId,
|
|
345
359
|
data,
|
|
346
|
-
|
|
347
|
-
schema: {
|
|
348
|
-
root: metadata.rootEntity,
|
|
349
|
-
paths: metadata.pathMapping
|
|
350
|
-
}
|
|
360
|
+
schema
|
|
351
361
|
};
|
|
352
362
|
|
|
353
363
|
wsLogger.response(method, result, Date.now() - startTime);
|