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
|
@@ -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 =
|
|
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)} =
|
|
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}.${
|
|
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 =
|
|
324
|
-
const selectExpr = columnsList ? columnsList.map((c) => `${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 ${
|
|
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 =
|
|
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}.${
|
|
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}
|
|
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
|
|
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
|
|
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 =
|
|
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,13 @@ 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) =>
|
|
728
|
-
const sql =
|
|
738
|
+
const placeholders = entries.map((_, i) => `${this.p(i + 1)}`);
|
|
739
|
+
const sql = this.dialect.buildInsertStatement({
|
|
740
|
+
table: this.q(this.table),
|
|
741
|
+
columns,
|
|
742
|
+
valuePlaceholders: placeholders,
|
|
743
|
+
returning: '*',
|
|
744
|
+
});
|
|
729
745
|
return {
|
|
730
746
|
sql,
|
|
731
747
|
params,
|
|
@@ -754,7 +770,7 @@ class QueryInterface {
|
|
|
754
770
|
});
|
|
755
771
|
}
|
|
756
772
|
buildCreateMany(args) {
|
|
757
|
-
const qt =
|
|
773
|
+
const qt = this.q(this.table);
|
|
758
774
|
if (args.data.length === 0) {
|
|
759
775
|
return {
|
|
760
776
|
sql: `SELECT * FROM ${qt} WHERE false`,
|
|
@@ -765,27 +781,24 @@ class QueryInterface {
|
|
|
765
781
|
}
|
|
766
782
|
const keys = Object.keys(args.data[0]).filter((k) => args.data[0][k] !== undefined);
|
|
767
783
|
const columns = keys.map((k) => this.toColumn(k));
|
|
768
|
-
|
|
769
|
-
const columnArrays = keys.map(() => []);
|
|
770
|
-
for (const row of args.data) {
|
|
784
|
+
const rowValues = args.data.map((row) => {
|
|
771
785
|
const record = row;
|
|
772
|
-
keys.
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
// Use actual Postgres types for array casts
|
|
786
|
+
return keys.map((key) => record[key]);
|
|
787
|
+
});
|
|
788
|
+
// Use actual Postgres types for array casts in the default PostgreSQL dialect.
|
|
777
789
|
const typeCasts = columns.map((col) => this.getColumnArrayType(col));
|
|
778
|
-
const
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
790
|
+
const quotedColumns = columns.map((c) => this.q(c));
|
|
791
|
+
const built = this.dialect.buildBulkInsertStatement({
|
|
792
|
+
table: qt,
|
|
793
|
+
columns: quotedColumns,
|
|
794
|
+
rowValues,
|
|
795
|
+
columnArrayTypes: typeCasts,
|
|
796
|
+
skipDuplicates: args.skipDuplicates,
|
|
797
|
+
returning: '*',
|
|
798
|
+
});
|
|
786
799
|
return {
|
|
787
|
-
sql,
|
|
788
|
-
params:
|
|
800
|
+
sql: built.sql,
|
|
801
|
+
params: built.params,
|
|
789
802
|
transform: (result) => result.rows.map((row) => this.parseRow(row, this.table)),
|
|
790
803
|
tag: `${this.table}.createMany`,
|
|
791
804
|
};
|
|
@@ -814,7 +827,7 @@ class QueryInterface {
|
|
|
814
827
|
const whereClause = this.buildWhereClause(whereObj, freshParams);
|
|
815
828
|
const whereSql = whereClause ? ` WHERE ${whereClause}` : '';
|
|
816
829
|
this.assertMutationHasPredicate('update', whereSql, args.allowFullTableScan);
|
|
817
|
-
return `UPDATE ${
|
|
830
|
+
return `UPDATE ${this.q(this.table)} SET ${setClauses.join(', ')}${whereSql}${this.dialect.buildReturningClause('*')}`;
|
|
818
831
|
});
|
|
819
832
|
// On cache hit, validate predicate
|
|
820
833
|
if (whereFp === '') {
|
|
@@ -863,7 +876,7 @@ class QueryInterface {
|
|
|
863
876
|
const clause = this.buildWhereClause(whereObj, freshParams);
|
|
864
877
|
const whereSql = clause ? ` WHERE ${clause}` : '';
|
|
865
878
|
this.assertMutationHasPredicate('delete', whereSql, args.allowFullTableScan);
|
|
866
|
-
return `DELETE FROM ${
|
|
879
|
+
return `DELETE FROM ${this.q(this.table)}${whereSql}${this.dialect.buildReturningClause('*')}`;
|
|
867
880
|
});
|
|
868
881
|
// On cache hit, still validate the predicate
|
|
869
882
|
if (whereFp === '') {
|
|
@@ -903,7 +916,7 @@ class QueryInterface {
|
|
|
903
916
|
const createEntries = Object.entries(args.create).filter(([, v]) => v !== undefined);
|
|
904
917
|
const columns = createEntries.map(([k]) => this.toSqlColumn(k));
|
|
905
918
|
const createParams = createEntries.map(([, v]) => v);
|
|
906
|
-
const placeholders = createEntries.map((_, i) =>
|
|
919
|
+
const placeholders = createEntries.map((_, i) => `${this.p(i + 1)}`);
|
|
907
920
|
// The conflict target comes from `where` keys — must be unique/PK columns
|
|
908
921
|
const conflictKeys = Object.keys(args.where).filter((k) => args.where[k] !== undefined);
|
|
909
922
|
const conflictColumns = conflictKeys.map((k) => this.toSqlColumn(k));
|
|
@@ -911,15 +924,20 @@ class QueryInterface {
|
|
|
911
924
|
const updateEntries = Object.entries(args.update).filter(([, v]) => v !== undefined);
|
|
912
925
|
let paramIdx = createParams.length + 1;
|
|
913
926
|
const setClauses = updateEntries.map(([k]) => {
|
|
914
|
-
const clause = `${this.toSqlColumn(k)} =
|
|
927
|
+
const clause = `${this.toSqlColumn(k)} = ${this.p(paramIdx)}`;
|
|
915
928
|
paramIdx++;
|
|
916
929
|
return clause;
|
|
917
930
|
});
|
|
918
931
|
const updateParams = updateEntries.map(([, v]) => v);
|
|
919
932
|
const params = [...createParams, ...updateParams];
|
|
920
|
-
const sql =
|
|
921
|
-
|
|
922
|
-
|
|
933
|
+
const sql = this.dialect.buildUpsertStatement({
|
|
934
|
+
table: this.q(this.table),
|
|
935
|
+
insertColumns: columns,
|
|
936
|
+
valuePlaceholders: placeholders,
|
|
937
|
+
conflictColumns,
|
|
938
|
+
updateSetClauses: setClauses,
|
|
939
|
+
returning: '*',
|
|
940
|
+
});
|
|
923
941
|
return {
|
|
924
942
|
sql,
|
|
925
943
|
params,
|
|
@@ -962,7 +980,7 @@ class QueryInterface {
|
|
|
962
980
|
const whereClause = this.buildWhereClause(whereObj, freshParams);
|
|
963
981
|
const whereSql = whereClause ? ` WHERE ${whereClause}` : '';
|
|
964
982
|
this.assertMutationHasPredicate('updateMany', whereSql, args.allowFullTableScan);
|
|
965
|
-
return `UPDATE ${
|
|
983
|
+
return `UPDATE ${this.q(this.table)} SET ${setClauses.join(', ')}${whereSql}`;
|
|
966
984
|
});
|
|
967
985
|
if (whereFp === '') {
|
|
968
986
|
this.assertMutationHasPredicate('updateMany', '', args.allowFullTableScan);
|
|
@@ -997,7 +1015,7 @@ class QueryInterface {
|
|
|
997
1015
|
const clause = this.buildWhereClause(whereObj, freshParams);
|
|
998
1016
|
const whereSql = clause ? ` WHERE ${clause}` : '';
|
|
999
1017
|
this.assertMutationHasPredicate('deleteMany', whereSql, args.allowFullTableScan);
|
|
1000
|
-
return `DELETE FROM ${
|
|
1018
|
+
return `DELETE FROM ${this.q(this.table)}${whereSql}`;
|
|
1001
1019
|
});
|
|
1002
1020
|
if (whereFp === '') {
|
|
1003
1021
|
this.assertMutationHasPredicate('deleteMany', '', args.allowFullTableScan);
|
|
@@ -1030,7 +1048,7 @@ class QueryInterface {
|
|
|
1030
1048
|
const freshParams = [];
|
|
1031
1049
|
const clause = args?.where ? this.buildWhereClause(whereObj, freshParams) : null;
|
|
1032
1050
|
const whereSql = clause ? ` WHERE ${clause}` : '';
|
|
1033
|
-
return `SELECT COUNT(*)::int AS count FROM ${
|
|
1051
|
+
return `SELECT COUNT(*)::int AS count FROM ${this.q(this.table)}${whereSql}`;
|
|
1034
1052
|
});
|
|
1035
1053
|
if (args?.where) {
|
|
1036
1054
|
this.collectWhereParams(whereObj, params);
|
|
@@ -1063,7 +1081,7 @@ class QueryInterface {
|
|
|
1063
1081
|
}
|
|
1064
1082
|
}
|
|
1065
1083
|
const groupColsRaw = args.by.map((k) => this.toColumn(k));
|
|
1066
|
-
const groupCols = groupColsRaw.map((c) =>
|
|
1084
|
+
const groupCols = groupColsRaw.map((c) => this.q(c));
|
|
1067
1085
|
const { sql: whereSql, params } = args.where ? this.buildWhere(args.where) : { sql: '', params: [] };
|
|
1068
1086
|
// Build SELECT expressions: group-by columns + aggregate functions
|
|
1069
1087
|
const selectExprs = [...groupCols];
|
|
@@ -1077,7 +1095,7 @@ class QueryInterface {
|
|
|
1077
1095
|
for (const [field, enabled] of Object.entries(args._sum)) {
|
|
1078
1096
|
if (enabled) {
|
|
1079
1097
|
const col = this.toColumn(field);
|
|
1080
|
-
selectExprs.push(`SUM(${
|
|
1098
|
+
selectExprs.push(`SUM(${this.q(col)}) AS ${this.q(`_sum_${col}`)}`);
|
|
1081
1099
|
}
|
|
1082
1100
|
}
|
|
1083
1101
|
}
|
|
@@ -1086,7 +1104,7 @@ class QueryInterface {
|
|
|
1086
1104
|
for (const [field, enabled] of Object.entries(args._avg)) {
|
|
1087
1105
|
if (enabled) {
|
|
1088
1106
|
const col = this.toColumn(field);
|
|
1089
|
-
selectExprs.push(`AVG(${
|
|
1107
|
+
selectExprs.push(`AVG(${this.q(col)})::float AS ${this.q(`_avg_${col}`)}`);
|
|
1090
1108
|
}
|
|
1091
1109
|
}
|
|
1092
1110
|
}
|
|
@@ -1095,7 +1113,7 @@ class QueryInterface {
|
|
|
1095
1113
|
for (const [field, enabled] of Object.entries(args._min)) {
|
|
1096
1114
|
if (enabled) {
|
|
1097
1115
|
const col = this.toColumn(field);
|
|
1098
|
-
selectExprs.push(`MIN(${
|
|
1116
|
+
selectExprs.push(`MIN(${this.q(col)}) AS ${this.q(`_min_${col}`)}`);
|
|
1099
1117
|
}
|
|
1100
1118
|
}
|
|
1101
1119
|
}
|
|
@@ -1104,11 +1122,11 @@ class QueryInterface {
|
|
|
1104
1122
|
for (const [field, enabled] of Object.entries(args._max)) {
|
|
1105
1123
|
if (enabled) {
|
|
1106
1124
|
const col = this.toColumn(field);
|
|
1107
|
-
selectExprs.push(`MAX(${
|
|
1125
|
+
selectExprs.push(`MAX(${this.q(col)}) AS ${this.q(`_max_${col}`)}`);
|
|
1108
1126
|
}
|
|
1109
1127
|
}
|
|
1110
1128
|
}
|
|
1111
|
-
let sql = `SELECT ${selectExprs.join(', ')} FROM ${
|
|
1129
|
+
let sql = `SELECT ${selectExprs.join(', ')} FROM ${this.q(this.table)}${whereSql} GROUP BY ${groupCols.join(', ')}`;
|
|
1112
1130
|
// ORDER BY
|
|
1113
1131
|
if (args.orderBy) {
|
|
1114
1132
|
sql += ` ORDER BY ${this.buildOrderBy(args.orderBy)}`;
|
|
@@ -1216,7 +1234,7 @@ class QueryInterface {
|
|
|
1216
1234
|
for (const [field, enabled] of Object.entries(args._count)) {
|
|
1217
1235
|
if (enabled) {
|
|
1218
1236
|
const col = this.toColumn(field);
|
|
1219
|
-
selectExprs.push(`COUNT(${
|
|
1237
|
+
selectExprs.push(`COUNT(${this.q(col)})::int AS ${this.q(`_count_${col}`)}`);
|
|
1220
1238
|
}
|
|
1221
1239
|
}
|
|
1222
1240
|
}
|
|
@@ -1225,7 +1243,7 @@ class QueryInterface {
|
|
|
1225
1243
|
for (const [field, enabled] of Object.entries(args._sum)) {
|
|
1226
1244
|
if (enabled) {
|
|
1227
1245
|
const col = this.toColumn(field);
|
|
1228
|
-
selectExprs.push(`SUM(${
|
|
1246
|
+
selectExprs.push(`SUM(${this.q(col)}) AS ${this.q(`_sum_${col}`)}`);
|
|
1229
1247
|
}
|
|
1230
1248
|
}
|
|
1231
1249
|
}
|
|
@@ -1234,7 +1252,7 @@ class QueryInterface {
|
|
|
1234
1252
|
for (const [field, enabled] of Object.entries(args._avg)) {
|
|
1235
1253
|
if (enabled) {
|
|
1236
1254
|
const col = this.toColumn(field);
|
|
1237
|
-
selectExprs.push(`AVG(${
|
|
1255
|
+
selectExprs.push(`AVG(${this.q(col)})::float AS ${this.q(`_avg_${col}`)}`);
|
|
1238
1256
|
}
|
|
1239
1257
|
}
|
|
1240
1258
|
}
|
|
@@ -1243,7 +1261,7 @@ class QueryInterface {
|
|
|
1243
1261
|
for (const [field, enabled] of Object.entries(args._min)) {
|
|
1244
1262
|
if (enabled) {
|
|
1245
1263
|
const col = this.toColumn(field);
|
|
1246
|
-
selectExprs.push(`MIN(${
|
|
1264
|
+
selectExprs.push(`MIN(${this.q(col)}) AS ${this.q(`_min_${col}`)}`);
|
|
1247
1265
|
}
|
|
1248
1266
|
}
|
|
1249
1267
|
}
|
|
@@ -1252,14 +1270,14 @@ class QueryInterface {
|
|
|
1252
1270
|
for (const [field, enabled] of Object.entries(args._max)) {
|
|
1253
1271
|
if (enabled) {
|
|
1254
1272
|
const col = this.toColumn(field);
|
|
1255
|
-
selectExprs.push(`MAX(${
|
|
1273
|
+
selectExprs.push(`MAX(${this.q(col)}) AS ${this.q(`_max_${col}`)}`);
|
|
1256
1274
|
}
|
|
1257
1275
|
}
|
|
1258
1276
|
}
|
|
1259
1277
|
if (selectExprs.length === 0) {
|
|
1260
1278
|
selectExprs.push('COUNT(*)::int AS _count');
|
|
1261
1279
|
}
|
|
1262
|
-
const sql = `SELECT ${selectExprs.join(', ')} FROM ${
|
|
1280
|
+
const sql = `SELECT ${selectExprs.join(', ')} FROM ${this.q(this.table)}${whereSql}`;
|
|
1263
1281
|
return {
|
|
1264
1282
|
sql,
|
|
1265
1283
|
params,
|
|
@@ -1376,7 +1394,7 @@ class QueryInterface {
|
|
|
1376
1394
|
}
|
|
1377
1395
|
/** Convert camelCase field name to a double-quoted SQL identifier */
|
|
1378
1396
|
toSqlColumn(field) {
|
|
1379
|
-
return
|
|
1397
|
+
return this.q(this.toColumn(field));
|
|
1380
1398
|
}
|
|
1381
1399
|
/**
|
|
1382
1400
|
* Build a single SET clause entry for update/updateMany.
|
|
@@ -1409,7 +1427,7 @@ class QueryInterface {
|
|
|
1409
1427
|
const opValue = v[op];
|
|
1410
1428
|
if (op === 'set') {
|
|
1411
1429
|
params.push(opValue);
|
|
1412
|
-
return `${col} =
|
|
1430
|
+
return `${col} = ${this.p(params.length)}`;
|
|
1413
1431
|
}
|
|
1414
1432
|
// Arithmetic operators: must be finite numbers
|
|
1415
1433
|
if (typeof opValue !== 'number' || !Number.isFinite(opValue)) {
|
|
@@ -1417,19 +1435,19 @@ class QueryInterface {
|
|
|
1417
1435
|
}
|
|
1418
1436
|
if (op === 'increment') {
|
|
1419
1437
|
params.push(opValue);
|
|
1420
|
-
return `${col} = ${col} +
|
|
1438
|
+
return `${col} = ${col} + ${this.p(params.length)}`;
|
|
1421
1439
|
}
|
|
1422
1440
|
if (op === 'decrement') {
|
|
1423
1441
|
params.push(opValue);
|
|
1424
|
-
return `${col} = ${col} -
|
|
1442
|
+
return `${col} = ${col} - ${this.p(params.length)}`;
|
|
1425
1443
|
}
|
|
1426
1444
|
if (op === 'multiply') {
|
|
1427
1445
|
params.push(opValue);
|
|
1428
|
-
return `${col} = ${col} *
|
|
1446
|
+
return `${col} = ${col} * ${this.p(params.length)}`;
|
|
1429
1447
|
}
|
|
1430
1448
|
if (op === 'divide') {
|
|
1431
1449
|
params.push(opValue);
|
|
1432
|
-
return `${col} = ${col} /
|
|
1450
|
+
return `${col} = ${col} / ${this.p(params.length)}`;
|
|
1433
1451
|
}
|
|
1434
1452
|
}
|
|
1435
1453
|
// Fall through: multi-key objects or non-operator single-key objects
|
|
@@ -1437,7 +1455,7 @@ class QueryInterface {
|
|
|
1437
1455
|
}
|
|
1438
1456
|
// Plain value (including null, Date, Buffer, arrays, JSON objects)
|
|
1439
1457
|
params.push(value);
|
|
1440
|
-
return `${col} =
|
|
1458
|
+
return `${col} = ${this.p(params.length)}`;
|
|
1441
1459
|
}
|
|
1442
1460
|
// =========================================================================
|
|
1443
1461
|
// Fingerprinting — value-invariant shape keys for SQL cache lookup
|
|
@@ -1960,7 +1978,7 @@ class QueryInterface {
|
|
|
1960
1978
|
}
|
|
1961
1979
|
}
|
|
1962
1980
|
const rawColumn = this.toColumn(key);
|
|
1963
|
-
const column =
|
|
1981
|
+
const column = this.q(rawColumn);
|
|
1964
1982
|
// Handle null → IS NULL
|
|
1965
1983
|
if (value === null) {
|
|
1966
1984
|
andClauses.push(`${column} IS NULL`);
|
|
@@ -2010,7 +2028,7 @@ class QueryInterface {
|
|
|
2010
2028
|
}
|
|
2011
2029
|
// Plain equality
|
|
2012
2030
|
params.push(value);
|
|
2013
|
-
andClauses.push(`${column} =
|
|
2031
|
+
andClauses.push(`${column} = ${this.p(params.length)}`);
|
|
2014
2032
|
}
|
|
2015
2033
|
if (andClauses.length === 0)
|
|
2016
2034
|
return null;
|
|
@@ -2025,18 +2043,18 @@ class QueryInterface {
|
|
|
2025
2043
|
const targetMeta = this.schema.tables[targetTable];
|
|
2026
2044
|
if (!targetMeta)
|
|
2027
2045
|
return null;
|
|
2028
|
-
const qt =
|
|
2029
|
-
const qSelf =
|
|
2046
|
+
const qt = this.q(targetTable);
|
|
2047
|
+
const qSelf = this.q(this.table);
|
|
2030
2048
|
const clauses = [];
|
|
2031
2049
|
// Correlation: link child table to parent table (supports composite FKs)
|
|
2032
2050
|
let correlation;
|
|
2033
2051
|
if (relDef.type === 'hasMany' || relDef.type === 'hasOne') {
|
|
2034
2052
|
// parent.pk = child.fk
|
|
2035
|
-
correlation =
|
|
2053
|
+
correlation = this.dialect.buildCorrelation(qt, relDef.foreignKey, qSelf, relDef.referenceKey);
|
|
2036
2054
|
}
|
|
2037
2055
|
else {
|
|
2038
2056
|
// belongsTo: parent.fk = child.pk
|
|
2039
|
-
correlation =
|
|
2057
|
+
correlation = this.dialect.buildCorrelation(qt, relDef.referenceKey, qSelf, relDef.foreignKey);
|
|
2040
2058
|
}
|
|
2041
2059
|
// "some": EXISTS (SELECT 1 FROM target WHERE correlation AND filter)
|
|
2042
2060
|
if (filterObj.some !== undefined) {
|
|
@@ -2073,7 +2091,7 @@ class QueryInterface {
|
|
|
2073
2091
|
const meta = this.schema.tables[targetTable];
|
|
2074
2092
|
if (!meta)
|
|
2075
2093
|
return null;
|
|
2076
|
-
const qt =
|
|
2094
|
+
const qt = this.q(targetTable);
|
|
2077
2095
|
const conditions = [];
|
|
2078
2096
|
for (const [field, value] of Object.entries(subWhere)) {
|
|
2079
2097
|
if (value === undefined)
|
|
@@ -2083,7 +2101,7 @@ class QueryInterface {
|
|
|
2083
2101
|
throw new errors_js_1.ValidationError(`[turbine] Unknown field "${field}" in relation filter for table "${targetTable}". ` +
|
|
2084
2102
|
`Known fields: ${Object.keys(meta.columnMap).join(', ') || '(none)'}.`);
|
|
2085
2103
|
}
|
|
2086
|
-
const qCol = `${qt}.${
|
|
2104
|
+
const qCol = `${qt}.${this.q(col)}`;
|
|
2087
2105
|
if (value === null) {
|
|
2088
2106
|
conditions.push(`${qCol} IS NULL`);
|
|
2089
2107
|
continue;
|
|
@@ -2094,7 +2112,7 @@ class QueryInterface {
|
|
|
2094
2112
|
continue;
|
|
2095
2113
|
}
|
|
2096
2114
|
params.push(value);
|
|
2097
|
-
conditions.push(`${qCol} =
|
|
2115
|
+
conditions.push(`${qCol} = ${this.p(params.length)}`);
|
|
2098
2116
|
}
|
|
2099
2117
|
return conditions.length > 0 ? conditions.join(' AND ') : null;
|
|
2100
2118
|
}
|
|
@@ -2106,19 +2124,19 @@ class QueryInterface {
|
|
|
2106
2124
|
const clauses = [];
|
|
2107
2125
|
if (op.gt !== undefined) {
|
|
2108
2126
|
params.push(op.gt);
|
|
2109
|
-
clauses.push(`${column} >
|
|
2127
|
+
clauses.push(`${column} > ${this.p(params.length)}`);
|
|
2110
2128
|
}
|
|
2111
2129
|
if (op.gte !== undefined) {
|
|
2112
2130
|
params.push(op.gte);
|
|
2113
|
-
clauses.push(`${column} >=
|
|
2131
|
+
clauses.push(`${column} >= ${this.p(params.length)}`);
|
|
2114
2132
|
}
|
|
2115
2133
|
if (op.lt !== undefined) {
|
|
2116
2134
|
params.push(op.lt);
|
|
2117
|
-
clauses.push(`${column} <
|
|
2135
|
+
clauses.push(`${column} < ${this.p(params.length)}`);
|
|
2118
2136
|
}
|
|
2119
2137
|
if (op.lte !== undefined) {
|
|
2120
2138
|
params.push(op.lte);
|
|
2121
|
-
clauses.push(`${column} <=
|
|
2139
|
+
clauses.push(`${column} <= ${this.p(params.length)}`);
|
|
2122
2140
|
}
|
|
2123
2141
|
if (op.not !== undefined) {
|
|
2124
2142
|
if (op.not === null) {
|
|
@@ -2126,30 +2144,29 @@ class QueryInterface {
|
|
|
2126
2144
|
}
|
|
2127
2145
|
else {
|
|
2128
2146
|
params.push(op.not);
|
|
2129
|
-
clauses.push(`${column} !=
|
|
2147
|
+
clauses.push(`${column} != ${this.p(params.length)}`);
|
|
2130
2148
|
}
|
|
2131
2149
|
}
|
|
2132
2150
|
if (op.in !== undefined) {
|
|
2133
2151
|
params.push(op.in);
|
|
2134
|
-
clauses.push(`${column} = ANY(
|
|
2152
|
+
clauses.push(`${column} = ANY(${this.p(params.length)})`);
|
|
2135
2153
|
}
|
|
2136
2154
|
if (op.notIn !== undefined) {
|
|
2137
2155
|
params.push(op.notIn);
|
|
2138
|
-
clauses.push(`${column} != ALL(
|
|
2156
|
+
clauses.push(`${column} != ALL(${this.p(params.length)})`);
|
|
2139
2157
|
}
|
|
2140
|
-
|
|
2141
|
-
const likeOp = op.mode === 'insensitive' ? 'ILIKE' : 'LIKE';
|
|
2158
|
+
const buildLikeClause = (paramRef) => op.mode === 'insensitive' ? this.dialect.buildInsensitiveLike(column, paramRef) : `${column} LIKE ${paramRef}`;
|
|
2142
2159
|
if (op.contains !== undefined) {
|
|
2143
2160
|
params.push(`%${(0, utils_js_1.escapeLike)(op.contains)}%`);
|
|
2144
|
-
clauses.push(`${
|
|
2161
|
+
clauses.push(`${buildLikeClause(this.p(params.length))} ESCAPE '\\'`);
|
|
2145
2162
|
}
|
|
2146
2163
|
if (op.startsWith !== undefined) {
|
|
2147
2164
|
params.push(`${(0, utils_js_1.escapeLike)(op.startsWith)}%`);
|
|
2148
|
-
clauses.push(`${
|
|
2165
|
+
clauses.push(`${buildLikeClause(this.p(params.length))} ESCAPE '\\'`);
|
|
2149
2166
|
}
|
|
2150
2167
|
if (op.endsWith !== undefined) {
|
|
2151
2168
|
params.push(`%${(0, utils_js_1.escapeLike)(op.endsWith)}`);
|
|
2152
|
-
clauses.push(`${
|
|
2169
|
+
clauses.push(`${buildLikeClause(this.p(params.length))} ESCAPE '\\'`);
|
|
2153
2170
|
}
|
|
2154
2171
|
return clauses;
|
|
2155
2172
|
}
|
|
@@ -2309,8 +2326,8 @@ class QueryInterface {
|
|
|
2309
2326
|
if (!meta)
|
|
2310
2327
|
throw new errors_js_1.ValidationError(`[turbine] Unknown table "${table}"`);
|
|
2311
2328
|
const cols = columnsList ?? meta.allColumns;
|
|
2312
|
-
const qtbl =
|
|
2313
|
-
const baseCols = cols.map((col) => `${qtbl}.${
|
|
2329
|
+
const qtbl = this.q(table);
|
|
2330
|
+
const baseCols = cols.map((col) => `${qtbl}.${this.q(col)}`).join(', ');
|
|
2314
2331
|
const relationSelects = [];
|
|
2315
2332
|
const aliasCounter = { n: 0 };
|
|
2316
2333
|
for (const [relName, relSpec] of Object.entries(withClause)) {
|
|
@@ -2321,7 +2338,7 @@ class QueryInterface {
|
|
|
2321
2338
|
}
|
|
2322
2339
|
// The main table is not aliased, so pass table name as parentRef
|
|
2323
2340
|
const subquery = this.buildRelationSubquery(relDef, relSpec, params, table, aliasCounter, depth, path);
|
|
2324
|
-
relationSelects.push(`(${subquery}) AS ${
|
|
2341
|
+
relationSelects.push(`(${subquery}) AS ${this.q(relName)}`);
|
|
2325
2342
|
}
|
|
2326
2343
|
return [baseCols, ...relationSelects].join(', ');
|
|
2327
2344
|
}
|
|
@@ -2383,7 +2400,7 @@ class QueryInterface {
|
|
|
2383
2400
|
* 8. **Parameter threading:** All user-supplied values (where filters, limit) are
|
|
2384
2401
|
* pushed to the shared `params` array with `$N` placeholders. No string
|
|
2385
2402
|
* interpolation of user data ever occurs -- all identifiers go through
|
|
2386
|
-
* `
|
|
2403
|
+
* `this.q()` and all values are parameterized.
|
|
2387
2404
|
*
|
|
2388
2405
|
* ### Example output (hasMany with nested relation)
|
|
2389
2406
|
* ```sql
|
|
@@ -2447,8 +2464,11 @@ class QueryInterface {
|
|
|
2447
2464
|
.map(([k]) => targetMeta.columnMap[k] ?? (0, schema_js_1.camelToSnake)(k)));
|
|
2448
2465
|
targetColumns = targetMeta.allColumns.filter((col) => !omittedFields.has(col));
|
|
2449
2466
|
}
|
|
2450
|
-
// Build
|
|
2451
|
-
const jsonPairs = targetColumns.map((col) =>
|
|
2467
|
+
// Build JSON object pairs for resolved columns
|
|
2468
|
+
const jsonPairs = targetColumns.map((col) => [
|
|
2469
|
+
targetMeta.reverseColumnMap[col] ?? (0, schema_js_1.snakeToCamel)(col),
|
|
2470
|
+
`${alias}.${this.q(col)}`,
|
|
2471
|
+
]);
|
|
2452
2472
|
// Determine if this hasMany will take the wrapped subquery path (LIMIT or ORDER BY).
|
|
2453
2473
|
// When wrapping, nested relations are built in the wrapped path referencing innerAlias,
|
|
2454
2474
|
// so we must NOT build them here (they would push orphaned params).
|
|
@@ -2464,14 +2484,14 @@ class QueryInterface {
|
|
|
2464
2484
|
// Recursively build nested subquery, passing THIS alias as the parent reference
|
|
2465
2485
|
const nestedSubquery = this.buildRelationSubquery(nestedRelDef, nestedSpec, params, alias, aliasCounter, currentDepth + 1, [...currentPath, relDef.name]);
|
|
2466
2486
|
// Use '[]'::json for hasMany (empty array), NULL for belongsTo/hasOne (no object)
|
|
2467
|
-
const fallback = nestedRelDef.type === 'hasMany' ?
|
|
2468
|
-
jsonPairs.push(
|
|
2487
|
+
const fallback = nestedRelDef.type === 'hasMany' ? this.dialect.emptyJsonArrayLiteral : this.dialect.nullJsonLiteral;
|
|
2488
|
+
jsonPairs.push([nestedRelName, `COALESCE((${nestedSubquery}), ${fallback})`]);
|
|
2469
2489
|
}
|
|
2470
2490
|
}
|
|
2471
|
-
const jsonObj =
|
|
2491
|
+
const jsonObj = this.dialect.buildJsonObject(jsonPairs);
|
|
2472
2492
|
// Quote parent ref — can be a table name or auto-generated alias
|
|
2473
|
-
const qParent =
|
|
2474
|
-
const qTarget =
|
|
2493
|
+
const qParent = this.q(parentRef);
|
|
2494
|
+
const qTarget = this.q(targetTable);
|
|
2475
2495
|
// Build ORDER BY for json_agg
|
|
2476
2496
|
let orderClause = '';
|
|
2477
2497
|
if (spec !== true && spec.orderBy) {
|
|
@@ -2482,7 +2502,7 @@ class QueryInterface {
|
|
|
2482
2502
|
throw new errors_js_1.ValidationError(`[turbine] Unknown column "${k}" in orderBy for table "${targetTable}"`);
|
|
2483
2503
|
}
|
|
2484
2504
|
const safeDir = dir.toLowerCase() === 'desc' ? 'DESC' : 'ASC';
|
|
2485
|
-
return `${alias}.${
|
|
2505
|
+
return `${alias}.${this.q(col)} ${safeDir}`;
|
|
2486
2506
|
})
|
|
2487
2507
|
.join(', ');
|
|
2488
2508
|
orderClause = ` ORDER BY ${orders}`;
|
|
@@ -2493,10 +2513,10 @@ class QueryInterface {
|
|
|
2493
2513
|
// Supports composite foreign keys (string[]) via buildCorrelation.
|
|
2494
2514
|
let whereClause;
|
|
2495
2515
|
if (relDef.type === 'belongsTo' || relDef.type === 'hasOne') {
|
|
2496
|
-
whereClause =
|
|
2516
|
+
whereClause = this.dialect.buildCorrelation(alias, relDef.referenceKey, qParent, relDef.foreignKey);
|
|
2497
2517
|
}
|
|
2498
2518
|
else {
|
|
2499
|
-
whereClause =
|
|
2519
|
+
whereClause = this.dialect.buildCorrelation(alias, relDef.foreignKey, qParent, relDef.referenceKey);
|
|
2500
2520
|
}
|
|
2501
2521
|
// Additional filters — properly parameterized
|
|
2502
2522
|
if (spec !== true && spec.where) {
|
|
@@ -2506,14 +2526,14 @@ class QueryInterface {
|
|
|
2506
2526
|
throw new errors_js_1.ValidationError(`[turbine] Unknown column "${k}" in where for table "${targetTable}"`);
|
|
2507
2527
|
}
|
|
2508
2528
|
params.push(v);
|
|
2509
|
-
whereClause += ` AND ${alias}.${
|
|
2529
|
+
whereClause += ` AND ${alias}.${this.q(col)} = ${this.p(params.length)}`;
|
|
2510
2530
|
}
|
|
2511
2531
|
}
|
|
2512
2532
|
// LIMIT
|
|
2513
2533
|
let limitClause = '';
|
|
2514
2534
|
if (spec !== true && spec.limit) {
|
|
2515
2535
|
params.push(Number(spec.limit));
|
|
2516
|
-
limitClause = ` LIMIT
|
|
2536
|
+
limitClause = ` LIMIT ${this.p(params.length)}`;
|
|
2517
2537
|
}
|
|
2518
2538
|
if (relDef.type === 'hasMany') {
|
|
2519
2539
|
// When LIMIT or ORDER BY is used, wrap in a subquery so LIMIT applies to rows
|
|
@@ -2522,9 +2542,12 @@ class QueryInterface {
|
|
|
2522
2542
|
const innerAlias = `${alias}i`;
|
|
2523
2543
|
// Rewrite: SELECT json_agg(json_build_object(...)) FROM (SELECT * FROM table WHERE ... ORDER BY ... LIMIT N) AS alias
|
|
2524
2544
|
// 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}.${
|
|
2545
|
+
const innerSql = `SELECT ${targetMeta.allColumns.map((c) => `${alias}.${this.q(c)}`).join(', ')} FROM ${qTarget} ${alias} WHERE ${whereClause}${orderClause}${limitClause}`;
|
|
2526
2546
|
// For the json_build_object, reference the inner alias — only include resolved columns
|
|
2527
|
-
const innerJsonPairs = targetColumns.map((col) =>
|
|
2547
|
+
const innerJsonPairs = targetColumns.map((col) => [
|
|
2548
|
+
targetMeta.reverseColumnMap[col] ?? (0, schema_js_1.snakeToCamel)(col),
|
|
2549
|
+
`${innerAlias}.${this.q(col)}`,
|
|
2550
|
+
]);
|
|
2528
2551
|
// Build nested relation subqueries referencing innerAlias
|
|
2529
2552
|
if (spec !== true && spec.with) {
|
|
2530
2553
|
for (const [nestedRelName, nestedSpec] of Object.entries(spec.with)) {
|
|
@@ -2534,14 +2557,14 @@ class QueryInterface {
|
|
|
2534
2557
|
`Available: ${Object.keys(targetMeta.relations).join(', ')}`);
|
|
2535
2558
|
}
|
|
2536
2559
|
const nestedSub = this.buildRelationSubquery(nestedRelDef, nestedSpec, params, innerAlias, aliasCounter, currentDepth + 1, [...currentPath, relDef.name]);
|
|
2537
|
-
const fallback = nestedRelDef.type === 'hasMany' ?
|
|
2538
|
-
innerJsonPairs.push(
|
|
2560
|
+
const fallback = nestedRelDef.type === 'hasMany' ? this.dialect.emptyJsonArrayLiteral : this.dialect.nullJsonLiteral;
|
|
2561
|
+
innerJsonPairs.push([nestedRelName, `COALESCE((${nestedSub}), ${fallback})`]);
|
|
2539
2562
|
}
|
|
2540
2563
|
}
|
|
2541
|
-
const innerJsonObj =
|
|
2542
|
-
return `SELECT
|
|
2564
|
+
const innerJsonObj = this.dialect.buildJsonObject(innerJsonPairs);
|
|
2565
|
+
return `SELECT ${this.dialect.buildJsonArrayAgg(innerJsonObj)} FROM (${innerSql}) ${innerAlias}`;
|
|
2543
2566
|
}
|
|
2544
|
-
return `SELECT
|
|
2567
|
+
return `SELECT ${this.dialect.buildJsonArrayAgg(jsonObj, orderClause.trim() || undefined)} FROM ${qTarget} ${alias} WHERE ${whereClause}`;
|
|
2545
2568
|
}
|
|
2546
2569
|
// belongsTo / hasOne — return single object
|
|
2547
2570
|
return `SELECT ${jsonObj} FROM ${qTarget} ${alias} WHERE ${whereClause} LIMIT 1`;
|
|
@@ -2588,22 +2611,22 @@ class QueryInterface {
|
|
|
2588
2611
|
params.push(filter.path);
|
|
2589
2612
|
const pathParam = params.length;
|
|
2590
2613
|
params.push(String(filter.equals));
|
|
2591
|
-
clauses.push(`${column
|
|
2614
|
+
clauses.push(`${this.dialect.buildJsonPathExtract(column, this.p(pathParam))} = ${this.p(params.length)}`);
|
|
2592
2615
|
}
|
|
2593
2616
|
else if (filter.equals !== undefined) {
|
|
2594
2617
|
// Containment equality: column @> $N::jsonb
|
|
2595
2618
|
params.push(JSON.stringify(filter.equals));
|
|
2596
|
-
clauses.push(
|
|
2619
|
+
clauses.push(this.dialect.buildJsonContains(column, this.p(params.length)));
|
|
2597
2620
|
}
|
|
2598
2621
|
if (filter.contains !== undefined) {
|
|
2599
2622
|
// Containment: column @> $N::jsonb
|
|
2600
2623
|
params.push(JSON.stringify(filter.contains));
|
|
2601
|
-
clauses.push(
|
|
2624
|
+
clauses.push(this.dialect.buildJsonContains(column, this.p(params.length)));
|
|
2602
2625
|
}
|
|
2603
2626
|
if (filter.hasKey !== undefined) {
|
|
2604
2627
|
// Key existence: column ? $N
|
|
2605
2628
|
params.push(filter.hasKey);
|
|
2606
|
-
clauses.push(`${column} ?
|
|
2629
|
+
clauses.push(`${column} ? ${this.p(params.length)}`);
|
|
2607
2630
|
}
|
|
2608
2631
|
return clauses;
|
|
2609
2632
|
}
|
|
@@ -2617,17 +2640,17 @@ class QueryInterface {
|
|
|
2617
2640
|
if (filter.has !== undefined) {
|
|
2618
2641
|
// value = ANY(column)
|
|
2619
2642
|
params.push(filter.has);
|
|
2620
|
-
clauses.push(
|
|
2643
|
+
clauses.push(`${this.p(params.length)} = ANY(${column})`);
|
|
2621
2644
|
}
|
|
2622
2645
|
if (filter.hasEvery !== undefined) {
|
|
2623
2646
|
// column @> ARRAY[...]::type[]
|
|
2624
2647
|
params.push(filter.hasEvery);
|
|
2625
|
-
clauses.push(`${column} @>
|
|
2648
|
+
clauses.push(`${column} @> ${this.p(params.length)}::${elementType}[]`);
|
|
2626
2649
|
}
|
|
2627
2650
|
if (filter.hasSome !== undefined) {
|
|
2628
2651
|
// column && ARRAY[...]::type[]
|
|
2629
2652
|
params.push(filter.hasSome);
|
|
2630
|
-
clauses.push(`${column} &&
|
|
2653
|
+
clauses.push(`${column} && ${this.p(params.length)}::${elementType}[]`);
|
|
2631
2654
|
}
|
|
2632
2655
|
if (filter.isEmpty === true) {
|
|
2633
2656
|
// array_length(column, 1) IS NULL
|