@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/src/sql/sql.ts
CHANGED
|
@@ -13,6 +13,7 @@ import SqlTableQueryBuilder from "./sqlTable"
|
|
|
13
13
|
import {
|
|
14
14
|
Aggregation,
|
|
15
15
|
AnySearchFilter,
|
|
16
|
+
ArrayFilter,
|
|
16
17
|
ArrayOperator,
|
|
17
18
|
BasicOperator,
|
|
18
19
|
BBReferenceFieldMetadata,
|
|
@@ -23,12 +24,14 @@ import {
|
|
|
23
24
|
InternalSearchFilterOperator,
|
|
24
25
|
JsonFieldMetadata,
|
|
25
26
|
JsonTypes,
|
|
27
|
+
LogicalOperator,
|
|
26
28
|
Operation,
|
|
27
29
|
prefixed,
|
|
28
30
|
QueryJson,
|
|
29
31
|
QueryOptions,
|
|
30
32
|
RangeOperator,
|
|
31
33
|
RelationshipsJson,
|
|
34
|
+
SearchFilterKey,
|
|
32
35
|
SearchFilters,
|
|
33
36
|
SortOrder,
|
|
34
37
|
SqlClient,
|
|
@@ -96,6 +99,39 @@ function isSqs(table: Table): boolean {
|
|
|
96
99
|
)
|
|
97
100
|
}
|
|
98
101
|
|
|
102
|
+
function escapeQuotes(value: string, quoteChar = '"'): string {
|
|
103
|
+
return value.replace(new RegExp(quoteChar, "g"), `${quoteChar}${quoteChar}`)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function wrap(value: string, quoteChar = '"'): string {
|
|
107
|
+
return `${quoteChar}${escapeQuotes(value, quoteChar)}${quoteChar}`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function stringifyArray(value: any[], quoteStyle = '"'): string {
|
|
111
|
+
for (let i in value) {
|
|
112
|
+
if (typeof value[i] === "string") {
|
|
113
|
+
value[i] = wrap(value[i], quoteStyle)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return `[${value.join(",")}]`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const allowEmptyRelationships: Record<SearchFilterKey, boolean> = {
|
|
120
|
+
[BasicOperator.EQUAL]: false,
|
|
121
|
+
[BasicOperator.NOT_EQUAL]: true,
|
|
122
|
+
[BasicOperator.EMPTY]: false,
|
|
123
|
+
[BasicOperator.NOT_EMPTY]: true,
|
|
124
|
+
[BasicOperator.FUZZY]: false,
|
|
125
|
+
[BasicOperator.STRING]: false,
|
|
126
|
+
[RangeOperator.RANGE]: false,
|
|
127
|
+
[ArrayOperator.CONTAINS]: false,
|
|
128
|
+
[ArrayOperator.NOT_CONTAINS]: true,
|
|
129
|
+
[ArrayOperator.CONTAINS_ANY]: false,
|
|
130
|
+
[ArrayOperator.ONE_OF]: false,
|
|
131
|
+
[LogicalOperator.AND]: false,
|
|
132
|
+
[LogicalOperator.OR]: false,
|
|
133
|
+
}
|
|
134
|
+
|
|
99
135
|
class InternalBuilder {
|
|
100
136
|
private readonly client: SqlClient
|
|
101
137
|
private readonly query: QueryJson
|
|
@@ -134,30 +170,30 @@ class InternalBuilder {
|
|
|
134
170
|
return this.query.meta.table
|
|
135
171
|
}
|
|
136
172
|
|
|
173
|
+
get knexClient(): Knex.Client {
|
|
174
|
+
return this.knex.client as Knex.Client
|
|
175
|
+
}
|
|
176
|
+
|
|
137
177
|
getFieldSchema(key: string): FieldSchema | undefined {
|
|
138
178
|
const { column } = this.splitter.run(key)
|
|
139
179
|
return this.table.schema[column]
|
|
140
180
|
}
|
|
141
181
|
|
|
182
|
+
private supportsILike(): boolean {
|
|
183
|
+
return !(
|
|
184
|
+
this.client === SqlClient.ORACLE || this.client === SqlClient.SQL_LITE
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
142
188
|
private quoteChars(): [string, string] {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
case SqlClient.POSTGRES:
|
|
146
|
-
return ['"', '"']
|
|
147
|
-
case SqlClient.MS_SQL:
|
|
148
|
-
return ["[", "]"]
|
|
149
|
-
case SqlClient.MARIADB:
|
|
150
|
-
case SqlClient.MY_SQL:
|
|
151
|
-
case SqlClient.SQL_LITE:
|
|
152
|
-
return ["`", "`"]
|
|
153
|
-
}
|
|
189
|
+
const wrapped = this.knexClient.wrapIdentifier("foo", {})
|
|
190
|
+
return [wrapped[0], wrapped[wrapped.length - 1]]
|
|
154
191
|
}
|
|
155
192
|
|
|
156
|
-
// Takes a string like foo and returns a quoted string like [foo] for SQL
|
|
157
|
-
// and "foo" for Postgres.
|
|
193
|
+
// Takes a string like foo and returns a quoted string like [foo] for SQL
|
|
194
|
+
// Server and "foo" for Postgres.
|
|
158
195
|
private quote(str: string): string {
|
|
159
|
-
|
|
160
|
-
return `${start}${str}${end}`
|
|
196
|
+
return this.knexClient.wrapIdentifier(str, {})
|
|
161
197
|
}
|
|
162
198
|
|
|
163
199
|
private isQuoted(key: string): boolean {
|
|
@@ -175,6 +211,30 @@ class InternalBuilder {
|
|
|
175
211
|
return key.map(part => this.quote(part)).join(".")
|
|
176
212
|
}
|
|
177
213
|
|
|
214
|
+
private quotedValue(value: string): string {
|
|
215
|
+
const formatter = this.knexClient.formatter(this.knexClient.queryBuilder())
|
|
216
|
+
return formatter.wrap(value, false)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private rawQuotedValue(value: string): Knex.Raw {
|
|
220
|
+
return this.knex.raw(this.quotedValue(value))
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Unfortuantely we cannot rely on knex's identifier escaping because it trims
|
|
224
|
+
// the identifier string before escaping it, which breaks cases for us where
|
|
225
|
+
// columns that start or end with a space aren't referenced correctly anymore.
|
|
226
|
+
//
|
|
227
|
+
// So whenever you're using an identifier binding in knex, e.g. knex.raw("??
|
|
228
|
+
// as ?", ["foo", "bar"]), you need to make sure you call this:
|
|
229
|
+
//
|
|
230
|
+
// knex.raw("?? as ?", [this.quotedIdentifier("foo"), "bar"])
|
|
231
|
+
//
|
|
232
|
+
// Issue we filed against knex about this:
|
|
233
|
+
// https://github.com/knex/knex/issues/6143
|
|
234
|
+
private rawQuotedIdentifier(key: string): Knex.Raw {
|
|
235
|
+
return this.knex.raw(this.quotedIdentifier(key))
|
|
236
|
+
}
|
|
237
|
+
|
|
178
238
|
// Turns an identifier like a.b.c or `a`.`b`.`c` into ["a", "b", "c"]
|
|
179
239
|
private splitIdentifier(key: string): string[] {
|
|
180
240
|
const [start, end] = this.quoteChars()
|
|
@@ -218,7 +278,7 @@ class InternalBuilder {
|
|
|
218
278
|
const alias = this.getTableName(endpoint.entityId)
|
|
219
279
|
const schema = meta.table.schema
|
|
220
280
|
if (!this.isFullSelectStatementRequired()) {
|
|
221
|
-
return [this.knex.raw(`${
|
|
281
|
+
return [this.knex.raw("??", [`${alias}.*`])]
|
|
222
282
|
}
|
|
223
283
|
// get just the fields for this table
|
|
224
284
|
return resource.fields
|
|
@@ -240,30 +300,40 @@ class InternalBuilder {
|
|
|
240
300
|
const columnSchema = schema[column]
|
|
241
301
|
|
|
242
302
|
if (this.SPECIAL_SELECT_CASES.POSTGRES_MONEY(columnSchema)) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
303
|
+
// TODO: figure out how to express this safely without string
|
|
304
|
+
// interpolation.
|
|
305
|
+
return this.knex.raw(`??::money::numeric as "${field}"`, [
|
|
306
|
+
this.rawQuotedIdentifier([table, column].join(".")),
|
|
307
|
+
field,
|
|
308
|
+
])
|
|
248
309
|
}
|
|
249
310
|
|
|
250
311
|
if (this.SPECIAL_SELECT_CASES.MSSQL_DATES(columnSchema)) {
|
|
251
312
|
// Time gets returned as timestamp from mssql, not matching the expected
|
|
252
313
|
// HH:mm format
|
|
253
|
-
|
|
314
|
+
|
|
315
|
+
// TODO: figure out how to express this safely without string
|
|
316
|
+
// interpolation.
|
|
317
|
+
return this.knex.raw(`CONVERT(varchar, ??, 108) as "${field}"`, [
|
|
318
|
+
this.rawQuotedIdentifier(field),
|
|
319
|
+
])
|
|
254
320
|
}
|
|
255
321
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
322
|
+
if (table) {
|
|
323
|
+
return this.rawQuotedIdentifier(`${table}.${column}`)
|
|
324
|
+
} else {
|
|
325
|
+
return this.rawQuotedIdentifier(field)
|
|
326
|
+
}
|
|
260
327
|
})
|
|
261
328
|
}
|
|
262
329
|
|
|
263
330
|
// OracleDB can't use character-large-objects (CLOBs) in WHERE clauses,
|
|
264
331
|
// so when we use them we need to wrap them in to_char(). This function
|
|
265
332
|
// converts a field name to the appropriate identifier.
|
|
266
|
-
private convertClobs(
|
|
333
|
+
private convertClobs(
|
|
334
|
+
field: string,
|
|
335
|
+
opts?: { forSelect?: boolean }
|
|
336
|
+
): Knex.Raw {
|
|
267
337
|
if (this.client !== SqlClient.ORACLE) {
|
|
268
338
|
throw new Error(
|
|
269
339
|
"you've called convertClobs on a DB that's not Oracle, this is a mistake"
|
|
@@ -272,7 +342,7 @@ class InternalBuilder {
|
|
|
272
342
|
const parts = this.splitIdentifier(field)
|
|
273
343
|
const col = parts.pop()!
|
|
274
344
|
const schema = this.table.schema[col]
|
|
275
|
-
let identifier = this.
|
|
345
|
+
let identifier = this.rawQuotedIdentifier(field)
|
|
276
346
|
|
|
277
347
|
if (
|
|
278
348
|
schema.type === FieldType.STRING ||
|
|
@@ -283,9 +353,12 @@ class InternalBuilder {
|
|
|
283
353
|
schema.type === FieldType.BARCODEQR
|
|
284
354
|
) {
|
|
285
355
|
if (opts?.forSelect) {
|
|
286
|
-
identifier =
|
|
356
|
+
identifier = this.knex.raw("to_char(??) as ??", [
|
|
357
|
+
identifier,
|
|
358
|
+
this.rawQuotedIdentifier(col),
|
|
359
|
+
])
|
|
287
360
|
} else {
|
|
288
|
-
identifier =
|
|
361
|
+
identifier = this.knex.raw("to_char(??)", [identifier])
|
|
289
362
|
}
|
|
290
363
|
}
|
|
291
364
|
return identifier
|
|
@@ -405,31 +478,47 @@ class InternalBuilder {
|
|
|
405
478
|
|
|
406
479
|
addRelationshipForFilter(
|
|
407
480
|
query: Knex.QueryBuilder,
|
|
481
|
+
allowEmptyRelationships: boolean,
|
|
408
482
|
filterKey: string,
|
|
409
|
-
whereCb: (query: Knex.QueryBuilder) => Knex.QueryBuilder
|
|
483
|
+
whereCb: (filterKey: string, query: Knex.QueryBuilder) => Knex.QueryBuilder
|
|
410
484
|
): Knex.QueryBuilder {
|
|
411
|
-
const mainKnex = this.knex
|
|
412
485
|
const { relationships, endpoint, tableAliases: aliases } = this.query
|
|
413
486
|
const tableName = endpoint.entityId
|
|
414
487
|
const fromAlias = aliases?.[tableName] || tableName
|
|
415
|
-
const matches = (
|
|
416
|
-
filterKey.
|
|
488
|
+
const matches = (value: string) =>
|
|
489
|
+
filterKey.match(new RegExp(`^${value}\\.`))
|
|
417
490
|
if (!relationships) {
|
|
418
491
|
return query
|
|
419
492
|
}
|
|
420
493
|
for (const relationship of relationships) {
|
|
421
494
|
const relatedTableName = relationship.tableName
|
|
422
495
|
const toAlias = aliases?.[relatedTableName] || relatedTableName
|
|
496
|
+
|
|
497
|
+
const matchesTableName = matches(relatedTableName) || matches(toAlias)
|
|
498
|
+
const matchesRelationName = matches(relationship.column)
|
|
499
|
+
|
|
423
500
|
// this is the relationship which is being filtered
|
|
424
501
|
if (
|
|
425
|
-
(
|
|
502
|
+
(matchesTableName || matchesRelationName) &&
|
|
426
503
|
relationship.to &&
|
|
427
504
|
relationship.tableName
|
|
428
505
|
) {
|
|
429
|
-
|
|
430
|
-
.select(
|
|
506
|
+
const joinTable = this.knex
|
|
507
|
+
.select(this.knex.raw(1))
|
|
431
508
|
.from({ [toAlias]: relatedTableName })
|
|
509
|
+
let subQuery = joinTable.clone()
|
|
432
510
|
const manyToMany = validateManyToMany(relationship)
|
|
511
|
+
let updatedKey
|
|
512
|
+
|
|
513
|
+
if (!matchesTableName) {
|
|
514
|
+
updatedKey = filterKey.replace(
|
|
515
|
+
new RegExp(`^${relationship.column}.`),
|
|
516
|
+
`${aliases![relationship.tableName]}.`
|
|
517
|
+
)
|
|
518
|
+
} else {
|
|
519
|
+
updatedKey = filterKey
|
|
520
|
+
}
|
|
521
|
+
|
|
433
522
|
if (manyToMany) {
|
|
434
523
|
const throughAlias =
|
|
435
524
|
aliases?.[manyToMany.through] || relationship.through
|
|
@@ -440,7 +529,6 @@ class InternalBuilder {
|
|
|
440
529
|
subQuery = subQuery
|
|
441
530
|
// add a join through the junction table
|
|
442
531
|
.innerJoin(throughTable, function () {
|
|
443
|
-
// @ts-ignore
|
|
444
532
|
this.on(
|
|
445
533
|
`${toAlias}.${manyToMany.toPrimary}`,
|
|
446
534
|
"=",
|
|
@@ -451,27 +539,45 @@ class InternalBuilder {
|
|
|
451
539
|
.where(
|
|
452
540
|
`${throughAlias}.${manyToMany.from}`,
|
|
453
541
|
"=",
|
|
454
|
-
|
|
455
|
-
this.quotedIdentifier(`${fromAlias}.${manyToMany.fromPrimary}`)
|
|
456
|
-
)
|
|
542
|
+
this.rawQuotedIdentifier(`${fromAlias}.${manyToMany.fromPrimary}`)
|
|
457
543
|
)
|
|
458
544
|
// in SQS the same junction table is used for different many-to-many relationships between the
|
|
459
545
|
// two same tables, this is needed to avoid rows ending up in all columns
|
|
460
546
|
if (this.client === SqlClient.SQL_LITE) {
|
|
461
547
|
subQuery = this.addJoinFieldCheck(subQuery, manyToMany)
|
|
462
548
|
}
|
|
549
|
+
|
|
550
|
+
query = query.where(q => {
|
|
551
|
+
q.whereExists(whereCb(updatedKey, subQuery))
|
|
552
|
+
if (allowEmptyRelationships) {
|
|
553
|
+
q.orWhereNotExists(
|
|
554
|
+
joinTable.clone().innerJoin(throughTable, function () {
|
|
555
|
+
this.on(
|
|
556
|
+
`${fromAlias}.${manyToMany.fromPrimary}`,
|
|
557
|
+
"=",
|
|
558
|
+
`${throughAlias}.${manyToMany.from}`
|
|
559
|
+
)
|
|
560
|
+
})
|
|
561
|
+
)
|
|
562
|
+
}
|
|
563
|
+
})
|
|
463
564
|
} else {
|
|
565
|
+
const toKey = `${toAlias}.${relationship.to}`
|
|
566
|
+
const foreignKey = `${fromAlias}.${relationship.from}`
|
|
464
567
|
// "join" to the main table, making sure the ID matches that of the main
|
|
465
568
|
subQuery = subQuery.where(
|
|
466
|
-
|
|
569
|
+
toKey,
|
|
467
570
|
"=",
|
|
468
|
-
|
|
469
|
-
this.quotedIdentifier(`${fromAlias}.${relationship.from}`)
|
|
470
|
-
)
|
|
571
|
+
this.rawQuotedIdentifier(foreignKey)
|
|
471
572
|
)
|
|
573
|
+
|
|
574
|
+
query = query.where(q => {
|
|
575
|
+
q.whereExists(whereCb(updatedKey, subQuery.clone()))
|
|
576
|
+
if (allowEmptyRelationships) {
|
|
577
|
+
q.orWhereNotExists(subQuery)
|
|
578
|
+
}
|
|
579
|
+
})
|
|
472
580
|
}
|
|
473
|
-
query = query.whereExists(whereCb(subQuery))
|
|
474
|
-
break
|
|
475
581
|
}
|
|
476
582
|
}
|
|
477
583
|
return query
|
|
@@ -492,7 +598,7 @@ class InternalBuilder {
|
|
|
492
598
|
filters = this.parseFilters({ ...filters })
|
|
493
599
|
const aliases = this.query.tableAliases
|
|
494
600
|
// if all or specified in filters, then everything is an or
|
|
495
|
-
const
|
|
601
|
+
const shouldOr = filters.allOr
|
|
496
602
|
const isSqlite = this.client === SqlClient.SQL_LITE
|
|
497
603
|
const tableName = isSqlite ? this.table._id! : this.table.name
|
|
498
604
|
|
|
@@ -502,6 +608,7 @@ class InternalBuilder {
|
|
|
502
608
|
}
|
|
503
609
|
function iterate(
|
|
504
610
|
structure: AnySearchFilter,
|
|
611
|
+
operation: SearchFilterKey,
|
|
505
612
|
fn: (
|
|
506
613
|
query: Knex.QueryBuilder,
|
|
507
614
|
key: string,
|
|
@@ -555,96 +662,118 @@ class InternalBuilder {
|
|
|
555
662
|
value
|
|
556
663
|
)
|
|
557
664
|
} else if (shouldProcessRelationship) {
|
|
558
|
-
if (
|
|
665
|
+
if (shouldOr) {
|
|
559
666
|
query = query.or
|
|
560
667
|
}
|
|
561
|
-
query = builder.addRelationshipForFilter(
|
|
562
|
-
|
|
563
|
-
|
|
668
|
+
query = builder.addRelationshipForFilter(
|
|
669
|
+
query,
|
|
670
|
+
allowEmptyRelationships[operation],
|
|
671
|
+
updatedKey,
|
|
672
|
+
(updatedKey, q) => {
|
|
673
|
+
return handleRelationship(q, updatedKey, value)
|
|
674
|
+
}
|
|
675
|
+
)
|
|
564
676
|
}
|
|
565
677
|
}
|
|
566
678
|
}
|
|
567
679
|
|
|
568
680
|
const like = (q: Knex.QueryBuilder, key: string, value: any) => {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if (
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
681
|
+
if (filters?.fuzzyOr || shouldOr) {
|
|
682
|
+
q = q.or
|
|
683
|
+
}
|
|
684
|
+
if (
|
|
685
|
+
this.client === SqlClient.ORACLE ||
|
|
686
|
+
this.client === SqlClient.SQL_LITE
|
|
687
|
+
) {
|
|
688
|
+
return q.whereRaw(`LOWER(??) LIKE ?`, [
|
|
689
|
+
this.rawQuotedIdentifier(key),
|
|
578
690
|
`%${value.toLowerCase()}%`,
|
|
579
691
|
])
|
|
580
692
|
}
|
|
693
|
+
return q.whereILike(
|
|
694
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
695
|
+
this.rawQuotedIdentifier(key),
|
|
696
|
+
this.knex.raw("?", [`%${value}%`])
|
|
697
|
+
)
|
|
581
698
|
}
|
|
582
699
|
|
|
583
|
-
const contains = (mode:
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
for (let i in value) {
|
|
588
|
-
if (typeof value[i] === "string") {
|
|
589
|
-
value[i] = `${quoteStyle}${value[i]}${quoteStyle}`
|
|
590
|
-
}
|
|
700
|
+
const contains = (mode: ArrayFilter, any = false) => {
|
|
701
|
+
function addModifiers<T extends {}, Q>(q: Knex.QueryBuilder<T, Q>) {
|
|
702
|
+
if (shouldOr || mode === filters?.containsAny) {
|
|
703
|
+
q = q.or
|
|
591
704
|
}
|
|
592
|
-
|
|
705
|
+
if (mode === filters?.notContains) {
|
|
706
|
+
q = q.not
|
|
707
|
+
}
|
|
708
|
+
return q
|
|
593
709
|
}
|
|
710
|
+
|
|
594
711
|
if (this.client === SqlClient.POSTGRES) {
|
|
595
|
-
iterate(mode, (q, key, value) => {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
712
|
+
iterate(mode, ArrayOperator.CONTAINS, (q, key, value) => {
|
|
713
|
+
q = addModifiers(q)
|
|
714
|
+
if (any) {
|
|
715
|
+
return q.whereRaw(`COALESCE(??::jsonb \\?| array??, FALSE)`, [
|
|
716
|
+
this.rawQuotedIdentifier(key),
|
|
717
|
+
this.knex.raw(stringifyArray(value, "'")),
|
|
718
|
+
])
|
|
719
|
+
} else {
|
|
720
|
+
return q.whereRaw(`COALESCE(??::jsonb @> '??', FALSE)`, [
|
|
721
|
+
this.rawQuotedIdentifier(key),
|
|
722
|
+
this.knex.raw(stringifyArray(value)),
|
|
723
|
+
])
|
|
724
|
+
}
|
|
607
725
|
})
|
|
608
726
|
} else if (
|
|
609
727
|
this.client === SqlClient.MY_SQL ||
|
|
610
728
|
this.client === SqlClient.MARIADB
|
|
611
729
|
) {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
)
|
|
730
|
+
iterate(mode, ArrayOperator.CONTAINS, (q, key, value) => {
|
|
731
|
+
return addModifiers(q).whereRaw(`COALESCE(?(??, ?), FALSE)`, [
|
|
732
|
+
this.knex.raw(any ? "JSON_OVERLAPS" : "JSON_CONTAINS"),
|
|
733
|
+
this.rawQuotedIdentifier(key),
|
|
734
|
+
this.knex.raw(wrap(stringifyArray(value))),
|
|
735
|
+
])
|
|
619
736
|
})
|
|
620
737
|
} else {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
let statement = ""
|
|
624
|
-
const identifier = this.quotedIdentifier(key)
|
|
625
|
-
for (let i in value) {
|
|
626
|
-
if (typeof value[i] === "string") {
|
|
627
|
-
value[i] = `%"${value[i].toLowerCase()}"%`
|
|
628
|
-
} else {
|
|
629
|
-
value[i] = `%${value[i]}%`
|
|
630
|
-
}
|
|
631
|
-
statement += `${
|
|
632
|
-
statement ? andOr : ""
|
|
633
|
-
}COALESCE(LOWER(${identifier}), '') LIKE ?`
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
if (statement === "") {
|
|
738
|
+
iterate(mode, ArrayOperator.CONTAINS, (q, key, value) => {
|
|
739
|
+
if (value.length === 0) {
|
|
637
740
|
return q
|
|
638
741
|
}
|
|
639
742
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
743
|
+
q = q.where(subQuery => {
|
|
744
|
+
if (mode === filters?.notContains) {
|
|
745
|
+
subQuery = subQuery.not
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
subQuery = subQuery.where(subSubQuery => {
|
|
749
|
+
for (const elem of value) {
|
|
750
|
+
if (mode === filters?.containsAny) {
|
|
751
|
+
subSubQuery = subSubQuery.or
|
|
752
|
+
} else {
|
|
753
|
+
subSubQuery = subSubQuery.and
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const lower =
|
|
757
|
+
typeof elem === "string" ? `"${elem.toLowerCase()}"` : elem
|
|
758
|
+
|
|
759
|
+
subSubQuery = subSubQuery.whereLike(
|
|
760
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
761
|
+
this.knex.raw(`COALESCE(LOWER(??), '')`, [
|
|
762
|
+
this.rawQuotedIdentifier(key),
|
|
763
|
+
]),
|
|
764
|
+
`%${lower}%`
|
|
765
|
+
)
|
|
766
|
+
}
|
|
767
|
+
})
|
|
768
|
+
if (mode === filters?.notContains) {
|
|
769
|
+
subQuery = subQuery.or.whereNull(
|
|
770
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
771
|
+
this.rawQuotedIdentifier(key)
|
|
772
|
+
)
|
|
773
|
+
}
|
|
774
|
+
return subQuery
|
|
775
|
+
})
|
|
776
|
+
return q
|
|
648
777
|
})
|
|
649
778
|
}
|
|
650
779
|
}
|
|
@@ -670,52 +799,54 @@ class InternalBuilder {
|
|
|
670
799
|
}
|
|
671
800
|
|
|
672
801
|
if (filters.oneOf) {
|
|
673
|
-
const fnc = allOr ? "orWhereIn" : "whereIn"
|
|
674
802
|
iterate(
|
|
675
803
|
filters.oneOf,
|
|
804
|
+
ArrayOperator.ONE_OF,
|
|
676
805
|
(q, key: string, array) => {
|
|
806
|
+
if (shouldOr) {
|
|
807
|
+
q = q.or
|
|
808
|
+
}
|
|
677
809
|
if (this.client === SqlClient.ORACLE) {
|
|
810
|
+
// @ts-ignore
|
|
678
811
|
key = this.convertClobs(key)
|
|
679
|
-
array = Array.isArray(array) ? array : [array]
|
|
680
|
-
const binding = new Array(array.length).fill("?").join(",")
|
|
681
|
-
return q.whereRaw(`${key} IN (${binding})`, array)
|
|
682
|
-
} else {
|
|
683
|
-
return q[fnc](key, Array.isArray(array) ? array : [array])
|
|
684
812
|
}
|
|
813
|
+
return q.whereIn(key, Array.isArray(array) ? array : [array])
|
|
685
814
|
},
|
|
686
815
|
(q, key: string[], array) => {
|
|
816
|
+
if (shouldOr) {
|
|
817
|
+
q = q.or
|
|
818
|
+
}
|
|
687
819
|
if (this.client === SqlClient.ORACLE) {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
.map((a: any) => `(${new Array(a.length).fill("?").join(",")})`)
|
|
691
|
-
.join(",")})`
|
|
692
|
-
return q.whereRaw(`${keyStr} IN ${binding}`, array.flat())
|
|
693
|
-
} else {
|
|
694
|
-
return q[fnc](key, Array.isArray(array) ? array : [array])
|
|
820
|
+
// @ts-ignore
|
|
821
|
+
key = key.map(k => this.convertClobs(k))
|
|
695
822
|
}
|
|
823
|
+
return q.whereIn(key, Array.isArray(array) ? array : [array])
|
|
696
824
|
}
|
|
697
825
|
)
|
|
698
826
|
}
|
|
699
827
|
if (filters.string) {
|
|
700
|
-
iterate(filters.string, (q, key, value) => {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
return q
|
|
828
|
+
iterate(filters.string, BasicOperator.STRING, (q, key, value) => {
|
|
829
|
+
if (shouldOr) {
|
|
830
|
+
q = q.or
|
|
831
|
+
}
|
|
832
|
+
if (
|
|
833
|
+
this.client === SqlClient.ORACLE ||
|
|
834
|
+
this.client === SqlClient.SQL_LITE
|
|
835
|
+
) {
|
|
836
|
+
return q.whereRaw(`LOWER(??) LIKE ?`, [
|
|
837
|
+
this.rawQuotedIdentifier(key),
|
|
709
838
|
`${value.toLowerCase()}%`,
|
|
710
839
|
])
|
|
840
|
+
} else {
|
|
841
|
+
return q.whereILike(key, `${value}%`)
|
|
711
842
|
}
|
|
712
843
|
})
|
|
713
844
|
}
|
|
714
845
|
if (filters.fuzzy) {
|
|
715
|
-
iterate(filters.fuzzy, like)
|
|
846
|
+
iterate(filters.fuzzy, BasicOperator.FUZZY, like)
|
|
716
847
|
}
|
|
717
848
|
if (filters.range) {
|
|
718
|
-
iterate(filters.range, (q, key, value) => {
|
|
849
|
+
iterate(filters.range, RangeOperator.RANGE, (q, key, value) => {
|
|
719
850
|
const isEmptyObject = (val: any) => {
|
|
720
851
|
return (
|
|
721
852
|
val &&
|
|
@@ -734,103 +865,109 @@ class InternalBuilder {
|
|
|
734
865
|
|
|
735
866
|
const schema = this.getFieldSchema(key)
|
|
736
867
|
|
|
868
|
+
let rawKey: string | Knex.Raw = key
|
|
869
|
+
let high = value.high
|
|
870
|
+
let low = value.low
|
|
871
|
+
|
|
737
872
|
if (this.client === SqlClient.ORACLE) {
|
|
738
|
-
|
|
739
|
-
|
|
873
|
+
rawKey = this.convertClobs(key)
|
|
874
|
+
} else if (
|
|
875
|
+
this.client === SqlClient.SQL_LITE &&
|
|
876
|
+
schema?.type === FieldType.BIGINT
|
|
877
|
+
) {
|
|
878
|
+
rawKey = this.knex.raw("CAST(?? AS INTEGER)", [
|
|
879
|
+
this.rawQuotedIdentifier(key),
|
|
880
|
+
])
|
|
881
|
+
high = this.knex.raw("CAST(? AS INTEGER)", [value.high])
|
|
882
|
+
low = this.knex.raw("CAST(? AS INTEGER)", [value.low])
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (shouldOr) {
|
|
886
|
+
q = q.or
|
|
740
887
|
}
|
|
741
888
|
|
|
742
889
|
if (lowValid && highValid) {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
this.client === SqlClient.SQL_LITE
|
|
746
|
-
) {
|
|
747
|
-
return q.whereRaw(
|
|
748
|
-
`CAST(${key} AS INTEGER) BETWEEN CAST(? AS INTEGER) AND CAST(? AS INTEGER)`,
|
|
749
|
-
[value.low, value.high]
|
|
750
|
-
)
|
|
751
|
-
} else {
|
|
752
|
-
const fnc = allOr ? "orWhereBetween" : "whereBetween"
|
|
753
|
-
return q[fnc](key, [value.low, value.high])
|
|
754
|
-
}
|
|
890
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
891
|
+
return q.whereBetween(rawKey, [low, high])
|
|
755
892
|
} else if (lowValid) {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
this.client === SqlClient.SQL_LITE
|
|
759
|
-
) {
|
|
760
|
-
return q.whereRaw(`CAST(${key} AS INTEGER) >= CAST(? AS INTEGER)`, [
|
|
761
|
-
value.low,
|
|
762
|
-
])
|
|
763
|
-
} else {
|
|
764
|
-
const fnc = allOr ? "orWhere" : "where"
|
|
765
|
-
return q[fnc](key, ">=", value.low)
|
|
766
|
-
}
|
|
893
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
894
|
+
return q.where(rawKey, ">=", low)
|
|
767
895
|
} else if (highValid) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
this.client === SqlClient.SQL_LITE
|
|
771
|
-
) {
|
|
772
|
-
return q.whereRaw(`CAST(${key} AS INTEGER) <= CAST(? AS INTEGER)`, [
|
|
773
|
-
value.high,
|
|
774
|
-
])
|
|
775
|
-
} else {
|
|
776
|
-
const fnc = allOr ? "orWhere" : "where"
|
|
777
|
-
return q[fnc](key, "<=", value.high)
|
|
778
|
-
}
|
|
896
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
897
|
+
return q.where(rawKey, "<=", high)
|
|
779
898
|
}
|
|
780
899
|
return q
|
|
781
900
|
})
|
|
782
901
|
}
|
|
783
902
|
if (filters.equal) {
|
|
784
|
-
iterate(filters.equal, (q, key, value) => {
|
|
785
|
-
|
|
903
|
+
iterate(filters.equal, BasicOperator.EQUAL, (q, key, value) => {
|
|
904
|
+
if (shouldOr) {
|
|
905
|
+
q = q.or
|
|
906
|
+
}
|
|
786
907
|
if (this.client === SqlClient.MS_SQL) {
|
|
787
|
-
return q[
|
|
788
|
-
|
|
789
|
-
[value]
|
|
790
|
-
)
|
|
791
|
-
} else if (this.client === SqlClient.ORACLE) {
|
|
792
|
-
const identifier = this.convertClobs(key)
|
|
793
|
-
return q[fnc](`(${identifier} IS NOT NULL AND ${identifier} = ?)`, [
|
|
908
|
+
return q.whereRaw(`CASE WHEN ?? = ? THEN 1 ELSE 0 END = 1`, [
|
|
909
|
+
this.rawQuotedIdentifier(key),
|
|
794
910
|
value,
|
|
795
911
|
])
|
|
912
|
+
} else if (this.client === SqlClient.ORACLE) {
|
|
913
|
+
const identifier = this.convertClobs(key)
|
|
914
|
+
return q.where(subq =>
|
|
915
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
916
|
+
subq.whereNotNull(identifier).andWhere(identifier, value)
|
|
917
|
+
)
|
|
796
918
|
} else {
|
|
797
|
-
return q
|
|
919
|
+
return q.whereRaw(`COALESCE(?? = ?, FALSE)`, [
|
|
920
|
+
this.rawQuotedIdentifier(key),
|
|
798
921
|
value,
|
|
799
922
|
])
|
|
800
923
|
}
|
|
801
924
|
})
|
|
802
925
|
}
|
|
803
926
|
if (filters.notEqual) {
|
|
804
|
-
iterate(filters.notEqual, (q, key, value) => {
|
|
805
|
-
|
|
927
|
+
iterate(filters.notEqual, BasicOperator.NOT_EQUAL, (q, key, value) => {
|
|
928
|
+
if (shouldOr) {
|
|
929
|
+
q = q.or
|
|
930
|
+
}
|
|
806
931
|
if (this.client === SqlClient.MS_SQL) {
|
|
807
|
-
return q[
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
)
|
|
932
|
+
return q.whereRaw(`CASE WHEN ?? = ? THEN 1 ELSE 0 END = 0`, [
|
|
933
|
+
this.rawQuotedIdentifier(key),
|
|
934
|
+
value,
|
|
935
|
+
])
|
|
811
936
|
} else if (this.client === SqlClient.ORACLE) {
|
|
812
937
|
const identifier = this.convertClobs(key)
|
|
813
|
-
return
|
|
814
|
-
|
|
815
|
-
|
|
938
|
+
return (
|
|
939
|
+
q
|
|
940
|
+
.where(subq =>
|
|
941
|
+
subq.not
|
|
942
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
943
|
+
.whereNull(identifier)
|
|
944
|
+
.and.where(identifier, "!=", value)
|
|
945
|
+
)
|
|
946
|
+
// @ts-expect-error knex types are wrong, raw is fine here
|
|
947
|
+
.or.whereNull(identifier)
|
|
816
948
|
)
|
|
817
949
|
} else {
|
|
818
|
-
return q
|
|
950
|
+
return q.whereRaw(`COALESCE(?? != ?, TRUE)`, [
|
|
951
|
+
this.rawQuotedIdentifier(key),
|
|
819
952
|
value,
|
|
820
953
|
])
|
|
821
954
|
}
|
|
822
955
|
})
|
|
823
956
|
}
|
|
824
957
|
if (filters.empty) {
|
|
825
|
-
iterate(filters.empty, (q, key) => {
|
|
826
|
-
|
|
827
|
-
|
|
958
|
+
iterate(filters.empty, BasicOperator.EMPTY, (q, key) => {
|
|
959
|
+
if (shouldOr) {
|
|
960
|
+
q = q.or
|
|
961
|
+
}
|
|
962
|
+
return q.whereNull(key)
|
|
828
963
|
})
|
|
829
964
|
}
|
|
830
965
|
if (filters.notEmpty) {
|
|
831
|
-
iterate(filters.notEmpty, (q, key) => {
|
|
832
|
-
|
|
833
|
-
|
|
966
|
+
iterate(filters.notEmpty, BasicOperator.NOT_EMPTY, (q, key) => {
|
|
967
|
+
if (shouldOr) {
|
|
968
|
+
q = q.or
|
|
969
|
+
}
|
|
970
|
+
return q.whereNotNull(key)
|
|
834
971
|
})
|
|
835
972
|
}
|
|
836
973
|
if (filters.contains) {
|
|
@@ -915,9 +1052,7 @@ class InternalBuilder {
|
|
|
915
1052
|
const selectFields = qualifiedFields.map(field =>
|
|
916
1053
|
this.convertClobs(field, { forSelect: true })
|
|
917
1054
|
)
|
|
918
|
-
query = query
|
|
919
|
-
.groupByRaw(groupByFields.join(", "))
|
|
920
|
-
.select(this.knex.raw(selectFields.join(", ")))
|
|
1055
|
+
query = query.groupBy(groupByFields).select(selectFields)
|
|
921
1056
|
} else {
|
|
922
1057
|
query = query.groupBy(qualifiedFields).select(qualifiedFields)
|
|
923
1058
|
}
|
|
@@ -929,11 +1064,10 @@ class InternalBuilder {
|
|
|
929
1064
|
if (this.client === SqlClient.ORACLE) {
|
|
930
1065
|
const field = this.convertClobs(`${tableName}.${aggregation.field}`)
|
|
931
1066
|
query = query.select(
|
|
932
|
-
this.knex.raw(
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
)
|
|
1067
|
+
this.knex.raw(`COUNT(DISTINCT ??) as ??`, [
|
|
1068
|
+
field,
|
|
1069
|
+
aggregation.name,
|
|
1070
|
+
])
|
|
937
1071
|
)
|
|
938
1072
|
} else {
|
|
939
1073
|
query = query.countDistinct(
|
|
@@ -998,9 +1132,11 @@ class InternalBuilder {
|
|
|
998
1132
|
} else {
|
|
999
1133
|
let composite = `${aliased}.${key}`
|
|
1000
1134
|
if (this.client === SqlClient.ORACLE) {
|
|
1001
|
-
query = query.orderByRaw(
|
|
1002
|
-
|
|
1003
|
-
|
|
1135
|
+
query = query.orderByRaw(`?? ?? nulls ??`, [
|
|
1136
|
+
this.convertClobs(composite),
|
|
1137
|
+
this.knex.raw(direction),
|
|
1138
|
+
this.knex.raw(nulls as string),
|
|
1139
|
+
])
|
|
1004
1140
|
} else {
|
|
1005
1141
|
query = query.orderBy(composite, direction, nulls)
|
|
1006
1142
|
}
|
|
@@ -1030,17 +1166,22 @@ class InternalBuilder {
|
|
|
1030
1166
|
|
|
1031
1167
|
private buildJsonField(field: string): string {
|
|
1032
1168
|
const parts = field.split(".")
|
|
1033
|
-
let
|
|
1169
|
+
let unaliased: string
|
|
1170
|
+
|
|
1171
|
+
let tableField: string
|
|
1034
1172
|
if (parts.length > 1) {
|
|
1035
1173
|
const alias = parts.shift()!
|
|
1036
1174
|
unaliased = parts.join(".")
|
|
1037
|
-
tableField = `${
|
|
1175
|
+
tableField = `${alias}.${unaliased}`
|
|
1038
1176
|
} else {
|
|
1039
1177
|
unaliased = parts.join(".")
|
|
1040
|
-
tableField =
|
|
1178
|
+
tableField = unaliased
|
|
1041
1179
|
}
|
|
1180
|
+
|
|
1042
1181
|
const separator = this.client === SqlClient.ORACLE ? " VALUE " : ","
|
|
1043
|
-
return
|
|
1182
|
+
return this.knex
|
|
1183
|
+
.raw(`?${separator}??`, [unaliased, this.rawQuotedIdentifier(tableField)])
|
|
1184
|
+
.toString()
|
|
1044
1185
|
}
|
|
1045
1186
|
|
|
1046
1187
|
maxFunctionParameters() {
|
|
@@ -1136,13 +1277,13 @@ class InternalBuilder {
|
|
|
1136
1277
|
subQuery = subQuery.where(
|
|
1137
1278
|
correlatedTo,
|
|
1138
1279
|
"=",
|
|
1139
|
-
|
|
1280
|
+
this.rawQuotedIdentifier(correlatedFrom)
|
|
1140
1281
|
)
|
|
1141
1282
|
|
|
1142
|
-
const standardWrap = (select:
|
|
1283
|
+
const standardWrap = (select: Knex.Raw): Knex.QueryBuilder => {
|
|
1143
1284
|
subQuery = subQuery.select(`${toAlias}.*`).limit(getRelationshipLimit())
|
|
1144
1285
|
// @ts-ignore - the from alias syntax isn't in Knex typing
|
|
1145
|
-
return knex.select(
|
|
1286
|
+
return knex.select(select).from({
|
|
1146
1287
|
[toAlias]: subQuery,
|
|
1147
1288
|
})
|
|
1148
1289
|
}
|
|
@@ -1152,12 +1293,12 @@ class InternalBuilder {
|
|
|
1152
1293
|
// need to check the junction table document is to the right column, this is just for SQS
|
|
1153
1294
|
subQuery = this.addJoinFieldCheck(subQuery, relationship)
|
|
1154
1295
|
wrapperQuery = standardWrap(
|
|
1155
|
-
`json_group_array(json_object(${fieldList}))`
|
|
1296
|
+
this.knex.raw(`json_group_array(json_object(${fieldList}))`)
|
|
1156
1297
|
)
|
|
1157
1298
|
break
|
|
1158
1299
|
case SqlClient.POSTGRES:
|
|
1159
1300
|
wrapperQuery = standardWrap(
|
|
1160
|
-
`json_agg(json_build_object(${fieldList}))`
|
|
1301
|
+
this.knex.raw(`json_agg(json_build_object(${fieldList}))`)
|
|
1161
1302
|
)
|
|
1162
1303
|
break
|
|
1163
1304
|
case SqlClient.MARIADB:
|
|
@@ -1171,21 +1312,25 @@ class InternalBuilder {
|
|
|
1171
1312
|
case SqlClient.MY_SQL:
|
|
1172
1313
|
case SqlClient.ORACLE:
|
|
1173
1314
|
wrapperQuery = standardWrap(
|
|
1174
|
-
`json_arrayagg(json_object(${fieldList}))`
|
|
1315
|
+
this.knex.raw(`json_arrayagg(json_object(${fieldList}))`)
|
|
1175
1316
|
)
|
|
1176
1317
|
break
|
|
1177
|
-
case SqlClient.MS_SQL:
|
|
1318
|
+
case SqlClient.MS_SQL: {
|
|
1319
|
+
const comparatorQuery = knex
|
|
1320
|
+
.select(`${fromAlias}.*`)
|
|
1321
|
+
// @ts-ignore - from alias syntax not TS supported
|
|
1322
|
+
.from({
|
|
1323
|
+
[fromAlias]: subQuery
|
|
1324
|
+
.select(`${toAlias}.*`)
|
|
1325
|
+
.limit(getRelationshipLimit()),
|
|
1326
|
+
})
|
|
1327
|
+
|
|
1178
1328
|
wrapperQuery = knex.raw(
|
|
1179
|
-
`(SELECT
|
|
1180
|
-
|
|
1181
|
-
// @ts-ignore - from alias syntax not TS supported
|
|
1182
|
-
.from({
|
|
1183
|
-
[fromAlias]: subQuery
|
|
1184
|
-
.select(`${toAlias}.*`)
|
|
1185
|
-
.limit(getRelationshipLimit()),
|
|
1186
|
-
})} FOR JSON PATH))`
|
|
1329
|
+
`(SELECT ?? = (${comparatorQuery} FOR JSON PATH))`,
|
|
1330
|
+
[this.rawQuotedIdentifier(toAlias)]
|
|
1187
1331
|
)
|
|
1188
1332
|
break
|
|
1333
|
+
}
|
|
1189
1334
|
default:
|
|
1190
1335
|
throw new Error(`JSON relationships not implement for ${sqlClient}`)
|
|
1191
1336
|
}
|
|
@@ -1224,12 +1369,10 @@ class InternalBuilder {
|
|
|
1224
1369
|
})
|
|
1225
1370
|
: undefined
|
|
1226
1371
|
if (!throughTable) {
|
|
1227
|
-
// @ts-ignore
|
|
1228
1372
|
query = query.leftJoin(toTableWithSchema, function () {
|
|
1229
1373
|
for (let relationship of columns) {
|
|
1230
1374
|
const from = relationship.from,
|
|
1231
1375
|
to = relationship.to
|
|
1232
|
-
// @ts-ignore
|
|
1233
1376
|
this.orOn(`${fromAlias}.${from}`, "=", `${toAlias}.${to}`)
|
|
1234
1377
|
}
|
|
1235
1378
|
})
|
|
@@ -1240,7 +1383,6 @@ class InternalBuilder {
|
|
|
1240
1383
|
for (let relationship of columns) {
|
|
1241
1384
|
const fromPrimary = relationship.fromPrimary
|
|
1242
1385
|
const from = relationship.from
|
|
1243
|
-
// @ts-ignore
|
|
1244
1386
|
this.orOn(
|
|
1245
1387
|
`${fromAlias}.${fromPrimary}`,
|
|
1246
1388
|
"=",
|
|
@@ -1252,7 +1394,6 @@ class InternalBuilder {
|
|
|
1252
1394
|
for (let relationship of columns) {
|
|
1253
1395
|
const toPrimary = relationship.toPrimary
|
|
1254
1396
|
const to = relationship.to
|
|
1255
|
-
// @ts-ignore
|
|
1256
1397
|
this.orOn(`${toAlias}.${toPrimary}`, `${throughAlias}.${to}`)
|
|
1257
1398
|
}
|
|
1258
1399
|
})
|