@budibase/backend-core 2.33.2 → 2.33.3
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/index.js +531 -265
- package/dist/index.js.map +4 -4
- package/dist/index.js.meta.json +1 -1
- package/dist/package.json +4 -4
- package/dist/plugins.js.map +1 -1
- package/dist/plugins.js.meta.json +1 -1
- package/dist/src/context/identity.js +1 -1
- package/dist/src/context/identity.js.map +1 -1
- package/dist/src/db/couch/DatabaseImpl.js +1 -1
- package/dist/src/db/couch/DatabaseImpl.js.map +1 -1
- package/dist/src/environment.js +28 -14
- package/dist/src/environment.js.map +1 -1
- package/dist/src/features/features.d.ts +1 -0
- package/dist/src/features/features.js +4 -3
- package/dist/src/features/features.js.map +1 -1
- package/dist/src/middleware/authenticated.js +16 -8
- package/dist/src/middleware/authenticated.js.map +1 -1
- package/dist/src/security/roles.d.ts +24 -3
- package/dist/src/security/roles.js +210 -51
- package/dist/src/security/roles.js.map +1 -1
- package/dist/src/sql/sql.js +243 -160
- package/dist/src/sql/sql.js.map +1 -1
- package/dist/src/sql/sqlTable.js +4 -1
- package/dist/src/sql/sqlTable.js.map +1 -1
- package/package.json +4 -4
- package/src/context/identity.ts +1 -1
- package/src/db/couch/DatabaseImpl.ts +2 -1
- package/src/environment.ts +33 -17
- package/src/features/features.ts +4 -3
- package/src/middleware/authenticated.ts +33 -17
- package/src/security/roles.ts +238 -56
- package/src/sql/sql.ts +290 -206
- package/src/sql/sqlTable.ts +4 -1
package/dist/src/sql/sql.js
CHANGED
|
@@ -90,6 +90,20 @@ function isSqs(table) {
|
|
|
90
90
|
return (table.sourceType === types_1.TableSourceType.INTERNAL ||
|
|
91
91
|
table.sourceId === types_1.INTERNAL_TABLE_SOURCE_ID);
|
|
92
92
|
}
|
|
93
|
+
function escapeQuotes(value, quoteChar = '"') {
|
|
94
|
+
return value.replace(new RegExp(quoteChar, "g"), `${quoteChar}${quoteChar}`);
|
|
95
|
+
}
|
|
96
|
+
function wrap(value, quoteChar = '"') {
|
|
97
|
+
return `${quoteChar}${escapeQuotes(value, quoteChar)}${quoteChar}`;
|
|
98
|
+
}
|
|
99
|
+
function stringifyArray(value, quoteStyle = '"') {
|
|
100
|
+
for (let i in value) {
|
|
101
|
+
if (typeof value[i] === "string") {
|
|
102
|
+
value[i] = wrap(value[i], quoteStyle);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return `[${value.join(",")}]`;
|
|
106
|
+
}
|
|
93
107
|
const allowEmptyRelationships = {
|
|
94
108
|
[types_1.BasicOperator.EQUAL]: false,
|
|
95
109
|
[types_1.BasicOperator.NOT_EQUAL]: true,
|
|
@@ -131,28 +145,24 @@ class InternalBuilder {
|
|
|
131
145
|
get table() {
|
|
132
146
|
return this.query.meta.table;
|
|
133
147
|
}
|
|
148
|
+
get knexClient() {
|
|
149
|
+
return this.knex.client;
|
|
150
|
+
}
|
|
134
151
|
getFieldSchema(key) {
|
|
135
152
|
const { column } = this.splitter.run(key);
|
|
136
153
|
return this.table.schema[column];
|
|
137
154
|
}
|
|
155
|
+
supportsILike() {
|
|
156
|
+
return !(this.client === types_1.SqlClient.ORACLE || this.client === types_1.SqlClient.SQL_LITE);
|
|
157
|
+
}
|
|
138
158
|
quoteChars() {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
case types_1.SqlClient.POSTGRES:
|
|
142
|
-
return ['"', '"'];
|
|
143
|
-
case types_1.SqlClient.MS_SQL:
|
|
144
|
-
return ["[", "]"];
|
|
145
|
-
case types_1.SqlClient.MARIADB:
|
|
146
|
-
case types_1.SqlClient.MY_SQL:
|
|
147
|
-
case types_1.SqlClient.SQL_LITE:
|
|
148
|
-
return ["`", "`"];
|
|
149
|
-
}
|
|
159
|
+
const wrapped = this.knexClient.wrapIdentifier("foo", {});
|
|
160
|
+
return [wrapped[0], wrapped[wrapped.length - 1]];
|
|
150
161
|
}
|
|
151
|
-
// Takes a string like foo and returns a quoted string like [foo] for SQL
|
|
152
|
-
// and "foo" for Postgres.
|
|
162
|
+
// Takes a string like foo and returns a quoted string like [foo] for SQL
|
|
163
|
+
// Server and "foo" for Postgres.
|
|
153
164
|
quote(str) {
|
|
154
|
-
|
|
155
|
-
return `${start}${str}${end}`;
|
|
165
|
+
return this.knexClient.wrapIdentifier(str, {});
|
|
156
166
|
}
|
|
157
167
|
isQuoted(key) {
|
|
158
168
|
const [start, end] = this.quoteChars();
|
|
@@ -167,6 +177,27 @@ class InternalBuilder {
|
|
|
167
177
|
}
|
|
168
178
|
return key.map(part => this.quote(part)).join(".");
|
|
169
179
|
}
|
|
180
|
+
quotedValue(value) {
|
|
181
|
+
const formatter = this.knexClient.formatter(this.knexClient.queryBuilder());
|
|
182
|
+
return formatter.wrap(value, false);
|
|
183
|
+
}
|
|
184
|
+
rawQuotedValue(value) {
|
|
185
|
+
return this.knex.raw(this.quotedValue(value));
|
|
186
|
+
}
|
|
187
|
+
// Unfortuantely we cannot rely on knex's identifier escaping because it trims
|
|
188
|
+
// the identifier string before escaping it, which breaks cases for us where
|
|
189
|
+
// columns that start or end with a space aren't referenced correctly anymore.
|
|
190
|
+
//
|
|
191
|
+
// So whenever you're using an identifier binding in knex, e.g. knex.raw("??
|
|
192
|
+
// as ?", ["foo", "bar"]), you need to make sure you call this:
|
|
193
|
+
//
|
|
194
|
+
// knex.raw("?? as ?", [this.quotedIdentifier("foo"), "bar"])
|
|
195
|
+
//
|
|
196
|
+
// Issue we filed against knex about this:
|
|
197
|
+
// https://github.com/knex/knex/issues/6143
|
|
198
|
+
rawQuotedIdentifier(key) {
|
|
199
|
+
return this.knex.raw(this.quotedIdentifier(key));
|
|
200
|
+
}
|
|
170
201
|
// Turns an identifier like a.b.c or `a`.`b`.`c` into ["a", "b", "c"]
|
|
171
202
|
splitIdentifier(key) {
|
|
172
203
|
const [start, end] = this.quoteChars();
|
|
@@ -206,7 +237,7 @@ class InternalBuilder {
|
|
|
206
237
|
const alias = this.getTableName(endpoint.entityId);
|
|
207
238
|
const schema = meta.table.schema;
|
|
208
239
|
if (!this.isFullSelectStatementRequired()) {
|
|
209
|
-
return [this.knex.raw(`${
|
|
240
|
+
return [this.knex.raw("??", [`${alias}.*`])];
|
|
210
241
|
}
|
|
211
242
|
// get just the fields for this table
|
|
212
243
|
return resource.fields
|
|
@@ -225,17 +256,28 @@ class InternalBuilder {
|
|
|
225
256
|
.map(({ table, column, field }) => {
|
|
226
257
|
const columnSchema = schema[column];
|
|
227
258
|
if (this.SPECIAL_SELECT_CASES.POSTGRES_MONEY(columnSchema)) {
|
|
228
|
-
|
|
259
|
+
// TODO: figure out how to express this safely without string
|
|
260
|
+
// interpolation.
|
|
261
|
+
return this.knex.raw(`??::money::numeric as "${field}"`, [
|
|
262
|
+
this.rawQuotedIdentifier([table, column].join(".")),
|
|
263
|
+
field,
|
|
264
|
+
]);
|
|
229
265
|
}
|
|
230
266
|
if (this.SPECIAL_SELECT_CASES.MSSQL_DATES(columnSchema)) {
|
|
231
267
|
// Time gets returned as timestamp from mssql, not matching the expected
|
|
232
268
|
// HH:mm format
|
|
233
|
-
|
|
269
|
+
// TODO: figure out how to express this safely without string
|
|
270
|
+
// interpolation.
|
|
271
|
+
return this.knex.raw(`CONVERT(varchar, ??, 108) as "${field}"`, [
|
|
272
|
+
this.rawQuotedIdentifier(field),
|
|
273
|
+
]);
|
|
274
|
+
}
|
|
275
|
+
if (table) {
|
|
276
|
+
return this.rawQuotedIdentifier(`${table}.${column}`);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
return this.rawQuotedIdentifier(field);
|
|
234
280
|
}
|
|
235
|
-
const quoted = table
|
|
236
|
-
? `${this.quote(table)}.${this.quote(column)}`
|
|
237
|
-
: this.quote(field);
|
|
238
|
-
return this.knex.raw(quoted);
|
|
239
281
|
});
|
|
240
282
|
}
|
|
241
283
|
// OracleDB can't use character-large-objects (CLOBs) in WHERE clauses,
|
|
@@ -248,7 +290,7 @@ class InternalBuilder {
|
|
|
248
290
|
const parts = this.splitIdentifier(field);
|
|
249
291
|
const col = parts.pop();
|
|
250
292
|
const schema = this.table.schema[col];
|
|
251
|
-
let identifier = this.
|
|
293
|
+
let identifier = this.rawQuotedIdentifier(field);
|
|
252
294
|
if (schema.type === types_1.FieldType.STRING ||
|
|
253
295
|
schema.type === types_1.FieldType.LONGFORM ||
|
|
254
296
|
schema.type === types_1.FieldType.BB_REFERENCE_SINGLE ||
|
|
@@ -256,10 +298,13 @@ class InternalBuilder {
|
|
|
256
298
|
schema.type === types_1.FieldType.OPTIONS ||
|
|
257
299
|
schema.type === types_1.FieldType.BARCODEQR) {
|
|
258
300
|
if (opts === null || opts === void 0 ? void 0 : opts.forSelect) {
|
|
259
|
-
identifier =
|
|
301
|
+
identifier = this.knex.raw("to_char(??) as ??", [
|
|
302
|
+
identifier,
|
|
303
|
+
this.rawQuotedIdentifier(col),
|
|
304
|
+
]);
|
|
260
305
|
}
|
|
261
306
|
else {
|
|
262
|
-
identifier =
|
|
307
|
+
identifier = this.knex.raw("to_char(??)", [identifier]);
|
|
263
308
|
}
|
|
264
309
|
}
|
|
265
310
|
return identifier;
|
|
@@ -367,7 +412,6 @@ class InternalBuilder {
|
|
|
367
412
|
return query.andWhere(`${document}.fieldName`, "=", relationship.column);
|
|
368
413
|
}
|
|
369
414
|
addRelationshipForFilter(query, allowEmptyRelationships, filterKey, whereCb) {
|
|
370
|
-
const mainKnex = this.knex;
|
|
371
415
|
const { relationships, endpoint, tableAliases: aliases } = this.query;
|
|
372
416
|
const tableName = endpoint.entityId;
|
|
373
417
|
const fromAlias = (aliases === null || aliases === void 0 ? void 0 : aliases[tableName]) || tableName;
|
|
@@ -384,8 +428,8 @@ class InternalBuilder {
|
|
|
384
428
|
if ((matchesTableName || matchesRelationName) &&
|
|
385
429
|
relationship.to &&
|
|
386
430
|
relationship.tableName) {
|
|
387
|
-
const joinTable =
|
|
388
|
-
.select(
|
|
431
|
+
const joinTable = this.knex
|
|
432
|
+
.select(this.knex.raw(1))
|
|
389
433
|
.from({ [toAlias]: relatedTableName });
|
|
390
434
|
let subQuery = joinTable.clone();
|
|
391
435
|
const manyToMany = (0, utils_1.validateManyToMany)(relationship);
|
|
@@ -408,7 +452,7 @@ class InternalBuilder {
|
|
|
408
452
|
this.on(`${toAlias}.${manyToMany.toPrimary}`, "=", `${throughAlias}.${manyToMany.to}`);
|
|
409
453
|
})
|
|
410
454
|
// check the document in the junction table points to the main table
|
|
411
|
-
.where(`${throughAlias}.${manyToMany.from}`, "=",
|
|
455
|
+
.where(`${throughAlias}.${manyToMany.from}`, "=", this.rawQuotedIdentifier(`${fromAlias}.${manyToMany.fromPrimary}`));
|
|
412
456
|
// in SQS the same junction table is used for different many-to-many relationships between the
|
|
413
457
|
// two same tables, this is needed to avoid rows ending up in all columns
|
|
414
458
|
if (this.client === types_1.SqlClient.SQL_LITE) {
|
|
@@ -427,7 +471,7 @@ class InternalBuilder {
|
|
|
427
471
|
const toKey = `${toAlias}.${relationship.to}`;
|
|
428
472
|
const foreignKey = `${fromAlias}.${relationship.from}`;
|
|
429
473
|
// "join" to the main table, making sure the ID matches that of the main
|
|
430
|
-
subQuery = subQuery.where(toKey, "=",
|
|
474
|
+
subQuery = subQuery.where(toKey, "=", this.rawQuotedIdentifier(foreignKey));
|
|
431
475
|
query = query.where(q => {
|
|
432
476
|
q.whereExists(whereCb(updatedKey, subQuery.clone()));
|
|
433
477
|
if (allowEmptyRelationships) {
|
|
@@ -448,7 +492,7 @@ class InternalBuilder {
|
|
|
448
492
|
filters = this.parseFilters(Object.assign({}, filters));
|
|
449
493
|
const aliases = this.query.tableAliases;
|
|
450
494
|
// if all or specified in filters, then everything is an or
|
|
451
|
-
const
|
|
495
|
+
const shouldOr = filters.allOr;
|
|
452
496
|
const isSqlite = this.client === types_1.SqlClient.SQL_LITE;
|
|
453
497
|
const tableName = isSqlite ? this.table._id : this.table.name;
|
|
454
498
|
function getTableAlias(name) {
|
|
@@ -479,7 +523,7 @@ class InternalBuilder {
|
|
|
479
523
|
query = fn(query, alias ? `${alias}.${updatedKey}` : updatedKey, value);
|
|
480
524
|
}
|
|
481
525
|
else if (shouldProcessRelationship) {
|
|
482
|
-
if (
|
|
526
|
+
if (shouldOr) {
|
|
483
527
|
query = query.or;
|
|
484
528
|
}
|
|
485
529
|
query = builder.addRelationshipForFilter(query, allowEmptyRelationships[operation], updatedKey, (updatedKey, q) => {
|
|
@@ -489,71 +533,90 @@ class InternalBuilder {
|
|
|
489
533
|
}
|
|
490
534
|
}
|
|
491
535
|
const like = (q, key, value) => {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
// postgres supports ilike, nothing else does
|
|
495
|
-
if (this.client === types_1.SqlClient.POSTGRES) {
|
|
496
|
-
return q[fnc](key, "ilike", `%${value}%`);
|
|
536
|
+
if ((filters === null || filters === void 0 ? void 0 : filters.fuzzyOr) || shouldOr) {
|
|
537
|
+
q = q.or;
|
|
497
538
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
539
|
+
if (this.client === types_1.SqlClient.ORACLE ||
|
|
540
|
+
this.client === types_1.SqlClient.SQL_LITE) {
|
|
541
|
+
return q.whereRaw(`LOWER(??) LIKE ?`, [
|
|
542
|
+
this.rawQuotedIdentifier(key),
|
|
502
543
|
`%${value.toLowerCase()}%`,
|
|
503
544
|
]);
|
|
504
545
|
}
|
|
546
|
+
return q.whereILike(
|
|
547
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
548
|
+
this.rawQuotedIdentifier(key), this.knex.raw("?", [`%${value}%`]));
|
|
505
549
|
};
|
|
506
550
|
const contains = (mode, any = false) => {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}
|
|
551
|
+
function addModifiers(q) {
|
|
552
|
+
if (shouldOr || mode === (filters === null || filters === void 0 ? void 0 : filters.containsAny)) {
|
|
553
|
+
q = q.or;
|
|
554
|
+
}
|
|
555
|
+
if (mode === (filters === null || filters === void 0 ? void 0 : filters.notContains)) {
|
|
556
|
+
q = q.not;
|
|
514
557
|
}
|
|
515
|
-
return
|
|
558
|
+
return q;
|
|
516
559
|
}
|
|
517
560
|
if (this.client === types_1.SqlClient.POSTGRES) {
|
|
518
561
|
iterate(mode, types_1.ArrayOperator.CONTAINS, (q, key, value) => {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
562
|
+
q = addModifiers(q);
|
|
563
|
+
if (any) {
|
|
564
|
+
return q.whereRaw(`COALESCE(??::jsonb \\?| array??, FALSE)`, [
|
|
565
|
+
this.rawQuotedIdentifier(key),
|
|
566
|
+
this.knex.raw(stringifyArray(value, "'")),
|
|
567
|
+
]);
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
return q.whereRaw(`COALESCE(??::jsonb @> '??', FALSE)`, [
|
|
571
|
+
this.rawQuotedIdentifier(key),
|
|
572
|
+
this.knex.raw(stringifyArray(value)),
|
|
573
|
+
]);
|
|
574
|
+
}
|
|
525
575
|
});
|
|
526
576
|
}
|
|
527
577
|
else if (this.client === types_1.SqlClient.MY_SQL ||
|
|
528
578
|
this.client === types_1.SqlClient.MARIADB) {
|
|
529
|
-
const jsonFnc = any ? "JSON_OVERLAPS" : "JSON_CONTAINS";
|
|
530
579
|
iterate(mode, types_1.ArrayOperator.CONTAINS, (q, key, value) => {
|
|
531
|
-
return q
|
|
580
|
+
return addModifiers(q).whereRaw(`COALESCE(?(??, ?), FALSE)`, [
|
|
581
|
+
this.knex.raw(any ? "JSON_OVERLAPS" : "JSON_CONTAINS"),
|
|
582
|
+
this.rawQuotedIdentifier(key),
|
|
583
|
+
this.knex.raw(wrap(stringifyArray(value))),
|
|
584
|
+
]);
|
|
532
585
|
});
|
|
533
586
|
}
|
|
534
587
|
else {
|
|
535
|
-
const andOr = mode === (filters === null || filters === void 0 ? void 0 : filters.containsAny) ? " OR " : " AND ";
|
|
536
588
|
iterate(mode, types_1.ArrayOperator.CONTAINS, (q, key, value) => {
|
|
537
|
-
|
|
538
|
-
const identifier = this.quotedIdentifier(key);
|
|
539
|
-
for (let i in value) {
|
|
540
|
-
if (typeof value[i] === "string") {
|
|
541
|
-
value[i] = `%"${value[i].toLowerCase()}"%`;
|
|
542
|
-
}
|
|
543
|
-
else {
|
|
544
|
-
value[i] = `%${value[i]}%`;
|
|
545
|
-
}
|
|
546
|
-
statement += `${statement ? andOr : ""}COALESCE(LOWER(${identifier}), '') LIKE ?`;
|
|
547
|
-
}
|
|
548
|
-
if (statement === "") {
|
|
589
|
+
if (value.length === 0) {
|
|
549
590
|
return q;
|
|
550
591
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
592
|
+
q = q.where(subQuery => {
|
|
593
|
+
if (mode === (filters === null || filters === void 0 ? void 0 : filters.notContains)) {
|
|
594
|
+
subQuery = subQuery.not;
|
|
595
|
+
}
|
|
596
|
+
subQuery = subQuery.where(subSubQuery => {
|
|
597
|
+
for (const elem of value) {
|
|
598
|
+
if (mode === (filters === null || filters === void 0 ? void 0 : filters.containsAny)) {
|
|
599
|
+
subSubQuery = subSubQuery.or;
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
subSubQuery = subSubQuery.and;
|
|
603
|
+
}
|
|
604
|
+
const lower = typeof elem === "string" ? `"${elem.toLowerCase()}"` : elem;
|
|
605
|
+
subSubQuery = subSubQuery.whereLike(
|
|
606
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
607
|
+
this.knex.raw(`COALESCE(LOWER(??), '')`, [
|
|
608
|
+
this.rawQuotedIdentifier(key),
|
|
609
|
+
]), `%${lower}%`);
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
if (mode === (filters === null || filters === void 0 ? void 0 : filters.notContains)) {
|
|
613
|
+
subQuery = subQuery.or.whereNull(
|
|
614
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
615
|
+
this.rawQuotedIdentifier(key));
|
|
616
|
+
}
|
|
617
|
+
return subQuery;
|
|
618
|
+
});
|
|
619
|
+
return q;
|
|
557
620
|
});
|
|
558
621
|
}
|
|
559
622
|
};
|
|
@@ -574,44 +637,41 @@ class InternalBuilder {
|
|
|
574
637
|
});
|
|
575
638
|
}
|
|
576
639
|
if (filters.oneOf) {
|
|
577
|
-
const fnc = allOr ? "orWhereIn" : "whereIn";
|
|
578
640
|
iterate(filters.oneOf, types_1.ArrayOperator.ONE_OF, (q, key, array) => {
|
|
641
|
+
if (shouldOr) {
|
|
642
|
+
q = q.or;
|
|
643
|
+
}
|
|
579
644
|
if (this.client === types_1.SqlClient.ORACLE) {
|
|
645
|
+
// @ts-ignore
|
|
580
646
|
key = this.convertClobs(key);
|
|
581
|
-
array = Array.isArray(array) ? array : [array];
|
|
582
|
-
const binding = new Array(array.length).fill("?").join(",");
|
|
583
|
-
return q.whereRaw(`${key} IN (${binding})`, array);
|
|
584
|
-
}
|
|
585
|
-
else {
|
|
586
|
-
return q[fnc](key, Array.isArray(array) ? array : [array]);
|
|
587
647
|
}
|
|
648
|
+
return q.whereIn(key, Array.isArray(array) ? array : [array]);
|
|
588
649
|
}, (q, key, array) => {
|
|
589
|
-
if (
|
|
590
|
-
|
|
591
|
-
const binding = `(${array
|
|
592
|
-
.map((a) => `(${new Array(a.length).fill("?").join(",")})`)
|
|
593
|
-
.join(",")})`;
|
|
594
|
-
return q.whereRaw(`${keyStr} IN ${binding}`, array.flat());
|
|
650
|
+
if (shouldOr) {
|
|
651
|
+
q = q.or;
|
|
595
652
|
}
|
|
596
|
-
|
|
597
|
-
|
|
653
|
+
if (this.client === types_1.SqlClient.ORACLE) {
|
|
654
|
+
// @ts-ignore
|
|
655
|
+
key = key.map(k => this.convertClobs(k));
|
|
598
656
|
}
|
|
657
|
+
return q.whereIn(key, Array.isArray(array) ? array : [array]);
|
|
599
658
|
});
|
|
600
659
|
}
|
|
601
660
|
if (filters.string) {
|
|
602
661
|
iterate(filters.string, types_1.BasicOperator.STRING, (q, key, value) => {
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
if (this.client === types_1.SqlClient.POSTGRES) {
|
|
606
|
-
return q[fnc](key, "ilike", `${value}%`);
|
|
662
|
+
if (shouldOr) {
|
|
663
|
+
q = q.or;
|
|
607
664
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
665
|
+
if (this.client === types_1.SqlClient.ORACLE ||
|
|
666
|
+
this.client === types_1.SqlClient.SQL_LITE) {
|
|
667
|
+
return q.whereRaw(`LOWER(??) LIKE ?`, [
|
|
668
|
+
this.rawQuotedIdentifier(key),
|
|
612
669
|
`${value.toLowerCase()}%`,
|
|
613
670
|
]);
|
|
614
671
|
}
|
|
672
|
+
else {
|
|
673
|
+
return q.whereILike(key, `${value}%`);
|
|
674
|
+
}
|
|
615
675
|
});
|
|
616
676
|
}
|
|
617
677
|
if (filters.fuzzy) {
|
|
@@ -632,61 +692,58 @@ class InternalBuilder {
|
|
|
632
692
|
}
|
|
633
693
|
const lowValid = (0, utils_1.isValidFilter)(value.low), highValid = (0, utils_1.isValidFilter)(value.high);
|
|
634
694
|
const schema = this.getFieldSchema(key);
|
|
695
|
+
let rawKey = key;
|
|
696
|
+
let high = value.high;
|
|
697
|
+
let low = value.low;
|
|
635
698
|
if (this.client === types_1.SqlClient.ORACLE) {
|
|
636
|
-
|
|
637
|
-
|
|
699
|
+
rawKey = this.convertClobs(key);
|
|
700
|
+
}
|
|
701
|
+
else if (this.client === types_1.SqlClient.SQL_LITE &&
|
|
702
|
+
(schema === null || schema === void 0 ? void 0 : schema.type) === types_1.FieldType.BIGINT) {
|
|
703
|
+
rawKey = this.knex.raw("CAST(?? AS INTEGER)", [
|
|
704
|
+
this.rawQuotedIdentifier(key),
|
|
705
|
+
]);
|
|
706
|
+
high = this.knex.raw("CAST(? AS INTEGER)", [value.high]);
|
|
707
|
+
low = this.knex.raw("CAST(? AS INTEGER)", [value.low]);
|
|
708
|
+
}
|
|
709
|
+
if (shouldOr) {
|
|
710
|
+
q = q.or;
|
|
638
711
|
}
|
|
639
712
|
if (lowValid && highValid) {
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
return q.whereRaw(`CAST(${key} AS INTEGER) BETWEEN CAST(? AS INTEGER) AND CAST(? AS INTEGER)`, [value.low, value.high]);
|
|
643
|
-
}
|
|
644
|
-
else {
|
|
645
|
-
const fnc = allOr ? "orWhereBetween" : "whereBetween";
|
|
646
|
-
return q[fnc](key, [value.low, value.high]);
|
|
647
|
-
}
|
|
713
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
714
|
+
return q.whereBetween(rawKey, [low, high]);
|
|
648
715
|
}
|
|
649
716
|
else if (lowValid) {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
return q.whereRaw(`CAST(${key} AS INTEGER) >= CAST(? AS INTEGER)`, [
|
|
653
|
-
value.low,
|
|
654
|
-
]);
|
|
655
|
-
}
|
|
656
|
-
else {
|
|
657
|
-
const fnc = allOr ? "orWhere" : "where";
|
|
658
|
-
return q[fnc](key, ">=", value.low);
|
|
659
|
-
}
|
|
717
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
718
|
+
return q.where(rawKey, ">=", low);
|
|
660
719
|
}
|
|
661
720
|
else if (highValid) {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
return q.whereRaw(`CAST(${key} AS INTEGER) <= CAST(? AS INTEGER)`, [
|
|
665
|
-
value.high,
|
|
666
|
-
]);
|
|
667
|
-
}
|
|
668
|
-
else {
|
|
669
|
-
const fnc = allOr ? "orWhere" : "where";
|
|
670
|
-
return q[fnc](key, "<=", value.high);
|
|
671
|
-
}
|
|
721
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
722
|
+
return q.where(rawKey, "<=", high);
|
|
672
723
|
}
|
|
673
724
|
return q;
|
|
674
725
|
});
|
|
675
726
|
}
|
|
676
727
|
if (filters.equal) {
|
|
677
728
|
iterate(filters.equal, types_1.BasicOperator.EQUAL, (q, key, value) => {
|
|
678
|
-
|
|
729
|
+
if (shouldOr) {
|
|
730
|
+
q = q.or;
|
|
731
|
+
}
|
|
679
732
|
if (this.client === types_1.SqlClient.MS_SQL) {
|
|
680
|
-
return q
|
|
733
|
+
return q.whereRaw(`CASE WHEN ?? = ? THEN 1 ELSE 0 END = 1`, [
|
|
734
|
+
this.rawQuotedIdentifier(key),
|
|
735
|
+
value,
|
|
736
|
+
]);
|
|
681
737
|
}
|
|
682
738
|
else if (this.client === types_1.SqlClient.ORACLE) {
|
|
683
739
|
const identifier = this.convertClobs(key);
|
|
684
|
-
return q
|
|
685
|
-
|
|
686
|
-
|
|
740
|
+
return q.where(subq =>
|
|
741
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
742
|
+
subq.whereNotNull(identifier).andWhere(identifier, value));
|
|
687
743
|
}
|
|
688
744
|
else {
|
|
689
|
-
return q
|
|
745
|
+
return q.whereRaw(`COALESCE(?? = ?, FALSE)`, [
|
|
746
|
+
this.rawQuotedIdentifier(key),
|
|
690
747
|
value,
|
|
691
748
|
]);
|
|
692
749
|
}
|
|
@@ -694,16 +751,28 @@ class InternalBuilder {
|
|
|
694
751
|
}
|
|
695
752
|
if (filters.notEqual) {
|
|
696
753
|
iterate(filters.notEqual, types_1.BasicOperator.NOT_EQUAL, (q, key, value) => {
|
|
697
|
-
|
|
754
|
+
if (shouldOr) {
|
|
755
|
+
q = q.or;
|
|
756
|
+
}
|
|
698
757
|
if (this.client === types_1.SqlClient.MS_SQL) {
|
|
699
|
-
return q
|
|
758
|
+
return q.whereRaw(`CASE WHEN ?? = ? THEN 1 ELSE 0 END = 0`, [
|
|
759
|
+
this.rawQuotedIdentifier(key),
|
|
760
|
+
value,
|
|
761
|
+
]);
|
|
700
762
|
}
|
|
701
763
|
else if (this.client === types_1.SqlClient.ORACLE) {
|
|
702
764
|
const identifier = this.convertClobs(key);
|
|
703
|
-
return q
|
|
765
|
+
return (q
|
|
766
|
+
.where(subq => subq.not
|
|
767
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
768
|
+
.whereNull(identifier)
|
|
769
|
+
.and.where(identifier, "!=", value))
|
|
770
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
771
|
+
.or.whereNull(identifier));
|
|
704
772
|
}
|
|
705
773
|
else {
|
|
706
|
-
return q
|
|
774
|
+
return q.whereRaw(`COALESCE(?? != ?, TRUE)`, [
|
|
775
|
+
this.rawQuotedIdentifier(key),
|
|
707
776
|
value,
|
|
708
777
|
]);
|
|
709
778
|
}
|
|
@@ -711,14 +780,18 @@ class InternalBuilder {
|
|
|
711
780
|
}
|
|
712
781
|
if (filters.empty) {
|
|
713
782
|
iterate(filters.empty, types_1.BasicOperator.EMPTY, (q, key) => {
|
|
714
|
-
|
|
715
|
-
|
|
783
|
+
if (shouldOr) {
|
|
784
|
+
q = q.or;
|
|
785
|
+
}
|
|
786
|
+
return q.whereNull(key);
|
|
716
787
|
});
|
|
717
788
|
}
|
|
718
789
|
if (filters.notEmpty) {
|
|
719
790
|
iterate(filters.notEmpty, types_1.BasicOperator.NOT_EMPTY, (q, key) => {
|
|
720
|
-
|
|
721
|
-
|
|
791
|
+
if (shouldOr) {
|
|
792
|
+
q = q.or;
|
|
793
|
+
}
|
|
794
|
+
return q.whereNotNull(key);
|
|
722
795
|
});
|
|
723
796
|
}
|
|
724
797
|
if (filters.contains) {
|
|
@@ -791,9 +864,7 @@ class InternalBuilder {
|
|
|
791
864
|
if (this.client === types_1.SqlClient.ORACLE) {
|
|
792
865
|
const groupByFields = qualifiedFields.map(field => this.convertClobs(field));
|
|
793
866
|
const selectFields = qualifiedFields.map(field => this.convertClobs(field, { forSelect: true }));
|
|
794
|
-
query = query
|
|
795
|
-
.groupByRaw(groupByFields.join(", "))
|
|
796
|
-
.select(this.knex.raw(selectFields.join(", ")));
|
|
867
|
+
query = query.groupBy(groupByFields).select(selectFields);
|
|
797
868
|
}
|
|
798
869
|
else {
|
|
799
870
|
query = query.groupBy(qualifiedFields).select(qualifiedFields);
|
|
@@ -805,7 +876,10 @@ class InternalBuilder {
|
|
|
805
876
|
if ("distinct" in aggregation && aggregation.distinct) {
|
|
806
877
|
if (this.client === types_1.SqlClient.ORACLE) {
|
|
807
878
|
const field = this.convertClobs(`${tableName}.${aggregation.field}`);
|
|
808
|
-
query = query.select(this.knex.raw(`COUNT(DISTINCT
|
|
879
|
+
query = query.select(this.knex.raw(`COUNT(DISTINCT ??) as ??`, [
|
|
880
|
+
field,
|
|
881
|
+
aggregation.name,
|
|
882
|
+
]));
|
|
809
883
|
}
|
|
810
884
|
else {
|
|
811
885
|
query = query.countDistinct(`${tableName}.${aggregation.field} as ${aggregation.name}`);
|
|
@@ -864,7 +938,11 @@ class InternalBuilder {
|
|
|
864
938
|
else {
|
|
865
939
|
let composite = `${aliased}.${key}`;
|
|
866
940
|
if (this.client === types_1.SqlClient.ORACLE) {
|
|
867
|
-
query = query.orderByRaw(
|
|
941
|
+
query = query.orderByRaw(`?? ?? nulls ??`, [
|
|
942
|
+
this.convertClobs(composite),
|
|
943
|
+
this.knex.raw(direction),
|
|
944
|
+
this.knex.raw(nulls),
|
|
945
|
+
]);
|
|
868
946
|
}
|
|
869
947
|
else {
|
|
870
948
|
query = query.orderBy(composite, direction, nulls);
|
|
@@ -889,18 +967,21 @@ class InternalBuilder {
|
|
|
889
967
|
}
|
|
890
968
|
buildJsonField(field) {
|
|
891
969
|
const parts = field.split(".");
|
|
892
|
-
let
|
|
970
|
+
let unaliased;
|
|
971
|
+
let tableField;
|
|
893
972
|
if (parts.length > 1) {
|
|
894
973
|
const alias = parts.shift();
|
|
895
974
|
unaliased = parts.join(".");
|
|
896
|
-
tableField = `${
|
|
975
|
+
tableField = `${alias}.${unaliased}`;
|
|
897
976
|
}
|
|
898
977
|
else {
|
|
899
978
|
unaliased = parts.join(".");
|
|
900
|
-
tableField =
|
|
979
|
+
tableField = unaliased;
|
|
901
980
|
}
|
|
902
981
|
const separator = this.client === types_1.SqlClient.ORACLE ? " VALUE " : ",";
|
|
903
|
-
return
|
|
982
|
+
return this.knex
|
|
983
|
+
.raw(`?${separator}??`, [unaliased, this.rawQuotedIdentifier(tableField)])
|
|
984
|
+
.toString();
|
|
904
985
|
}
|
|
905
986
|
maxFunctionParameters() {
|
|
906
987
|
// functions like say json_build_object() in SQL have a limit as to how many can be performed
|
|
@@ -969,11 +1050,11 @@ class InternalBuilder {
|
|
|
969
1050
|
});
|
|
970
1051
|
}
|
|
971
1052
|
// add the correlation to the overall query
|
|
972
|
-
subQuery = subQuery.where(correlatedTo, "=",
|
|
1053
|
+
subQuery = subQuery.where(correlatedTo, "=", this.rawQuotedIdentifier(correlatedFrom));
|
|
973
1054
|
const standardWrap = (select) => {
|
|
974
1055
|
subQuery = subQuery.select(`${toAlias}.*`).limit(getRelationshipLimit());
|
|
975
1056
|
// @ts-ignore - the from alias syntax isn't in Knex typing
|
|
976
|
-
return knex.select(
|
|
1057
|
+
return knex.select(select).from({
|
|
977
1058
|
[toAlias]: subQuery,
|
|
978
1059
|
});
|
|
979
1060
|
};
|
|
@@ -982,10 +1063,10 @@ class InternalBuilder {
|
|
|
982
1063
|
case types_1.SqlClient.SQL_LITE:
|
|
983
1064
|
// need to check the junction table document is to the right column, this is just for SQS
|
|
984
1065
|
subQuery = this.addJoinFieldCheck(subQuery, relationship);
|
|
985
|
-
wrapperQuery = standardWrap(`json_group_array(json_object(${fieldList}))`);
|
|
1066
|
+
wrapperQuery = standardWrap(this.knex.raw(`json_group_array(json_object(${fieldList}))`));
|
|
986
1067
|
break;
|
|
987
1068
|
case types_1.SqlClient.POSTGRES:
|
|
988
|
-
wrapperQuery = standardWrap(`json_agg(json_build_object(${fieldList}))`);
|
|
1069
|
+
wrapperQuery = standardWrap(this.knex.raw(`json_agg(json_build_object(${fieldList}))`));
|
|
989
1070
|
break;
|
|
990
1071
|
case types_1.SqlClient.MARIADB:
|
|
991
1072
|
// can't use the standard wrap due to correlated sub-query limitations in MariaDB
|
|
@@ -993,18 +1074,20 @@ class InternalBuilder {
|
|
|
993
1074
|
break;
|
|
994
1075
|
case types_1.SqlClient.MY_SQL:
|
|
995
1076
|
case types_1.SqlClient.ORACLE:
|
|
996
|
-
wrapperQuery = standardWrap(`json_arrayagg(json_object(${fieldList}))`);
|
|
1077
|
+
wrapperQuery = standardWrap(this.knex.raw(`json_arrayagg(json_object(${fieldList}))`));
|
|
997
1078
|
break;
|
|
998
|
-
case types_1.SqlClient.MS_SQL:
|
|
999
|
-
|
|
1079
|
+
case types_1.SqlClient.MS_SQL: {
|
|
1080
|
+
const comparatorQuery = knex
|
|
1000
1081
|
.select(`${fromAlias}.*`)
|
|
1001
1082
|
// @ts-ignore - from alias syntax not TS supported
|
|
1002
1083
|
.from({
|
|
1003
1084
|
[fromAlias]: subQuery
|
|
1004
1085
|
.select(`${toAlias}.*`)
|
|
1005
1086
|
.limit(getRelationshipLimit()),
|
|
1006
|
-
})
|
|
1087
|
+
});
|
|
1088
|
+
wrapperQuery = knex.raw(`(SELECT ?? = (${comparatorQuery} FOR JSON PATH))`, [this.rawQuotedIdentifier(toAlias)]);
|
|
1007
1089
|
break;
|
|
1090
|
+
}
|
|
1008
1091
|
default:
|
|
1009
1092
|
throw new Error(`JSON relationships not implement for ${sqlClient}`);
|
|
1010
1093
|
}
|