@budibase/backend-core 2.31.8 → 2.32.1
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 +420 -186
- 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/cache/user.d.ts +7 -1
- package/dist/src/cache/user.js +4 -3
- package/dist/src/cache/user.js.map +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/index.d.ts +1 -0
- package/dist/src/events/index.js +3 -1
- package/dist/src/events/index.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/middleware/authenticated.js +16 -3
- package/dist/src/middleware/authenticated.js.map +1 -1
- package/dist/src/sql/sql.js +333 -161
- 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/cache/user.ts +17 -6
- package/src/environment.ts +1 -0
- package/src/events/index.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/middleware/authenticated.ts +25 -8
- package/src/sql/sql.ts +460 -224
- 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/src/sql/sql.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
isValidFilter,
|
|
8
8
|
isValidISODateString,
|
|
9
9
|
sqlLog,
|
|
10
|
+
validateManyToMany,
|
|
10
11
|
} from "./utils"
|
|
11
12
|
import SqlTableQueryBuilder from "./sqlTable"
|
|
12
13
|
import {
|
|
@@ -39,6 +40,7 @@ import { dataFilters, helpers } from "@budibase/shared-core"
|
|
|
39
40
|
import { cloneDeep } from "lodash"
|
|
40
41
|
|
|
41
42
|
type QueryFunction = (query: SqlQuery | SqlQuery[], operation: Operation) => any
|
|
43
|
+
const MAX_SQS_RELATIONSHIP_FIELDS = 63
|
|
42
44
|
|
|
43
45
|
function getBaseLimit() {
|
|
44
46
|
const envLimit = environment.SQL_MAX_ROWS
|
|
@@ -47,6 +49,13 @@ function getBaseLimit() {
|
|
|
47
49
|
return envLimit || 5000
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
function getRelationshipLimit() {
|
|
53
|
+
const envLimit = environment.SQL_MAX_RELATED_ROWS
|
|
54
|
+
? parseInt(environment.SQL_MAX_RELATED_ROWS)
|
|
55
|
+
: null
|
|
56
|
+
return envLimit || 500
|
|
57
|
+
}
|
|
58
|
+
|
|
50
59
|
function getTableName(table?: Table): string | undefined {
|
|
51
60
|
// SQS uses the table ID rather than the table name
|
|
52
61
|
if (
|
|
@@ -92,6 +101,23 @@ class InternalBuilder {
|
|
|
92
101
|
})
|
|
93
102
|
}
|
|
94
103
|
|
|
104
|
+
// states the various situations in which we need a full mapped select statement
|
|
105
|
+
private readonly SPECIAL_SELECT_CASES = {
|
|
106
|
+
POSTGRES_MONEY: (field: FieldSchema | undefined) => {
|
|
107
|
+
return (
|
|
108
|
+
this.client === SqlClient.POSTGRES &&
|
|
109
|
+
field?.externalType?.includes("money")
|
|
110
|
+
)
|
|
111
|
+
},
|
|
112
|
+
MSSQL_DATES: (field: FieldSchema | undefined) => {
|
|
113
|
+
return (
|
|
114
|
+
this.client === SqlClient.MS_SQL &&
|
|
115
|
+
field?.type === FieldType.DATETIME &&
|
|
116
|
+
field.timeOnly
|
|
117
|
+
)
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
|
|
95
121
|
get table(): Table {
|
|
96
122
|
return this.query.meta.table
|
|
97
123
|
}
|
|
@@ -125,79 +151,70 @@ class InternalBuilder {
|
|
|
125
151
|
.join(".")
|
|
126
152
|
}
|
|
127
153
|
|
|
154
|
+
private isFullSelectStatementRequired(): boolean {
|
|
155
|
+
const { meta } = this.query
|
|
156
|
+
for (let column of Object.values(meta.table.schema)) {
|
|
157
|
+
if (this.SPECIAL_SELECT_CASES.POSTGRES_MONEY(column)) {
|
|
158
|
+
return true
|
|
159
|
+
} else if (this.SPECIAL_SELECT_CASES.MSSQL_DATES(column)) {
|
|
160
|
+
return true
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return false
|
|
164
|
+
}
|
|
165
|
+
|
|
128
166
|
private generateSelectStatement(): (string | Knex.Raw)[] | "*" {
|
|
129
|
-
const { resource,
|
|
167
|
+
const { meta, endpoint, resource, tableAliases } = this.query
|
|
130
168
|
|
|
131
169
|
if (!resource || !resource.fields || resource.fields.length === 0) {
|
|
132
170
|
return "*"
|
|
133
171
|
}
|
|
134
172
|
|
|
173
|
+
const alias = tableAliases?.[endpoint.entityId]
|
|
174
|
+
? tableAliases?.[endpoint.entityId]
|
|
175
|
+
: endpoint.entityId
|
|
135
176
|
const schema = meta.table.schema
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
// A link doc, e.g.: "table.doc1.fieldName"
|
|
153
|
-
if (parts.length > 2) {
|
|
154
|
-
table = parts[0]
|
|
155
|
-
column = parts.slice(1).join(".")
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (!column) {
|
|
159
|
-
throw new Error(`Invalid field name: ${field}`)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const columnSchema = schema[column]
|
|
177
|
+
if (!this.isFullSelectStatementRequired()) {
|
|
178
|
+
return [this.knex.raw(`${this.quote(alias)}.*`)]
|
|
179
|
+
}
|
|
180
|
+
// get just the fields for this table
|
|
181
|
+
return resource.fields
|
|
182
|
+
.map(field => {
|
|
183
|
+
const parts = field.split(/\./g)
|
|
184
|
+
let table: string | undefined = undefined
|
|
185
|
+
let column = parts[0]
|
|
186
|
+
|
|
187
|
+
// Just a column name, e.g.: "column"
|
|
188
|
+
if (parts.length > 1) {
|
|
189
|
+
table = parts[0]
|
|
190
|
+
column = parts.slice(1).join(".")
|
|
191
|
+
}
|
|
163
192
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
193
|
+
return { table, column, field }
|
|
194
|
+
})
|
|
195
|
+
.filter(({ table }) => !table || table === alias)
|
|
196
|
+
.map(({ table, column, field }) => {
|
|
197
|
+
const columnSchema = schema[column]
|
|
198
|
+
|
|
199
|
+
if (this.SPECIAL_SELECT_CASES.POSTGRES_MONEY(columnSchema)) {
|
|
200
|
+
return this.knex.raw(
|
|
201
|
+
`${this.quotedIdentifier(
|
|
202
|
+
[table, column].join(".")
|
|
203
|
+
)}::money::numeric as ${this.quote(field)}`
|
|
204
|
+
)
|
|
205
|
+
}
|
|
174
206
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
// Time gets returned as timestamp from mssql, not matching the expected
|
|
181
|
-
// HH:mm format
|
|
182
|
-
return this.knex.raw(`CONVERT(varchar, ${field}, 108) as "${field}"`)
|
|
183
|
-
}
|
|
207
|
+
if (this.SPECIAL_SELECT_CASES.MSSQL_DATES(columnSchema)) {
|
|
208
|
+
// Time gets returned as timestamp from mssql, not matching the expected
|
|
209
|
+
// HH:mm format
|
|
210
|
+
return this.knex.raw(`CONVERT(varchar, ${field}, 108) as "${field}"`)
|
|
211
|
+
}
|
|
184
212
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// case, we want to split it into `table`.`doc1.column` for reasons that
|
|
191
|
-
// aren't actually clear to me, but `table`.`doc1` breaks things with the
|
|
192
|
-
// sample data tests.
|
|
193
|
-
if (table) {
|
|
194
|
-
return this.knex.raw(
|
|
195
|
-
`${this.quote(table)}.${this.quote(column)} as ${this.quote(field)}`
|
|
196
|
-
)
|
|
197
|
-
} else {
|
|
198
|
-
return this.knex.raw(`${this.quote(field)} as ${this.quote(field)}`)
|
|
199
|
-
}
|
|
200
|
-
})
|
|
213
|
+
const quoted = table
|
|
214
|
+
? `${this.quote(table)}.${this.quote(column)}`
|
|
215
|
+
: this.quote(field)
|
|
216
|
+
return this.knex.raw(quoted)
|
|
217
|
+
})
|
|
201
218
|
}
|
|
202
219
|
|
|
203
220
|
// OracleDB can't use character-large-objects (CLOBs) in WHERE clauses,
|
|
@@ -328,6 +345,85 @@ class InternalBuilder {
|
|
|
328
345
|
return filters
|
|
329
346
|
}
|
|
330
347
|
|
|
348
|
+
addJoinFieldCheck(query: Knex.QueryBuilder, relationship: RelationshipsJson) {
|
|
349
|
+
const document = relationship.from?.split(".")[0] || ""
|
|
350
|
+
return query.andWhere(`${document}.fieldName`, "=", relationship.column)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
addRelationshipForFilter(
|
|
354
|
+
query: Knex.QueryBuilder,
|
|
355
|
+
filterKey: string,
|
|
356
|
+
whereCb: (query: Knex.QueryBuilder) => Knex.QueryBuilder
|
|
357
|
+
): Knex.QueryBuilder {
|
|
358
|
+
const mainKnex = this.knex
|
|
359
|
+
const { relationships, endpoint, tableAliases: aliases } = this.query
|
|
360
|
+
const tableName = endpoint.entityId
|
|
361
|
+
const fromAlias = aliases?.[tableName] || tableName
|
|
362
|
+
const matches = (possibleTable: string) =>
|
|
363
|
+
filterKey.startsWith(`${possibleTable}`)
|
|
364
|
+
if (!relationships) {
|
|
365
|
+
return query
|
|
366
|
+
}
|
|
367
|
+
for (const relationship of relationships) {
|
|
368
|
+
const relatedTableName = relationship.tableName
|
|
369
|
+
const toAlias = aliases?.[relatedTableName] || relatedTableName
|
|
370
|
+
// this is the relationship which is being filtered
|
|
371
|
+
if (
|
|
372
|
+
(matches(relatedTableName) || matches(toAlias)) &&
|
|
373
|
+
relationship.to &&
|
|
374
|
+
relationship.tableName
|
|
375
|
+
) {
|
|
376
|
+
let subQuery = mainKnex
|
|
377
|
+
.select(mainKnex.raw(1))
|
|
378
|
+
.from({ [toAlias]: relatedTableName })
|
|
379
|
+
const manyToMany = validateManyToMany(relationship)
|
|
380
|
+
if (manyToMany) {
|
|
381
|
+
const throughAlias =
|
|
382
|
+
aliases?.[manyToMany.through] || relationship.through
|
|
383
|
+
let throughTable = this.tableNameWithSchema(manyToMany.through, {
|
|
384
|
+
alias: throughAlias,
|
|
385
|
+
schema: endpoint.schema,
|
|
386
|
+
})
|
|
387
|
+
subQuery = subQuery
|
|
388
|
+
// add a join through the junction table
|
|
389
|
+
.innerJoin(throughTable, function () {
|
|
390
|
+
// @ts-ignore
|
|
391
|
+
this.on(
|
|
392
|
+
`${toAlias}.${manyToMany.toPrimary}`,
|
|
393
|
+
"=",
|
|
394
|
+
`${throughAlias}.${manyToMany.to}`
|
|
395
|
+
)
|
|
396
|
+
})
|
|
397
|
+
// check the document in the junction table points to the main table
|
|
398
|
+
.where(
|
|
399
|
+
`${throughAlias}.${manyToMany.from}`,
|
|
400
|
+
"=",
|
|
401
|
+
mainKnex.raw(
|
|
402
|
+
this.quotedIdentifier(`${fromAlias}.${manyToMany.fromPrimary}`)
|
|
403
|
+
)
|
|
404
|
+
)
|
|
405
|
+
// in SQS the same junction table is used for different many-to-many relationships between the
|
|
406
|
+
// two same tables, this is needed to avoid rows ending up in all columns
|
|
407
|
+
if (this.client === SqlClient.SQL_LITE) {
|
|
408
|
+
subQuery = this.addJoinFieldCheck(subQuery, manyToMany)
|
|
409
|
+
}
|
|
410
|
+
} else {
|
|
411
|
+
// "join" to the main table, making sure the ID matches that of the main
|
|
412
|
+
subQuery = subQuery.where(
|
|
413
|
+
`${toAlias}.${relationship.to}`,
|
|
414
|
+
"=",
|
|
415
|
+
mainKnex.raw(
|
|
416
|
+
this.quotedIdentifier(`${fromAlias}.${relationship.from}`)
|
|
417
|
+
)
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
query = query.whereExists(whereCb(subQuery))
|
|
421
|
+
break
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return query
|
|
425
|
+
}
|
|
426
|
+
|
|
331
427
|
// right now we only do filters on the specific table being queried
|
|
332
428
|
addFilters(
|
|
333
429
|
query: Knex.QueryBuilder,
|
|
@@ -339,12 +435,13 @@ class InternalBuilder {
|
|
|
339
435
|
if (!filters) {
|
|
340
436
|
return query
|
|
341
437
|
}
|
|
438
|
+
const builder = this
|
|
342
439
|
filters = this.parseFilters({ ...filters })
|
|
343
440
|
const aliases = this.query.tableAliases
|
|
344
441
|
// if all or specified in filters, then everything is an or
|
|
345
442
|
const allOr = filters.allOr
|
|
346
|
-
const
|
|
347
|
-
|
|
443
|
+
const isSqlite = this.client === SqlClient.SQL_LITE
|
|
444
|
+
const tableName = isSqlite ? this.table._id! : this.table.name
|
|
348
445
|
|
|
349
446
|
function getTableAlias(name: string) {
|
|
350
447
|
const alias = aliases?.[name]
|
|
@@ -352,13 +449,33 @@ class InternalBuilder {
|
|
|
352
449
|
}
|
|
353
450
|
function iterate(
|
|
354
451
|
structure: AnySearchFilter,
|
|
355
|
-
fn: (
|
|
356
|
-
|
|
452
|
+
fn: (
|
|
453
|
+
query: Knex.QueryBuilder,
|
|
454
|
+
key: string,
|
|
455
|
+
value: any
|
|
456
|
+
) => Knex.QueryBuilder,
|
|
457
|
+
complexKeyFn?: (
|
|
458
|
+
query: Knex.QueryBuilder,
|
|
459
|
+
key: string[],
|
|
460
|
+
value: any
|
|
461
|
+
) => Knex.QueryBuilder
|
|
357
462
|
) {
|
|
463
|
+
const handleRelationship = (
|
|
464
|
+
q: Knex.QueryBuilder,
|
|
465
|
+
key: string,
|
|
466
|
+
value: any
|
|
467
|
+
) => {
|
|
468
|
+
const [filterTableName, ...otherProperties] = key.split(".")
|
|
469
|
+
const property = otherProperties.join(".")
|
|
470
|
+
const alias = getTableAlias(filterTableName)
|
|
471
|
+
return fn(q, alias ? `${alias}.${property}` : property, value)
|
|
472
|
+
}
|
|
358
473
|
for (const key in structure) {
|
|
359
474
|
const value = structure[key]
|
|
360
475
|
const updatedKey = dbCore.removeKeyNumbering(key)
|
|
361
476
|
const isRelationshipField = updatedKey.includes(".")
|
|
477
|
+
const shouldProcessRelationship =
|
|
478
|
+
opts?.relationship && isRelationshipField
|
|
362
479
|
|
|
363
480
|
let castedTypeValue
|
|
364
481
|
if (
|
|
@@ -367,7 +484,8 @@ class InternalBuilder {
|
|
|
367
484
|
complexKeyFn
|
|
368
485
|
) {
|
|
369
486
|
const alias = getTableAlias(tableName)
|
|
370
|
-
complexKeyFn(
|
|
487
|
+
query = complexKeyFn(
|
|
488
|
+
query,
|
|
371
489
|
castedTypeValue.id.map((x: string) =>
|
|
372
490
|
alias ? `${alias}.${x}` : x
|
|
373
491
|
),
|
|
@@ -375,26 +493,29 @@ class InternalBuilder {
|
|
|
375
493
|
)
|
|
376
494
|
} else if (!isRelationshipField) {
|
|
377
495
|
const alias = getTableAlias(tableName)
|
|
378
|
-
fn(
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
496
|
+
query = fn(
|
|
497
|
+
query,
|
|
498
|
+
alias ? `${alias}.${updatedKey}` : updatedKey,
|
|
499
|
+
value
|
|
500
|
+
)
|
|
501
|
+
} else if (shouldProcessRelationship) {
|
|
502
|
+
query = builder.addRelationshipForFilter(query, updatedKey, q => {
|
|
503
|
+
return handleRelationship(q, updatedKey, value)
|
|
504
|
+
})
|
|
384
505
|
}
|
|
385
506
|
}
|
|
386
507
|
}
|
|
387
508
|
|
|
388
|
-
const like = (key: string, value: any) => {
|
|
509
|
+
const like = (q: Knex.QueryBuilder, key: string, value: any) => {
|
|
389
510
|
const fuzzyOr = filters?.fuzzyOr
|
|
390
511
|
const fnc = fuzzyOr || allOr ? "orWhere" : "where"
|
|
391
512
|
// postgres supports ilike, nothing else does
|
|
392
513
|
if (this.client === SqlClient.POSTGRES) {
|
|
393
|
-
|
|
514
|
+
return q[fnc](key, "ilike", `%${value}%`)
|
|
394
515
|
} else {
|
|
395
516
|
const rawFnc = `${fnc}Raw`
|
|
396
517
|
// @ts-ignore
|
|
397
|
-
|
|
518
|
+
return q[rawFnc](`LOWER(${this.quotedIdentifier(key)}) LIKE ?`, [
|
|
398
519
|
`%${value.toLowerCase()}%`,
|
|
399
520
|
])
|
|
400
521
|
}
|
|
@@ -412,13 +533,13 @@ class InternalBuilder {
|
|
|
412
533
|
return `[${value.join(",")}]`
|
|
413
534
|
}
|
|
414
535
|
if (this.client === SqlClient.POSTGRES) {
|
|
415
|
-
iterate(mode, (key, value) => {
|
|
536
|
+
iterate(mode, (q, key, value) => {
|
|
416
537
|
const wrap = any ? "" : "'"
|
|
417
538
|
const op = any ? "\\?| array" : "@>"
|
|
418
539
|
const fieldNames = key.split(/\./g)
|
|
419
540
|
const table = fieldNames[0]
|
|
420
541
|
const col = fieldNames[1]
|
|
421
|
-
|
|
542
|
+
return q[rawFnc](
|
|
422
543
|
`${not}COALESCE("${table}"."${col}"::jsonb ${op} ${wrap}${stringifyArray(
|
|
423
544
|
value,
|
|
424
545
|
any ? "'" : '"'
|
|
@@ -427,8 +548,8 @@ class InternalBuilder {
|
|
|
427
548
|
})
|
|
428
549
|
} else if (this.client === SqlClient.MY_SQL) {
|
|
429
550
|
const jsonFnc = any ? "JSON_OVERLAPS" : "JSON_CONTAINS"
|
|
430
|
-
iterate(mode, (key, value) => {
|
|
431
|
-
|
|
551
|
+
iterate(mode, (q, key, value) => {
|
|
552
|
+
return q[rawFnc](
|
|
432
553
|
`${not}COALESCE(${jsonFnc}(${key}, '${stringifyArray(
|
|
433
554
|
value
|
|
434
555
|
)}'), FALSE)`
|
|
@@ -436,7 +557,7 @@ class InternalBuilder {
|
|
|
436
557
|
})
|
|
437
558
|
} else {
|
|
438
559
|
const andOr = mode === filters?.containsAny ? " OR " : " AND "
|
|
439
|
-
iterate(mode, (key, value) => {
|
|
560
|
+
iterate(mode, (q, key, value) => {
|
|
440
561
|
let statement = ""
|
|
441
562
|
const identifier = this.quotedIdentifier(key)
|
|
442
563
|
for (let i in value) {
|
|
@@ -451,16 +572,16 @@ class InternalBuilder {
|
|
|
451
572
|
}
|
|
452
573
|
|
|
453
574
|
if (statement === "") {
|
|
454
|
-
return
|
|
575
|
+
return q
|
|
455
576
|
}
|
|
456
577
|
|
|
457
578
|
if (not) {
|
|
458
|
-
|
|
579
|
+
return q[rawFnc](
|
|
459
580
|
`(NOT (${statement}) OR ${identifier} IS NULL)`,
|
|
460
581
|
value
|
|
461
582
|
)
|
|
462
583
|
} else {
|
|
463
|
-
|
|
584
|
+
return q[rawFnc](statement, value)
|
|
464
585
|
}
|
|
465
586
|
})
|
|
466
587
|
}
|
|
@@ -490,39 +611,39 @@ class InternalBuilder {
|
|
|
490
611
|
const fnc = allOr ? "orWhereIn" : "whereIn"
|
|
491
612
|
iterate(
|
|
492
613
|
filters.oneOf,
|
|
493
|
-
(key: string, array) => {
|
|
614
|
+
(q, key: string, array) => {
|
|
494
615
|
if (this.client === SqlClient.ORACLE) {
|
|
495
616
|
key = this.convertClobs(key)
|
|
496
617
|
array = Array.isArray(array) ? array : [array]
|
|
497
618
|
const binding = new Array(array.length).fill("?").join(",")
|
|
498
|
-
|
|
619
|
+
return q.whereRaw(`${key} IN (${binding})`, array)
|
|
499
620
|
} else {
|
|
500
|
-
|
|
621
|
+
return q[fnc](key, Array.isArray(array) ? array : [array])
|
|
501
622
|
}
|
|
502
623
|
},
|
|
503
|
-
(key: string[], array) => {
|
|
624
|
+
(q, key: string[], array) => {
|
|
504
625
|
if (this.client === SqlClient.ORACLE) {
|
|
505
626
|
const keyStr = `(${key.map(k => this.convertClobs(k)).join(",")})`
|
|
506
627
|
const binding = `(${array
|
|
507
628
|
.map((a: any) => `(${new Array(a.length).fill("?").join(",")})`)
|
|
508
629
|
.join(",")})`
|
|
509
|
-
|
|
630
|
+
return q.whereRaw(`${keyStr} IN ${binding}`, array.flat())
|
|
510
631
|
} else {
|
|
511
|
-
|
|
632
|
+
return q[fnc](key, Array.isArray(array) ? array : [array])
|
|
512
633
|
}
|
|
513
634
|
}
|
|
514
635
|
)
|
|
515
636
|
}
|
|
516
637
|
if (filters.string) {
|
|
517
|
-
iterate(filters.string, (key, value) => {
|
|
638
|
+
iterate(filters.string, (q, key, value) => {
|
|
518
639
|
const fnc = allOr ? "orWhere" : "where"
|
|
519
640
|
// postgres supports ilike, nothing else does
|
|
520
641
|
if (this.client === SqlClient.POSTGRES) {
|
|
521
|
-
|
|
642
|
+
return q[fnc](key, "ilike", `${value}%`)
|
|
522
643
|
} else {
|
|
523
644
|
const rawFnc = `${fnc}Raw`
|
|
524
645
|
// @ts-ignore
|
|
525
|
-
|
|
646
|
+
return q[rawFnc](`LOWER(${this.quotedIdentifier(key)}) LIKE ?`, [
|
|
526
647
|
`${value.toLowerCase()}%`,
|
|
527
648
|
])
|
|
528
649
|
}
|
|
@@ -532,7 +653,7 @@ class InternalBuilder {
|
|
|
532
653
|
iterate(filters.fuzzy, like)
|
|
533
654
|
}
|
|
534
655
|
if (filters.range) {
|
|
535
|
-
iterate(filters.range, (key, value) => {
|
|
656
|
+
iterate(filters.range, (q, key, value) => {
|
|
536
657
|
const isEmptyObject = (val: any) => {
|
|
537
658
|
return (
|
|
538
659
|
val &&
|
|
@@ -561,97 +682,93 @@ class InternalBuilder {
|
|
|
561
682
|
schema?.type === FieldType.BIGINT &&
|
|
562
683
|
this.client === SqlClient.SQL_LITE
|
|
563
684
|
) {
|
|
564
|
-
|
|
685
|
+
return q.whereRaw(
|
|
565
686
|
`CAST(${key} AS INTEGER) BETWEEN CAST(? AS INTEGER) AND CAST(? AS INTEGER)`,
|
|
566
687
|
[value.low, value.high]
|
|
567
688
|
)
|
|
568
689
|
} else {
|
|
569
690
|
const fnc = allOr ? "orWhereBetween" : "whereBetween"
|
|
570
|
-
|
|
691
|
+
return q[fnc](key, [value.low, value.high])
|
|
571
692
|
}
|
|
572
693
|
} else if (lowValid) {
|
|
573
694
|
if (
|
|
574
695
|
schema?.type === FieldType.BIGINT &&
|
|
575
696
|
this.client === SqlClient.SQL_LITE
|
|
576
697
|
) {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
)
|
|
698
|
+
return q.whereRaw(`CAST(${key} AS INTEGER) >= CAST(? AS INTEGER)`, [
|
|
699
|
+
value.low,
|
|
700
|
+
])
|
|
581
701
|
} else {
|
|
582
702
|
const fnc = allOr ? "orWhere" : "where"
|
|
583
|
-
|
|
703
|
+
return q[fnc](key, ">=", value.low)
|
|
584
704
|
}
|
|
585
705
|
} else if (highValid) {
|
|
586
706
|
if (
|
|
587
707
|
schema?.type === FieldType.BIGINT &&
|
|
588
708
|
this.client === SqlClient.SQL_LITE
|
|
589
709
|
) {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
)
|
|
710
|
+
return q.whereRaw(`CAST(${key} AS INTEGER) <= CAST(? AS INTEGER)`, [
|
|
711
|
+
value.high,
|
|
712
|
+
])
|
|
594
713
|
} else {
|
|
595
714
|
const fnc = allOr ? "orWhere" : "where"
|
|
596
|
-
|
|
715
|
+
return q[fnc](key, "<=", value.high)
|
|
597
716
|
}
|
|
598
717
|
}
|
|
718
|
+
return q
|
|
599
719
|
})
|
|
600
720
|
}
|
|
601
721
|
if (filters.equal) {
|
|
602
|
-
iterate(filters.equal, (key, value) => {
|
|
722
|
+
iterate(filters.equal, (q, key, value) => {
|
|
603
723
|
const fnc = allOr ? "orWhereRaw" : "whereRaw"
|
|
604
724
|
if (this.client === SqlClient.MS_SQL) {
|
|
605
|
-
|
|
725
|
+
return q[fnc](
|
|
606
726
|
`CASE WHEN ${this.quotedIdentifier(key)} = ? THEN 1 ELSE 0 END = 1`,
|
|
607
727
|
[value]
|
|
608
728
|
)
|
|
609
729
|
} else if (this.client === SqlClient.ORACLE) {
|
|
610
730
|
const identifier = this.convertClobs(key)
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
)
|
|
731
|
+
return q[fnc](`(${identifier} IS NOT NULL AND ${identifier} = ?)`, [
|
|
732
|
+
value,
|
|
733
|
+
])
|
|
615
734
|
} else {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
)
|
|
735
|
+
return q[fnc](`COALESCE(${this.quotedIdentifier(key)} = ?, FALSE)`, [
|
|
736
|
+
value,
|
|
737
|
+
])
|
|
620
738
|
}
|
|
621
739
|
})
|
|
622
740
|
}
|
|
623
741
|
if (filters.notEqual) {
|
|
624
|
-
iterate(filters.notEqual, (key, value) => {
|
|
742
|
+
iterate(filters.notEqual, (q, key, value) => {
|
|
625
743
|
const fnc = allOr ? "orWhereRaw" : "whereRaw"
|
|
626
744
|
if (this.client === SqlClient.MS_SQL) {
|
|
627
|
-
|
|
745
|
+
return q[fnc](
|
|
628
746
|
`CASE WHEN ${this.quotedIdentifier(key)} = ? THEN 1 ELSE 0 END = 0`,
|
|
629
747
|
[value]
|
|
630
748
|
)
|
|
631
749
|
} else if (this.client === SqlClient.ORACLE) {
|
|
632
750
|
const identifier = this.convertClobs(key)
|
|
633
|
-
|
|
751
|
+
return q[fnc](
|
|
634
752
|
`(${identifier} IS NOT NULL AND ${identifier} != ?) OR ${identifier} IS NULL`,
|
|
635
753
|
[value]
|
|
636
754
|
)
|
|
637
755
|
} else {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
)
|
|
756
|
+
return q[fnc](`COALESCE(${this.quotedIdentifier(key)} != ?, TRUE)`, [
|
|
757
|
+
value,
|
|
758
|
+
])
|
|
642
759
|
}
|
|
643
760
|
})
|
|
644
761
|
}
|
|
645
762
|
if (filters.empty) {
|
|
646
|
-
iterate(filters.empty, key => {
|
|
763
|
+
iterate(filters.empty, (q, key) => {
|
|
647
764
|
const fnc = allOr ? "orWhereNull" : "whereNull"
|
|
648
|
-
|
|
765
|
+
return q[fnc](key)
|
|
649
766
|
})
|
|
650
767
|
}
|
|
651
768
|
if (filters.notEmpty) {
|
|
652
|
-
iterate(filters.notEmpty, key => {
|
|
769
|
+
iterate(filters.notEmpty, (q, key) => {
|
|
653
770
|
const fnc = allOr ? "orWhereNotNull" : "whereNotNull"
|
|
654
|
-
|
|
771
|
+
return q[fnc](key)
|
|
655
772
|
})
|
|
656
773
|
}
|
|
657
774
|
if (filters.contains) {
|
|
@@ -745,80 +862,207 @@ class InternalBuilder {
|
|
|
745
862
|
return withSchema
|
|
746
863
|
}
|
|
747
864
|
|
|
748
|
-
|
|
865
|
+
private buildJsonField(field: string): string {
|
|
866
|
+
const parts = field.split(".")
|
|
867
|
+
let tableField: string, unaliased: string
|
|
868
|
+
if (parts.length > 1) {
|
|
869
|
+
const alias = parts.shift()!
|
|
870
|
+
unaliased = parts.join(".")
|
|
871
|
+
tableField = `${this.quote(alias)}.${this.quote(unaliased)}`
|
|
872
|
+
} else {
|
|
873
|
+
unaliased = parts.join(".")
|
|
874
|
+
tableField = this.quote(unaliased)
|
|
875
|
+
}
|
|
876
|
+
const separator = this.client === SqlClient.ORACLE ? " VALUE " : ","
|
|
877
|
+
return `'${unaliased}'${separator}${tableField}`
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
addJsonRelationships(
|
|
749
881
|
query: Knex.QueryBuilder,
|
|
750
882
|
fromTable: string,
|
|
751
|
-
relationships: RelationshipsJson[]
|
|
752
|
-
schema: string | undefined,
|
|
753
|
-
aliases?: Record<string, string>
|
|
883
|
+
relationships: RelationshipsJson[]
|
|
754
884
|
): Knex.QueryBuilder {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
}
|
|
758
|
-
const
|
|
759
|
-
// aggregate into table sets (all the same to tables)
|
|
885
|
+
const sqlClient = this.client
|
|
886
|
+
const knex = this.knex
|
|
887
|
+
const { resource, tableAliases: aliases, endpoint } = this.query
|
|
888
|
+
const fields = resource?.fields || []
|
|
760
889
|
for (let relationship of relationships) {
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
tableSets[key] = [relationship]
|
|
890
|
+
const {
|
|
891
|
+
tableName: toTable,
|
|
892
|
+
through: throughTable,
|
|
893
|
+
to: toKey,
|
|
894
|
+
from: fromKey,
|
|
895
|
+
fromPrimary,
|
|
896
|
+
toPrimary,
|
|
897
|
+
} = relationship
|
|
898
|
+
// skip invalid relationships
|
|
899
|
+
if (!toTable || !fromTable) {
|
|
900
|
+
continue
|
|
773
901
|
}
|
|
774
|
-
}
|
|
775
|
-
for (let [key, relationships] of Object.entries(tableSets)) {
|
|
776
|
-
const { toTable, throughTable } = JSON.parse(key)
|
|
777
902
|
const toAlias = aliases?.[toTable] || toTable,
|
|
778
|
-
throughAlias = aliases?.[throughTable] || throughTable,
|
|
779
903
|
fromAlias = aliases?.[fromTable] || fromTable
|
|
780
904
|
let toTableWithSchema = this.tableNameWithSchema(toTable, {
|
|
781
905
|
alias: toAlias,
|
|
782
|
-
schema,
|
|
906
|
+
schema: endpoint.schema,
|
|
783
907
|
})
|
|
784
|
-
let
|
|
785
|
-
|
|
786
|
-
|
|
908
|
+
let relationshipFields = fields.filter(
|
|
909
|
+
field => field.split(".")[0] === toAlias
|
|
910
|
+
)
|
|
911
|
+
if (this.client === SqlClient.SQL_LITE) {
|
|
912
|
+
relationshipFields = relationshipFields.slice(
|
|
913
|
+
0,
|
|
914
|
+
MAX_SQS_RELATIONSHIP_FIELDS
|
|
915
|
+
)
|
|
916
|
+
}
|
|
917
|
+
const fieldList: string = relationshipFields
|
|
918
|
+
.map(field => this.buildJsonField(field))
|
|
919
|
+
.join(",")
|
|
920
|
+
// SQL Server uses TOP - which performs a little differently to the normal LIMIT syntax
|
|
921
|
+
// it reduces the result set rather than limiting how much data it filters over
|
|
922
|
+
const primaryKey = `${toAlias}.${toPrimary || toKey}`
|
|
923
|
+
let subQuery: Knex.QueryBuilder = knex
|
|
924
|
+
.from(toTableWithSchema)
|
|
925
|
+
.limit(getRelationshipLimit())
|
|
926
|
+
// add sorting to get consistent order
|
|
927
|
+
.orderBy(primaryKey)
|
|
928
|
+
|
|
929
|
+
// many-to-many relationship with junction table
|
|
930
|
+
if (throughTable && toPrimary && fromPrimary) {
|
|
931
|
+
const throughAlias = aliases?.[throughTable] || throughTable
|
|
932
|
+
let throughTableWithSchema = this.tableNameWithSchema(throughTable, {
|
|
933
|
+
alias: throughAlias,
|
|
934
|
+
schema: endpoint.schema,
|
|
935
|
+
})
|
|
936
|
+
subQuery = subQuery
|
|
937
|
+
.join(throughTableWithSchema, function () {
|
|
938
|
+
this.on(`${toAlias}.${toPrimary}`, "=", `${throughAlias}.${toKey}`)
|
|
939
|
+
})
|
|
940
|
+
.where(
|
|
941
|
+
`${throughAlias}.${fromKey}`,
|
|
942
|
+
"=",
|
|
943
|
+
knex.raw(this.quotedIdentifier(`${fromAlias}.${fromPrimary}`))
|
|
944
|
+
)
|
|
945
|
+
}
|
|
946
|
+
// one-to-many relationship with foreign key
|
|
947
|
+
else {
|
|
948
|
+
subQuery = subQuery.where(
|
|
949
|
+
`${toAlias}.${toKey}`,
|
|
950
|
+
"=",
|
|
951
|
+
knex.raw(this.quotedIdentifier(`${fromAlias}.${fromKey}`))
|
|
952
|
+
)
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
const standardWrap = (select: string): Knex.QueryBuilder => {
|
|
956
|
+
subQuery = subQuery.select(`${toAlias}.*`)
|
|
957
|
+
// @ts-ignore - the from alias syntax isn't in Knex typing
|
|
958
|
+
return knex.select(knex.raw(select)).from({
|
|
959
|
+
[toAlias]: subQuery,
|
|
960
|
+
})
|
|
961
|
+
}
|
|
962
|
+
let wrapperQuery: Knex.QueryBuilder | Knex.Raw
|
|
963
|
+
switch (sqlClient) {
|
|
964
|
+
case SqlClient.SQL_LITE:
|
|
965
|
+
// need to check the junction table document is to the right column, this is just for SQS
|
|
966
|
+
subQuery = this.addJoinFieldCheck(subQuery, relationship)
|
|
967
|
+
wrapperQuery = standardWrap(
|
|
968
|
+
`json_group_array(json_object(${fieldList}))`
|
|
969
|
+
)
|
|
970
|
+
break
|
|
971
|
+
case SqlClient.POSTGRES:
|
|
972
|
+
wrapperQuery = standardWrap(
|
|
973
|
+
`json_agg(json_build_object(${fieldList}))`
|
|
974
|
+
)
|
|
975
|
+
break
|
|
976
|
+
case SqlClient.MY_SQL:
|
|
977
|
+
wrapperQuery = subQuery.select(
|
|
978
|
+
knex.raw(`json_arrayagg(json_object(${fieldList}))`)
|
|
979
|
+
)
|
|
980
|
+
break
|
|
981
|
+
case SqlClient.ORACLE:
|
|
982
|
+
wrapperQuery = standardWrap(
|
|
983
|
+
`json_arrayagg(json_object(${fieldList}))`
|
|
984
|
+
)
|
|
985
|
+
break
|
|
986
|
+
case SqlClient.MS_SQL:
|
|
987
|
+
wrapperQuery = knex.raw(
|
|
988
|
+
`(SELECT ${this.quote(toAlias)} = (${knex
|
|
989
|
+
.select(`${fromAlias}.*`)
|
|
990
|
+
// @ts-ignore - from alias syntax not TS supported
|
|
991
|
+
.from({
|
|
992
|
+
[fromAlias]: subQuery.select(`${toAlias}.*`),
|
|
993
|
+
})} FOR JSON PATH))`
|
|
994
|
+
)
|
|
995
|
+
break
|
|
996
|
+
default:
|
|
997
|
+
throw new Error(`JSON relationships not implement for ${sqlClient}`)
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
query = query.select({ [relationship.column]: wrapperQuery })
|
|
1001
|
+
}
|
|
1002
|
+
return query
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
addJoin(
|
|
1006
|
+
query: Knex.QueryBuilder,
|
|
1007
|
+
tables: { from: string; to: string; through?: string },
|
|
1008
|
+
columns: {
|
|
1009
|
+
from?: string
|
|
1010
|
+
to?: string
|
|
1011
|
+
fromPrimary?: string
|
|
1012
|
+
toPrimary?: string
|
|
1013
|
+
}[]
|
|
1014
|
+
): Knex.QueryBuilder {
|
|
1015
|
+
const { tableAliases: aliases, endpoint } = this.query
|
|
1016
|
+
const schema = endpoint.schema
|
|
1017
|
+
const toTable = tables.to,
|
|
1018
|
+
fromTable = tables.from,
|
|
1019
|
+
throughTable = tables.through
|
|
1020
|
+
const toAlias = aliases?.[toTable] || toTable,
|
|
1021
|
+
throughAlias = (throughTable && aliases?.[throughTable]) || throughTable,
|
|
1022
|
+
fromAlias = aliases?.[fromTable] || fromTable
|
|
1023
|
+
let toTableWithSchema = this.tableNameWithSchema(toTable, {
|
|
1024
|
+
alias: toAlias,
|
|
1025
|
+
schema,
|
|
1026
|
+
})
|
|
1027
|
+
let throughTableWithSchema = throughTable
|
|
1028
|
+
? this.tableNameWithSchema(throughTable, {
|
|
1029
|
+
alias: throughAlias,
|
|
1030
|
+
schema,
|
|
1031
|
+
})
|
|
1032
|
+
: undefined
|
|
1033
|
+
if (!throughTable) {
|
|
1034
|
+
// @ts-ignore
|
|
1035
|
+
query = query.leftJoin(toTableWithSchema, function () {
|
|
1036
|
+
for (let relationship of columns) {
|
|
1037
|
+
const from = relationship.from,
|
|
1038
|
+
to = relationship.to
|
|
1039
|
+
// @ts-ignore
|
|
1040
|
+
this.orOn(`${fromAlias}.${from}`, "=", `${toAlias}.${to}`)
|
|
1041
|
+
}
|
|
787
1042
|
})
|
|
788
|
-
|
|
1043
|
+
} else {
|
|
1044
|
+
query = query
|
|
789
1045
|
// @ts-ignore
|
|
790
|
-
|
|
791
|
-
for (let relationship of
|
|
792
|
-
const
|
|
793
|
-
|
|
1046
|
+
.leftJoin(throughTableWithSchema, function () {
|
|
1047
|
+
for (let relationship of columns) {
|
|
1048
|
+
const fromPrimary = relationship.fromPrimary
|
|
1049
|
+
const from = relationship.from
|
|
794
1050
|
// @ts-ignore
|
|
795
|
-
this.orOn(
|
|
1051
|
+
this.orOn(
|
|
1052
|
+
`${fromAlias}.${fromPrimary}`,
|
|
1053
|
+
"=",
|
|
1054
|
+
`${throughAlias}.${from}`
|
|
1055
|
+
)
|
|
1056
|
+
}
|
|
1057
|
+
})
|
|
1058
|
+
.leftJoin(toTableWithSchema, function () {
|
|
1059
|
+
for (let relationship of columns) {
|
|
1060
|
+
const toPrimary = relationship.toPrimary
|
|
1061
|
+
const to = relationship.to
|
|
1062
|
+
// @ts-ignore
|
|
1063
|
+
this.orOn(`${toAlias}.${toPrimary}`, `${throughAlias}.${to}`)
|
|
796
1064
|
}
|
|
797
1065
|
})
|
|
798
|
-
} else {
|
|
799
|
-
query = query
|
|
800
|
-
// @ts-ignore
|
|
801
|
-
.leftJoin(throughTableWithSchema, function () {
|
|
802
|
-
for (let relationship of relationships) {
|
|
803
|
-
const fromPrimary = relationship.fromPrimary
|
|
804
|
-
const from = relationship.from
|
|
805
|
-
// @ts-ignore
|
|
806
|
-
this.orOn(
|
|
807
|
-
`${fromAlias}.${fromPrimary}`,
|
|
808
|
-
"=",
|
|
809
|
-
`${throughAlias}.${from}`
|
|
810
|
-
)
|
|
811
|
-
}
|
|
812
|
-
})
|
|
813
|
-
.leftJoin(toTableWithSchema, function () {
|
|
814
|
-
for (let relationship of relationships) {
|
|
815
|
-
const toPrimary = relationship.toPrimary
|
|
816
|
-
const to = relationship.to
|
|
817
|
-
// @ts-ignore
|
|
818
|
-
this.orOn(`${toAlias}.${toPrimary}`, `${throughAlias}.${to}`)
|
|
819
|
-
}
|
|
820
|
-
})
|
|
821
|
-
}
|
|
822
1066
|
}
|
|
823
1067
|
return query
|
|
824
1068
|
}
|
|
@@ -906,8 +1150,7 @@ class InternalBuilder {
|
|
|
906
1150
|
if (!primary) {
|
|
907
1151
|
throw new Error("Primary key is required for upsert")
|
|
908
1152
|
}
|
|
909
|
-
|
|
910
|
-
return ret
|
|
1153
|
+
return query.insert(parsedBody).onConflict(primary).merge()
|
|
911
1154
|
} else if (
|
|
912
1155
|
this.client === SqlClient.MS_SQL ||
|
|
913
1156
|
this.client === SqlClient.ORACLE
|
|
@@ -924,8 +1167,7 @@ class InternalBuilder {
|
|
|
924
1167
|
limits?: { base: number; query: number }
|
|
925
1168
|
} = {}
|
|
926
1169
|
): Knex.QueryBuilder {
|
|
927
|
-
let { endpoint, filters, paginate, relationships
|
|
928
|
-
this.query
|
|
1170
|
+
let { endpoint, filters, paginate, relationships } = this.query
|
|
929
1171
|
const { limits } = opts
|
|
930
1172
|
const counting = endpoint.operation === Operation.COUNT
|
|
931
1173
|
|
|
@@ -957,45 +1199,39 @@ class InternalBuilder {
|
|
|
957
1199
|
if (foundOffset != null) {
|
|
958
1200
|
query = query.offset(foundOffset)
|
|
959
1201
|
}
|
|
960
|
-
// add sorting to pre-query
|
|
961
|
-
// no point in sorting when counting
|
|
962
|
-
query = this.addSorting(query)
|
|
963
1202
|
}
|
|
964
|
-
// add filters to the query (where)
|
|
965
|
-
query = this.addFilters(query, filters)
|
|
966
1203
|
|
|
967
|
-
const alias = tableAliases?.[tableName] || tableName
|
|
968
|
-
let preQuery: Knex.QueryBuilder = this.knex({
|
|
969
|
-
// the typescript definition for the knex constructor doesn't support this
|
|
970
|
-
// syntax, but it is the only way to alias a pre-query result as part of
|
|
971
|
-
// a query - there is an alias dictionary type, but it assumes it can only
|
|
972
|
-
// be a table name, not a pre-query
|
|
973
|
-
[alias]: query as any,
|
|
974
|
-
})
|
|
975
1204
|
// if counting, use distinct count, else select
|
|
976
|
-
|
|
977
|
-
?
|
|
978
|
-
: this.addDistinctCount(
|
|
1205
|
+
query = !counting
|
|
1206
|
+
? query.select(this.generateSelectStatement())
|
|
1207
|
+
: this.addDistinctCount(query)
|
|
979
1208
|
// have to add after as well (this breaks MS-SQL)
|
|
980
|
-
if (
|
|
981
|
-
|
|
982
|
-
}
|
|
983
|
-
// handle joins
|
|
984
|
-
query = this.addRelationships(
|
|
985
|
-
preQuery,
|
|
986
|
-
tableName,
|
|
987
|
-
relationships,
|
|
988
|
-
endpoint.schema,
|
|
989
|
-
tableAliases
|
|
990
|
-
)
|
|
991
|
-
|
|
992
|
-
// add a base limit over the whole query
|
|
993
|
-
// if counting we can't set this limit
|
|
994
|
-
if (limits?.base) {
|
|
995
|
-
query = query.limit(limits.base)
|
|
1209
|
+
if (!counting) {
|
|
1210
|
+
query = this.addSorting(query)
|
|
996
1211
|
}
|
|
997
1212
|
|
|
998
|
-
|
|
1213
|
+
query = this.addFilters(query, filters, { relationship: true })
|
|
1214
|
+
|
|
1215
|
+
// handle relationships with a CTE for all others
|
|
1216
|
+
if (relationships?.length) {
|
|
1217
|
+
const mainTable =
|
|
1218
|
+
this.query.tableAliases?.[this.query.endpoint.entityId] ||
|
|
1219
|
+
this.query.endpoint.entityId
|
|
1220
|
+
const cte = this.addSorting(
|
|
1221
|
+
this.knex
|
|
1222
|
+
.with("paginated", query)
|
|
1223
|
+
.select(this.generateSelectStatement())
|
|
1224
|
+
.from({
|
|
1225
|
+
[mainTable]: "paginated",
|
|
1226
|
+
})
|
|
1227
|
+
)
|
|
1228
|
+
// add JSON aggregations attached to the CTE
|
|
1229
|
+
return this.addJsonRelationships(cte, tableName, relationships)
|
|
1230
|
+
}
|
|
1231
|
+
// no relationships found - return query
|
|
1232
|
+
else {
|
|
1233
|
+
return query
|
|
1234
|
+
}
|
|
999
1235
|
}
|
|
1000
1236
|
|
|
1001
1237
|
update(opts: QueryOptions): Knex.QueryBuilder {
|