@budibase/backend-core 2.33.1 → 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 +604 -287
- 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 +300 -183
- 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 +374 -233
- package/src/sql/sqlTable.ts +4 -1
package/dist/src/sql/sql.js
CHANGED
|
@@ -90,6 +90,35 @@ 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
|
+
}
|
|
107
|
+
const allowEmptyRelationships = {
|
|
108
|
+
[types_1.BasicOperator.EQUAL]: false,
|
|
109
|
+
[types_1.BasicOperator.NOT_EQUAL]: true,
|
|
110
|
+
[types_1.BasicOperator.EMPTY]: false,
|
|
111
|
+
[types_1.BasicOperator.NOT_EMPTY]: true,
|
|
112
|
+
[types_1.BasicOperator.FUZZY]: false,
|
|
113
|
+
[types_1.BasicOperator.STRING]: false,
|
|
114
|
+
[types_1.RangeOperator.RANGE]: false,
|
|
115
|
+
[types_1.ArrayOperator.CONTAINS]: false,
|
|
116
|
+
[types_1.ArrayOperator.NOT_CONTAINS]: true,
|
|
117
|
+
[types_1.ArrayOperator.CONTAINS_ANY]: false,
|
|
118
|
+
[types_1.ArrayOperator.ONE_OF]: false,
|
|
119
|
+
[types_1.LogicalOperator.AND]: false,
|
|
120
|
+
[types_1.LogicalOperator.OR]: false,
|
|
121
|
+
};
|
|
93
122
|
class InternalBuilder {
|
|
94
123
|
constructor(client, knex, query) {
|
|
95
124
|
// states the various situations in which we need a full mapped select statement
|
|
@@ -116,28 +145,24 @@ class InternalBuilder {
|
|
|
116
145
|
get table() {
|
|
117
146
|
return this.query.meta.table;
|
|
118
147
|
}
|
|
148
|
+
get knexClient() {
|
|
149
|
+
return this.knex.client;
|
|
150
|
+
}
|
|
119
151
|
getFieldSchema(key) {
|
|
120
152
|
const { column } = this.splitter.run(key);
|
|
121
153
|
return this.table.schema[column];
|
|
122
154
|
}
|
|
155
|
+
supportsILike() {
|
|
156
|
+
return !(this.client === types_1.SqlClient.ORACLE || this.client === types_1.SqlClient.SQL_LITE);
|
|
157
|
+
}
|
|
123
158
|
quoteChars() {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
case types_1.SqlClient.POSTGRES:
|
|
127
|
-
return ['"', '"'];
|
|
128
|
-
case types_1.SqlClient.MS_SQL:
|
|
129
|
-
return ["[", "]"];
|
|
130
|
-
case types_1.SqlClient.MARIADB:
|
|
131
|
-
case types_1.SqlClient.MY_SQL:
|
|
132
|
-
case types_1.SqlClient.SQL_LITE:
|
|
133
|
-
return ["`", "`"];
|
|
134
|
-
}
|
|
159
|
+
const wrapped = this.knexClient.wrapIdentifier("foo", {});
|
|
160
|
+
return [wrapped[0], wrapped[wrapped.length - 1]];
|
|
135
161
|
}
|
|
136
|
-
// Takes a string like foo and returns a quoted string like [foo] for SQL
|
|
137
|
-
// 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.
|
|
138
164
|
quote(str) {
|
|
139
|
-
|
|
140
|
-
return `${start}${str}${end}`;
|
|
165
|
+
return this.knexClient.wrapIdentifier(str, {});
|
|
141
166
|
}
|
|
142
167
|
isQuoted(key) {
|
|
143
168
|
const [start, end] = this.quoteChars();
|
|
@@ -152,6 +177,27 @@ class InternalBuilder {
|
|
|
152
177
|
}
|
|
153
178
|
return key.map(part => this.quote(part)).join(".");
|
|
154
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
|
+
}
|
|
155
201
|
// Turns an identifier like a.b.c or `a`.`b`.`c` into ["a", "b", "c"]
|
|
156
202
|
splitIdentifier(key) {
|
|
157
203
|
const [start, end] = this.quoteChars();
|
|
@@ -191,7 +237,7 @@ class InternalBuilder {
|
|
|
191
237
|
const alias = this.getTableName(endpoint.entityId);
|
|
192
238
|
const schema = meta.table.schema;
|
|
193
239
|
if (!this.isFullSelectStatementRequired()) {
|
|
194
|
-
return [this.knex.raw(`${
|
|
240
|
+
return [this.knex.raw("??", [`${alias}.*`])];
|
|
195
241
|
}
|
|
196
242
|
// get just the fields for this table
|
|
197
243
|
return resource.fields
|
|
@@ -210,17 +256,28 @@ class InternalBuilder {
|
|
|
210
256
|
.map(({ table, column, field }) => {
|
|
211
257
|
const columnSchema = schema[column];
|
|
212
258
|
if (this.SPECIAL_SELECT_CASES.POSTGRES_MONEY(columnSchema)) {
|
|
213
|
-
|
|
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
|
+
]);
|
|
214
265
|
}
|
|
215
266
|
if (this.SPECIAL_SELECT_CASES.MSSQL_DATES(columnSchema)) {
|
|
216
267
|
// Time gets returned as timestamp from mssql, not matching the expected
|
|
217
268
|
// HH:mm format
|
|
218
|
-
|
|
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);
|
|
219
280
|
}
|
|
220
|
-
const quoted = table
|
|
221
|
-
? `${this.quote(table)}.${this.quote(column)}`
|
|
222
|
-
: this.quote(field);
|
|
223
|
-
return this.knex.raw(quoted);
|
|
224
281
|
});
|
|
225
282
|
}
|
|
226
283
|
// OracleDB can't use character-large-objects (CLOBs) in WHERE clauses,
|
|
@@ -233,7 +290,7 @@ class InternalBuilder {
|
|
|
233
290
|
const parts = this.splitIdentifier(field);
|
|
234
291
|
const col = parts.pop();
|
|
235
292
|
const schema = this.table.schema[col];
|
|
236
|
-
let identifier = this.
|
|
293
|
+
let identifier = this.rawQuotedIdentifier(field);
|
|
237
294
|
if (schema.type === types_1.FieldType.STRING ||
|
|
238
295
|
schema.type === types_1.FieldType.LONGFORM ||
|
|
239
296
|
schema.type === types_1.FieldType.BB_REFERENCE_SINGLE ||
|
|
@@ -241,10 +298,13 @@ class InternalBuilder {
|
|
|
241
298
|
schema.type === types_1.FieldType.OPTIONS ||
|
|
242
299
|
schema.type === types_1.FieldType.BARCODEQR) {
|
|
243
300
|
if (opts === null || opts === void 0 ? void 0 : opts.forSelect) {
|
|
244
|
-
identifier =
|
|
301
|
+
identifier = this.knex.raw("to_char(??) as ??", [
|
|
302
|
+
identifier,
|
|
303
|
+
this.rawQuotedIdentifier(col),
|
|
304
|
+
]);
|
|
245
305
|
}
|
|
246
306
|
else {
|
|
247
|
-
identifier =
|
|
307
|
+
identifier = this.knex.raw("to_char(??)", [identifier]);
|
|
248
308
|
}
|
|
249
309
|
}
|
|
250
310
|
return identifier;
|
|
@@ -351,26 +411,35 @@ class InternalBuilder {
|
|
|
351
411
|
const document = ((_a = relationship.from) === null || _a === void 0 ? void 0 : _a.split(".")[0]) || "";
|
|
352
412
|
return query.andWhere(`${document}.fieldName`, "=", relationship.column);
|
|
353
413
|
}
|
|
354
|
-
addRelationshipForFilter(query, filterKey, whereCb) {
|
|
355
|
-
const mainKnex = this.knex;
|
|
414
|
+
addRelationshipForFilter(query, allowEmptyRelationships, filterKey, whereCb) {
|
|
356
415
|
const { relationships, endpoint, tableAliases: aliases } = this.query;
|
|
357
416
|
const tableName = endpoint.entityId;
|
|
358
417
|
const fromAlias = (aliases === null || aliases === void 0 ? void 0 : aliases[tableName]) || tableName;
|
|
359
|
-
const matches = (
|
|
418
|
+
const matches = (value) => filterKey.match(new RegExp(`^${value}\\.`));
|
|
360
419
|
if (!relationships) {
|
|
361
420
|
return query;
|
|
362
421
|
}
|
|
363
422
|
for (const relationship of relationships) {
|
|
364
423
|
const relatedTableName = relationship.tableName;
|
|
365
424
|
const toAlias = (aliases === null || aliases === void 0 ? void 0 : aliases[relatedTableName]) || relatedTableName;
|
|
425
|
+
const matchesTableName = matches(relatedTableName) || matches(toAlias);
|
|
426
|
+
const matchesRelationName = matches(relationship.column);
|
|
366
427
|
// this is the relationship which is being filtered
|
|
367
|
-
if ((
|
|
428
|
+
if ((matchesTableName || matchesRelationName) &&
|
|
368
429
|
relationship.to &&
|
|
369
430
|
relationship.tableName) {
|
|
370
|
-
|
|
371
|
-
.select(
|
|
431
|
+
const joinTable = this.knex
|
|
432
|
+
.select(this.knex.raw(1))
|
|
372
433
|
.from({ [toAlias]: relatedTableName });
|
|
434
|
+
let subQuery = joinTable.clone();
|
|
373
435
|
const manyToMany = (0, utils_1.validateManyToMany)(relationship);
|
|
436
|
+
let updatedKey;
|
|
437
|
+
if (!matchesTableName) {
|
|
438
|
+
updatedKey = filterKey.replace(new RegExp(`^${relationship.column}.`), `${aliases[relationship.tableName]}.`);
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
updatedKey = filterKey;
|
|
442
|
+
}
|
|
374
443
|
if (manyToMany) {
|
|
375
444
|
const throughAlias = (aliases === null || aliases === void 0 ? void 0 : aliases[manyToMany.through]) || relationship.through;
|
|
376
445
|
let throughTable = this.tableNameWithSchema(manyToMany.through, {
|
|
@@ -380,23 +449,36 @@ class InternalBuilder {
|
|
|
380
449
|
subQuery = subQuery
|
|
381
450
|
// add a join through the junction table
|
|
382
451
|
.innerJoin(throughTable, function () {
|
|
383
|
-
// @ts-ignore
|
|
384
452
|
this.on(`${toAlias}.${manyToMany.toPrimary}`, "=", `${throughAlias}.${manyToMany.to}`);
|
|
385
453
|
})
|
|
386
454
|
// check the document in the junction table points to the main table
|
|
387
|
-
.where(`${throughAlias}.${manyToMany.from}`, "=",
|
|
455
|
+
.where(`${throughAlias}.${manyToMany.from}`, "=", this.rawQuotedIdentifier(`${fromAlias}.${manyToMany.fromPrimary}`));
|
|
388
456
|
// in SQS the same junction table is used for different many-to-many relationships between the
|
|
389
457
|
// two same tables, this is needed to avoid rows ending up in all columns
|
|
390
458
|
if (this.client === types_1.SqlClient.SQL_LITE) {
|
|
391
459
|
subQuery = this.addJoinFieldCheck(subQuery, manyToMany);
|
|
392
460
|
}
|
|
461
|
+
query = query.where(q => {
|
|
462
|
+
q.whereExists(whereCb(updatedKey, subQuery));
|
|
463
|
+
if (allowEmptyRelationships) {
|
|
464
|
+
q.orWhereNotExists(joinTable.clone().innerJoin(throughTable, function () {
|
|
465
|
+
this.on(`${fromAlias}.${manyToMany.fromPrimary}`, "=", `${throughAlias}.${manyToMany.from}`);
|
|
466
|
+
}));
|
|
467
|
+
}
|
|
468
|
+
});
|
|
393
469
|
}
|
|
394
470
|
else {
|
|
471
|
+
const toKey = `${toAlias}.${relationship.to}`;
|
|
472
|
+
const foreignKey = `${fromAlias}.${relationship.from}`;
|
|
395
473
|
// "join" to the main table, making sure the ID matches that of the main
|
|
396
|
-
subQuery = subQuery.where(
|
|
474
|
+
subQuery = subQuery.where(toKey, "=", this.rawQuotedIdentifier(foreignKey));
|
|
475
|
+
query = query.where(q => {
|
|
476
|
+
q.whereExists(whereCb(updatedKey, subQuery.clone()));
|
|
477
|
+
if (allowEmptyRelationships) {
|
|
478
|
+
q.orWhereNotExists(subQuery);
|
|
479
|
+
}
|
|
480
|
+
});
|
|
397
481
|
}
|
|
398
|
-
query = query.whereExists(whereCb(subQuery));
|
|
399
|
-
break;
|
|
400
482
|
}
|
|
401
483
|
}
|
|
402
484
|
return query;
|
|
@@ -410,14 +492,14 @@ class InternalBuilder {
|
|
|
410
492
|
filters = this.parseFilters(Object.assign({}, filters));
|
|
411
493
|
const aliases = this.query.tableAliases;
|
|
412
494
|
// if all or specified in filters, then everything is an or
|
|
413
|
-
const
|
|
495
|
+
const shouldOr = filters.allOr;
|
|
414
496
|
const isSqlite = this.client === types_1.SqlClient.SQL_LITE;
|
|
415
497
|
const tableName = isSqlite ? this.table._id : this.table.name;
|
|
416
498
|
function getTableAlias(name) {
|
|
417
499
|
const alias = aliases === null || aliases === void 0 ? void 0 : aliases[name];
|
|
418
500
|
return alias || name;
|
|
419
501
|
}
|
|
420
|
-
function iterate(structure, fn, complexKeyFn) {
|
|
502
|
+
function iterate(structure, operation, fn, complexKeyFn) {
|
|
421
503
|
const handleRelationship = (q, key, value) => {
|
|
422
504
|
const [filterTableName, ...otherProperties] = key.split(".");
|
|
423
505
|
const property = otherProperties.join(".");
|
|
@@ -441,81 +523,100 @@ class InternalBuilder {
|
|
|
441
523
|
query = fn(query, alias ? `${alias}.${updatedKey}` : updatedKey, value);
|
|
442
524
|
}
|
|
443
525
|
else if (shouldProcessRelationship) {
|
|
444
|
-
if (
|
|
526
|
+
if (shouldOr) {
|
|
445
527
|
query = query.or;
|
|
446
528
|
}
|
|
447
|
-
query = builder.addRelationshipForFilter(query, updatedKey, q => {
|
|
529
|
+
query = builder.addRelationshipForFilter(query, allowEmptyRelationships[operation], updatedKey, (updatedKey, q) => {
|
|
448
530
|
return handleRelationship(q, updatedKey, value);
|
|
449
531
|
});
|
|
450
532
|
}
|
|
451
533
|
}
|
|
452
534
|
}
|
|
453
535
|
const like = (q, key, value) => {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
// postgres supports ilike, nothing else does
|
|
457
|
-
if (this.client === types_1.SqlClient.POSTGRES) {
|
|
458
|
-
return q[fnc](key, "ilike", `%${value}%`);
|
|
536
|
+
if ((filters === null || filters === void 0 ? void 0 : filters.fuzzyOr) || shouldOr) {
|
|
537
|
+
q = q.or;
|
|
459
538
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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),
|
|
464
543
|
`%${value.toLowerCase()}%`,
|
|
465
544
|
]);
|
|
466
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}%`]));
|
|
467
549
|
};
|
|
468
550
|
const contains = (mode, any = false) => {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
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;
|
|
476
557
|
}
|
|
477
|
-
return
|
|
558
|
+
return q;
|
|
478
559
|
}
|
|
479
560
|
if (this.client === types_1.SqlClient.POSTGRES) {
|
|
480
|
-
iterate(mode, (q, key, value) => {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
561
|
+
iterate(mode, types_1.ArrayOperator.CONTAINS, (q, key, value) => {
|
|
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
|
+
}
|
|
487
575
|
});
|
|
488
576
|
}
|
|
489
577
|
else if (this.client === types_1.SqlClient.MY_SQL ||
|
|
490
578
|
this.client === types_1.SqlClient.MARIADB) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
579
|
+
iterate(mode, types_1.ArrayOperator.CONTAINS, (q, key, value) => {
|
|
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
|
+
]);
|
|
494
585
|
});
|
|
495
586
|
}
|
|
496
587
|
else {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
let statement = "";
|
|
500
|
-
const identifier = this.quotedIdentifier(key);
|
|
501
|
-
for (let i in value) {
|
|
502
|
-
if (typeof value[i] === "string") {
|
|
503
|
-
value[i] = `%"${value[i].toLowerCase()}"%`;
|
|
504
|
-
}
|
|
505
|
-
else {
|
|
506
|
-
value[i] = `%${value[i]}%`;
|
|
507
|
-
}
|
|
508
|
-
statement += `${statement ? andOr : ""}COALESCE(LOWER(${identifier}), '') LIKE ?`;
|
|
509
|
-
}
|
|
510
|
-
if (statement === "") {
|
|
588
|
+
iterate(mode, types_1.ArrayOperator.CONTAINS, (q, key, value) => {
|
|
589
|
+
if (value.length === 0) {
|
|
511
590
|
return q;
|
|
512
591
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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;
|
|
519
620
|
});
|
|
520
621
|
}
|
|
521
622
|
};
|
|
@@ -536,51 +637,48 @@ class InternalBuilder {
|
|
|
536
637
|
});
|
|
537
638
|
}
|
|
538
639
|
if (filters.oneOf) {
|
|
539
|
-
|
|
540
|
-
|
|
640
|
+
iterate(filters.oneOf, types_1.ArrayOperator.ONE_OF, (q, key, array) => {
|
|
641
|
+
if (shouldOr) {
|
|
642
|
+
q = q.or;
|
|
643
|
+
}
|
|
541
644
|
if (this.client === types_1.SqlClient.ORACLE) {
|
|
645
|
+
// @ts-ignore
|
|
542
646
|
key = this.convertClobs(key);
|
|
543
|
-
array = Array.isArray(array) ? array : [array];
|
|
544
|
-
const binding = new Array(array.length).fill("?").join(",");
|
|
545
|
-
return q.whereRaw(`${key} IN (${binding})`, array);
|
|
546
|
-
}
|
|
547
|
-
else {
|
|
548
|
-
return q[fnc](key, Array.isArray(array) ? array : [array]);
|
|
549
647
|
}
|
|
648
|
+
return q.whereIn(key, Array.isArray(array) ? array : [array]);
|
|
550
649
|
}, (q, key, array) => {
|
|
551
|
-
if (
|
|
552
|
-
|
|
553
|
-
const binding = `(${array
|
|
554
|
-
.map((a) => `(${new Array(a.length).fill("?").join(",")})`)
|
|
555
|
-
.join(",")})`;
|
|
556
|
-
return q.whereRaw(`${keyStr} IN ${binding}`, array.flat());
|
|
650
|
+
if (shouldOr) {
|
|
651
|
+
q = q.or;
|
|
557
652
|
}
|
|
558
|
-
|
|
559
|
-
|
|
653
|
+
if (this.client === types_1.SqlClient.ORACLE) {
|
|
654
|
+
// @ts-ignore
|
|
655
|
+
key = key.map(k => this.convertClobs(k));
|
|
560
656
|
}
|
|
657
|
+
return q.whereIn(key, Array.isArray(array) ? array : [array]);
|
|
561
658
|
});
|
|
562
659
|
}
|
|
563
660
|
if (filters.string) {
|
|
564
|
-
iterate(filters.string, (q, key, value) => {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
if (this.client === types_1.SqlClient.POSTGRES) {
|
|
568
|
-
return q[fnc](key, "ilike", `${value}%`);
|
|
661
|
+
iterate(filters.string, types_1.BasicOperator.STRING, (q, key, value) => {
|
|
662
|
+
if (shouldOr) {
|
|
663
|
+
q = q.or;
|
|
569
664
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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),
|
|
574
669
|
`${value.toLowerCase()}%`,
|
|
575
670
|
]);
|
|
576
671
|
}
|
|
672
|
+
else {
|
|
673
|
+
return q.whereILike(key, `${value}%`);
|
|
674
|
+
}
|
|
577
675
|
});
|
|
578
676
|
}
|
|
579
677
|
if (filters.fuzzy) {
|
|
580
|
-
iterate(filters.fuzzy, like);
|
|
678
|
+
iterate(filters.fuzzy, types_1.BasicOperator.FUZZY, like);
|
|
581
679
|
}
|
|
582
680
|
if (filters.range) {
|
|
583
|
-
iterate(filters.range, (q, key, value) => {
|
|
681
|
+
iterate(filters.range, types_1.RangeOperator.RANGE, (q, key, value) => {
|
|
584
682
|
const isEmptyObject = (val) => {
|
|
585
683
|
return (val &&
|
|
586
684
|
Object.keys(val).length === 0 &&
|
|
@@ -594,93 +692,106 @@ class InternalBuilder {
|
|
|
594
692
|
}
|
|
595
693
|
const lowValid = (0, utils_1.isValidFilter)(value.low), highValid = (0, utils_1.isValidFilter)(value.high);
|
|
596
694
|
const schema = this.getFieldSchema(key);
|
|
695
|
+
let rawKey = key;
|
|
696
|
+
let high = value.high;
|
|
697
|
+
let low = value.low;
|
|
597
698
|
if (this.client === types_1.SqlClient.ORACLE) {
|
|
598
|
-
|
|
599
|
-
|
|
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;
|
|
600
711
|
}
|
|
601
712
|
if (lowValid && highValid) {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
return q.whereRaw(`CAST(${key} AS INTEGER) BETWEEN CAST(? AS INTEGER) AND CAST(? AS INTEGER)`, [value.low, value.high]);
|
|
605
|
-
}
|
|
606
|
-
else {
|
|
607
|
-
const fnc = allOr ? "orWhereBetween" : "whereBetween";
|
|
608
|
-
return q[fnc](key, [value.low, value.high]);
|
|
609
|
-
}
|
|
713
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
714
|
+
return q.whereBetween(rawKey, [low, high]);
|
|
610
715
|
}
|
|
611
716
|
else if (lowValid) {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
return q.whereRaw(`CAST(${key} AS INTEGER) >= CAST(? AS INTEGER)`, [
|
|
615
|
-
value.low,
|
|
616
|
-
]);
|
|
617
|
-
}
|
|
618
|
-
else {
|
|
619
|
-
const fnc = allOr ? "orWhere" : "where";
|
|
620
|
-
return q[fnc](key, ">=", value.low);
|
|
621
|
-
}
|
|
717
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
718
|
+
return q.where(rawKey, ">=", low);
|
|
622
719
|
}
|
|
623
720
|
else if (highValid) {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
return q.whereRaw(`CAST(${key} AS INTEGER) <= CAST(? AS INTEGER)`, [
|
|
627
|
-
value.high,
|
|
628
|
-
]);
|
|
629
|
-
}
|
|
630
|
-
else {
|
|
631
|
-
const fnc = allOr ? "orWhere" : "where";
|
|
632
|
-
return q[fnc](key, "<=", value.high);
|
|
633
|
-
}
|
|
721
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
722
|
+
return q.where(rawKey, "<=", high);
|
|
634
723
|
}
|
|
635
724
|
return q;
|
|
636
725
|
});
|
|
637
726
|
}
|
|
638
727
|
if (filters.equal) {
|
|
639
|
-
iterate(filters.equal, (q, key, value) => {
|
|
640
|
-
|
|
728
|
+
iterate(filters.equal, types_1.BasicOperator.EQUAL, (q, key, value) => {
|
|
729
|
+
if (shouldOr) {
|
|
730
|
+
q = q.or;
|
|
731
|
+
}
|
|
641
732
|
if (this.client === types_1.SqlClient.MS_SQL) {
|
|
642
|
-
return q
|
|
733
|
+
return q.whereRaw(`CASE WHEN ?? = ? THEN 1 ELSE 0 END = 1`, [
|
|
734
|
+
this.rawQuotedIdentifier(key),
|
|
735
|
+
value,
|
|
736
|
+
]);
|
|
643
737
|
}
|
|
644
738
|
else if (this.client === types_1.SqlClient.ORACLE) {
|
|
645
739
|
const identifier = this.convertClobs(key);
|
|
646
|
-
return q
|
|
647
|
-
|
|
648
|
-
|
|
740
|
+
return q.where(subq =>
|
|
741
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
742
|
+
subq.whereNotNull(identifier).andWhere(identifier, value));
|
|
649
743
|
}
|
|
650
744
|
else {
|
|
651
|
-
return q
|
|
745
|
+
return q.whereRaw(`COALESCE(?? = ?, FALSE)`, [
|
|
746
|
+
this.rawQuotedIdentifier(key),
|
|
652
747
|
value,
|
|
653
748
|
]);
|
|
654
749
|
}
|
|
655
750
|
});
|
|
656
751
|
}
|
|
657
752
|
if (filters.notEqual) {
|
|
658
|
-
iterate(filters.notEqual, (q, key, value) => {
|
|
659
|
-
|
|
753
|
+
iterate(filters.notEqual, types_1.BasicOperator.NOT_EQUAL, (q, key, value) => {
|
|
754
|
+
if (shouldOr) {
|
|
755
|
+
q = q.or;
|
|
756
|
+
}
|
|
660
757
|
if (this.client === types_1.SqlClient.MS_SQL) {
|
|
661
|
-
return q
|
|
758
|
+
return q.whereRaw(`CASE WHEN ?? = ? THEN 1 ELSE 0 END = 0`, [
|
|
759
|
+
this.rawQuotedIdentifier(key),
|
|
760
|
+
value,
|
|
761
|
+
]);
|
|
662
762
|
}
|
|
663
763
|
else if (this.client === types_1.SqlClient.ORACLE) {
|
|
664
764
|
const identifier = this.convertClobs(key);
|
|
665
|
-
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));
|
|
666
772
|
}
|
|
667
773
|
else {
|
|
668
|
-
return q
|
|
774
|
+
return q.whereRaw(`COALESCE(?? != ?, TRUE)`, [
|
|
775
|
+
this.rawQuotedIdentifier(key),
|
|
669
776
|
value,
|
|
670
777
|
]);
|
|
671
778
|
}
|
|
672
779
|
});
|
|
673
780
|
}
|
|
674
781
|
if (filters.empty) {
|
|
675
|
-
iterate(filters.empty, (q, key) => {
|
|
676
|
-
|
|
677
|
-
|
|
782
|
+
iterate(filters.empty, types_1.BasicOperator.EMPTY, (q, key) => {
|
|
783
|
+
if (shouldOr) {
|
|
784
|
+
q = q.or;
|
|
785
|
+
}
|
|
786
|
+
return q.whereNull(key);
|
|
678
787
|
});
|
|
679
788
|
}
|
|
680
789
|
if (filters.notEmpty) {
|
|
681
|
-
iterate(filters.notEmpty, (q, key) => {
|
|
682
|
-
|
|
683
|
-
|
|
790
|
+
iterate(filters.notEmpty, types_1.BasicOperator.NOT_EMPTY, (q, key) => {
|
|
791
|
+
if (shouldOr) {
|
|
792
|
+
q = q.or;
|
|
793
|
+
}
|
|
794
|
+
return q.whereNotNull(key);
|
|
684
795
|
});
|
|
685
796
|
}
|
|
686
797
|
if (filters.contains) {
|
|
@@ -753,9 +864,7 @@ class InternalBuilder {
|
|
|
753
864
|
if (this.client === types_1.SqlClient.ORACLE) {
|
|
754
865
|
const groupByFields = qualifiedFields.map(field => this.convertClobs(field));
|
|
755
866
|
const selectFields = qualifiedFields.map(field => this.convertClobs(field, { forSelect: true }));
|
|
756
|
-
query = query
|
|
757
|
-
.groupByRaw(groupByFields.join(", "))
|
|
758
|
-
.select(this.knex.raw(selectFields.join(", ")));
|
|
867
|
+
query = query.groupBy(groupByFields).select(selectFields);
|
|
759
868
|
}
|
|
760
869
|
else {
|
|
761
870
|
query = query.groupBy(qualifiedFields).select(qualifiedFields);
|
|
@@ -767,7 +876,10 @@ class InternalBuilder {
|
|
|
767
876
|
if ("distinct" in aggregation && aggregation.distinct) {
|
|
768
877
|
if (this.client === types_1.SqlClient.ORACLE) {
|
|
769
878
|
const field = this.convertClobs(`${tableName}.${aggregation.field}`);
|
|
770
|
-
query = query.select(this.knex.raw(`COUNT(DISTINCT
|
|
879
|
+
query = query.select(this.knex.raw(`COUNT(DISTINCT ??) as ??`, [
|
|
880
|
+
field,
|
|
881
|
+
aggregation.name,
|
|
882
|
+
]));
|
|
771
883
|
}
|
|
772
884
|
else {
|
|
773
885
|
query = query.countDistinct(`${tableName}.${aggregation.field} as ${aggregation.name}`);
|
|
@@ -826,7 +938,11 @@ class InternalBuilder {
|
|
|
826
938
|
else {
|
|
827
939
|
let composite = `${aliased}.${key}`;
|
|
828
940
|
if (this.client === types_1.SqlClient.ORACLE) {
|
|
829
|
-
query = query.orderByRaw(
|
|
941
|
+
query = query.orderByRaw(`?? ?? nulls ??`, [
|
|
942
|
+
this.convertClobs(composite),
|
|
943
|
+
this.knex.raw(direction),
|
|
944
|
+
this.knex.raw(nulls),
|
|
945
|
+
]);
|
|
830
946
|
}
|
|
831
947
|
else {
|
|
832
948
|
query = query.orderBy(composite, direction, nulls);
|
|
@@ -851,18 +967,21 @@ class InternalBuilder {
|
|
|
851
967
|
}
|
|
852
968
|
buildJsonField(field) {
|
|
853
969
|
const parts = field.split(".");
|
|
854
|
-
let
|
|
970
|
+
let unaliased;
|
|
971
|
+
let tableField;
|
|
855
972
|
if (parts.length > 1) {
|
|
856
973
|
const alias = parts.shift();
|
|
857
974
|
unaliased = parts.join(".");
|
|
858
|
-
tableField = `${
|
|
975
|
+
tableField = `${alias}.${unaliased}`;
|
|
859
976
|
}
|
|
860
977
|
else {
|
|
861
978
|
unaliased = parts.join(".");
|
|
862
|
-
tableField =
|
|
979
|
+
tableField = unaliased;
|
|
863
980
|
}
|
|
864
981
|
const separator = this.client === types_1.SqlClient.ORACLE ? " VALUE " : ",";
|
|
865
|
-
return
|
|
982
|
+
return this.knex
|
|
983
|
+
.raw(`?${separator}??`, [unaliased, this.rawQuotedIdentifier(tableField)])
|
|
984
|
+
.toString();
|
|
866
985
|
}
|
|
867
986
|
maxFunctionParameters() {
|
|
868
987
|
// functions like say json_build_object() in SQL have a limit as to how many can be performed
|
|
@@ -931,11 +1050,11 @@ class InternalBuilder {
|
|
|
931
1050
|
});
|
|
932
1051
|
}
|
|
933
1052
|
// add the correlation to the overall query
|
|
934
|
-
subQuery = subQuery.where(correlatedTo, "=",
|
|
1053
|
+
subQuery = subQuery.where(correlatedTo, "=", this.rawQuotedIdentifier(correlatedFrom));
|
|
935
1054
|
const standardWrap = (select) => {
|
|
936
1055
|
subQuery = subQuery.select(`${toAlias}.*`).limit(getRelationshipLimit());
|
|
937
1056
|
// @ts-ignore - the from alias syntax isn't in Knex typing
|
|
938
|
-
return knex.select(
|
|
1057
|
+
return knex.select(select).from({
|
|
939
1058
|
[toAlias]: subQuery,
|
|
940
1059
|
});
|
|
941
1060
|
};
|
|
@@ -944,10 +1063,10 @@ class InternalBuilder {
|
|
|
944
1063
|
case types_1.SqlClient.SQL_LITE:
|
|
945
1064
|
// need to check the junction table document is to the right column, this is just for SQS
|
|
946
1065
|
subQuery = this.addJoinFieldCheck(subQuery, relationship);
|
|
947
|
-
wrapperQuery = standardWrap(`json_group_array(json_object(${fieldList}))`);
|
|
1066
|
+
wrapperQuery = standardWrap(this.knex.raw(`json_group_array(json_object(${fieldList}))`));
|
|
948
1067
|
break;
|
|
949
1068
|
case types_1.SqlClient.POSTGRES:
|
|
950
|
-
wrapperQuery = standardWrap(`json_agg(json_build_object(${fieldList}))`);
|
|
1069
|
+
wrapperQuery = standardWrap(this.knex.raw(`json_agg(json_build_object(${fieldList}))`));
|
|
951
1070
|
break;
|
|
952
1071
|
case types_1.SqlClient.MARIADB:
|
|
953
1072
|
// can't use the standard wrap due to correlated sub-query limitations in MariaDB
|
|
@@ -955,18 +1074,20 @@ class InternalBuilder {
|
|
|
955
1074
|
break;
|
|
956
1075
|
case types_1.SqlClient.MY_SQL:
|
|
957
1076
|
case types_1.SqlClient.ORACLE:
|
|
958
|
-
wrapperQuery = standardWrap(`json_arrayagg(json_object(${fieldList}))`);
|
|
1077
|
+
wrapperQuery = standardWrap(this.knex.raw(`json_arrayagg(json_object(${fieldList}))`));
|
|
959
1078
|
break;
|
|
960
|
-
case types_1.SqlClient.MS_SQL:
|
|
961
|
-
|
|
1079
|
+
case types_1.SqlClient.MS_SQL: {
|
|
1080
|
+
const comparatorQuery = knex
|
|
962
1081
|
.select(`${fromAlias}.*`)
|
|
963
1082
|
// @ts-ignore - from alias syntax not TS supported
|
|
964
1083
|
.from({
|
|
965
1084
|
[fromAlias]: subQuery
|
|
966
1085
|
.select(`${toAlias}.*`)
|
|
967
1086
|
.limit(getRelationshipLimit()),
|
|
968
|
-
})
|
|
1087
|
+
});
|
|
1088
|
+
wrapperQuery = knex.raw(`(SELECT ?? = (${comparatorQuery} FOR JSON PATH))`, [this.rawQuotedIdentifier(toAlias)]);
|
|
969
1089
|
break;
|
|
1090
|
+
}
|
|
970
1091
|
default:
|
|
971
1092
|
throw new Error(`JSON relationships not implement for ${sqlClient}`);
|
|
972
1093
|
}
|
|
@@ -990,11 +1111,9 @@ class InternalBuilder {
|
|
|
990
1111
|
})
|
|
991
1112
|
: undefined;
|
|
992
1113
|
if (!throughTable) {
|
|
993
|
-
// @ts-ignore
|
|
994
1114
|
query = query.leftJoin(toTableWithSchema, function () {
|
|
995
1115
|
for (let relationship of columns) {
|
|
996
1116
|
const from = relationship.from, to = relationship.to;
|
|
997
|
-
// @ts-ignore
|
|
998
1117
|
this.orOn(`${fromAlias}.${from}`, "=", `${toAlias}.${to}`);
|
|
999
1118
|
}
|
|
1000
1119
|
});
|
|
@@ -1006,7 +1125,6 @@ class InternalBuilder {
|
|
|
1006
1125
|
for (let relationship of columns) {
|
|
1007
1126
|
const fromPrimary = relationship.fromPrimary;
|
|
1008
1127
|
const from = relationship.from;
|
|
1009
|
-
// @ts-ignore
|
|
1010
1128
|
this.orOn(`${fromAlias}.${fromPrimary}`, "=", `${throughAlias}.${from}`);
|
|
1011
1129
|
}
|
|
1012
1130
|
})
|
|
@@ -1014,7 +1132,6 @@ class InternalBuilder {
|
|
|
1014
1132
|
for (let relationship of columns) {
|
|
1015
1133
|
const toPrimary = relationship.toPrimary;
|
|
1016
1134
|
const to = relationship.to;
|
|
1017
|
-
// @ts-ignore
|
|
1018
1135
|
this.orOn(`${toAlias}.${toPrimary}`, `${throughAlias}.${to}`);
|
|
1019
1136
|
}
|
|
1020
1137
|
});
|