turbine-orm 0.10.0 → 0.12.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/dist/cjs/client.js +1 -0
- package/dist/cjs/dialect.js +79 -0
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/query/builder.js +136 -113
- package/dist/cjs/query/index.js +3 -1
- package/dist/client.d.ts +3 -0
- package/dist/client.js +1 -0
- package/dist/dialect.d.ts +111 -0
- package/dist/dialect.js +77 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/query/builder.d.ts +9 -1
- package/dist/query/builder.js +137 -114
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +1 -0
- package/package.json +3 -3
package/dist/query/builder.js
CHANGED
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
* Schema-driven: all column names, types, and relations come from introspected
|
|
11
11
|
* metadata — nothing is hardcoded.
|
|
12
12
|
*/
|
|
13
|
+
import { postgresDialect } from '../dialect.js';
|
|
13
14
|
import { CircularRelationError, NotFoundError, RelationError, TimeoutError, ValidationError, wrapPgError, } from '../errors.js';
|
|
14
15
|
import { camelToSnake, snakeToCamel } from '../schema.js';
|
|
15
|
-
import {
|
|
16
|
+
import { escapeLike, LRUCache, OPERATOR_KEYS, sqlToPreparedName } from './utils.js';
|
|
16
17
|
// ---------------------------------------------------------------------------
|
|
17
18
|
// Internal detection helpers — used by QueryInterface
|
|
18
19
|
// ---------------------------------------------------------------------------
|
|
@@ -109,6 +110,7 @@ export class QueryInterface {
|
|
|
109
110
|
warnOnUnlimited;
|
|
110
111
|
preparedStatementsEnabled;
|
|
111
112
|
sqlCacheEnabled;
|
|
113
|
+
dialect;
|
|
112
114
|
/**
|
|
113
115
|
* Tracks tables that have already triggered an unlimited-query warning so
|
|
114
116
|
* the user is not spammed once per row. Per-instance state — each
|
|
@@ -143,6 +145,7 @@ export class QueryInterface {
|
|
|
143
145
|
this.warnOnUnlimited = options?.warnOnUnlimited !== false;
|
|
144
146
|
this.preparedStatementsEnabled = options?.preparedStatements ?? true;
|
|
145
147
|
this.sqlCacheEnabled = options?.sqlCache !== false;
|
|
148
|
+
this.dialect = options?.dialect ?? postgresDialect;
|
|
146
149
|
// Pre-compute column type lookup maps (TASK-26)
|
|
147
150
|
this.columnPgTypeMap = new Map();
|
|
148
151
|
this.columnArrayTypeMap = new Map();
|
|
@@ -151,6 +154,14 @@ export class QueryInterface {
|
|
|
151
154
|
this.columnArrayTypeMap.set(col.name, col.pgArrayType);
|
|
152
155
|
}
|
|
153
156
|
}
|
|
157
|
+
/** Quote an identifier through the active SQL dialect. */
|
|
158
|
+
q(name) {
|
|
159
|
+
return this.dialect.quoteIdentifier(name);
|
|
160
|
+
}
|
|
161
|
+
/** Return the active dialect's placeholder for a 1-indexed parameter position. */
|
|
162
|
+
p(index) {
|
|
163
|
+
return this.dialect.paramPlaceholder(index);
|
|
164
|
+
}
|
|
154
165
|
/**
|
|
155
166
|
* Return cache hit/miss statistics for this QueryInterface instance.
|
|
156
167
|
* Useful for monitoring and benchmarking.
|
|
@@ -288,11 +299,11 @@ export class QueryInterface {
|
|
|
288
299
|
// Simple path: plain equality, no operators/null/OR
|
|
289
300
|
if (!args.with && isSimpleWhere) {
|
|
290
301
|
const entry = this.acquireSql(ck, () => {
|
|
291
|
-
const qt =
|
|
302
|
+
const qt = this.q(this.table);
|
|
292
303
|
const tempParams = whereKeys.map((k) => whereObj[k]);
|
|
293
|
-
const whereClauses = whereKeys.map((k, i) => `${this.toSqlColumn(k)} =
|
|
304
|
+
const whereClauses = whereKeys.map((k, i) => `${this.toSqlColumn(k)} = ${this.p(i + 1)}`);
|
|
294
305
|
const whereSql = whereClauses.length > 0 ? ` WHERE ${whereClauses.join(' AND ')}` : '';
|
|
295
|
-
const selectExpr = columnsList ? columnsList.map((c) => `${qt}.${
|
|
306
|
+
const selectExpr = columnsList ? columnsList.map((c) => `${qt}.${this.q(c)}`).join(', ') : `${qt}.*`;
|
|
296
307
|
void tempParams; // params are positional, SQL is value-invariant
|
|
297
308
|
return `SELECT ${selectExpr} FROM ${qt}${whereSql} LIMIT 1`;
|
|
298
309
|
});
|
|
@@ -317,8 +328,8 @@ export class QueryInterface {
|
|
|
317
328
|
const freshParams = [];
|
|
318
329
|
const clause = this.buildWhereClause(whereObj, freshParams);
|
|
319
330
|
const whereSql = clause ? ` WHERE ${clause}` : '';
|
|
320
|
-
const qt =
|
|
321
|
-
const selectExpr = columnsList ? columnsList.map((c) => `${qt}.${
|
|
331
|
+
const qt = this.q(this.table);
|
|
332
|
+
const selectExpr = columnsList ? columnsList.map((c) => `${qt}.${this.q(c)}`).join(', ') : `${qt}.*`;
|
|
322
333
|
return `SELECT ${selectExpr} FROM ${qt}${whereSql} LIMIT 1`;
|
|
323
334
|
});
|
|
324
335
|
// Collect params
|
|
@@ -344,7 +355,7 @@ export class QueryInterface {
|
|
|
344
355
|
const clause = this.buildWhereClause(whereObj, freshParams);
|
|
345
356
|
const whereSql = clause ? ` WHERE ${clause}` : '';
|
|
346
357
|
const selectClause = this.buildSelectWithRelations(this.table, args.with, freshParams, columnsList);
|
|
347
|
-
return `SELECT ${selectClause} FROM ${
|
|
358
|
+
return `SELECT ${selectClause} FROM ${this.q(this.table)}${whereSql} LIMIT 1`;
|
|
348
359
|
});
|
|
349
360
|
// Collect params in exact build order: where first, then with-clause relations
|
|
350
361
|
this.collectWhereParams(whereObj, params);
|
|
@@ -455,7 +466,7 @@ export class QueryInterface {
|
|
|
455
466
|
return { sql: clause ? ` WHERE ${clause}` : '' };
|
|
456
467
|
})()
|
|
457
468
|
: { sql: '' };
|
|
458
|
-
const qt =
|
|
469
|
+
const qt = this.q(this.table);
|
|
459
470
|
let distinctPrefix = '';
|
|
460
471
|
if (args?.distinct && args.distinct.length > 0) {
|
|
461
472
|
const distinctCols = args.distinct.map((k) => this.toSqlColumn(k));
|
|
@@ -466,7 +477,7 @@ export class QueryInterface {
|
|
|
466
477
|
selectClause = this.buildSelectWithRelations(this.table, args.with, freshParams, columnsList);
|
|
467
478
|
}
|
|
468
479
|
else if (columnsList) {
|
|
469
|
-
selectClause = columnsList.map((c) => `${qt}.${
|
|
480
|
+
selectClause = columnsList.map((c) => `${qt}.${this.q(c)}`).join(', ');
|
|
470
481
|
}
|
|
471
482
|
else {
|
|
472
483
|
selectClause = `${qt}.*`;
|
|
@@ -480,7 +491,7 @@ export class QueryInterface {
|
|
|
480
491
|
const dir = args.orderBy?.[k] ?? 'asc';
|
|
481
492
|
const op = dir === 'desc' ? '<' : '>';
|
|
482
493
|
freshParams.push(v);
|
|
483
|
-
return `${qt}.${col} ${op}
|
|
494
|
+
return `${qt}.${col} ${op} ${this.p(freshParams.length)}`;
|
|
484
495
|
});
|
|
485
496
|
if (freshWhereSql) {
|
|
486
497
|
sql += ` AND ${cursorConditions.join(' AND ')}`;
|
|
@@ -495,11 +506,11 @@ export class QueryInterface {
|
|
|
495
506
|
}
|
|
496
507
|
if (effectiveLimit !== undefined) {
|
|
497
508
|
freshParams.push(Number(effectiveLimit));
|
|
498
|
-
sql += ` LIMIT
|
|
509
|
+
sql += ` LIMIT ${this.p(freshParams.length)}`;
|
|
499
510
|
}
|
|
500
511
|
if (args?.offset !== undefined) {
|
|
501
512
|
freshParams.push(Number(args.offset));
|
|
502
|
-
sql += ` OFFSET
|
|
513
|
+
sql += ` OFFSET ${this.p(freshParams.length)}`;
|
|
503
514
|
}
|
|
504
515
|
return sql;
|
|
505
516
|
});
|
|
@@ -587,7 +598,7 @@ export class QueryInterface {
|
|
|
587
598
|
// Acquire a dedicated connection — cursors require a single connection in a transaction
|
|
588
599
|
const client = await this.pool.connect();
|
|
589
600
|
const cursorName = `turbine_cursor_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
590
|
-
const quotedCursor =
|
|
601
|
+
const quotedCursor = this.q(cursorName);
|
|
591
602
|
try {
|
|
592
603
|
await client.query('BEGIN');
|
|
593
604
|
await client.query(`DECLARE ${quotedCursor} NO SCROLL CURSOR FOR ${deferred.sql}`, deferred.params);
|
|
@@ -721,8 +732,13 @@ export class QueryInterface {
|
|
|
721
732
|
const entries = Object.entries(args.data).filter(([, v]) => v !== undefined);
|
|
722
733
|
const columns = entries.map(([k]) => this.toSqlColumn(k));
|
|
723
734
|
const params = entries.map(([, v]) => v);
|
|
724
|
-
const placeholders = entries.map((_, i) =>
|
|
725
|
-
const sql =
|
|
735
|
+
const placeholders = entries.map((_, i) => `${this.p(i + 1)}`);
|
|
736
|
+
const sql = this.dialect.buildInsertStatement({
|
|
737
|
+
table: this.q(this.table),
|
|
738
|
+
columns,
|
|
739
|
+
valuePlaceholders: placeholders,
|
|
740
|
+
returning: '*',
|
|
741
|
+
});
|
|
726
742
|
return {
|
|
727
743
|
sql,
|
|
728
744
|
params,
|
|
@@ -751,7 +767,7 @@ export class QueryInterface {
|
|
|
751
767
|
});
|
|
752
768
|
}
|
|
753
769
|
buildCreateMany(args) {
|
|
754
|
-
const qt =
|
|
770
|
+
const qt = this.q(this.table);
|
|
755
771
|
if (args.data.length === 0) {
|
|
756
772
|
return {
|
|
757
773
|
sql: `SELECT * FROM ${qt} WHERE false`,
|
|
@@ -762,27 +778,24 @@ export class QueryInterface {
|
|
|
762
778
|
}
|
|
763
779
|
const keys = Object.keys(args.data[0]).filter((k) => args.data[0][k] !== undefined);
|
|
764
780
|
const columns = keys.map((k) => this.toColumn(k));
|
|
765
|
-
|
|
766
|
-
const columnArrays = keys.map(() => []);
|
|
767
|
-
for (const row of args.data) {
|
|
781
|
+
const rowValues = args.data.map((row) => {
|
|
768
782
|
const record = row;
|
|
769
|
-
keys.
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
}
|
|
773
|
-
// Use actual Postgres types for array casts
|
|
783
|
+
return keys.map((key) => record[key]);
|
|
784
|
+
});
|
|
785
|
+
// Use actual Postgres types for array casts in the default PostgreSQL dialect.
|
|
774
786
|
const typeCasts = columns.map((col) => this.getColumnArrayType(col));
|
|
775
|
-
const
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
787
|
+
const quotedColumns = columns.map((c) => this.q(c));
|
|
788
|
+
const built = this.dialect.buildBulkInsertStatement({
|
|
789
|
+
table: qt,
|
|
790
|
+
columns: quotedColumns,
|
|
791
|
+
rowValues,
|
|
792
|
+
columnArrayTypes: typeCasts,
|
|
793
|
+
skipDuplicates: args.skipDuplicates,
|
|
794
|
+
returning: '*',
|
|
795
|
+
});
|
|
783
796
|
return {
|
|
784
|
-
sql,
|
|
785
|
-
params:
|
|
797
|
+
sql: built.sql,
|
|
798
|
+
params: built.params,
|
|
786
799
|
transform: (result) => result.rows.map((row) => this.parseRow(row, this.table)),
|
|
787
800
|
tag: `${this.table}.createMany`,
|
|
788
801
|
};
|
|
@@ -811,7 +824,7 @@ export class QueryInterface {
|
|
|
811
824
|
const whereClause = this.buildWhereClause(whereObj, freshParams);
|
|
812
825
|
const whereSql = whereClause ? ` WHERE ${whereClause}` : '';
|
|
813
826
|
this.assertMutationHasPredicate('update', whereSql, args.allowFullTableScan);
|
|
814
|
-
return `UPDATE ${
|
|
827
|
+
return `UPDATE ${this.q(this.table)} SET ${setClauses.join(', ')}${whereSql}${this.dialect.buildReturningClause('*')}`;
|
|
815
828
|
});
|
|
816
829
|
// On cache hit, validate predicate
|
|
817
830
|
if (whereFp === '') {
|
|
@@ -860,7 +873,7 @@ export class QueryInterface {
|
|
|
860
873
|
const clause = this.buildWhereClause(whereObj, freshParams);
|
|
861
874
|
const whereSql = clause ? ` WHERE ${clause}` : '';
|
|
862
875
|
this.assertMutationHasPredicate('delete', whereSql, args.allowFullTableScan);
|
|
863
|
-
return `DELETE FROM ${
|
|
876
|
+
return `DELETE FROM ${this.q(this.table)}${whereSql}${this.dialect.buildReturningClause('*')}`;
|
|
864
877
|
});
|
|
865
878
|
// On cache hit, still validate the predicate
|
|
866
879
|
if (whereFp === '') {
|
|
@@ -900,7 +913,7 @@ export class QueryInterface {
|
|
|
900
913
|
const createEntries = Object.entries(args.create).filter(([, v]) => v !== undefined);
|
|
901
914
|
const columns = createEntries.map(([k]) => this.toSqlColumn(k));
|
|
902
915
|
const createParams = createEntries.map(([, v]) => v);
|
|
903
|
-
const placeholders = createEntries.map((_, i) =>
|
|
916
|
+
const placeholders = createEntries.map((_, i) => `${this.p(i + 1)}`);
|
|
904
917
|
// The conflict target comes from `where` keys — must be unique/PK columns
|
|
905
918
|
const conflictKeys = Object.keys(args.where).filter((k) => args.where[k] !== undefined);
|
|
906
919
|
const conflictColumns = conflictKeys.map((k) => this.toSqlColumn(k));
|
|
@@ -908,15 +921,20 @@ export class QueryInterface {
|
|
|
908
921
|
const updateEntries = Object.entries(args.update).filter(([, v]) => v !== undefined);
|
|
909
922
|
let paramIdx = createParams.length + 1;
|
|
910
923
|
const setClauses = updateEntries.map(([k]) => {
|
|
911
|
-
const clause = `${this.toSqlColumn(k)} =
|
|
924
|
+
const clause = `${this.toSqlColumn(k)} = ${this.p(paramIdx)}`;
|
|
912
925
|
paramIdx++;
|
|
913
926
|
return clause;
|
|
914
927
|
});
|
|
915
928
|
const updateParams = updateEntries.map(([, v]) => v);
|
|
916
929
|
const params = [...createParams, ...updateParams];
|
|
917
|
-
const sql =
|
|
918
|
-
|
|
919
|
-
|
|
930
|
+
const sql = this.dialect.buildUpsertStatement({
|
|
931
|
+
table: this.q(this.table),
|
|
932
|
+
insertColumns: columns,
|
|
933
|
+
valuePlaceholders: placeholders,
|
|
934
|
+
conflictColumns,
|
|
935
|
+
updateSetClauses: setClauses,
|
|
936
|
+
returning: '*',
|
|
937
|
+
});
|
|
920
938
|
return {
|
|
921
939
|
sql,
|
|
922
940
|
params,
|
|
@@ -959,7 +977,7 @@ export class QueryInterface {
|
|
|
959
977
|
const whereClause = this.buildWhereClause(whereObj, freshParams);
|
|
960
978
|
const whereSql = whereClause ? ` WHERE ${whereClause}` : '';
|
|
961
979
|
this.assertMutationHasPredicate('updateMany', whereSql, args.allowFullTableScan);
|
|
962
|
-
return `UPDATE ${
|
|
980
|
+
return `UPDATE ${this.q(this.table)} SET ${setClauses.join(', ')}${whereSql}`;
|
|
963
981
|
});
|
|
964
982
|
if (whereFp === '') {
|
|
965
983
|
this.assertMutationHasPredicate('updateMany', '', args.allowFullTableScan);
|
|
@@ -994,7 +1012,7 @@ export class QueryInterface {
|
|
|
994
1012
|
const clause = this.buildWhereClause(whereObj, freshParams);
|
|
995
1013
|
const whereSql = clause ? ` WHERE ${clause}` : '';
|
|
996
1014
|
this.assertMutationHasPredicate('deleteMany', whereSql, args.allowFullTableScan);
|
|
997
|
-
return `DELETE FROM ${
|
|
1015
|
+
return `DELETE FROM ${this.q(this.table)}${whereSql}`;
|
|
998
1016
|
});
|
|
999
1017
|
if (whereFp === '') {
|
|
1000
1018
|
this.assertMutationHasPredicate('deleteMany', '', args.allowFullTableScan);
|
|
@@ -1027,7 +1045,7 @@ export class QueryInterface {
|
|
|
1027
1045
|
const freshParams = [];
|
|
1028
1046
|
const clause = args?.where ? this.buildWhereClause(whereObj, freshParams) : null;
|
|
1029
1047
|
const whereSql = clause ? ` WHERE ${clause}` : '';
|
|
1030
|
-
return `SELECT COUNT(*)::int AS count FROM ${
|
|
1048
|
+
return `SELECT COUNT(*)::int AS count FROM ${this.q(this.table)}${whereSql}`;
|
|
1031
1049
|
});
|
|
1032
1050
|
if (args?.where) {
|
|
1033
1051
|
this.collectWhereParams(whereObj, params);
|
|
@@ -1060,7 +1078,7 @@ export class QueryInterface {
|
|
|
1060
1078
|
}
|
|
1061
1079
|
}
|
|
1062
1080
|
const groupColsRaw = args.by.map((k) => this.toColumn(k));
|
|
1063
|
-
const groupCols = groupColsRaw.map((c) =>
|
|
1081
|
+
const groupCols = groupColsRaw.map((c) => this.q(c));
|
|
1064
1082
|
const { sql: whereSql, params } = args.where ? this.buildWhere(args.where) : { sql: '', params: [] };
|
|
1065
1083
|
// Build SELECT expressions: group-by columns + aggregate functions
|
|
1066
1084
|
const selectExprs = [...groupCols];
|
|
@@ -1074,7 +1092,7 @@ export class QueryInterface {
|
|
|
1074
1092
|
for (const [field, enabled] of Object.entries(args._sum)) {
|
|
1075
1093
|
if (enabled) {
|
|
1076
1094
|
const col = this.toColumn(field);
|
|
1077
|
-
selectExprs.push(`SUM(${
|
|
1095
|
+
selectExprs.push(`SUM(${this.q(col)}) AS ${this.q(`_sum_${col}`)}`);
|
|
1078
1096
|
}
|
|
1079
1097
|
}
|
|
1080
1098
|
}
|
|
@@ -1083,7 +1101,7 @@ export class QueryInterface {
|
|
|
1083
1101
|
for (const [field, enabled] of Object.entries(args._avg)) {
|
|
1084
1102
|
if (enabled) {
|
|
1085
1103
|
const col = this.toColumn(field);
|
|
1086
|
-
selectExprs.push(`AVG(${
|
|
1104
|
+
selectExprs.push(`AVG(${this.q(col)})::float AS ${this.q(`_avg_${col}`)}`);
|
|
1087
1105
|
}
|
|
1088
1106
|
}
|
|
1089
1107
|
}
|
|
@@ -1092,7 +1110,7 @@ export class QueryInterface {
|
|
|
1092
1110
|
for (const [field, enabled] of Object.entries(args._min)) {
|
|
1093
1111
|
if (enabled) {
|
|
1094
1112
|
const col = this.toColumn(field);
|
|
1095
|
-
selectExprs.push(`MIN(${
|
|
1113
|
+
selectExprs.push(`MIN(${this.q(col)}) AS ${this.q(`_min_${col}`)}`);
|
|
1096
1114
|
}
|
|
1097
1115
|
}
|
|
1098
1116
|
}
|
|
@@ -1101,11 +1119,11 @@ export class QueryInterface {
|
|
|
1101
1119
|
for (const [field, enabled] of Object.entries(args._max)) {
|
|
1102
1120
|
if (enabled) {
|
|
1103
1121
|
const col = this.toColumn(field);
|
|
1104
|
-
selectExprs.push(`MAX(${
|
|
1122
|
+
selectExprs.push(`MAX(${this.q(col)}) AS ${this.q(`_max_${col}`)}`);
|
|
1105
1123
|
}
|
|
1106
1124
|
}
|
|
1107
1125
|
}
|
|
1108
|
-
let sql = `SELECT ${selectExprs.join(', ')} FROM ${
|
|
1126
|
+
let sql = `SELECT ${selectExprs.join(', ')} FROM ${this.q(this.table)}${whereSql} GROUP BY ${groupCols.join(', ')}`;
|
|
1109
1127
|
// ORDER BY
|
|
1110
1128
|
if (args.orderBy) {
|
|
1111
1129
|
sql += ` ORDER BY ${this.buildOrderBy(args.orderBy)}`;
|
|
@@ -1213,7 +1231,7 @@ export class QueryInterface {
|
|
|
1213
1231
|
for (const [field, enabled] of Object.entries(args._count)) {
|
|
1214
1232
|
if (enabled) {
|
|
1215
1233
|
const col = this.toColumn(field);
|
|
1216
|
-
selectExprs.push(`COUNT(${
|
|
1234
|
+
selectExprs.push(`COUNT(${this.q(col)})::int AS ${this.q(`_count_${col}`)}`);
|
|
1217
1235
|
}
|
|
1218
1236
|
}
|
|
1219
1237
|
}
|
|
@@ -1222,7 +1240,7 @@ export class QueryInterface {
|
|
|
1222
1240
|
for (const [field, enabled] of Object.entries(args._sum)) {
|
|
1223
1241
|
if (enabled) {
|
|
1224
1242
|
const col = this.toColumn(field);
|
|
1225
|
-
selectExprs.push(`SUM(${
|
|
1243
|
+
selectExprs.push(`SUM(${this.q(col)}) AS ${this.q(`_sum_${col}`)}`);
|
|
1226
1244
|
}
|
|
1227
1245
|
}
|
|
1228
1246
|
}
|
|
@@ -1231,7 +1249,7 @@ export class QueryInterface {
|
|
|
1231
1249
|
for (const [field, enabled] of Object.entries(args._avg)) {
|
|
1232
1250
|
if (enabled) {
|
|
1233
1251
|
const col = this.toColumn(field);
|
|
1234
|
-
selectExprs.push(`AVG(${
|
|
1252
|
+
selectExprs.push(`AVG(${this.q(col)})::float AS ${this.q(`_avg_${col}`)}`);
|
|
1235
1253
|
}
|
|
1236
1254
|
}
|
|
1237
1255
|
}
|
|
@@ -1240,7 +1258,7 @@ export class QueryInterface {
|
|
|
1240
1258
|
for (const [field, enabled] of Object.entries(args._min)) {
|
|
1241
1259
|
if (enabled) {
|
|
1242
1260
|
const col = this.toColumn(field);
|
|
1243
|
-
selectExprs.push(`MIN(${
|
|
1261
|
+
selectExprs.push(`MIN(${this.q(col)}) AS ${this.q(`_min_${col}`)}`);
|
|
1244
1262
|
}
|
|
1245
1263
|
}
|
|
1246
1264
|
}
|
|
@@ -1249,14 +1267,14 @@ export class QueryInterface {
|
|
|
1249
1267
|
for (const [field, enabled] of Object.entries(args._max)) {
|
|
1250
1268
|
if (enabled) {
|
|
1251
1269
|
const col = this.toColumn(field);
|
|
1252
|
-
selectExprs.push(`MAX(${
|
|
1270
|
+
selectExprs.push(`MAX(${this.q(col)}) AS ${this.q(`_max_${col}`)}`);
|
|
1253
1271
|
}
|
|
1254
1272
|
}
|
|
1255
1273
|
}
|
|
1256
1274
|
if (selectExprs.length === 0) {
|
|
1257
1275
|
selectExprs.push('COUNT(*)::int AS _count');
|
|
1258
1276
|
}
|
|
1259
|
-
const sql = `SELECT ${selectExprs.join(', ')} FROM ${
|
|
1277
|
+
const sql = `SELECT ${selectExprs.join(', ')} FROM ${this.q(this.table)}${whereSql}`;
|
|
1260
1278
|
return {
|
|
1261
1279
|
sql,
|
|
1262
1280
|
params,
|
|
@@ -1373,7 +1391,7 @@ export class QueryInterface {
|
|
|
1373
1391
|
}
|
|
1374
1392
|
/** Convert camelCase field name to a double-quoted SQL identifier */
|
|
1375
1393
|
toSqlColumn(field) {
|
|
1376
|
-
return
|
|
1394
|
+
return this.q(this.toColumn(field));
|
|
1377
1395
|
}
|
|
1378
1396
|
/**
|
|
1379
1397
|
* Build a single SET clause entry for update/updateMany.
|
|
@@ -1406,7 +1424,7 @@ export class QueryInterface {
|
|
|
1406
1424
|
const opValue = v[op];
|
|
1407
1425
|
if (op === 'set') {
|
|
1408
1426
|
params.push(opValue);
|
|
1409
|
-
return `${col} =
|
|
1427
|
+
return `${col} = ${this.p(params.length)}`;
|
|
1410
1428
|
}
|
|
1411
1429
|
// Arithmetic operators: must be finite numbers
|
|
1412
1430
|
if (typeof opValue !== 'number' || !Number.isFinite(opValue)) {
|
|
@@ -1414,19 +1432,19 @@ export class QueryInterface {
|
|
|
1414
1432
|
}
|
|
1415
1433
|
if (op === 'increment') {
|
|
1416
1434
|
params.push(opValue);
|
|
1417
|
-
return `${col} = ${col} +
|
|
1435
|
+
return `${col} = ${col} + ${this.p(params.length)}`;
|
|
1418
1436
|
}
|
|
1419
1437
|
if (op === 'decrement') {
|
|
1420
1438
|
params.push(opValue);
|
|
1421
|
-
return `${col} = ${col} -
|
|
1439
|
+
return `${col} = ${col} - ${this.p(params.length)}`;
|
|
1422
1440
|
}
|
|
1423
1441
|
if (op === 'multiply') {
|
|
1424
1442
|
params.push(opValue);
|
|
1425
|
-
return `${col} = ${col} *
|
|
1443
|
+
return `${col} = ${col} * ${this.p(params.length)}`;
|
|
1426
1444
|
}
|
|
1427
1445
|
if (op === 'divide') {
|
|
1428
1446
|
params.push(opValue);
|
|
1429
|
-
return `${col} = ${col} /
|
|
1447
|
+
return `${col} = ${col} / ${this.p(params.length)}`;
|
|
1430
1448
|
}
|
|
1431
1449
|
}
|
|
1432
1450
|
// Fall through: multi-key objects or non-operator single-key objects
|
|
@@ -1434,7 +1452,7 @@ export class QueryInterface {
|
|
|
1434
1452
|
}
|
|
1435
1453
|
// Plain value (including null, Date, Buffer, arrays, JSON objects)
|
|
1436
1454
|
params.push(value);
|
|
1437
|
-
return `${col} =
|
|
1455
|
+
return `${col} = ${this.p(params.length)}`;
|
|
1438
1456
|
}
|
|
1439
1457
|
// =========================================================================
|
|
1440
1458
|
// Fingerprinting — value-invariant shape keys for SQL cache lookup
|
|
@@ -1957,7 +1975,7 @@ export class QueryInterface {
|
|
|
1957
1975
|
}
|
|
1958
1976
|
}
|
|
1959
1977
|
const rawColumn = this.toColumn(key);
|
|
1960
|
-
const column =
|
|
1978
|
+
const column = this.q(rawColumn);
|
|
1961
1979
|
// Handle null → IS NULL
|
|
1962
1980
|
if (value === null) {
|
|
1963
1981
|
andClauses.push(`${column} IS NULL`);
|
|
@@ -2007,7 +2025,7 @@ export class QueryInterface {
|
|
|
2007
2025
|
}
|
|
2008
2026
|
// Plain equality
|
|
2009
2027
|
params.push(value);
|
|
2010
|
-
andClauses.push(`${column} =
|
|
2028
|
+
andClauses.push(`${column} = ${this.p(params.length)}`);
|
|
2011
2029
|
}
|
|
2012
2030
|
if (andClauses.length === 0)
|
|
2013
2031
|
return null;
|
|
@@ -2022,18 +2040,18 @@ export class QueryInterface {
|
|
|
2022
2040
|
const targetMeta = this.schema.tables[targetTable];
|
|
2023
2041
|
if (!targetMeta)
|
|
2024
2042
|
return null;
|
|
2025
|
-
const qt =
|
|
2026
|
-
const qSelf =
|
|
2043
|
+
const qt = this.q(targetTable);
|
|
2044
|
+
const qSelf = this.q(this.table);
|
|
2027
2045
|
const clauses = [];
|
|
2028
2046
|
// Correlation: link child table to parent table (supports composite FKs)
|
|
2029
2047
|
let correlation;
|
|
2030
2048
|
if (relDef.type === 'hasMany' || relDef.type === 'hasOne') {
|
|
2031
2049
|
// parent.pk = child.fk
|
|
2032
|
-
correlation = buildCorrelation(qt, relDef.foreignKey, qSelf, relDef.referenceKey);
|
|
2050
|
+
correlation = this.dialect.buildCorrelation(qt, relDef.foreignKey, qSelf, relDef.referenceKey);
|
|
2033
2051
|
}
|
|
2034
2052
|
else {
|
|
2035
2053
|
// belongsTo: parent.fk = child.pk
|
|
2036
|
-
correlation = buildCorrelation(qt, relDef.referenceKey, qSelf, relDef.foreignKey);
|
|
2054
|
+
correlation = this.dialect.buildCorrelation(qt, relDef.referenceKey, qSelf, relDef.foreignKey);
|
|
2037
2055
|
}
|
|
2038
2056
|
// "some": EXISTS (SELECT 1 FROM target WHERE correlation AND filter)
|
|
2039
2057
|
if (filterObj.some !== undefined) {
|
|
@@ -2070,7 +2088,7 @@ export class QueryInterface {
|
|
|
2070
2088
|
const meta = this.schema.tables[targetTable];
|
|
2071
2089
|
if (!meta)
|
|
2072
2090
|
return null;
|
|
2073
|
-
const qt =
|
|
2091
|
+
const qt = this.q(targetTable);
|
|
2074
2092
|
const conditions = [];
|
|
2075
2093
|
for (const [field, value] of Object.entries(subWhere)) {
|
|
2076
2094
|
if (value === undefined)
|
|
@@ -2080,7 +2098,7 @@ export class QueryInterface {
|
|
|
2080
2098
|
throw new ValidationError(`[turbine] Unknown field "${field}" in relation filter for table "${targetTable}". ` +
|
|
2081
2099
|
`Known fields: ${Object.keys(meta.columnMap).join(', ') || '(none)'}.`);
|
|
2082
2100
|
}
|
|
2083
|
-
const qCol = `${qt}.${
|
|
2101
|
+
const qCol = `${qt}.${this.q(col)}`;
|
|
2084
2102
|
if (value === null) {
|
|
2085
2103
|
conditions.push(`${qCol} IS NULL`);
|
|
2086
2104
|
continue;
|
|
@@ -2091,7 +2109,7 @@ export class QueryInterface {
|
|
|
2091
2109
|
continue;
|
|
2092
2110
|
}
|
|
2093
2111
|
params.push(value);
|
|
2094
|
-
conditions.push(`${qCol} =
|
|
2112
|
+
conditions.push(`${qCol} = ${this.p(params.length)}`);
|
|
2095
2113
|
}
|
|
2096
2114
|
return conditions.length > 0 ? conditions.join(' AND ') : null;
|
|
2097
2115
|
}
|
|
@@ -2103,19 +2121,19 @@ export class QueryInterface {
|
|
|
2103
2121
|
const clauses = [];
|
|
2104
2122
|
if (op.gt !== undefined) {
|
|
2105
2123
|
params.push(op.gt);
|
|
2106
|
-
clauses.push(`${column} >
|
|
2124
|
+
clauses.push(`${column} > ${this.p(params.length)}`);
|
|
2107
2125
|
}
|
|
2108
2126
|
if (op.gte !== undefined) {
|
|
2109
2127
|
params.push(op.gte);
|
|
2110
|
-
clauses.push(`${column} >=
|
|
2128
|
+
clauses.push(`${column} >= ${this.p(params.length)}`);
|
|
2111
2129
|
}
|
|
2112
2130
|
if (op.lt !== undefined) {
|
|
2113
2131
|
params.push(op.lt);
|
|
2114
|
-
clauses.push(`${column} <
|
|
2132
|
+
clauses.push(`${column} < ${this.p(params.length)}`);
|
|
2115
2133
|
}
|
|
2116
2134
|
if (op.lte !== undefined) {
|
|
2117
2135
|
params.push(op.lte);
|
|
2118
|
-
clauses.push(`${column} <=
|
|
2136
|
+
clauses.push(`${column} <= ${this.p(params.length)}`);
|
|
2119
2137
|
}
|
|
2120
2138
|
if (op.not !== undefined) {
|
|
2121
2139
|
if (op.not === null) {
|
|
@@ -2123,30 +2141,29 @@ export class QueryInterface {
|
|
|
2123
2141
|
}
|
|
2124
2142
|
else {
|
|
2125
2143
|
params.push(op.not);
|
|
2126
|
-
clauses.push(`${column} !=
|
|
2144
|
+
clauses.push(`${column} != ${this.p(params.length)}`);
|
|
2127
2145
|
}
|
|
2128
2146
|
}
|
|
2129
2147
|
if (op.in !== undefined) {
|
|
2130
2148
|
params.push(op.in);
|
|
2131
|
-
clauses.push(`${column} = ANY(
|
|
2149
|
+
clauses.push(`${column} = ANY(${this.p(params.length)})`);
|
|
2132
2150
|
}
|
|
2133
2151
|
if (op.notIn !== undefined) {
|
|
2134
2152
|
params.push(op.notIn);
|
|
2135
|
-
clauses.push(`${column} != ALL(
|
|
2153
|
+
clauses.push(`${column} != ALL(${this.p(params.length)})`);
|
|
2136
2154
|
}
|
|
2137
|
-
|
|
2138
|
-
const likeOp = op.mode === 'insensitive' ? 'ILIKE' : 'LIKE';
|
|
2155
|
+
const buildLikeClause = (paramRef) => op.mode === 'insensitive' ? this.dialect.buildInsensitiveLike(column, paramRef) : `${column} LIKE ${paramRef}`;
|
|
2139
2156
|
if (op.contains !== undefined) {
|
|
2140
2157
|
params.push(`%${escapeLike(op.contains)}%`);
|
|
2141
|
-
clauses.push(`${
|
|
2158
|
+
clauses.push(`${buildLikeClause(this.p(params.length))} ESCAPE '\\'`);
|
|
2142
2159
|
}
|
|
2143
2160
|
if (op.startsWith !== undefined) {
|
|
2144
2161
|
params.push(`${escapeLike(op.startsWith)}%`);
|
|
2145
|
-
clauses.push(`${
|
|
2162
|
+
clauses.push(`${buildLikeClause(this.p(params.length))} ESCAPE '\\'`);
|
|
2146
2163
|
}
|
|
2147
2164
|
if (op.endsWith !== undefined) {
|
|
2148
2165
|
params.push(`%${escapeLike(op.endsWith)}`);
|
|
2149
|
-
clauses.push(`${
|
|
2166
|
+
clauses.push(`${buildLikeClause(this.p(params.length))} ESCAPE '\\'`);
|
|
2150
2167
|
}
|
|
2151
2168
|
return clauses;
|
|
2152
2169
|
}
|
|
@@ -2306,8 +2323,8 @@ export class QueryInterface {
|
|
|
2306
2323
|
if (!meta)
|
|
2307
2324
|
throw new ValidationError(`[turbine] Unknown table "${table}"`);
|
|
2308
2325
|
const cols = columnsList ?? meta.allColumns;
|
|
2309
|
-
const qtbl =
|
|
2310
|
-
const baseCols = cols.map((col) => `${qtbl}.${
|
|
2326
|
+
const qtbl = this.q(table);
|
|
2327
|
+
const baseCols = cols.map((col) => `${qtbl}.${this.q(col)}`).join(', ');
|
|
2311
2328
|
const relationSelects = [];
|
|
2312
2329
|
const aliasCounter = { n: 0 };
|
|
2313
2330
|
for (const [relName, relSpec] of Object.entries(withClause)) {
|
|
@@ -2318,7 +2335,7 @@ export class QueryInterface {
|
|
|
2318
2335
|
}
|
|
2319
2336
|
// The main table is not aliased, so pass table name as parentRef
|
|
2320
2337
|
const subquery = this.buildRelationSubquery(relDef, relSpec, params, table, aliasCounter, depth, path);
|
|
2321
|
-
relationSelects.push(`(${subquery}) AS ${
|
|
2338
|
+
relationSelects.push(`(${subquery}) AS ${this.q(relName)}`);
|
|
2322
2339
|
}
|
|
2323
2340
|
return [baseCols, ...relationSelects].join(', ');
|
|
2324
2341
|
}
|
|
@@ -2380,7 +2397,7 @@ export class QueryInterface {
|
|
|
2380
2397
|
* 8. **Parameter threading:** All user-supplied values (where filters, limit) are
|
|
2381
2398
|
* pushed to the shared `params` array with `$N` placeholders. No string
|
|
2382
2399
|
* interpolation of user data ever occurs -- all identifiers go through
|
|
2383
|
-
* `
|
|
2400
|
+
* `this.q()` and all values are parameterized.
|
|
2384
2401
|
*
|
|
2385
2402
|
* ### Example output (hasMany with nested relation)
|
|
2386
2403
|
* ```sql
|
|
@@ -2444,8 +2461,11 @@ export class QueryInterface {
|
|
|
2444
2461
|
.map(([k]) => targetMeta.columnMap[k] ?? camelToSnake(k)));
|
|
2445
2462
|
targetColumns = targetMeta.allColumns.filter((col) => !omittedFields.has(col));
|
|
2446
2463
|
}
|
|
2447
|
-
// Build
|
|
2448
|
-
const jsonPairs = targetColumns.map((col) =>
|
|
2464
|
+
// Build JSON object pairs for resolved columns
|
|
2465
|
+
const jsonPairs = targetColumns.map((col) => [
|
|
2466
|
+
targetMeta.reverseColumnMap[col] ?? snakeToCamel(col),
|
|
2467
|
+
`${alias}.${this.q(col)}`,
|
|
2468
|
+
]);
|
|
2449
2469
|
// Determine if this hasMany will take the wrapped subquery path (LIMIT or ORDER BY).
|
|
2450
2470
|
// When wrapping, nested relations are built in the wrapped path referencing innerAlias,
|
|
2451
2471
|
// so we must NOT build them here (they would push orphaned params).
|
|
@@ -2461,14 +2481,14 @@ export class QueryInterface {
|
|
|
2461
2481
|
// Recursively build nested subquery, passing THIS alias as the parent reference
|
|
2462
2482
|
const nestedSubquery = this.buildRelationSubquery(nestedRelDef, nestedSpec, params, alias, aliasCounter, currentDepth + 1, [...currentPath, relDef.name]);
|
|
2463
2483
|
// Use '[]'::json for hasMany (empty array), NULL for belongsTo/hasOne (no object)
|
|
2464
|
-
const fallback = nestedRelDef.type === 'hasMany' ?
|
|
2465
|
-
jsonPairs.push(
|
|
2484
|
+
const fallback = nestedRelDef.type === 'hasMany' ? this.dialect.emptyJsonArrayLiteral : this.dialect.nullJsonLiteral;
|
|
2485
|
+
jsonPairs.push([nestedRelName, `COALESCE((${nestedSubquery}), ${fallback})`]);
|
|
2466
2486
|
}
|
|
2467
2487
|
}
|
|
2468
|
-
const jsonObj =
|
|
2488
|
+
const jsonObj = this.dialect.buildJsonObject(jsonPairs);
|
|
2469
2489
|
// Quote parent ref — can be a table name or auto-generated alias
|
|
2470
|
-
const qParent =
|
|
2471
|
-
const qTarget =
|
|
2490
|
+
const qParent = this.q(parentRef);
|
|
2491
|
+
const qTarget = this.q(targetTable);
|
|
2472
2492
|
// Build ORDER BY for json_agg
|
|
2473
2493
|
let orderClause = '';
|
|
2474
2494
|
if (spec !== true && spec.orderBy) {
|
|
@@ -2479,7 +2499,7 @@ export class QueryInterface {
|
|
|
2479
2499
|
throw new ValidationError(`[turbine] Unknown column "${k}" in orderBy for table "${targetTable}"`);
|
|
2480
2500
|
}
|
|
2481
2501
|
const safeDir = dir.toLowerCase() === 'desc' ? 'DESC' : 'ASC';
|
|
2482
|
-
return `${alias}.${
|
|
2502
|
+
return `${alias}.${this.q(col)} ${safeDir}`;
|
|
2483
2503
|
})
|
|
2484
2504
|
.join(', ');
|
|
2485
2505
|
orderClause = ` ORDER BY ${orders}`;
|
|
@@ -2490,10 +2510,10 @@ export class QueryInterface {
|
|
|
2490
2510
|
// Supports composite foreign keys (string[]) via buildCorrelation.
|
|
2491
2511
|
let whereClause;
|
|
2492
2512
|
if (relDef.type === 'belongsTo' || relDef.type === 'hasOne') {
|
|
2493
|
-
whereClause = buildCorrelation(alias, relDef.referenceKey, qParent, relDef.foreignKey);
|
|
2513
|
+
whereClause = this.dialect.buildCorrelation(alias, relDef.referenceKey, qParent, relDef.foreignKey);
|
|
2494
2514
|
}
|
|
2495
2515
|
else {
|
|
2496
|
-
whereClause = buildCorrelation(alias, relDef.foreignKey, qParent, relDef.referenceKey);
|
|
2516
|
+
whereClause = this.dialect.buildCorrelation(alias, relDef.foreignKey, qParent, relDef.referenceKey);
|
|
2497
2517
|
}
|
|
2498
2518
|
// Additional filters — properly parameterized
|
|
2499
2519
|
if (spec !== true && spec.where) {
|
|
@@ -2503,14 +2523,14 @@ export class QueryInterface {
|
|
|
2503
2523
|
throw new ValidationError(`[turbine] Unknown column "${k}" in where for table "${targetTable}"`);
|
|
2504
2524
|
}
|
|
2505
2525
|
params.push(v);
|
|
2506
|
-
whereClause += ` AND ${alias}.${
|
|
2526
|
+
whereClause += ` AND ${alias}.${this.q(col)} = ${this.p(params.length)}`;
|
|
2507
2527
|
}
|
|
2508
2528
|
}
|
|
2509
2529
|
// LIMIT
|
|
2510
2530
|
let limitClause = '';
|
|
2511
2531
|
if (spec !== true && spec.limit) {
|
|
2512
2532
|
params.push(Number(spec.limit));
|
|
2513
|
-
limitClause = ` LIMIT
|
|
2533
|
+
limitClause = ` LIMIT ${this.p(params.length)}`;
|
|
2514
2534
|
}
|
|
2515
2535
|
if (relDef.type === 'hasMany') {
|
|
2516
2536
|
// When LIMIT or ORDER BY is used, wrap in a subquery so LIMIT applies to rows
|
|
@@ -2519,9 +2539,12 @@ export class QueryInterface {
|
|
|
2519
2539
|
const innerAlias = `${alias}i`;
|
|
2520
2540
|
// Rewrite: SELECT json_agg(json_build_object(...)) FROM (SELECT * FROM table WHERE ... ORDER BY ... LIMIT N) AS alias
|
|
2521
2541
|
// Inner SELECT always needs all columns for WHERE/ORDER to work; json_build_object filters later
|
|
2522
|
-
const innerSql = `SELECT ${targetMeta.allColumns.map((c) => `${alias}.${
|
|
2542
|
+
const innerSql = `SELECT ${targetMeta.allColumns.map((c) => `${alias}.${this.q(c)}`).join(', ')} FROM ${qTarget} ${alias} WHERE ${whereClause}${orderClause}${limitClause}`;
|
|
2523
2543
|
// For the json_build_object, reference the inner alias — only include resolved columns
|
|
2524
|
-
const innerJsonPairs = targetColumns.map((col) =>
|
|
2544
|
+
const innerJsonPairs = targetColumns.map((col) => [
|
|
2545
|
+
targetMeta.reverseColumnMap[col] ?? snakeToCamel(col),
|
|
2546
|
+
`${innerAlias}.${this.q(col)}`,
|
|
2547
|
+
]);
|
|
2525
2548
|
// Build nested relation subqueries referencing innerAlias
|
|
2526
2549
|
if (spec !== true && spec.with) {
|
|
2527
2550
|
for (const [nestedRelName, nestedSpec] of Object.entries(spec.with)) {
|
|
@@ -2531,14 +2554,14 @@ export class QueryInterface {
|
|
|
2531
2554
|
`Available: ${Object.keys(targetMeta.relations).join(', ')}`);
|
|
2532
2555
|
}
|
|
2533
2556
|
const nestedSub = this.buildRelationSubquery(nestedRelDef, nestedSpec, params, innerAlias, aliasCounter, currentDepth + 1, [...currentPath, relDef.name]);
|
|
2534
|
-
const fallback = nestedRelDef.type === 'hasMany' ?
|
|
2535
|
-
innerJsonPairs.push(
|
|
2557
|
+
const fallback = nestedRelDef.type === 'hasMany' ? this.dialect.emptyJsonArrayLiteral : this.dialect.nullJsonLiteral;
|
|
2558
|
+
innerJsonPairs.push([nestedRelName, `COALESCE((${nestedSub}), ${fallback})`]);
|
|
2536
2559
|
}
|
|
2537
2560
|
}
|
|
2538
|
-
const innerJsonObj =
|
|
2539
|
-
return `SELECT
|
|
2561
|
+
const innerJsonObj = this.dialect.buildJsonObject(innerJsonPairs);
|
|
2562
|
+
return `SELECT ${this.dialect.buildJsonArrayAgg(innerJsonObj)} FROM (${innerSql}) ${innerAlias}`;
|
|
2540
2563
|
}
|
|
2541
|
-
return `SELECT
|
|
2564
|
+
return `SELECT ${this.dialect.buildJsonArrayAgg(jsonObj, orderClause.trim() || undefined)} FROM ${qTarget} ${alias} WHERE ${whereClause}`;
|
|
2542
2565
|
}
|
|
2543
2566
|
// belongsTo / hasOne — return single object
|
|
2544
2567
|
return `SELECT ${jsonObj} FROM ${qTarget} ${alias} WHERE ${whereClause} LIMIT 1`;
|
|
@@ -2585,22 +2608,22 @@ export class QueryInterface {
|
|
|
2585
2608
|
params.push(filter.path);
|
|
2586
2609
|
const pathParam = params.length;
|
|
2587
2610
|
params.push(String(filter.equals));
|
|
2588
|
-
clauses.push(`${column
|
|
2611
|
+
clauses.push(`${this.dialect.buildJsonPathExtract(column, this.p(pathParam))} = ${this.p(params.length)}`);
|
|
2589
2612
|
}
|
|
2590
2613
|
else if (filter.equals !== undefined) {
|
|
2591
2614
|
// Containment equality: column @> $N::jsonb
|
|
2592
2615
|
params.push(JSON.stringify(filter.equals));
|
|
2593
|
-
clauses.push(
|
|
2616
|
+
clauses.push(this.dialect.buildJsonContains(column, this.p(params.length)));
|
|
2594
2617
|
}
|
|
2595
2618
|
if (filter.contains !== undefined) {
|
|
2596
2619
|
// Containment: column @> $N::jsonb
|
|
2597
2620
|
params.push(JSON.stringify(filter.contains));
|
|
2598
|
-
clauses.push(
|
|
2621
|
+
clauses.push(this.dialect.buildJsonContains(column, this.p(params.length)));
|
|
2599
2622
|
}
|
|
2600
2623
|
if (filter.hasKey !== undefined) {
|
|
2601
2624
|
// Key existence: column ? $N
|
|
2602
2625
|
params.push(filter.hasKey);
|
|
2603
|
-
clauses.push(`${column} ?
|
|
2626
|
+
clauses.push(`${column} ? ${this.p(params.length)}`);
|
|
2604
2627
|
}
|
|
2605
2628
|
return clauses;
|
|
2606
2629
|
}
|
|
@@ -2614,17 +2637,17 @@ export class QueryInterface {
|
|
|
2614
2637
|
if (filter.has !== undefined) {
|
|
2615
2638
|
// value = ANY(column)
|
|
2616
2639
|
params.push(filter.has);
|
|
2617
|
-
clauses.push(
|
|
2640
|
+
clauses.push(`${this.p(params.length)} = ANY(${column})`);
|
|
2618
2641
|
}
|
|
2619
2642
|
if (filter.hasEvery !== undefined) {
|
|
2620
2643
|
// column @> ARRAY[...]::type[]
|
|
2621
2644
|
params.push(filter.hasEvery);
|
|
2622
|
-
clauses.push(`${column} @>
|
|
2645
|
+
clauses.push(`${column} @> ${this.p(params.length)}::${elementType}[]`);
|
|
2623
2646
|
}
|
|
2624
2647
|
if (filter.hasSome !== undefined) {
|
|
2625
2648
|
// column && ARRAY[...]::type[]
|
|
2626
2649
|
params.push(filter.hasSome);
|
|
2627
|
-
clauses.push(`${column} &&
|
|
2650
|
+
clauses.push(`${column} && ${this.p(params.length)}::${elementType}[]`);
|
|
2628
2651
|
}
|
|
2629
2652
|
if (filter.isEmpty === true) {
|
|
2630
2653
|
// array_length(column, 1) IS NULL
|