@budibase/backend-core 2.31.7 → 2.32.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/index.js +380 -155
- 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 +2 -2
- package/dist/plugins.js.meta.json +1 -1
- package/dist/src/environment.d.ts +1 -0
- package/dist/src/environment.js +1 -1
- package/dist/src/environment.js.map +1 -1
- package/dist/src/events/publishers/ai.d.ts +7 -0
- package/dist/src/events/publishers/ai.js +30 -0
- package/dist/src/events/publishers/ai.js.map +1 -0
- package/dist/src/events/publishers/index.d.ts +1 -0
- package/dist/src/events/publishers/index.js +3 -1
- package/dist/src/events/publishers/index.js.map +1 -1
- package/dist/src/features/index.d.ts +2 -0
- package/dist/src/features/index.js +2 -0
- package/dist/src/features/index.js.map +1 -1
- package/dist/src/sql/sql.js +343 -148
- package/dist/src/sql/sql.js.map +1 -1
- package/dist/src/sql/utils.d.ts +2 -1
- package/dist/src/sql/utils.js +14 -0
- package/dist/src/sql/utils.js.map +1 -1
- package/dist/tests/core/utilities/structures/licenses.js +10 -0
- package/dist/tests/core/utilities/structures/licenses.js.map +1 -1
- package/dist/tests/core/utilities/structures/quotas.js +4 -0
- package/dist/tests/core/utilities/structures/quotas.js.map +1 -1
- package/package.json +4 -4
- package/src/environment.ts +1 -0
- package/src/events/publishers/ai.ts +21 -0
- package/src/events/publishers/index.ts +1 -0
- package/src/features/index.ts +2 -0
- package/src/sql/sql.ts +474 -211
- package/src/sql/utils.ts +29 -1
- package/tests/core/utilities/structures/licenses.ts +10 -0
- package/tests/core/utilities/structures/quotas.ts +4 -0
package/dist/src/sql/sql.js
CHANGED
|
@@ -43,12 +43,19 @@ const types_1 = require("@budibase/types");
|
|
|
43
43
|
const environment_1 = __importDefault(require("../environment"));
|
|
44
44
|
const shared_core_1 = require("@budibase/shared-core");
|
|
45
45
|
const lodash_1 = require("lodash");
|
|
46
|
+
const MAX_SQS_RELATIONSHIP_FIELDS = 63;
|
|
46
47
|
function getBaseLimit() {
|
|
47
48
|
const envLimit = environment_1.default.SQL_MAX_ROWS
|
|
48
49
|
? parseInt(environment_1.default.SQL_MAX_ROWS)
|
|
49
50
|
: null;
|
|
50
51
|
return envLimit || 5000;
|
|
51
52
|
}
|
|
53
|
+
function getRelationshipLimit() {
|
|
54
|
+
const envLimit = environment_1.default.SQL_MAX_RELATED_ROWS
|
|
55
|
+
? parseInt(environment_1.default.SQL_MAX_RELATED_ROWS)
|
|
56
|
+
: null;
|
|
57
|
+
return envLimit || 500;
|
|
58
|
+
}
|
|
52
59
|
function getTableName(table) {
|
|
53
60
|
// SQS uses the table ID rather than the table name
|
|
54
61
|
if ((table === null || table === void 0 ? void 0 : table.sourceType) === types_1.TableSourceType.INTERNAL ||
|
|
@@ -77,6 +84,19 @@ function convertBooleans(query) {
|
|
|
77
84
|
}
|
|
78
85
|
class InternalBuilder {
|
|
79
86
|
constructor(client, knex, query) {
|
|
87
|
+
// states the various situations in which we need a full mapped select statement
|
|
88
|
+
this.SPECIAL_SELECT_CASES = {
|
|
89
|
+
POSTGRES_MONEY: (field) => {
|
|
90
|
+
var _a;
|
|
91
|
+
return (this.client === types_1.SqlClient.POSTGRES &&
|
|
92
|
+
((_a = field === null || field === void 0 ? void 0 : field.externalType) === null || _a === void 0 ? void 0 : _a.includes("money")));
|
|
93
|
+
},
|
|
94
|
+
MSSQL_DATES: (field) => {
|
|
95
|
+
return (this.client === types_1.SqlClient.MS_SQL &&
|
|
96
|
+
(field === null || field === void 0 ? void 0 : field.type) === types_1.FieldType.DATETIME &&
|
|
97
|
+
field.timeOnly);
|
|
98
|
+
},
|
|
99
|
+
};
|
|
80
100
|
this.client = client;
|
|
81
101
|
this.query = query;
|
|
82
102
|
this.knex = knex;
|
|
@@ -114,60 +134,58 @@ class InternalBuilder {
|
|
|
114
134
|
.map(part => this.quote(part))
|
|
115
135
|
.join(".");
|
|
116
136
|
}
|
|
137
|
+
isFullSelectStatementRequired() {
|
|
138
|
+
const { meta } = this.query;
|
|
139
|
+
for (let column of Object.values(meta.table.schema)) {
|
|
140
|
+
if (this.SPECIAL_SELECT_CASES.POSTGRES_MONEY(column)) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
else if (this.SPECIAL_SELECT_CASES.MSSQL_DATES(column)) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
117
149
|
generateSelectStatement() {
|
|
118
|
-
const { resource,
|
|
150
|
+
const { meta, endpoint, resource, tableAliases } = this.query;
|
|
119
151
|
if (!resource || !resource.fields || resource.fields.length === 0) {
|
|
120
152
|
return "*";
|
|
121
153
|
}
|
|
154
|
+
const alias = (tableAliases === null || tableAliases === void 0 ? void 0 : tableAliases[endpoint.entityId])
|
|
155
|
+
? tableAliases === null || tableAliases === void 0 ? void 0 : tableAliases[endpoint.entityId]
|
|
156
|
+
: endpoint.entityId;
|
|
122
157
|
const schema = meta.table.schema;
|
|
123
|
-
|
|
124
|
-
|
|
158
|
+
if (!this.isFullSelectStatementRequired()) {
|
|
159
|
+
return [this.knex.raw(`${this.quote(alias)}.*`)];
|
|
160
|
+
}
|
|
161
|
+
// get just the fields for this table
|
|
162
|
+
return resource.fields
|
|
163
|
+
.map(field => {
|
|
125
164
|
const parts = field.split(/\./g);
|
|
126
165
|
let table = undefined;
|
|
127
|
-
let column =
|
|
166
|
+
let column = parts[0];
|
|
128
167
|
// Just a column name, e.g.: "column"
|
|
129
|
-
if (parts.length
|
|
130
|
-
column = parts[0];
|
|
131
|
-
}
|
|
132
|
-
// A table name and a column name, e.g.: "table.column"
|
|
133
|
-
if (parts.length === 2) {
|
|
134
|
-
table = parts[0];
|
|
135
|
-
column = parts[1];
|
|
136
|
-
}
|
|
137
|
-
// A link doc, e.g.: "table.doc1.fieldName"
|
|
138
|
-
if (parts.length > 2) {
|
|
168
|
+
if (parts.length > 1) {
|
|
139
169
|
table = parts[0];
|
|
140
170
|
column = parts.slice(1).join(".");
|
|
141
171
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
172
|
+
return { table, column, field };
|
|
173
|
+
})
|
|
174
|
+
.filter(({ table }) => !table || table === alias)
|
|
175
|
+
.map(({ table, column, field }) => {
|
|
145
176
|
const columnSchema = schema[column];
|
|
146
|
-
if (this.
|
|
147
|
-
((_a = columnSchema === null || columnSchema === void 0 ? void 0 : columnSchema.externalType) === null || _a === void 0 ? void 0 : _a.includes("money"))) {
|
|
177
|
+
if (this.SPECIAL_SELECT_CASES.POSTGRES_MONEY(columnSchema)) {
|
|
148
178
|
return this.knex.raw(`${this.quotedIdentifier([table, column].join("."))}::money::numeric as ${this.quote(field)}`);
|
|
149
179
|
}
|
|
150
|
-
if (this.
|
|
151
|
-
(columnSchema === null || columnSchema === void 0 ? void 0 : columnSchema.type) === types_1.FieldType.DATETIME &&
|
|
152
|
-
columnSchema.timeOnly) {
|
|
180
|
+
if (this.SPECIAL_SELECT_CASES.MSSQL_DATES(columnSchema)) {
|
|
153
181
|
// Time gets returned as timestamp from mssql, not matching the expected
|
|
154
182
|
// HH:mm format
|
|
155
183
|
return this.knex.raw(`CONVERT(varchar, ${field}, 108) as "${field}"`);
|
|
156
184
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
// in the case of relationships, where it's table.doc1.column. In that
|
|
162
|
-
// case, we want to split it into `table`.`doc1.column` for reasons that
|
|
163
|
-
// aren't actually clear to me, but `table`.`doc1` breaks things with the
|
|
164
|
-
// sample data tests.
|
|
165
|
-
if (table) {
|
|
166
|
-
return this.knex.raw(`${this.quote(table)}.${this.quote(column)} as ${this.quote(field)}`);
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
return this.knex.raw(`${this.quote(field)} as ${this.quote(field)}`);
|
|
170
|
-
}
|
|
185
|
+
const quoted = table
|
|
186
|
+
? `${this.quote(table)}.${this.quote(column)}`
|
|
187
|
+
: this.quote(field);
|
|
188
|
+
return this.knex.raw(quoted);
|
|
171
189
|
});
|
|
172
190
|
}
|
|
173
191
|
// OracleDB can't use character-large-objects (CLOBs) in WHERE clauses,
|
|
@@ -285,54 +303,118 @@ class InternalBuilder {
|
|
|
285
303
|
}
|
|
286
304
|
return filters;
|
|
287
305
|
}
|
|
306
|
+
addJoinFieldCheck(query, relationship) {
|
|
307
|
+
var _a;
|
|
308
|
+
const document = ((_a = relationship.from) === null || _a === void 0 ? void 0 : _a.split(".")[0]) || "";
|
|
309
|
+
return query.andWhere(`${document}.fieldName`, "=", relationship.column);
|
|
310
|
+
}
|
|
311
|
+
addRelationshipForFilter(query, filterKey, whereCb) {
|
|
312
|
+
const mainKnex = this.knex;
|
|
313
|
+
const { relationships, endpoint, tableAliases: aliases } = this.query;
|
|
314
|
+
const tableName = endpoint.entityId;
|
|
315
|
+
const fromAlias = (aliases === null || aliases === void 0 ? void 0 : aliases[tableName]) || tableName;
|
|
316
|
+
const matches = (possibleTable) => filterKey.startsWith(`${possibleTable}`);
|
|
317
|
+
if (!relationships) {
|
|
318
|
+
return query;
|
|
319
|
+
}
|
|
320
|
+
for (const relationship of relationships) {
|
|
321
|
+
const relatedTableName = relationship.tableName;
|
|
322
|
+
const toAlias = (aliases === null || aliases === void 0 ? void 0 : aliases[relatedTableName]) || relatedTableName;
|
|
323
|
+
// this is the relationship which is being filtered
|
|
324
|
+
if ((matches(relatedTableName) || matches(toAlias)) &&
|
|
325
|
+
relationship.to &&
|
|
326
|
+
relationship.tableName) {
|
|
327
|
+
let subQuery = mainKnex
|
|
328
|
+
.select(mainKnex.raw(1))
|
|
329
|
+
.from({ [toAlias]: relatedTableName });
|
|
330
|
+
const manyToMany = (0, utils_1.validateManyToMany)(relationship);
|
|
331
|
+
if (manyToMany) {
|
|
332
|
+
const throughAlias = (aliases === null || aliases === void 0 ? void 0 : aliases[manyToMany.through]) || relationship.through;
|
|
333
|
+
let throughTable = this.tableNameWithSchema(manyToMany.through, {
|
|
334
|
+
alias: throughAlias,
|
|
335
|
+
schema: endpoint.schema,
|
|
336
|
+
});
|
|
337
|
+
subQuery = subQuery
|
|
338
|
+
// add a join through the junction table
|
|
339
|
+
.innerJoin(throughTable, function () {
|
|
340
|
+
// @ts-ignore
|
|
341
|
+
this.on(`${toAlias}.${manyToMany.toPrimary}`, "=", `${throughAlias}.${manyToMany.to}`);
|
|
342
|
+
})
|
|
343
|
+
// check the document in the junction table points to the main table
|
|
344
|
+
.where(`${throughAlias}.${manyToMany.from}`, "=", mainKnex.raw(this.quotedIdentifier(`${fromAlias}.${manyToMany.fromPrimary}`)));
|
|
345
|
+
// in SQS the same junction table is used for different many-to-many relationships between the
|
|
346
|
+
// two same tables, this is needed to avoid rows ending up in all columns
|
|
347
|
+
if (this.client === types_1.SqlClient.SQL_LITE) {
|
|
348
|
+
subQuery = this.addJoinFieldCheck(subQuery, manyToMany);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
// "join" to the main table, making sure the ID matches that of the main
|
|
353
|
+
subQuery = subQuery.where(`${toAlias}.${relationship.to}`, "=", mainKnex.raw(this.quotedIdentifier(`${fromAlias}.${relationship.from}`)));
|
|
354
|
+
}
|
|
355
|
+
query = query.whereExists(whereCb(subQuery));
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return query;
|
|
360
|
+
}
|
|
288
361
|
// right now we only do filters on the specific table being queried
|
|
289
362
|
addFilters(query, filters, opts) {
|
|
290
363
|
if (!filters) {
|
|
291
364
|
return query;
|
|
292
365
|
}
|
|
366
|
+
const builder = this;
|
|
293
367
|
filters = this.parseFilters(Object.assign({}, filters));
|
|
294
368
|
const aliases = this.query.tableAliases;
|
|
295
369
|
// if all or specified in filters, then everything is an or
|
|
296
370
|
const allOr = filters.allOr;
|
|
297
|
-
const
|
|
371
|
+
const isSqlite = this.client === types_1.SqlClient.SQL_LITE;
|
|
372
|
+
const tableName = isSqlite ? this.table._id : this.table.name;
|
|
298
373
|
function getTableAlias(name) {
|
|
299
374
|
const alias = aliases === null || aliases === void 0 ? void 0 : aliases[name];
|
|
300
375
|
return alias || name;
|
|
301
376
|
}
|
|
302
377
|
function iterate(structure, fn, complexKeyFn) {
|
|
378
|
+
const handleRelationship = (q, key, value) => {
|
|
379
|
+
const [filterTableName, ...otherProperties] = key.split(".");
|
|
380
|
+
const property = otherProperties.join(".");
|
|
381
|
+
const alias = getTableAlias(filterTableName);
|
|
382
|
+
return fn(q, alias ? `${alias}.${property}` : property, value);
|
|
383
|
+
};
|
|
303
384
|
for (const key in structure) {
|
|
304
385
|
const value = structure[key];
|
|
305
386
|
const updatedKey = dbCore.removeKeyNumbering(key);
|
|
306
387
|
const isRelationshipField = updatedKey.includes(".");
|
|
388
|
+
const shouldProcessRelationship = (opts === null || opts === void 0 ? void 0 : opts.relationship) && isRelationshipField;
|
|
307
389
|
let castedTypeValue;
|
|
308
390
|
if (key === types_1.InternalSearchFilterOperator.COMPLEX_ID_OPERATOR &&
|
|
309
391
|
(castedTypeValue = structure[key]) &&
|
|
310
392
|
complexKeyFn) {
|
|
311
393
|
const alias = getTableAlias(tableName);
|
|
312
|
-
complexKeyFn(castedTypeValue.id.map((x) => alias ? `${alias}.${x}` : x), castedTypeValue.values);
|
|
394
|
+
query = complexKeyFn(query, castedTypeValue.id.map((x) => alias ? `${alias}.${x}` : x), castedTypeValue.values);
|
|
313
395
|
}
|
|
314
396
|
else if (!isRelationshipField) {
|
|
315
397
|
const alias = getTableAlias(tableName);
|
|
316
|
-
fn(alias ? `${alias}.${updatedKey}` : updatedKey, value);
|
|
398
|
+
query = fn(query, alias ? `${alias}.${updatedKey}` : updatedKey, value);
|
|
317
399
|
}
|
|
318
|
-
if (
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
400
|
+
else if (shouldProcessRelationship) {
|
|
401
|
+
query = builder.addRelationshipForFilter(query, updatedKey, q => {
|
|
402
|
+
return handleRelationship(q, updatedKey, value);
|
|
403
|
+
});
|
|
322
404
|
}
|
|
323
405
|
}
|
|
324
406
|
}
|
|
325
|
-
const like = (key, value) => {
|
|
407
|
+
const like = (q, key, value) => {
|
|
326
408
|
const fuzzyOr = filters === null || filters === void 0 ? void 0 : filters.fuzzyOr;
|
|
327
409
|
const fnc = fuzzyOr || allOr ? "orWhere" : "where";
|
|
328
410
|
// postgres supports ilike, nothing else does
|
|
329
411
|
if (this.client === types_1.SqlClient.POSTGRES) {
|
|
330
|
-
|
|
412
|
+
return q[fnc](key, "ilike", `%${value}%`);
|
|
331
413
|
}
|
|
332
414
|
else {
|
|
333
415
|
const rawFnc = `${fnc}Raw`;
|
|
334
416
|
// @ts-ignore
|
|
335
|
-
|
|
417
|
+
return q[rawFnc](`LOWER(${this.quotedIdentifier(key)}) LIKE ?`, [
|
|
336
418
|
`%${value.toLowerCase()}%`,
|
|
337
419
|
]);
|
|
338
420
|
}
|
|
@@ -349,24 +431,24 @@ class InternalBuilder {
|
|
|
349
431
|
return `[${value.join(",")}]`;
|
|
350
432
|
}
|
|
351
433
|
if (this.client === types_1.SqlClient.POSTGRES) {
|
|
352
|
-
iterate(mode, (key, value) => {
|
|
434
|
+
iterate(mode, (q, key, value) => {
|
|
353
435
|
const wrap = any ? "" : "'";
|
|
354
436
|
const op = any ? "\\?| array" : "@>";
|
|
355
437
|
const fieldNames = key.split(/\./g);
|
|
356
438
|
const table = fieldNames[0];
|
|
357
439
|
const col = fieldNames[1];
|
|
358
|
-
|
|
440
|
+
return q[rawFnc](`${not}COALESCE("${table}"."${col}"::jsonb ${op} ${wrap}${stringifyArray(value, any ? "'" : '"')}${wrap}, FALSE)`);
|
|
359
441
|
});
|
|
360
442
|
}
|
|
361
443
|
else if (this.client === types_1.SqlClient.MY_SQL) {
|
|
362
444
|
const jsonFnc = any ? "JSON_OVERLAPS" : "JSON_CONTAINS";
|
|
363
|
-
iterate(mode, (key, value) => {
|
|
364
|
-
|
|
445
|
+
iterate(mode, (q, key, value) => {
|
|
446
|
+
return q[rawFnc](`${not}COALESCE(${jsonFnc}(${key}, '${stringifyArray(value)}'), FALSE)`);
|
|
365
447
|
});
|
|
366
448
|
}
|
|
367
449
|
else {
|
|
368
450
|
const andOr = mode === (filters === null || filters === void 0 ? void 0 : filters.containsAny) ? " OR " : " AND ";
|
|
369
|
-
iterate(mode, (key, value) => {
|
|
451
|
+
iterate(mode, (q, key, value) => {
|
|
370
452
|
let statement = "";
|
|
371
453
|
const identifier = this.quotedIdentifier(key);
|
|
372
454
|
for (let i in value) {
|
|
@@ -379,13 +461,13 @@ class InternalBuilder {
|
|
|
379
461
|
statement += `${statement ? andOr : ""}COALESCE(LOWER(${identifier}), '') LIKE ?`;
|
|
380
462
|
}
|
|
381
463
|
if (statement === "") {
|
|
382
|
-
return;
|
|
464
|
+
return q;
|
|
383
465
|
}
|
|
384
466
|
if (not) {
|
|
385
|
-
|
|
467
|
+
return q[rawFnc](`(NOT (${statement}) OR ${identifier} IS NULL)`, value);
|
|
386
468
|
}
|
|
387
469
|
else {
|
|
388
|
-
|
|
470
|
+
return q[rawFnc](statement, value);
|
|
389
471
|
}
|
|
390
472
|
});
|
|
391
473
|
}
|
|
@@ -408,40 +490,40 @@ class InternalBuilder {
|
|
|
408
490
|
}
|
|
409
491
|
if (filters.oneOf) {
|
|
410
492
|
const fnc = allOr ? "orWhereIn" : "whereIn";
|
|
411
|
-
iterate(filters.oneOf, (key, array) => {
|
|
493
|
+
iterate(filters.oneOf, (q, key, array) => {
|
|
412
494
|
if (this.client === types_1.SqlClient.ORACLE) {
|
|
413
495
|
key = this.convertClobs(key);
|
|
414
496
|
array = Array.isArray(array) ? array : [array];
|
|
415
497
|
const binding = new Array(array.length).fill("?").join(",");
|
|
416
|
-
|
|
498
|
+
return q.whereRaw(`${key} IN (${binding})`, array);
|
|
417
499
|
}
|
|
418
500
|
else {
|
|
419
|
-
|
|
501
|
+
return q[fnc](key, Array.isArray(array) ? array : [array]);
|
|
420
502
|
}
|
|
421
|
-
}, (key, array) => {
|
|
503
|
+
}, (q, key, array) => {
|
|
422
504
|
if (this.client === types_1.SqlClient.ORACLE) {
|
|
423
505
|
const keyStr = `(${key.map(k => this.convertClobs(k)).join(",")})`;
|
|
424
506
|
const binding = `(${array
|
|
425
507
|
.map((a) => `(${new Array(a.length).fill("?").join(",")})`)
|
|
426
508
|
.join(",")})`;
|
|
427
|
-
|
|
509
|
+
return q.whereRaw(`${keyStr} IN ${binding}`, array.flat());
|
|
428
510
|
}
|
|
429
511
|
else {
|
|
430
|
-
|
|
512
|
+
return q[fnc](key, Array.isArray(array) ? array : [array]);
|
|
431
513
|
}
|
|
432
514
|
});
|
|
433
515
|
}
|
|
434
516
|
if (filters.string) {
|
|
435
|
-
iterate(filters.string, (key, value) => {
|
|
517
|
+
iterate(filters.string, (q, key, value) => {
|
|
436
518
|
const fnc = allOr ? "orWhere" : "where";
|
|
437
519
|
// postgres supports ilike, nothing else does
|
|
438
520
|
if (this.client === types_1.SqlClient.POSTGRES) {
|
|
439
|
-
|
|
521
|
+
return q[fnc](key, "ilike", `${value}%`);
|
|
440
522
|
}
|
|
441
523
|
else {
|
|
442
524
|
const rawFnc = `${fnc}Raw`;
|
|
443
525
|
// @ts-ignore
|
|
444
|
-
|
|
526
|
+
return q[rawFnc](`LOWER(${this.quotedIdentifier(key)}) LIKE ?`, [
|
|
445
527
|
`${value.toLowerCase()}%`,
|
|
446
528
|
]);
|
|
447
529
|
}
|
|
@@ -451,7 +533,7 @@ class InternalBuilder {
|
|
|
451
533
|
iterate(filters.fuzzy, like);
|
|
452
534
|
}
|
|
453
535
|
if (filters.range) {
|
|
454
|
-
iterate(filters.range, (key, value) => {
|
|
536
|
+
iterate(filters.range, (q, key, value) => {
|
|
455
537
|
const isEmptyObject = (val) => {
|
|
456
538
|
return (val &&
|
|
457
539
|
Object.keys(val).length === 0 &&
|
|
@@ -472,75 +554,86 @@ class InternalBuilder {
|
|
|
472
554
|
if (lowValid && highValid) {
|
|
473
555
|
if ((schema === null || schema === void 0 ? void 0 : schema.type) === types_1.FieldType.BIGINT &&
|
|
474
556
|
this.client === types_1.SqlClient.SQL_LITE) {
|
|
475
|
-
|
|
557
|
+
return q.whereRaw(`CAST(${key} AS INTEGER) BETWEEN CAST(? AS INTEGER) AND CAST(? AS INTEGER)`, [value.low, value.high]);
|
|
476
558
|
}
|
|
477
559
|
else {
|
|
478
560
|
const fnc = allOr ? "orWhereBetween" : "whereBetween";
|
|
479
|
-
|
|
561
|
+
return q[fnc](key, [value.low, value.high]);
|
|
480
562
|
}
|
|
481
563
|
}
|
|
482
564
|
else if (lowValid) {
|
|
483
565
|
if ((schema === null || schema === void 0 ? void 0 : schema.type) === types_1.FieldType.BIGINT &&
|
|
484
566
|
this.client === types_1.SqlClient.SQL_LITE) {
|
|
485
|
-
|
|
567
|
+
return q.whereRaw(`CAST(${key} AS INTEGER) >= CAST(? AS INTEGER)`, [
|
|
568
|
+
value.low,
|
|
569
|
+
]);
|
|
486
570
|
}
|
|
487
571
|
else {
|
|
488
572
|
const fnc = allOr ? "orWhere" : "where";
|
|
489
|
-
|
|
573
|
+
return q[fnc](key, ">=", value.low);
|
|
490
574
|
}
|
|
491
575
|
}
|
|
492
576
|
else if (highValid) {
|
|
493
577
|
if ((schema === null || schema === void 0 ? void 0 : schema.type) === types_1.FieldType.BIGINT &&
|
|
494
578
|
this.client === types_1.SqlClient.SQL_LITE) {
|
|
495
|
-
|
|
579
|
+
return q.whereRaw(`CAST(${key} AS INTEGER) <= CAST(? AS INTEGER)`, [
|
|
580
|
+
value.high,
|
|
581
|
+
]);
|
|
496
582
|
}
|
|
497
583
|
else {
|
|
498
584
|
const fnc = allOr ? "orWhere" : "where";
|
|
499
|
-
|
|
585
|
+
return q[fnc](key, "<=", value.high);
|
|
500
586
|
}
|
|
501
587
|
}
|
|
588
|
+
return q;
|
|
502
589
|
});
|
|
503
590
|
}
|
|
504
591
|
if (filters.equal) {
|
|
505
|
-
iterate(filters.equal, (key, value) => {
|
|
592
|
+
iterate(filters.equal, (q, key, value) => {
|
|
506
593
|
const fnc = allOr ? "orWhereRaw" : "whereRaw";
|
|
507
594
|
if (this.client === types_1.SqlClient.MS_SQL) {
|
|
508
|
-
|
|
595
|
+
return q[fnc](`CASE WHEN ${this.quotedIdentifier(key)} = ? THEN 1 ELSE 0 END = 1`, [value]);
|
|
509
596
|
}
|
|
510
597
|
else if (this.client === types_1.SqlClient.ORACLE) {
|
|
511
598
|
const identifier = this.convertClobs(key);
|
|
512
|
-
|
|
599
|
+
return q[fnc](`(${identifier} IS NOT NULL AND ${identifier} = ?)`, [
|
|
600
|
+
value,
|
|
601
|
+
]);
|
|
513
602
|
}
|
|
514
603
|
else {
|
|
515
|
-
|
|
604
|
+
return q[fnc](`COALESCE(${this.quotedIdentifier(key)} = ?, FALSE)`, [
|
|
605
|
+
value,
|
|
606
|
+
]);
|
|
516
607
|
}
|
|
517
608
|
});
|
|
518
609
|
}
|
|
519
610
|
if (filters.notEqual) {
|
|
520
|
-
iterate(filters.notEqual, (key, value) => {
|
|
611
|
+
iterate(filters.notEqual, (q, key, value) => {
|
|
521
612
|
const fnc = allOr ? "orWhereRaw" : "whereRaw";
|
|
522
613
|
if (this.client === types_1.SqlClient.MS_SQL) {
|
|
523
|
-
|
|
614
|
+
return q[fnc](`CASE WHEN ${this.quotedIdentifier(key)} = ? THEN 1 ELSE 0 END = 0`, [value]);
|
|
524
615
|
}
|
|
525
616
|
else if (this.client === types_1.SqlClient.ORACLE) {
|
|
526
617
|
const identifier = this.convertClobs(key);
|
|
527
|
-
|
|
618
|
+
return q[fnc](`(${identifier} IS NOT NULL AND ${identifier} != ?) OR ${identifier} IS NULL`, [value]);
|
|
528
619
|
}
|
|
529
620
|
else {
|
|
530
|
-
|
|
621
|
+
return q[fnc](`COALESCE(${this.quotedIdentifier(key)} != ?, TRUE)`, [
|
|
622
|
+
value,
|
|
623
|
+
]);
|
|
531
624
|
}
|
|
532
625
|
});
|
|
533
626
|
}
|
|
534
627
|
if (filters.empty) {
|
|
535
|
-
iterate(filters.empty, key => {
|
|
628
|
+
iterate(filters.empty, (q, key) => {
|
|
536
629
|
const fnc = allOr ? "orWhereNull" : "whereNull";
|
|
537
|
-
|
|
630
|
+
return q[fnc](key);
|
|
538
631
|
});
|
|
539
632
|
}
|
|
540
633
|
if (filters.notEmpty) {
|
|
541
|
-
iterate(filters.notEmpty, key => {
|
|
634
|
+
iterate(filters.notEmpty, (q, key) => {
|
|
542
635
|
const fnc = allOr ? "orWhereNotNull" : "whereNotNull";
|
|
543
|
-
|
|
636
|
+
return q[fnc](key);
|
|
544
637
|
});
|
|
545
638
|
}
|
|
546
639
|
if (filters.contains) {
|
|
@@ -614,10 +707,162 @@ class InternalBuilder {
|
|
|
614
707
|
}
|
|
615
708
|
return withSchema;
|
|
616
709
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
710
|
+
addJsonRelationships(query, fromTable, relationships) {
|
|
711
|
+
const sqlClient = this.client;
|
|
712
|
+
const knex = this.knex;
|
|
713
|
+
const { resource, tableAliases: aliases, endpoint } = this.query;
|
|
714
|
+
const fields = (resource === null || resource === void 0 ? void 0 : resource.fields) || [];
|
|
715
|
+
const jsonField = (field) => {
|
|
716
|
+
const parts = field.split(".");
|
|
717
|
+
let tableField, unaliased;
|
|
718
|
+
if (parts.length > 1) {
|
|
719
|
+
const alias = parts.shift();
|
|
720
|
+
unaliased = parts.join(".");
|
|
721
|
+
tableField = `${this.quote(alias)}.${this.quote(unaliased)}`;
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
unaliased = parts.join(".");
|
|
725
|
+
tableField = this.quote(unaliased);
|
|
726
|
+
}
|
|
727
|
+
let separator = ",";
|
|
728
|
+
switch (sqlClient) {
|
|
729
|
+
case types_1.SqlClient.ORACLE:
|
|
730
|
+
separator = " VALUE ";
|
|
731
|
+
break;
|
|
732
|
+
case types_1.SqlClient.MS_SQL:
|
|
733
|
+
separator = ":";
|
|
734
|
+
}
|
|
735
|
+
return `'${unaliased}'${separator}${tableField}`;
|
|
736
|
+
};
|
|
737
|
+
for (let relationship of relationships) {
|
|
738
|
+
const { tableName: toTable, through: throughTable, to: toKey, from: fromKey, fromPrimary, toPrimary, } = relationship;
|
|
739
|
+
// skip invalid relationships
|
|
740
|
+
if (!toTable || !fromTable) {
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
const toAlias = (aliases === null || aliases === void 0 ? void 0 : aliases[toTable]) || toTable, fromAlias = (aliases === null || aliases === void 0 ? void 0 : aliases[fromTable]) || fromTable;
|
|
744
|
+
let toTableWithSchema = this.tableNameWithSchema(toTable, {
|
|
745
|
+
alias: toAlias,
|
|
746
|
+
schema: endpoint.schema,
|
|
747
|
+
});
|
|
748
|
+
let relationshipFields = fields.filter(field => field.split(".")[0] === toAlias);
|
|
749
|
+
if (this.client === types_1.SqlClient.SQL_LITE) {
|
|
750
|
+
relationshipFields = relationshipFields.slice(0, MAX_SQS_RELATIONSHIP_FIELDS);
|
|
751
|
+
}
|
|
752
|
+
const fieldList = relationshipFields
|
|
753
|
+
.map(field => jsonField(field))
|
|
754
|
+
.join(",");
|
|
755
|
+
// SQL Server uses TOP - which performs a little differently to the normal LIMIT syntax
|
|
756
|
+
// it reduces the result set rather than limiting how much data it filters over
|
|
757
|
+
const primaryKey = `${toAlias}.${toPrimary || toKey}`;
|
|
758
|
+
let subQuery = knex
|
|
759
|
+
.from(toTableWithSchema)
|
|
760
|
+
.limit(getRelationshipLimit())
|
|
761
|
+
// add sorting to get consistent order
|
|
762
|
+
.orderBy(primaryKey);
|
|
763
|
+
// many-to-many relationship with junction table
|
|
764
|
+
if (throughTable && toPrimary && fromPrimary) {
|
|
765
|
+
const throughAlias = (aliases === null || aliases === void 0 ? void 0 : aliases[throughTable]) || throughTable;
|
|
766
|
+
let throughTableWithSchema = this.tableNameWithSchema(throughTable, {
|
|
767
|
+
alias: throughAlias,
|
|
768
|
+
schema: endpoint.schema,
|
|
769
|
+
});
|
|
770
|
+
subQuery = subQuery
|
|
771
|
+
.join(throughTableWithSchema, function () {
|
|
772
|
+
this.on(`${toAlias}.${toPrimary}`, "=", `${throughAlias}.${toKey}`);
|
|
773
|
+
})
|
|
774
|
+
.where(`${throughAlias}.${fromKey}`, "=", knex.raw(this.quotedIdentifier(`${fromAlias}.${fromPrimary}`)));
|
|
775
|
+
}
|
|
776
|
+
// one-to-many relationship with foreign key
|
|
777
|
+
else {
|
|
778
|
+
subQuery = subQuery.where(`${toAlias}.${toKey}`, "=", knex.raw(this.quotedIdentifier(`${fromAlias}.${fromKey}`)));
|
|
779
|
+
}
|
|
780
|
+
const standardWrap = (select) => {
|
|
781
|
+
subQuery = subQuery.select(`${toAlias}.*`);
|
|
782
|
+
// @ts-ignore - the from alias syntax isn't in Knex typing
|
|
783
|
+
return knex.select(knex.raw(select)).from({
|
|
784
|
+
[toAlias]: subQuery,
|
|
785
|
+
});
|
|
786
|
+
};
|
|
787
|
+
let wrapperQuery;
|
|
788
|
+
switch (sqlClient) {
|
|
789
|
+
case types_1.SqlClient.SQL_LITE:
|
|
790
|
+
// need to check the junction table document is to the right column, this is just for SQS
|
|
791
|
+
subQuery = this.addJoinFieldCheck(subQuery, relationship);
|
|
792
|
+
wrapperQuery = standardWrap(`json_group_array(json_object(${fieldList}))`);
|
|
793
|
+
break;
|
|
794
|
+
case types_1.SqlClient.POSTGRES:
|
|
795
|
+
wrapperQuery = standardWrap(`json_agg(json_build_object(${fieldList}))`);
|
|
796
|
+
break;
|
|
797
|
+
case types_1.SqlClient.MY_SQL:
|
|
798
|
+
wrapperQuery = subQuery.select(knex.raw(`json_arrayagg(json_object(${fieldList}))`));
|
|
799
|
+
break;
|
|
800
|
+
case types_1.SqlClient.ORACLE:
|
|
801
|
+
wrapperQuery = standardWrap(`json_arrayagg(json_object(${fieldList}))`);
|
|
802
|
+
break;
|
|
803
|
+
case types_1.SqlClient.MS_SQL:
|
|
804
|
+
wrapperQuery = knex.raw(`(SELECT ${this.quote(toAlias)} = (${knex
|
|
805
|
+
.select(`${fromAlias}.*`)
|
|
806
|
+
// @ts-ignore - from alias syntax not TS supported
|
|
807
|
+
.from({
|
|
808
|
+
[fromAlias]: subQuery.select(`${toAlias}.*`),
|
|
809
|
+
})} FOR JSON PATH))`);
|
|
810
|
+
break;
|
|
811
|
+
default:
|
|
812
|
+
throw new Error(`JSON relationships not implement for ${sqlClient}`);
|
|
813
|
+
}
|
|
814
|
+
query = query.select({ [relationship.column]: wrapperQuery });
|
|
620
815
|
}
|
|
816
|
+
return query;
|
|
817
|
+
}
|
|
818
|
+
addJoin(query, tables, columns) {
|
|
819
|
+
const { tableAliases: aliases, endpoint } = this.query;
|
|
820
|
+
const schema = endpoint.schema;
|
|
821
|
+
const toTable = tables.to, fromTable = tables.from, throughTable = tables.through;
|
|
822
|
+
const toAlias = (aliases === null || aliases === void 0 ? void 0 : aliases[toTable]) || toTable, throughAlias = (throughTable && (aliases === null || aliases === void 0 ? void 0 : aliases[throughTable])) || throughTable, fromAlias = (aliases === null || aliases === void 0 ? void 0 : aliases[fromTable]) || fromTable;
|
|
823
|
+
let toTableWithSchema = this.tableNameWithSchema(toTable, {
|
|
824
|
+
alias: toAlias,
|
|
825
|
+
schema,
|
|
826
|
+
});
|
|
827
|
+
let throughTableWithSchema = throughTable
|
|
828
|
+
? this.tableNameWithSchema(throughTable, {
|
|
829
|
+
alias: throughAlias,
|
|
830
|
+
schema,
|
|
831
|
+
})
|
|
832
|
+
: undefined;
|
|
833
|
+
if (!throughTable) {
|
|
834
|
+
// @ts-ignore
|
|
835
|
+
query = query.leftJoin(toTableWithSchema, function () {
|
|
836
|
+
for (let relationship of columns) {
|
|
837
|
+
const from = relationship.from, to = relationship.to;
|
|
838
|
+
// @ts-ignore
|
|
839
|
+
this.orOn(`${fromAlias}.${from}`, "=", `${toAlias}.${to}`);
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
query = query
|
|
845
|
+
// @ts-ignore
|
|
846
|
+
.leftJoin(throughTableWithSchema, function () {
|
|
847
|
+
for (let relationship of columns) {
|
|
848
|
+
const fromPrimary = relationship.fromPrimary;
|
|
849
|
+
const from = relationship.from;
|
|
850
|
+
// @ts-ignore
|
|
851
|
+
this.orOn(`${fromAlias}.${fromPrimary}`, "=", `${throughAlias}.${from}`);
|
|
852
|
+
}
|
|
853
|
+
})
|
|
854
|
+
.leftJoin(toTableWithSchema, function () {
|
|
855
|
+
for (let relationship of columns) {
|
|
856
|
+
const toPrimary = relationship.toPrimary;
|
|
857
|
+
const to = relationship.to;
|
|
858
|
+
// @ts-ignore
|
|
859
|
+
this.orOn(`${toAlias}.${toPrimary}`, `${throughAlias}.${to}`);
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
return query;
|
|
864
|
+
}
|
|
865
|
+
addRelationships(query, fromTable, relationships) {
|
|
621
866
|
const tableSets = {};
|
|
622
867
|
// aggregate into table sets (all the same to tables)
|
|
623
868
|
for (let relationship of relationships) {
|
|
@@ -638,45 +883,11 @@ class InternalBuilder {
|
|
|
638
883
|
}
|
|
639
884
|
for (let [key, relationships] of Object.entries(tableSets)) {
|
|
640
885
|
const { toTable, throughTable } = JSON.parse(key);
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
});
|
|
646
|
-
let throughTableWithSchema = this.tableNameWithSchema(throughTable, {
|
|
647
|
-
alias: throughAlias,
|
|
648
|
-
schema,
|
|
649
|
-
});
|
|
650
|
-
if (!throughTable) {
|
|
651
|
-
// @ts-ignore
|
|
652
|
-
query = query.leftJoin(toTableWithSchema, function () {
|
|
653
|
-
for (let relationship of relationships) {
|
|
654
|
-
const from = relationship.from, to = relationship.to;
|
|
655
|
-
// @ts-ignore
|
|
656
|
-
this.orOn(`${fromAlias}.${from}`, "=", `${toAlias}.${to}`);
|
|
657
|
-
}
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
else {
|
|
661
|
-
query = query
|
|
662
|
-
// @ts-ignore
|
|
663
|
-
.leftJoin(throughTableWithSchema, function () {
|
|
664
|
-
for (let relationship of relationships) {
|
|
665
|
-
const fromPrimary = relationship.fromPrimary;
|
|
666
|
-
const from = relationship.from;
|
|
667
|
-
// @ts-ignore
|
|
668
|
-
this.orOn(`${fromAlias}.${fromPrimary}`, "=", `${throughAlias}.${from}`);
|
|
669
|
-
}
|
|
670
|
-
})
|
|
671
|
-
.leftJoin(toTableWithSchema, function () {
|
|
672
|
-
for (let relationship of relationships) {
|
|
673
|
-
const toPrimary = relationship.toPrimary;
|
|
674
|
-
const to = relationship.to;
|
|
675
|
-
// @ts-ignore
|
|
676
|
-
this.orOn(`${toAlias}.${toPrimary}`, `${throughAlias}.${to}`);
|
|
677
|
-
}
|
|
678
|
-
});
|
|
679
|
-
}
|
|
886
|
+
query = this.addJoin(query, {
|
|
887
|
+
from: fromTable,
|
|
888
|
+
to: toTable,
|
|
889
|
+
through: throughTable,
|
|
890
|
+
}, relationships);
|
|
680
891
|
}
|
|
681
892
|
return query;
|
|
682
893
|
}
|
|
@@ -766,7 +977,7 @@ class InternalBuilder {
|
|
|
766
977
|
return query.upsert(parsedBody);
|
|
767
978
|
}
|
|
768
979
|
read(opts = {}) {
|
|
769
|
-
let { endpoint, filters, paginate, relationships
|
|
980
|
+
let { endpoint, filters, paginate, relationships } = this.query;
|
|
770
981
|
const { limits } = opts;
|
|
771
982
|
const counting = endpoint.operation === types_1.Operation.COUNT;
|
|
772
983
|
const tableName = endpoint.entityId;
|
|
@@ -799,34 +1010,18 @@ class InternalBuilder {
|
|
|
799
1010
|
if (foundOffset != null) {
|
|
800
1011
|
query = query.offset(foundOffset);
|
|
801
1012
|
}
|
|
802
|
-
// add sorting to pre-query
|
|
803
|
-
// no point in sorting when counting
|
|
804
|
-
query = this.addSorting(query);
|
|
805
1013
|
}
|
|
806
|
-
// add filters to the query (where)
|
|
807
|
-
query = this.addFilters(query, filters);
|
|
808
|
-
const alias = (tableAliases === null || tableAliases === void 0 ? void 0 : tableAliases[tableName]) || tableName;
|
|
809
|
-
let preQuery = this.knex({
|
|
810
|
-
// the typescript definition for the knex constructor doesn't support this
|
|
811
|
-
// syntax, but it is the only way to alias a pre-query result as part of
|
|
812
|
-
// a query - there is an alias dictionary type, but it assumes it can only
|
|
813
|
-
// be a table name, not a pre-query
|
|
814
|
-
[alias]: query,
|
|
815
|
-
});
|
|
816
1014
|
// if counting, use distinct count, else select
|
|
817
|
-
|
|
818
|
-
?
|
|
819
|
-
: this.addDistinctCount(
|
|
1015
|
+
query = !counting
|
|
1016
|
+
? query.select(this.generateSelectStatement())
|
|
1017
|
+
: this.addDistinctCount(query);
|
|
820
1018
|
// have to add after as well (this breaks MS-SQL)
|
|
821
|
-
if (
|
|
822
|
-
|
|
1019
|
+
if (!counting) {
|
|
1020
|
+
query = this.addSorting(query);
|
|
823
1021
|
}
|
|
824
1022
|
// handle joins
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
// if counting we can't set this limit
|
|
828
|
-
if (limits === null || limits === void 0 ? void 0 : limits.base) {
|
|
829
|
-
query = query.limit(limits.base);
|
|
1023
|
+
if (relationships) {
|
|
1024
|
+
query = this.addJsonRelationships(query, tableName, relationships);
|
|
830
1025
|
}
|
|
831
1026
|
return this.addFilters(query, filters, { relationship: true });
|
|
832
1027
|
}
|