turbine-orm 0.10.0 → 0.11.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.
@@ -13,6 +13,7 @@
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.QueryInterface = void 0;
16
+ const dialect_js_1 = require("../dialect.js");
16
17
  const errors_js_1 = require("../errors.js");
17
18
  const schema_js_1 = require("../schema.js");
18
19
  const utils_js_1 = require("./utils.js");
@@ -112,6 +113,7 @@ class QueryInterface {
112
113
  warnOnUnlimited;
113
114
  preparedStatementsEnabled;
114
115
  sqlCacheEnabled;
116
+ dialect;
115
117
  /**
116
118
  * Tracks tables that have already triggered an unlimited-query warning so
117
119
  * the user is not spammed once per row. Per-instance state — each
@@ -146,6 +148,7 @@ class QueryInterface {
146
148
  this.warnOnUnlimited = options?.warnOnUnlimited !== false;
147
149
  this.preparedStatementsEnabled = options?.preparedStatements ?? true;
148
150
  this.sqlCacheEnabled = options?.sqlCache !== false;
151
+ this.dialect = options?.dialect ?? dialect_js_1.postgresDialect;
149
152
  // Pre-compute column type lookup maps (TASK-26)
150
153
  this.columnPgTypeMap = new Map();
151
154
  this.columnArrayTypeMap = new Map();
@@ -154,6 +157,14 @@ class QueryInterface {
154
157
  this.columnArrayTypeMap.set(col.name, col.pgArrayType);
155
158
  }
156
159
  }
160
+ /** Quote an identifier through the active SQL dialect. */
161
+ q(name) {
162
+ return this.dialect.quoteIdentifier(name);
163
+ }
164
+ /** Return the active dialect's placeholder for a 1-indexed parameter position. */
165
+ p(index) {
166
+ return this.dialect.paramPlaceholder(index);
167
+ }
157
168
  /**
158
169
  * Return cache hit/miss statistics for this QueryInterface instance.
159
170
  * Useful for monitoring and benchmarking.
@@ -291,11 +302,11 @@ class QueryInterface {
291
302
  // Simple path: plain equality, no operators/null/OR
292
303
  if (!args.with && isSimpleWhere) {
293
304
  const entry = this.acquireSql(ck, () => {
294
- const qt = (0, utils_js_1.quoteIdent)(this.table);
305
+ const qt = this.q(this.table);
295
306
  const tempParams = whereKeys.map((k) => whereObj[k]);
296
- const whereClauses = whereKeys.map((k, i) => `${this.toSqlColumn(k)} = $${i + 1}`);
307
+ const whereClauses = whereKeys.map((k, i) => `${this.toSqlColumn(k)} = ${this.p(i + 1)}`);
297
308
  const whereSql = whereClauses.length > 0 ? ` WHERE ${whereClauses.join(' AND ')}` : '';
298
- const selectExpr = columnsList ? columnsList.map((c) => `${qt}.${(0, utils_js_1.quoteIdent)(c)}`).join(', ') : `${qt}.*`;
309
+ const selectExpr = columnsList ? columnsList.map((c) => `${qt}.${this.q(c)}`).join(', ') : `${qt}.*`;
299
310
  void tempParams; // params are positional, SQL is value-invariant
300
311
  return `SELECT ${selectExpr} FROM ${qt}${whereSql} LIMIT 1`;
301
312
  });
@@ -320,8 +331,8 @@ class QueryInterface {
320
331
  const freshParams = [];
321
332
  const clause = this.buildWhereClause(whereObj, freshParams);
322
333
  const whereSql = clause ? ` WHERE ${clause}` : '';
323
- const qt = (0, utils_js_1.quoteIdent)(this.table);
324
- const selectExpr = columnsList ? columnsList.map((c) => `${qt}.${(0, utils_js_1.quoteIdent)(c)}`).join(', ') : `${qt}.*`;
334
+ const qt = this.q(this.table);
335
+ const selectExpr = columnsList ? columnsList.map((c) => `${qt}.${this.q(c)}`).join(', ') : `${qt}.*`;
325
336
  return `SELECT ${selectExpr} FROM ${qt}${whereSql} LIMIT 1`;
326
337
  });
327
338
  // Collect params
@@ -347,7 +358,7 @@ class QueryInterface {
347
358
  const clause = this.buildWhereClause(whereObj, freshParams);
348
359
  const whereSql = clause ? ` WHERE ${clause}` : '';
349
360
  const selectClause = this.buildSelectWithRelations(this.table, args.with, freshParams, columnsList);
350
- return `SELECT ${selectClause} FROM ${(0, utils_js_1.quoteIdent)(this.table)}${whereSql} LIMIT 1`;
361
+ return `SELECT ${selectClause} FROM ${this.q(this.table)}${whereSql} LIMIT 1`;
351
362
  });
352
363
  // Collect params in exact build order: where first, then with-clause relations
353
364
  this.collectWhereParams(whereObj, params);
@@ -458,7 +469,7 @@ class QueryInterface {
458
469
  return { sql: clause ? ` WHERE ${clause}` : '' };
459
470
  })()
460
471
  : { sql: '' };
461
- const qt = (0, utils_js_1.quoteIdent)(this.table);
472
+ const qt = this.q(this.table);
462
473
  let distinctPrefix = '';
463
474
  if (args?.distinct && args.distinct.length > 0) {
464
475
  const distinctCols = args.distinct.map((k) => this.toSqlColumn(k));
@@ -469,7 +480,7 @@ class QueryInterface {
469
480
  selectClause = this.buildSelectWithRelations(this.table, args.with, freshParams, columnsList);
470
481
  }
471
482
  else if (columnsList) {
472
- selectClause = columnsList.map((c) => `${qt}.${(0, utils_js_1.quoteIdent)(c)}`).join(', ');
483
+ selectClause = columnsList.map((c) => `${qt}.${this.q(c)}`).join(', ');
473
484
  }
474
485
  else {
475
486
  selectClause = `${qt}.*`;
@@ -483,7 +494,7 @@ class QueryInterface {
483
494
  const dir = args.orderBy?.[k] ?? 'asc';
484
495
  const op = dir === 'desc' ? '<' : '>';
485
496
  freshParams.push(v);
486
- return `${qt}.${col} ${op} $${freshParams.length}`;
497
+ return `${qt}.${col} ${op} ${this.p(freshParams.length)}`;
487
498
  });
488
499
  if (freshWhereSql) {
489
500
  sql += ` AND ${cursorConditions.join(' AND ')}`;
@@ -498,11 +509,11 @@ class QueryInterface {
498
509
  }
499
510
  if (effectiveLimit !== undefined) {
500
511
  freshParams.push(Number(effectiveLimit));
501
- sql += ` LIMIT $${freshParams.length}`;
512
+ sql += ` LIMIT ${this.p(freshParams.length)}`;
502
513
  }
503
514
  if (args?.offset !== undefined) {
504
515
  freshParams.push(Number(args.offset));
505
- sql += ` OFFSET $${freshParams.length}`;
516
+ sql += ` OFFSET ${this.p(freshParams.length)}`;
506
517
  }
507
518
  return sql;
508
519
  });
@@ -590,7 +601,7 @@ class QueryInterface {
590
601
  // Acquire a dedicated connection — cursors require a single connection in a transaction
591
602
  const client = await this.pool.connect();
592
603
  const cursorName = `turbine_cursor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
593
- const quotedCursor = (0, utils_js_1.quoteIdent)(cursorName);
604
+ const quotedCursor = this.q(cursorName);
594
605
  try {
595
606
  await client.query('BEGIN');
596
607
  await client.query(`DECLARE ${quotedCursor} NO SCROLL CURSOR FOR ${deferred.sql}`, deferred.params);
@@ -724,8 +735,8 @@ class QueryInterface {
724
735
  const entries = Object.entries(args.data).filter(([, v]) => v !== undefined);
725
736
  const columns = entries.map(([k]) => this.toSqlColumn(k));
726
737
  const params = entries.map(([, v]) => v);
727
- const placeholders = entries.map((_, i) => `$${i + 1}`);
728
- const sql = `INSERT INTO ${(0, utils_js_1.quoteIdent)(this.table)} (${columns.join(', ')}) VALUES (${placeholders.join(', ')}) RETURNING *`;
738
+ const placeholders = entries.map((_, i) => `${this.p(i + 1)}`);
739
+ const sql = `INSERT INTO ${this.q(this.table)} (${columns.join(', ')}) VALUES (${placeholders.join(', ')}) RETURNING *`;
729
740
  return {
730
741
  sql,
731
742
  params,
@@ -754,7 +765,7 @@ class QueryInterface {
754
765
  });
755
766
  }
756
767
  buildCreateMany(args) {
757
- const qt = (0, utils_js_1.quoteIdent)(this.table);
768
+ const qt = this.q(this.table);
758
769
  if (args.data.length === 0) {
759
770
  return {
760
771
  sql: `SELECT * FROM ${qt} WHERE false`,
@@ -775,8 +786,8 @@ class QueryInterface {
775
786
  }
776
787
  // Use actual Postgres types for array casts
777
788
  const typeCasts = columns.map((col) => this.getColumnArrayType(col));
778
- const unnestArgs = columnArrays.map((_, i) => `$${i + 1}::${typeCasts[i]}`);
779
- const quotedColumns = columns.map((c) => (0, utils_js_1.quoteIdent)(c));
789
+ const unnestArgs = columnArrays.map((_, i) => `${this.p(i + 1)}::${typeCasts[i]}`);
790
+ const quotedColumns = columns.map((c) => this.q(c));
780
791
  let sql = `INSERT INTO ${qt} (${quotedColumns.join(', ')}) SELECT * FROM UNNEST(${unnestArgs.join(', ')})`;
781
792
  // skipDuplicates: add ON CONFLICT DO NOTHING
782
793
  if (args.skipDuplicates) {
@@ -814,7 +825,7 @@ class QueryInterface {
814
825
  const whereClause = this.buildWhereClause(whereObj, freshParams);
815
826
  const whereSql = whereClause ? ` WHERE ${whereClause}` : '';
816
827
  this.assertMutationHasPredicate('update', whereSql, args.allowFullTableScan);
817
- return `UPDATE ${(0, utils_js_1.quoteIdent)(this.table)} SET ${setClauses.join(', ')}${whereSql} RETURNING *`;
828
+ return `UPDATE ${this.q(this.table)} SET ${setClauses.join(', ')}${whereSql} RETURNING *`;
818
829
  });
819
830
  // On cache hit, validate predicate
820
831
  if (whereFp === '') {
@@ -863,7 +874,7 @@ class QueryInterface {
863
874
  const clause = this.buildWhereClause(whereObj, freshParams);
864
875
  const whereSql = clause ? ` WHERE ${clause}` : '';
865
876
  this.assertMutationHasPredicate('delete', whereSql, args.allowFullTableScan);
866
- return `DELETE FROM ${(0, utils_js_1.quoteIdent)(this.table)}${whereSql} RETURNING *`;
877
+ return `DELETE FROM ${this.q(this.table)}${whereSql} RETURNING *`;
867
878
  });
868
879
  // On cache hit, still validate the predicate
869
880
  if (whereFp === '') {
@@ -903,7 +914,7 @@ class QueryInterface {
903
914
  const createEntries = Object.entries(args.create).filter(([, v]) => v !== undefined);
904
915
  const columns = createEntries.map(([k]) => this.toSqlColumn(k));
905
916
  const createParams = createEntries.map(([, v]) => v);
906
- const placeholders = createEntries.map((_, i) => `$${i + 1}`);
917
+ const placeholders = createEntries.map((_, i) => `${this.p(i + 1)}`);
907
918
  // The conflict target comes from `where` keys — must be unique/PK columns
908
919
  const conflictKeys = Object.keys(args.where).filter((k) => args.where[k] !== undefined);
909
920
  const conflictColumns = conflictKeys.map((k) => this.toSqlColumn(k));
@@ -911,13 +922,13 @@ class QueryInterface {
911
922
  const updateEntries = Object.entries(args.update).filter(([, v]) => v !== undefined);
912
923
  let paramIdx = createParams.length + 1;
913
924
  const setClauses = updateEntries.map(([k]) => {
914
- const clause = `${this.toSqlColumn(k)} = $${paramIdx}`;
925
+ const clause = `${this.toSqlColumn(k)} = ${this.p(paramIdx)}`;
915
926
  paramIdx++;
916
927
  return clause;
917
928
  });
918
929
  const updateParams = updateEntries.map(([, v]) => v);
919
930
  const params = [...createParams, ...updateParams];
920
- const sql = `INSERT INTO ${(0, utils_js_1.quoteIdent)(this.table)} (${columns.join(', ')}) VALUES (${placeholders.join(', ')})` +
931
+ const sql = `INSERT INTO ${this.q(this.table)} (${columns.join(', ')}) VALUES (${placeholders.join(', ')})` +
921
932
  ` ON CONFLICT (${conflictColumns.join(', ')}) DO UPDATE SET ${setClauses.join(', ')}` +
922
933
  ` RETURNING *`;
923
934
  return {
@@ -962,7 +973,7 @@ class QueryInterface {
962
973
  const whereClause = this.buildWhereClause(whereObj, freshParams);
963
974
  const whereSql = whereClause ? ` WHERE ${whereClause}` : '';
964
975
  this.assertMutationHasPredicate('updateMany', whereSql, args.allowFullTableScan);
965
- return `UPDATE ${(0, utils_js_1.quoteIdent)(this.table)} SET ${setClauses.join(', ')}${whereSql}`;
976
+ return `UPDATE ${this.q(this.table)} SET ${setClauses.join(', ')}${whereSql}`;
966
977
  });
967
978
  if (whereFp === '') {
968
979
  this.assertMutationHasPredicate('updateMany', '', args.allowFullTableScan);
@@ -997,7 +1008,7 @@ class QueryInterface {
997
1008
  const clause = this.buildWhereClause(whereObj, freshParams);
998
1009
  const whereSql = clause ? ` WHERE ${clause}` : '';
999
1010
  this.assertMutationHasPredicate('deleteMany', whereSql, args.allowFullTableScan);
1000
- return `DELETE FROM ${(0, utils_js_1.quoteIdent)(this.table)}${whereSql}`;
1011
+ return `DELETE FROM ${this.q(this.table)}${whereSql}`;
1001
1012
  });
1002
1013
  if (whereFp === '') {
1003
1014
  this.assertMutationHasPredicate('deleteMany', '', args.allowFullTableScan);
@@ -1030,7 +1041,7 @@ class QueryInterface {
1030
1041
  const freshParams = [];
1031
1042
  const clause = args?.where ? this.buildWhereClause(whereObj, freshParams) : null;
1032
1043
  const whereSql = clause ? ` WHERE ${clause}` : '';
1033
- return `SELECT COUNT(*)::int AS count FROM ${(0, utils_js_1.quoteIdent)(this.table)}${whereSql}`;
1044
+ return `SELECT COUNT(*)::int AS count FROM ${this.q(this.table)}${whereSql}`;
1034
1045
  });
1035
1046
  if (args?.where) {
1036
1047
  this.collectWhereParams(whereObj, params);
@@ -1063,7 +1074,7 @@ class QueryInterface {
1063
1074
  }
1064
1075
  }
1065
1076
  const groupColsRaw = args.by.map((k) => this.toColumn(k));
1066
- const groupCols = groupColsRaw.map((c) => (0, utils_js_1.quoteIdent)(c));
1077
+ const groupCols = groupColsRaw.map((c) => this.q(c));
1067
1078
  const { sql: whereSql, params } = args.where ? this.buildWhere(args.where) : { sql: '', params: [] };
1068
1079
  // Build SELECT expressions: group-by columns + aggregate functions
1069
1080
  const selectExprs = [...groupCols];
@@ -1077,7 +1088,7 @@ class QueryInterface {
1077
1088
  for (const [field, enabled] of Object.entries(args._sum)) {
1078
1089
  if (enabled) {
1079
1090
  const col = this.toColumn(field);
1080
- selectExprs.push(`SUM(${(0, utils_js_1.quoteIdent)(col)}) AS ${(0, utils_js_1.quoteIdent)(`_sum_${col}`)}`);
1091
+ selectExprs.push(`SUM(${this.q(col)}) AS ${this.q(`_sum_${col}`)}`);
1081
1092
  }
1082
1093
  }
1083
1094
  }
@@ -1086,7 +1097,7 @@ class QueryInterface {
1086
1097
  for (const [field, enabled] of Object.entries(args._avg)) {
1087
1098
  if (enabled) {
1088
1099
  const col = this.toColumn(field);
1089
- selectExprs.push(`AVG(${(0, utils_js_1.quoteIdent)(col)})::float AS ${(0, utils_js_1.quoteIdent)(`_avg_${col}`)}`);
1100
+ selectExprs.push(`AVG(${this.q(col)})::float AS ${this.q(`_avg_${col}`)}`);
1090
1101
  }
1091
1102
  }
1092
1103
  }
@@ -1095,7 +1106,7 @@ class QueryInterface {
1095
1106
  for (const [field, enabled] of Object.entries(args._min)) {
1096
1107
  if (enabled) {
1097
1108
  const col = this.toColumn(field);
1098
- selectExprs.push(`MIN(${(0, utils_js_1.quoteIdent)(col)}) AS ${(0, utils_js_1.quoteIdent)(`_min_${col}`)}`);
1109
+ selectExprs.push(`MIN(${this.q(col)}) AS ${this.q(`_min_${col}`)}`);
1099
1110
  }
1100
1111
  }
1101
1112
  }
@@ -1104,11 +1115,11 @@ class QueryInterface {
1104
1115
  for (const [field, enabled] of Object.entries(args._max)) {
1105
1116
  if (enabled) {
1106
1117
  const col = this.toColumn(field);
1107
- selectExprs.push(`MAX(${(0, utils_js_1.quoteIdent)(col)}) AS ${(0, utils_js_1.quoteIdent)(`_max_${col}`)}`);
1118
+ selectExprs.push(`MAX(${this.q(col)}) AS ${this.q(`_max_${col}`)}`);
1108
1119
  }
1109
1120
  }
1110
1121
  }
1111
- let sql = `SELECT ${selectExprs.join(', ')} FROM ${(0, utils_js_1.quoteIdent)(this.table)}${whereSql} GROUP BY ${groupCols.join(', ')}`;
1122
+ let sql = `SELECT ${selectExprs.join(', ')} FROM ${this.q(this.table)}${whereSql} GROUP BY ${groupCols.join(', ')}`;
1112
1123
  // ORDER BY
1113
1124
  if (args.orderBy) {
1114
1125
  sql += ` ORDER BY ${this.buildOrderBy(args.orderBy)}`;
@@ -1216,7 +1227,7 @@ class QueryInterface {
1216
1227
  for (const [field, enabled] of Object.entries(args._count)) {
1217
1228
  if (enabled) {
1218
1229
  const col = this.toColumn(field);
1219
- selectExprs.push(`COUNT(${(0, utils_js_1.quoteIdent)(col)})::int AS ${(0, utils_js_1.quoteIdent)(`_count_${col}`)}`);
1230
+ selectExprs.push(`COUNT(${this.q(col)})::int AS ${this.q(`_count_${col}`)}`);
1220
1231
  }
1221
1232
  }
1222
1233
  }
@@ -1225,7 +1236,7 @@ class QueryInterface {
1225
1236
  for (const [field, enabled] of Object.entries(args._sum)) {
1226
1237
  if (enabled) {
1227
1238
  const col = this.toColumn(field);
1228
- selectExprs.push(`SUM(${(0, utils_js_1.quoteIdent)(col)}) AS ${(0, utils_js_1.quoteIdent)(`_sum_${col}`)}`);
1239
+ selectExprs.push(`SUM(${this.q(col)}) AS ${this.q(`_sum_${col}`)}`);
1229
1240
  }
1230
1241
  }
1231
1242
  }
@@ -1234,7 +1245,7 @@ class QueryInterface {
1234
1245
  for (const [field, enabled] of Object.entries(args._avg)) {
1235
1246
  if (enabled) {
1236
1247
  const col = this.toColumn(field);
1237
- selectExprs.push(`AVG(${(0, utils_js_1.quoteIdent)(col)})::float AS ${(0, utils_js_1.quoteIdent)(`_avg_${col}`)}`);
1248
+ selectExprs.push(`AVG(${this.q(col)})::float AS ${this.q(`_avg_${col}`)}`);
1238
1249
  }
1239
1250
  }
1240
1251
  }
@@ -1243,7 +1254,7 @@ class QueryInterface {
1243
1254
  for (const [field, enabled] of Object.entries(args._min)) {
1244
1255
  if (enabled) {
1245
1256
  const col = this.toColumn(field);
1246
- selectExprs.push(`MIN(${(0, utils_js_1.quoteIdent)(col)}) AS ${(0, utils_js_1.quoteIdent)(`_min_${col}`)}`);
1257
+ selectExprs.push(`MIN(${this.q(col)}) AS ${this.q(`_min_${col}`)}`);
1247
1258
  }
1248
1259
  }
1249
1260
  }
@@ -1252,14 +1263,14 @@ class QueryInterface {
1252
1263
  for (const [field, enabled] of Object.entries(args._max)) {
1253
1264
  if (enabled) {
1254
1265
  const col = this.toColumn(field);
1255
- selectExprs.push(`MAX(${(0, utils_js_1.quoteIdent)(col)}) AS ${(0, utils_js_1.quoteIdent)(`_max_${col}`)}`);
1266
+ selectExprs.push(`MAX(${this.q(col)}) AS ${this.q(`_max_${col}`)}`);
1256
1267
  }
1257
1268
  }
1258
1269
  }
1259
1270
  if (selectExprs.length === 0) {
1260
1271
  selectExprs.push('COUNT(*)::int AS _count');
1261
1272
  }
1262
- const sql = `SELECT ${selectExprs.join(', ')} FROM ${(0, utils_js_1.quoteIdent)(this.table)}${whereSql}`;
1273
+ const sql = `SELECT ${selectExprs.join(', ')} FROM ${this.q(this.table)}${whereSql}`;
1263
1274
  return {
1264
1275
  sql,
1265
1276
  params,
@@ -1376,7 +1387,7 @@ class QueryInterface {
1376
1387
  }
1377
1388
  /** Convert camelCase field name to a double-quoted SQL identifier */
1378
1389
  toSqlColumn(field) {
1379
- return (0, utils_js_1.quoteIdent)(this.toColumn(field));
1390
+ return this.q(this.toColumn(field));
1380
1391
  }
1381
1392
  /**
1382
1393
  * Build a single SET clause entry for update/updateMany.
@@ -1409,7 +1420,7 @@ class QueryInterface {
1409
1420
  const opValue = v[op];
1410
1421
  if (op === 'set') {
1411
1422
  params.push(opValue);
1412
- return `${col} = $${params.length}`;
1423
+ return `${col} = ${this.p(params.length)}`;
1413
1424
  }
1414
1425
  // Arithmetic operators: must be finite numbers
1415
1426
  if (typeof opValue !== 'number' || !Number.isFinite(opValue)) {
@@ -1417,19 +1428,19 @@ class QueryInterface {
1417
1428
  }
1418
1429
  if (op === 'increment') {
1419
1430
  params.push(opValue);
1420
- return `${col} = ${col} + $${params.length}`;
1431
+ return `${col} = ${col} + ${this.p(params.length)}`;
1421
1432
  }
1422
1433
  if (op === 'decrement') {
1423
1434
  params.push(opValue);
1424
- return `${col} = ${col} - $${params.length}`;
1435
+ return `${col} = ${col} - ${this.p(params.length)}`;
1425
1436
  }
1426
1437
  if (op === 'multiply') {
1427
1438
  params.push(opValue);
1428
- return `${col} = ${col} * $${params.length}`;
1439
+ return `${col} = ${col} * ${this.p(params.length)}`;
1429
1440
  }
1430
1441
  if (op === 'divide') {
1431
1442
  params.push(opValue);
1432
- return `${col} = ${col} / $${params.length}`;
1443
+ return `${col} = ${col} / ${this.p(params.length)}`;
1433
1444
  }
1434
1445
  }
1435
1446
  // Fall through: multi-key objects or non-operator single-key objects
@@ -1437,7 +1448,7 @@ class QueryInterface {
1437
1448
  }
1438
1449
  // Plain value (including null, Date, Buffer, arrays, JSON objects)
1439
1450
  params.push(value);
1440
- return `${col} = $${params.length}`;
1451
+ return `${col} = ${this.p(params.length)}`;
1441
1452
  }
1442
1453
  // =========================================================================
1443
1454
  // Fingerprinting — value-invariant shape keys for SQL cache lookup
@@ -1960,7 +1971,7 @@ class QueryInterface {
1960
1971
  }
1961
1972
  }
1962
1973
  const rawColumn = this.toColumn(key);
1963
- const column = (0, utils_js_1.quoteIdent)(rawColumn);
1974
+ const column = this.q(rawColumn);
1964
1975
  // Handle null → IS NULL
1965
1976
  if (value === null) {
1966
1977
  andClauses.push(`${column} IS NULL`);
@@ -2010,7 +2021,7 @@ class QueryInterface {
2010
2021
  }
2011
2022
  // Plain equality
2012
2023
  params.push(value);
2013
- andClauses.push(`${column} = $${params.length}`);
2024
+ andClauses.push(`${column} = ${this.p(params.length)}`);
2014
2025
  }
2015
2026
  if (andClauses.length === 0)
2016
2027
  return null;
@@ -2025,18 +2036,18 @@ class QueryInterface {
2025
2036
  const targetMeta = this.schema.tables[targetTable];
2026
2037
  if (!targetMeta)
2027
2038
  return null;
2028
- const qt = (0, utils_js_1.quoteIdent)(targetTable);
2029
- const qSelf = (0, utils_js_1.quoteIdent)(this.table);
2039
+ const qt = this.q(targetTable);
2040
+ const qSelf = this.q(this.table);
2030
2041
  const clauses = [];
2031
2042
  // Correlation: link child table to parent table (supports composite FKs)
2032
2043
  let correlation;
2033
2044
  if (relDef.type === 'hasMany' || relDef.type === 'hasOne') {
2034
2045
  // parent.pk = child.fk
2035
- correlation = (0, utils_js_1.buildCorrelation)(qt, relDef.foreignKey, qSelf, relDef.referenceKey);
2046
+ correlation = this.dialect.buildCorrelation(qt, relDef.foreignKey, qSelf, relDef.referenceKey);
2036
2047
  }
2037
2048
  else {
2038
2049
  // belongsTo: parent.fk = child.pk
2039
- correlation = (0, utils_js_1.buildCorrelation)(qt, relDef.referenceKey, qSelf, relDef.foreignKey);
2050
+ correlation = this.dialect.buildCorrelation(qt, relDef.referenceKey, qSelf, relDef.foreignKey);
2040
2051
  }
2041
2052
  // "some": EXISTS (SELECT 1 FROM target WHERE correlation AND filter)
2042
2053
  if (filterObj.some !== undefined) {
@@ -2073,7 +2084,7 @@ class QueryInterface {
2073
2084
  const meta = this.schema.tables[targetTable];
2074
2085
  if (!meta)
2075
2086
  return null;
2076
- const qt = (0, utils_js_1.quoteIdent)(targetTable);
2087
+ const qt = this.q(targetTable);
2077
2088
  const conditions = [];
2078
2089
  for (const [field, value] of Object.entries(subWhere)) {
2079
2090
  if (value === undefined)
@@ -2083,7 +2094,7 @@ class QueryInterface {
2083
2094
  throw new errors_js_1.ValidationError(`[turbine] Unknown field "${field}" in relation filter for table "${targetTable}". ` +
2084
2095
  `Known fields: ${Object.keys(meta.columnMap).join(', ') || '(none)'}.`);
2085
2096
  }
2086
- const qCol = `${qt}.${(0, utils_js_1.quoteIdent)(col)}`;
2097
+ const qCol = `${qt}.${this.q(col)}`;
2087
2098
  if (value === null) {
2088
2099
  conditions.push(`${qCol} IS NULL`);
2089
2100
  continue;
@@ -2094,7 +2105,7 @@ class QueryInterface {
2094
2105
  continue;
2095
2106
  }
2096
2107
  params.push(value);
2097
- conditions.push(`${qCol} = $${params.length}`);
2108
+ conditions.push(`${qCol} = ${this.p(params.length)}`);
2098
2109
  }
2099
2110
  return conditions.length > 0 ? conditions.join(' AND ') : null;
2100
2111
  }
@@ -2106,19 +2117,19 @@ class QueryInterface {
2106
2117
  const clauses = [];
2107
2118
  if (op.gt !== undefined) {
2108
2119
  params.push(op.gt);
2109
- clauses.push(`${column} > $${params.length}`);
2120
+ clauses.push(`${column} > ${this.p(params.length)}`);
2110
2121
  }
2111
2122
  if (op.gte !== undefined) {
2112
2123
  params.push(op.gte);
2113
- clauses.push(`${column} >= $${params.length}`);
2124
+ clauses.push(`${column} >= ${this.p(params.length)}`);
2114
2125
  }
2115
2126
  if (op.lt !== undefined) {
2116
2127
  params.push(op.lt);
2117
- clauses.push(`${column} < $${params.length}`);
2128
+ clauses.push(`${column} < ${this.p(params.length)}`);
2118
2129
  }
2119
2130
  if (op.lte !== undefined) {
2120
2131
  params.push(op.lte);
2121
- clauses.push(`${column} <= $${params.length}`);
2132
+ clauses.push(`${column} <= ${this.p(params.length)}`);
2122
2133
  }
2123
2134
  if (op.not !== undefined) {
2124
2135
  if (op.not === null) {
@@ -2126,30 +2137,29 @@ class QueryInterface {
2126
2137
  }
2127
2138
  else {
2128
2139
  params.push(op.not);
2129
- clauses.push(`${column} != $${params.length}`);
2140
+ clauses.push(`${column} != ${this.p(params.length)}`);
2130
2141
  }
2131
2142
  }
2132
2143
  if (op.in !== undefined) {
2133
2144
  params.push(op.in);
2134
- clauses.push(`${column} = ANY($${params.length})`);
2145
+ clauses.push(`${column} = ANY(${this.p(params.length)})`);
2135
2146
  }
2136
2147
  if (op.notIn !== undefined) {
2137
2148
  params.push(op.notIn);
2138
- clauses.push(`${column} != ALL($${params.length})`);
2149
+ clauses.push(`${column} != ALL(${this.p(params.length)})`);
2139
2150
  }
2140
- // Use ILIKE for case-insensitive mode, LIKE otherwise
2141
- const likeOp = op.mode === 'insensitive' ? 'ILIKE' : 'LIKE';
2151
+ const buildLikeClause = (paramRef) => op.mode === 'insensitive' ? this.dialect.buildInsensitiveLike(column, paramRef) : `${column} LIKE ${paramRef}`;
2142
2152
  if (op.contains !== undefined) {
2143
2153
  params.push(`%${(0, utils_js_1.escapeLike)(op.contains)}%`);
2144
- clauses.push(`${column} ${likeOp} $${params.length} ESCAPE '\\'`);
2154
+ clauses.push(`${buildLikeClause(this.p(params.length))} ESCAPE '\\'`);
2145
2155
  }
2146
2156
  if (op.startsWith !== undefined) {
2147
2157
  params.push(`${(0, utils_js_1.escapeLike)(op.startsWith)}%`);
2148
- clauses.push(`${column} ${likeOp} $${params.length} ESCAPE '\\'`);
2158
+ clauses.push(`${buildLikeClause(this.p(params.length))} ESCAPE '\\'`);
2149
2159
  }
2150
2160
  if (op.endsWith !== undefined) {
2151
2161
  params.push(`%${(0, utils_js_1.escapeLike)(op.endsWith)}`);
2152
- clauses.push(`${column} ${likeOp} $${params.length} ESCAPE '\\'`);
2162
+ clauses.push(`${buildLikeClause(this.p(params.length))} ESCAPE '\\'`);
2153
2163
  }
2154
2164
  return clauses;
2155
2165
  }
@@ -2309,8 +2319,8 @@ class QueryInterface {
2309
2319
  if (!meta)
2310
2320
  throw new errors_js_1.ValidationError(`[turbine] Unknown table "${table}"`);
2311
2321
  const cols = columnsList ?? meta.allColumns;
2312
- const qtbl = (0, utils_js_1.quoteIdent)(table);
2313
- const baseCols = cols.map((col) => `${qtbl}.${(0, utils_js_1.quoteIdent)(col)}`).join(', ');
2322
+ const qtbl = this.q(table);
2323
+ const baseCols = cols.map((col) => `${qtbl}.${this.q(col)}`).join(', ');
2314
2324
  const relationSelects = [];
2315
2325
  const aliasCounter = { n: 0 };
2316
2326
  for (const [relName, relSpec] of Object.entries(withClause)) {
@@ -2321,7 +2331,7 @@ class QueryInterface {
2321
2331
  }
2322
2332
  // The main table is not aliased, so pass table name as parentRef
2323
2333
  const subquery = this.buildRelationSubquery(relDef, relSpec, params, table, aliasCounter, depth, path);
2324
- relationSelects.push(`(${subquery}) AS ${(0, utils_js_1.quoteIdent)(relName)}`);
2334
+ relationSelects.push(`(${subquery}) AS ${this.q(relName)}`);
2325
2335
  }
2326
2336
  return [baseCols, ...relationSelects].join(', ');
2327
2337
  }
@@ -2383,7 +2393,7 @@ class QueryInterface {
2383
2393
  * 8. **Parameter threading:** All user-supplied values (where filters, limit) are
2384
2394
  * pushed to the shared `params` array with `$N` placeholders. No string
2385
2395
  * interpolation of user data ever occurs -- all identifiers go through
2386
- * `quoteIdent()` and all values are parameterized.
2396
+ * `this.q()` and all values are parameterized.
2387
2397
  *
2388
2398
  * ### Example output (hasMany with nested relation)
2389
2399
  * ```sql
@@ -2447,8 +2457,11 @@ class QueryInterface {
2447
2457
  .map(([k]) => targetMeta.columnMap[k] ?? (0, schema_js_1.camelToSnake)(k)));
2448
2458
  targetColumns = targetMeta.allColumns.filter((col) => !omittedFields.has(col));
2449
2459
  }
2450
- // Build json_build_object pairs for resolved columns
2451
- const jsonPairs = targetColumns.map((col) => `'${(0, utils_js_1.escSingleQuote)(targetMeta.reverseColumnMap[col] ?? (0, schema_js_1.snakeToCamel)(col))}', ${alias}.${(0, utils_js_1.quoteIdent)(col)}`);
2460
+ // Build JSON object pairs for resolved columns
2461
+ const jsonPairs = targetColumns.map((col) => [
2462
+ targetMeta.reverseColumnMap[col] ?? (0, schema_js_1.snakeToCamel)(col),
2463
+ `${alias}.${this.q(col)}`,
2464
+ ]);
2452
2465
  // Determine if this hasMany will take the wrapped subquery path (LIMIT or ORDER BY).
2453
2466
  // When wrapping, nested relations are built in the wrapped path referencing innerAlias,
2454
2467
  // so we must NOT build them here (they would push orphaned params).
@@ -2464,14 +2477,14 @@ class QueryInterface {
2464
2477
  // Recursively build nested subquery, passing THIS alias as the parent reference
2465
2478
  const nestedSubquery = this.buildRelationSubquery(nestedRelDef, nestedSpec, params, alias, aliasCounter, currentDepth + 1, [...currentPath, relDef.name]);
2466
2479
  // Use '[]'::json for hasMany (empty array), NULL for belongsTo/hasOne (no object)
2467
- const fallback = nestedRelDef.type === 'hasMany' ? "'[]'::json" : 'NULL';
2468
- jsonPairs.push(`'${(0, utils_js_1.escSingleQuote)(nestedRelName)}', COALESCE((${nestedSubquery}), ${fallback})`);
2480
+ const fallback = nestedRelDef.type === 'hasMany' ? this.dialect.emptyJsonArrayLiteral : this.dialect.nullJsonLiteral;
2481
+ jsonPairs.push([nestedRelName, `COALESCE((${nestedSubquery}), ${fallback})`]);
2469
2482
  }
2470
2483
  }
2471
- const jsonObj = `json_build_object(${jsonPairs.join(', ')})`;
2484
+ const jsonObj = this.dialect.buildJsonObject(jsonPairs);
2472
2485
  // Quote parent ref — can be a table name or auto-generated alias
2473
- const qParent = (0, utils_js_1.quoteIdent)(parentRef);
2474
- const qTarget = (0, utils_js_1.quoteIdent)(targetTable);
2486
+ const qParent = this.q(parentRef);
2487
+ const qTarget = this.q(targetTable);
2475
2488
  // Build ORDER BY for json_agg
2476
2489
  let orderClause = '';
2477
2490
  if (spec !== true && spec.orderBy) {
@@ -2482,7 +2495,7 @@ class QueryInterface {
2482
2495
  throw new errors_js_1.ValidationError(`[turbine] Unknown column "${k}" in orderBy for table "${targetTable}"`);
2483
2496
  }
2484
2497
  const safeDir = dir.toLowerCase() === 'desc' ? 'DESC' : 'ASC';
2485
- return `${alias}.${(0, utils_js_1.quoteIdent)(col)} ${safeDir}`;
2498
+ return `${alias}.${this.q(col)} ${safeDir}`;
2486
2499
  })
2487
2500
  .join(', ');
2488
2501
  orderClause = ` ORDER BY ${orders}`;
@@ -2493,10 +2506,10 @@ class QueryInterface {
2493
2506
  // Supports composite foreign keys (string[]) via buildCorrelation.
2494
2507
  let whereClause;
2495
2508
  if (relDef.type === 'belongsTo' || relDef.type === 'hasOne') {
2496
- whereClause = (0, utils_js_1.buildCorrelation)(alias, relDef.referenceKey, qParent, relDef.foreignKey);
2509
+ whereClause = this.dialect.buildCorrelation(alias, relDef.referenceKey, qParent, relDef.foreignKey);
2497
2510
  }
2498
2511
  else {
2499
- whereClause = (0, utils_js_1.buildCorrelation)(alias, relDef.foreignKey, qParent, relDef.referenceKey);
2512
+ whereClause = this.dialect.buildCorrelation(alias, relDef.foreignKey, qParent, relDef.referenceKey);
2500
2513
  }
2501
2514
  // Additional filters — properly parameterized
2502
2515
  if (spec !== true && spec.where) {
@@ -2506,14 +2519,14 @@ class QueryInterface {
2506
2519
  throw new errors_js_1.ValidationError(`[turbine] Unknown column "${k}" in where for table "${targetTable}"`);
2507
2520
  }
2508
2521
  params.push(v);
2509
- whereClause += ` AND ${alias}.${(0, utils_js_1.quoteIdent)(col)} = $${params.length}`;
2522
+ whereClause += ` AND ${alias}.${this.q(col)} = ${this.p(params.length)}`;
2510
2523
  }
2511
2524
  }
2512
2525
  // LIMIT
2513
2526
  let limitClause = '';
2514
2527
  if (spec !== true && spec.limit) {
2515
2528
  params.push(Number(spec.limit));
2516
- limitClause = ` LIMIT $${params.length}`;
2529
+ limitClause = ` LIMIT ${this.p(params.length)}`;
2517
2530
  }
2518
2531
  if (relDef.type === 'hasMany') {
2519
2532
  // When LIMIT or ORDER BY is used, wrap in a subquery so LIMIT applies to rows
@@ -2522,9 +2535,12 @@ class QueryInterface {
2522
2535
  const innerAlias = `${alias}i`;
2523
2536
  // Rewrite: SELECT json_agg(json_build_object(...)) FROM (SELECT * FROM table WHERE ... ORDER BY ... LIMIT N) AS alias
2524
2537
  // Inner SELECT always needs all columns for WHERE/ORDER to work; json_build_object filters later
2525
- const innerSql = `SELECT ${targetMeta.allColumns.map((c) => `${alias}.${(0, utils_js_1.quoteIdent)(c)}`).join(', ')} FROM ${qTarget} ${alias} WHERE ${whereClause}${orderClause}${limitClause}`;
2538
+ const innerSql = `SELECT ${targetMeta.allColumns.map((c) => `${alias}.${this.q(c)}`).join(', ')} FROM ${qTarget} ${alias} WHERE ${whereClause}${orderClause}${limitClause}`;
2526
2539
  // For the json_build_object, reference the inner alias — only include resolved columns
2527
- const innerJsonPairs = targetColumns.map((col) => `'${(0, utils_js_1.escSingleQuote)(targetMeta.reverseColumnMap[col] ?? (0, schema_js_1.snakeToCamel)(col))}', ${innerAlias}.${(0, utils_js_1.quoteIdent)(col)}`);
2540
+ const innerJsonPairs = targetColumns.map((col) => [
2541
+ targetMeta.reverseColumnMap[col] ?? (0, schema_js_1.snakeToCamel)(col),
2542
+ `${innerAlias}.${this.q(col)}`,
2543
+ ]);
2528
2544
  // Build nested relation subqueries referencing innerAlias
2529
2545
  if (spec !== true && spec.with) {
2530
2546
  for (const [nestedRelName, nestedSpec] of Object.entries(spec.with)) {
@@ -2534,14 +2550,14 @@ class QueryInterface {
2534
2550
  `Available: ${Object.keys(targetMeta.relations).join(', ')}`);
2535
2551
  }
2536
2552
  const nestedSub = this.buildRelationSubquery(nestedRelDef, nestedSpec, params, innerAlias, aliasCounter, currentDepth + 1, [...currentPath, relDef.name]);
2537
- const fallback = nestedRelDef.type === 'hasMany' ? "'[]'::json" : 'NULL';
2538
- innerJsonPairs.push(`'${(0, utils_js_1.escSingleQuote)(nestedRelName)}', COALESCE((${nestedSub}), ${fallback})`);
2553
+ const fallback = nestedRelDef.type === 'hasMany' ? this.dialect.emptyJsonArrayLiteral : this.dialect.nullJsonLiteral;
2554
+ innerJsonPairs.push([nestedRelName, `COALESCE((${nestedSub}), ${fallback})`]);
2539
2555
  }
2540
2556
  }
2541
- const innerJsonObj = `json_build_object(${innerJsonPairs.join(', ')})`;
2542
- return `SELECT COALESCE(json_agg(${innerJsonObj}), '[]'::json) FROM (${innerSql}) ${innerAlias}`;
2557
+ const innerJsonObj = this.dialect.buildJsonObject(innerJsonPairs);
2558
+ return `SELECT ${this.dialect.buildJsonArrayAgg(innerJsonObj)} FROM (${innerSql}) ${innerAlias}`;
2543
2559
  }
2544
- return `SELECT COALESCE(json_agg(${jsonObj}${orderClause}), '[]'::json) FROM ${qTarget} ${alias} WHERE ${whereClause}`;
2560
+ return `SELECT ${this.dialect.buildJsonArrayAgg(jsonObj, orderClause.trim() || undefined)} FROM ${qTarget} ${alias} WHERE ${whereClause}`;
2545
2561
  }
2546
2562
  // belongsTo / hasOne — return single object
2547
2563
  return `SELECT ${jsonObj} FROM ${qTarget} ${alias} WHERE ${whereClause} LIMIT 1`;
@@ -2588,22 +2604,22 @@ class QueryInterface {
2588
2604
  params.push(filter.path);
2589
2605
  const pathParam = params.length;
2590
2606
  params.push(String(filter.equals));
2591
- clauses.push(`${column} #>> $${pathParam}::text[] = $${params.length}`);
2607
+ clauses.push(`${this.dialect.buildJsonPathExtract(column, this.p(pathParam))} = ${this.p(params.length)}`);
2592
2608
  }
2593
2609
  else if (filter.equals !== undefined) {
2594
2610
  // Containment equality: column @> $N::jsonb
2595
2611
  params.push(JSON.stringify(filter.equals));
2596
- clauses.push(`${column} @> $${params.length}::jsonb`);
2612
+ clauses.push(this.dialect.buildJsonContains(column, this.p(params.length)));
2597
2613
  }
2598
2614
  if (filter.contains !== undefined) {
2599
2615
  // Containment: column @> $N::jsonb
2600
2616
  params.push(JSON.stringify(filter.contains));
2601
- clauses.push(`${column} @> $${params.length}::jsonb`);
2617
+ clauses.push(this.dialect.buildJsonContains(column, this.p(params.length)));
2602
2618
  }
2603
2619
  if (filter.hasKey !== undefined) {
2604
2620
  // Key existence: column ? $N
2605
2621
  params.push(filter.hasKey);
2606
- clauses.push(`${column} ? $${params.length}`);
2622
+ clauses.push(`${column} ? ${this.p(params.length)}`);
2607
2623
  }
2608
2624
  return clauses;
2609
2625
  }
@@ -2617,17 +2633,17 @@ class QueryInterface {
2617
2633
  if (filter.has !== undefined) {
2618
2634
  // value = ANY(column)
2619
2635
  params.push(filter.has);
2620
- clauses.push(`$${params.length} = ANY(${column})`);
2636
+ clauses.push(`${this.p(params.length)} = ANY(${column})`);
2621
2637
  }
2622
2638
  if (filter.hasEvery !== undefined) {
2623
2639
  // column @> ARRAY[...]::type[]
2624
2640
  params.push(filter.hasEvery);
2625
- clauses.push(`${column} @> $${params.length}::${elementType}[]`);
2641
+ clauses.push(`${column} @> ${this.p(params.length)}::${elementType}[]`);
2626
2642
  }
2627
2643
  if (filter.hasSome !== undefined) {
2628
2644
  // column && ARRAY[...]::type[]
2629
2645
  params.push(filter.hasSome);
2630
- clauses.push(`${column} && $${params.length}::${elementType}[]`);
2646
+ clauses.push(`${column} && ${this.p(params.length)}::${elementType}[]`);
2631
2647
  }
2632
2648
  if (filter.isEmpty === true) {
2633
2649
  // array_length(column, 1) IS NULL