@budibase/backend-core 2.33.0 → 2.33.2
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 +75 -24
- package/dist/index.js.map +3 -3
- package/dist/index.js.meta.json +1 -1
- package/dist/package.json +4 -4
- package/dist/src/sql/sql.js +59 -25
- package/dist/src/sql/sql.js.map +1 -1
- package/package.json +4 -4
- package/src/sql/sql.ts +86 -29
package/src/sql/sql.ts
CHANGED
|
@@ -23,12 +23,14 @@ import {
|
|
|
23
23
|
InternalSearchFilterOperator,
|
|
24
24
|
JsonFieldMetadata,
|
|
25
25
|
JsonTypes,
|
|
26
|
+
LogicalOperator,
|
|
26
27
|
Operation,
|
|
27
28
|
prefixed,
|
|
28
29
|
QueryJson,
|
|
29
30
|
QueryOptions,
|
|
30
31
|
RangeOperator,
|
|
31
32
|
RelationshipsJson,
|
|
33
|
+
SearchFilterKey,
|
|
32
34
|
SearchFilters,
|
|
33
35
|
SortOrder,
|
|
34
36
|
SqlClient,
|
|
@@ -96,6 +98,22 @@ function isSqs(table: Table): boolean {
|
|
|
96
98
|
)
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
const allowEmptyRelationships: Record<SearchFilterKey, boolean> = {
|
|
102
|
+
[BasicOperator.EQUAL]: false,
|
|
103
|
+
[BasicOperator.NOT_EQUAL]: true,
|
|
104
|
+
[BasicOperator.EMPTY]: false,
|
|
105
|
+
[BasicOperator.NOT_EMPTY]: true,
|
|
106
|
+
[BasicOperator.FUZZY]: false,
|
|
107
|
+
[BasicOperator.STRING]: false,
|
|
108
|
+
[RangeOperator.RANGE]: false,
|
|
109
|
+
[ArrayOperator.CONTAINS]: false,
|
|
110
|
+
[ArrayOperator.NOT_CONTAINS]: true,
|
|
111
|
+
[ArrayOperator.CONTAINS_ANY]: false,
|
|
112
|
+
[ArrayOperator.ONE_OF]: false,
|
|
113
|
+
[LogicalOperator.AND]: false,
|
|
114
|
+
[LogicalOperator.OR]: false,
|
|
115
|
+
}
|
|
116
|
+
|
|
99
117
|
class InternalBuilder {
|
|
100
118
|
private readonly client: SqlClient
|
|
101
119
|
private readonly query: QueryJson
|
|
@@ -405,31 +423,48 @@ class InternalBuilder {
|
|
|
405
423
|
|
|
406
424
|
addRelationshipForFilter(
|
|
407
425
|
query: Knex.QueryBuilder,
|
|
426
|
+
allowEmptyRelationships: boolean,
|
|
408
427
|
filterKey: string,
|
|
409
|
-
whereCb: (query: Knex.QueryBuilder) => Knex.QueryBuilder
|
|
428
|
+
whereCb: (filterKey: string, query: Knex.QueryBuilder) => Knex.QueryBuilder
|
|
410
429
|
): Knex.QueryBuilder {
|
|
411
430
|
const mainKnex = this.knex
|
|
412
431
|
const { relationships, endpoint, tableAliases: aliases } = this.query
|
|
413
432
|
const tableName = endpoint.entityId
|
|
414
433
|
const fromAlias = aliases?.[tableName] || tableName
|
|
415
|
-
const matches = (
|
|
416
|
-
filterKey.
|
|
434
|
+
const matches = (value: string) =>
|
|
435
|
+
filterKey.match(new RegExp(`^${value}\\.`))
|
|
417
436
|
if (!relationships) {
|
|
418
437
|
return query
|
|
419
438
|
}
|
|
420
439
|
for (const relationship of relationships) {
|
|
421
440
|
const relatedTableName = relationship.tableName
|
|
422
441
|
const toAlias = aliases?.[relatedTableName] || relatedTableName
|
|
442
|
+
|
|
443
|
+
const matchesTableName = matches(relatedTableName) || matches(toAlias)
|
|
444
|
+
const matchesRelationName = matches(relationship.column)
|
|
445
|
+
|
|
423
446
|
// this is the relationship which is being filtered
|
|
424
447
|
if (
|
|
425
|
-
(
|
|
448
|
+
(matchesTableName || matchesRelationName) &&
|
|
426
449
|
relationship.to &&
|
|
427
450
|
relationship.tableName
|
|
428
451
|
) {
|
|
429
|
-
|
|
452
|
+
const joinTable = mainKnex
|
|
430
453
|
.select(mainKnex.raw(1))
|
|
431
454
|
.from({ [toAlias]: relatedTableName })
|
|
455
|
+
let subQuery = joinTable.clone()
|
|
432
456
|
const manyToMany = validateManyToMany(relationship)
|
|
457
|
+
let updatedKey
|
|
458
|
+
|
|
459
|
+
if (!matchesTableName) {
|
|
460
|
+
updatedKey = filterKey.replace(
|
|
461
|
+
new RegExp(`^${relationship.column}.`),
|
|
462
|
+
`${aliases![relationship.tableName]}.`
|
|
463
|
+
)
|
|
464
|
+
} else {
|
|
465
|
+
updatedKey = filterKey
|
|
466
|
+
}
|
|
467
|
+
|
|
433
468
|
if (manyToMany) {
|
|
434
469
|
const throughAlias =
|
|
435
470
|
aliases?.[manyToMany.through] || relationship.through
|
|
@@ -440,7 +475,6 @@ class InternalBuilder {
|
|
|
440
475
|
subQuery = subQuery
|
|
441
476
|
// add a join through the junction table
|
|
442
477
|
.innerJoin(throughTable, function () {
|
|
443
|
-
// @ts-ignore
|
|
444
478
|
this.on(
|
|
445
479
|
`${toAlias}.${manyToMany.toPrimary}`,
|
|
446
480
|
"=",
|
|
@@ -460,18 +494,38 @@ class InternalBuilder {
|
|
|
460
494
|
if (this.client === SqlClient.SQL_LITE) {
|
|
461
495
|
subQuery = this.addJoinFieldCheck(subQuery, manyToMany)
|
|
462
496
|
}
|
|
497
|
+
|
|
498
|
+
query = query.where(q => {
|
|
499
|
+
q.whereExists(whereCb(updatedKey, subQuery))
|
|
500
|
+
if (allowEmptyRelationships) {
|
|
501
|
+
q.orWhereNotExists(
|
|
502
|
+
joinTable.clone().innerJoin(throughTable, function () {
|
|
503
|
+
this.on(
|
|
504
|
+
`${fromAlias}.${manyToMany.fromPrimary}`,
|
|
505
|
+
"=",
|
|
506
|
+
`${throughAlias}.${manyToMany.from}`
|
|
507
|
+
)
|
|
508
|
+
})
|
|
509
|
+
)
|
|
510
|
+
}
|
|
511
|
+
})
|
|
463
512
|
} else {
|
|
513
|
+
const toKey = `${toAlias}.${relationship.to}`
|
|
514
|
+
const foreignKey = `${fromAlias}.${relationship.from}`
|
|
464
515
|
// "join" to the main table, making sure the ID matches that of the main
|
|
465
516
|
subQuery = subQuery.where(
|
|
466
|
-
|
|
517
|
+
toKey,
|
|
467
518
|
"=",
|
|
468
|
-
mainKnex.raw(
|
|
469
|
-
this.quotedIdentifier(`${fromAlias}.${relationship.from}`)
|
|
470
|
-
)
|
|
519
|
+
mainKnex.raw(this.quotedIdentifier(foreignKey))
|
|
471
520
|
)
|
|
521
|
+
|
|
522
|
+
query = query.where(q => {
|
|
523
|
+
q.whereExists(whereCb(updatedKey, subQuery.clone()))
|
|
524
|
+
if (allowEmptyRelationships) {
|
|
525
|
+
q.orWhereNotExists(subQuery)
|
|
526
|
+
}
|
|
527
|
+
})
|
|
472
528
|
}
|
|
473
|
-
query = query.whereExists(whereCb(subQuery))
|
|
474
|
-
break
|
|
475
529
|
}
|
|
476
530
|
}
|
|
477
531
|
return query
|
|
@@ -502,6 +556,7 @@ class InternalBuilder {
|
|
|
502
556
|
}
|
|
503
557
|
function iterate(
|
|
504
558
|
structure: AnySearchFilter,
|
|
559
|
+
operation: SearchFilterKey,
|
|
505
560
|
fn: (
|
|
506
561
|
query: Knex.QueryBuilder,
|
|
507
562
|
key: string,
|
|
@@ -558,9 +613,14 @@ class InternalBuilder {
|
|
|
558
613
|
if (allOr) {
|
|
559
614
|
query = query.or
|
|
560
615
|
}
|
|
561
|
-
query = builder.addRelationshipForFilter(
|
|
562
|
-
|
|
563
|
-
|
|
616
|
+
query = builder.addRelationshipForFilter(
|
|
617
|
+
query,
|
|
618
|
+
allowEmptyRelationships[operation],
|
|
619
|
+
updatedKey,
|
|
620
|
+
(updatedKey, q) => {
|
|
621
|
+
return handleRelationship(q, updatedKey, value)
|
|
622
|
+
}
|
|
623
|
+
)
|
|
564
624
|
}
|
|
565
625
|
}
|
|
566
626
|
}
|
|
@@ -592,7 +652,7 @@ class InternalBuilder {
|
|
|
592
652
|
return `[${value.join(",")}]`
|
|
593
653
|
}
|
|
594
654
|
if (this.client === SqlClient.POSTGRES) {
|
|
595
|
-
iterate(mode, (q, key, value) => {
|
|
655
|
+
iterate(mode, ArrayOperator.CONTAINS, (q, key, value) => {
|
|
596
656
|
const wrap = any ? "" : "'"
|
|
597
657
|
const op = any ? "\\?| array" : "@>"
|
|
598
658
|
const fieldNames = key.split(/\./g)
|
|
@@ -610,7 +670,7 @@ class InternalBuilder {
|
|
|
610
670
|
this.client === SqlClient.MARIADB
|
|
611
671
|
) {
|
|
612
672
|
const jsonFnc = any ? "JSON_OVERLAPS" : "JSON_CONTAINS"
|
|
613
|
-
iterate(mode, (q, key, value) => {
|
|
673
|
+
iterate(mode, ArrayOperator.CONTAINS, (q, key, value) => {
|
|
614
674
|
return q[rawFnc](
|
|
615
675
|
`${not}COALESCE(${jsonFnc}(${key}, '${stringifyArray(
|
|
616
676
|
value
|
|
@@ -619,7 +679,7 @@ class InternalBuilder {
|
|
|
619
679
|
})
|
|
620
680
|
} else {
|
|
621
681
|
const andOr = mode === filters?.containsAny ? " OR " : " AND "
|
|
622
|
-
iterate(mode, (q, key, value) => {
|
|
682
|
+
iterate(mode, ArrayOperator.CONTAINS, (q, key, value) => {
|
|
623
683
|
let statement = ""
|
|
624
684
|
const identifier = this.quotedIdentifier(key)
|
|
625
685
|
for (let i in value) {
|
|
@@ -673,6 +733,7 @@ class InternalBuilder {
|
|
|
673
733
|
const fnc = allOr ? "orWhereIn" : "whereIn"
|
|
674
734
|
iterate(
|
|
675
735
|
filters.oneOf,
|
|
736
|
+
ArrayOperator.ONE_OF,
|
|
676
737
|
(q, key: string, array) => {
|
|
677
738
|
if (this.client === SqlClient.ORACLE) {
|
|
678
739
|
key = this.convertClobs(key)
|
|
@@ -697,7 +758,7 @@ class InternalBuilder {
|
|
|
697
758
|
)
|
|
698
759
|
}
|
|
699
760
|
if (filters.string) {
|
|
700
|
-
iterate(filters.string, (q, key, value) => {
|
|
761
|
+
iterate(filters.string, BasicOperator.STRING, (q, key, value) => {
|
|
701
762
|
const fnc = allOr ? "orWhere" : "where"
|
|
702
763
|
// postgres supports ilike, nothing else does
|
|
703
764
|
if (this.client === SqlClient.POSTGRES) {
|
|
@@ -712,10 +773,10 @@ class InternalBuilder {
|
|
|
712
773
|
})
|
|
713
774
|
}
|
|
714
775
|
if (filters.fuzzy) {
|
|
715
|
-
iterate(filters.fuzzy, like)
|
|
776
|
+
iterate(filters.fuzzy, BasicOperator.FUZZY, like)
|
|
716
777
|
}
|
|
717
778
|
if (filters.range) {
|
|
718
|
-
iterate(filters.range, (q, key, value) => {
|
|
779
|
+
iterate(filters.range, RangeOperator.RANGE, (q, key, value) => {
|
|
719
780
|
const isEmptyObject = (val: any) => {
|
|
720
781
|
return (
|
|
721
782
|
val &&
|
|
@@ -781,7 +842,7 @@ class InternalBuilder {
|
|
|
781
842
|
})
|
|
782
843
|
}
|
|
783
844
|
if (filters.equal) {
|
|
784
|
-
iterate(filters.equal, (q, key, value) => {
|
|
845
|
+
iterate(filters.equal, BasicOperator.EQUAL, (q, key, value) => {
|
|
785
846
|
const fnc = allOr ? "orWhereRaw" : "whereRaw"
|
|
786
847
|
if (this.client === SqlClient.MS_SQL) {
|
|
787
848
|
return q[fnc](
|
|
@@ -801,7 +862,7 @@ class InternalBuilder {
|
|
|
801
862
|
})
|
|
802
863
|
}
|
|
803
864
|
if (filters.notEqual) {
|
|
804
|
-
iterate(filters.notEqual, (q, key, value) => {
|
|
865
|
+
iterate(filters.notEqual, BasicOperator.NOT_EQUAL, (q, key, value) => {
|
|
805
866
|
const fnc = allOr ? "orWhereRaw" : "whereRaw"
|
|
806
867
|
if (this.client === SqlClient.MS_SQL) {
|
|
807
868
|
return q[fnc](
|
|
@@ -822,13 +883,13 @@ class InternalBuilder {
|
|
|
822
883
|
})
|
|
823
884
|
}
|
|
824
885
|
if (filters.empty) {
|
|
825
|
-
iterate(filters.empty, (q, key) => {
|
|
886
|
+
iterate(filters.empty, BasicOperator.EMPTY, (q, key) => {
|
|
826
887
|
const fnc = allOr ? "orWhereNull" : "whereNull"
|
|
827
888
|
return q[fnc](key)
|
|
828
889
|
})
|
|
829
890
|
}
|
|
830
891
|
if (filters.notEmpty) {
|
|
831
|
-
iterate(filters.notEmpty, (q, key) => {
|
|
892
|
+
iterate(filters.notEmpty, BasicOperator.NOT_EMPTY, (q, key) => {
|
|
832
893
|
const fnc = allOr ? "orWhereNotNull" : "whereNotNull"
|
|
833
894
|
return q[fnc](key)
|
|
834
895
|
})
|
|
@@ -1224,12 +1285,10 @@ class InternalBuilder {
|
|
|
1224
1285
|
})
|
|
1225
1286
|
: undefined
|
|
1226
1287
|
if (!throughTable) {
|
|
1227
|
-
// @ts-ignore
|
|
1228
1288
|
query = query.leftJoin(toTableWithSchema, function () {
|
|
1229
1289
|
for (let relationship of columns) {
|
|
1230
1290
|
const from = relationship.from,
|
|
1231
1291
|
to = relationship.to
|
|
1232
|
-
// @ts-ignore
|
|
1233
1292
|
this.orOn(`${fromAlias}.${from}`, "=", `${toAlias}.${to}`)
|
|
1234
1293
|
}
|
|
1235
1294
|
})
|
|
@@ -1240,7 +1299,6 @@ class InternalBuilder {
|
|
|
1240
1299
|
for (let relationship of columns) {
|
|
1241
1300
|
const fromPrimary = relationship.fromPrimary
|
|
1242
1301
|
const from = relationship.from
|
|
1243
|
-
// @ts-ignore
|
|
1244
1302
|
this.orOn(
|
|
1245
1303
|
`${fromAlias}.${fromPrimary}`,
|
|
1246
1304
|
"=",
|
|
@@ -1252,7 +1310,6 @@ class InternalBuilder {
|
|
|
1252
1310
|
for (let relationship of columns) {
|
|
1253
1311
|
const toPrimary = relationship.toPrimary
|
|
1254
1312
|
const to = relationship.to
|
|
1255
|
-
// @ts-ignore
|
|
1256
1313
|
this.orOn(`${toAlias}.${toPrimary}`, `${throughAlias}.${to}`)
|
|
1257
1314
|
}
|
|
1258
1315
|
})
|