@budibase/backend-core 2.28.7 → 2.29.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 +117 -49
- package/dist/index.js.map +3 -3
- 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/db/constants.d.ts +1 -3
- package/dist/src/db/constants.js +4 -13
- package/dist/src/db/constants.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/sql/sql.d.ts +2 -1
- package/dist/src/sql/sql.js +113 -43
- package/dist/src/sql/sql.js.map +1 -1
- package/dist/src/tenancy/db.d.ts +1 -0
- package/dist/src/tenancy/db.js +9 -2
- package/dist/src/tenancy/db.js.map +1 -1
- package/package.json +4 -4
- package/src/db/constants.ts +5 -14
- package/src/sql/sql.ts +138 -53
- package/src/tenancy/db.ts +6 -1
package/src/sql/sql.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Knex, knex } from "knex"
|
|
2
2
|
import * as dbCore from "../db"
|
|
3
3
|
import {
|
|
4
|
-
isIsoDateString,
|
|
5
|
-
isValidFilter,
|
|
6
4
|
getNativeSql,
|
|
7
5
|
isExternalTable,
|
|
6
|
+
isIsoDateString,
|
|
7
|
+
isValidFilter,
|
|
8
8
|
} from "./utils"
|
|
9
9
|
import { SqlStatements } from "./sqlStatements"
|
|
10
10
|
import SqlTableQueryBuilder from "./sqlTable"
|
|
@@ -12,21 +12,21 @@ import {
|
|
|
12
12
|
BBReferenceFieldMetadata,
|
|
13
13
|
FieldSchema,
|
|
14
14
|
FieldType,
|
|
15
|
+
INTERNAL_TABLE_SOURCE_ID,
|
|
15
16
|
JsonFieldMetadata,
|
|
17
|
+
JsonTypes,
|
|
16
18
|
Operation,
|
|
19
|
+
prefixed,
|
|
17
20
|
QueryJson,
|
|
18
|
-
|
|
21
|
+
QueryOptions,
|
|
19
22
|
RelationshipsJson,
|
|
20
23
|
SearchFilters,
|
|
24
|
+
SortOrder,
|
|
25
|
+
SqlClient,
|
|
26
|
+
SqlQuery,
|
|
21
27
|
SqlQueryBinding,
|
|
22
28
|
Table,
|
|
23
29
|
TableSourceType,
|
|
24
|
-
INTERNAL_TABLE_SOURCE_ID,
|
|
25
|
-
SqlClient,
|
|
26
|
-
QueryOptions,
|
|
27
|
-
JsonTypes,
|
|
28
|
-
prefixed,
|
|
29
|
-
SortOrder,
|
|
30
30
|
} from "@budibase/types"
|
|
31
31
|
import environment from "../environment"
|
|
32
32
|
import { helpers } from "@budibase/shared-core"
|
|
@@ -114,7 +114,7 @@ function generateSelectStatement(
|
|
|
114
114
|
): (string | Knex.Raw)[] | "*" {
|
|
115
115
|
const { resource, meta } = json
|
|
116
116
|
|
|
117
|
-
if (!resource) {
|
|
117
|
+
if (!resource || !resource.fields || resource.fields.length === 0) {
|
|
118
118
|
return "*"
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -410,13 +410,32 @@ class InternalBuilder {
|
|
|
410
410
|
return query
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
+
addDistinctCount(
|
|
414
|
+
query: Knex.QueryBuilder,
|
|
415
|
+
json: QueryJson
|
|
416
|
+
): Knex.QueryBuilder {
|
|
417
|
+
const table = json.meta.table
|
|
418
|
+
const primary = table.primary
|
|
419
|
+
const aliases = json.tableAliases
|
|
420
|
+
const aliased =
|
|
421
|
+
table.name && aliases?.[table.name] ? aliases[table.name] : table.name
|
|
422
|
+
if (!primary) {
|
|
423
|
+
throw new Error("SQL counting requires primary key to be supplied")
|
|
424
|
+
}
|
|
425
|
+
return query.countDistinct(`${aliased}.${primary[0]} as total`)
|
|
426
|
+
}
|
|
427
|
+
|
|
413
428
|
addSorting(query: Knex.QueryBuilder, json: QueryJson): Knex.QueryBuilder {
|
|
414
|
-
let { sort
|
|
429
|
+
let { sort } = json
|
|
415
430
|
const table = json.meta.table
|
|
431
|
+
const primaryKey = table.primary
|
|
416
432
|
const tableName = getTableName(table)
|
|
417
433
|
const aliases = json.tableAliases
|
|
418
434
|
const aliased =
|
|
419
435
|
tableName && aliases?.[tableName] ? aliases[tableName] : table?.name
|
|
436
|
+
if (!Array.isArray(primaryKey)) {
|
|
437
|
+
throw new Error("Sorting requires primary key to be specified for table")
|
|
438
|
+
}
|
|
420
439
|
if (sort && Object.keys(sort || {}).length > 0) {
|
|
421
440
|
for (let [key, value] of Object.entries(sort)) {
|
|
422
441
|
const direction =
|
|
@@ -429,10 +448,9 @@ class InternalBuilder {
|
|
|
429
448
|
|
|
430
449
|
query = query.orderBy(`${aliased}.${key}`, direction, nulls)
|
|
431
450
|
}
|
|
432
|
-
} else if (this.client === SqlClient.MS_SQL && paginate?.limit) {
|
|
433
|
-
// @ts-ignore
|
|
434
|
-
query = query.orderBy(`${aliased}.${table?.primary[0]}`)
|
|
435
451
|
}
|
|
452
|
+
// always add sorting by the primary key - make sure result is deterministic
|
|
453
|
+
query = query.orderBy(`${aliased}.${primaryKey[0]}`)
|
|
436
454
|
return query
|
|
437
455
|
}
|
|
438
456
|
|
|
@@ -522,7 +540,7 @@ class InternalBuilder {
|
|
|
522
540
|
})
|
|
523
541
|
}
|
|
524
542
|
}
|
|
525
|
-
return query
|
|
543
|
+
return query
|
|
526
544
|
}
|
|
527
545
|
|
|
528
546
|
knexWithAlias(
|
|
@@ -533,13 +551,12 @@ class InternalBuilder {
|
|
|
533
551
|
const tableName = endpoint.entityId
|
|
534
552
|
const tableAlias = aliases?.[tableName]
|
|
535
553
|
|
|
536
|
-
|
|
554
|
+
return knex(
|
|
537
555
|
this.tableNameWithSchema(tableName, {
|
|
538
556
|
alias: tableAlias,
|
|
539
557
|
schema: endpoint.schema,
|
|
540
558
|
})
|
|
541
559
|
)
|
|
542
|
-
return query
|
|
543
560
|
}
|
|
544
561
|
|
|
545
562
|
create(knex: Knex, json: QueryJson, opts: QueryOptions): Knex.QueryBuilder {
|
|
@@ -571,52 +588,93 @@ class InternalBuilder {
|
|
|
571
588
|
return query.insert(parsedBody)
|
|
572
589
|
}
|
|
573
590
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
591
|
+
bulkUpsert(knex: Knex, json: QueryJson): Knex.QueryBuilder {
|
|
592
|
+
const { endpoint, body } = json
|
|
593
|
+
let query = this.knexWithAlias(knex, endpoint)
|
|
594
|
+
if (!Array.isArray(body)) {
|
|
595
|
+
return query
|
|
596
|
+
}
|
|
597
|
+
const parsedBody = body.map(row => parseBody(row))
|
|
598
|
+
if (
|
|
599
|
+
this.client === SqlClient.POSTGRES ||
|
|
600
|
+
this.client === SqlClient.SQL_LITE ||
|
|
601
|
+
this.client === SqlClient.MY_SQL
|
|
602
|
+
) {
|
|
603
|
+
const primary = json.meta.table.primary
|
|
604
|
+
if (!primary) {
|
|
605
|
+
throw new Error("Primary key is required for upsert")
|
|
606
|
+
}
|
|
607
|
+
return query.insert(parsedBody).onConflict(primary).merge()
|
|
608
|
+
} else if (this.client === SqlClient.MS_SQL) {
|
|
609
|
+
// No upsert or onConflict support in MSSQL yet, see:
|
|
610
|
+
// https://github.com/knex/knex/pull/6050
|
|
611
|
+
return query.insert(parsedBody)
|
|
612
|
+
}
|
|
613
|
+
return query.upsert(parsedBody)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
read(
|
|
617
|
+
knex: Knex,
|
|
618
|
+
json: QueryJson,
|
|
619
|
+
opts: {
|
|
620
|
+
limits?: { base: number; query: number }
|
|
621
|
+
} = {}
|
|
622
|
+
): Knex.QueryBuilder {
|
|
623
|
+
let { endpoint, filters, paginate, relationships, tableAliases } = json
|
|
624
|
+
const { limits } = opts
|
|
625
|
+
const counting = endpoint.operation === Operation.COUNT
|
|
577
626
|
|
|
578
627
|
const tableName = endpoint.entityId
|
|
579
|
-
//
|
|
580
|
-
|
|
581
|
-
resource = { fields: [] }
|
|
582
|
-
}
|
|
583
|
-
let selectStatement: string | (string | Knex.Raw)[] = "*"
|
|
584
|
-
// handle select
|
|
585
|
-
if (resource.fields && resource.fields.length > 0) {
|
|
586
|
-
// select the resources as the format "table.columnName" - this is what is provided
|
|
587
|
-
// by the resource builder further up
|
|
588
|
-
selectStatement = generateSelectStatement(json, knex)
|
|
589
|
-
}
|
|
590
|
-
let foundLimit = limit || BASE_LIMIT
|
|
628
|
+
// start building the query
|
|
629
|
+
let query = this.knexWithAlias(knex, endpoint, tableAliases)
|
|
591
630
|
// handle pagination
|
|
592
631
|
let foundOffset: number | null = null
|
|
632
|
+
let foundLimit = limits?.query || limits?.base
|
|
593
633
|
if (paginate && paginate.page && paginate.limit) {
|
|
594
634
|
// @ts-ignore
|
|
595
635
|
const page = paginate.page <= 1 ? 0 : paginate.page - 1
|
|
596
636
|
const offset = page * paginate.limit
|
|
597
637
|
foundLimit = paginate.limit
|
|
598
638
|
foundOffset = offset
|
|
639
|
+
} else if (paginate && paginate.offset && paginate.limit) {
|
|
640
|
+
foundLimit = paginate.limit
|
|
641
|
+
foundOffset = paginate.offset
|
|
599
642
|
} else if (paginate && paginate.limit) {
|
|
600
643
|
foundLimit = paginate.limit
|
|
601
644
|
}
|
|
602
|
-
//
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
645
|
+
// counting should not sort, limit or offset
|
|
646
|
+
if (!counting) {
|
|
647
|
+
// add the found limit if supplied
|
|
648
|
+
if (foundLimit != null) {
|
|
649
|
+
query = query.limit(foundLimit)
|
|
650
|
+
}
|
|
651
|
+
// add overall pagination
|
|
652
|
+
if (foundOffset != null) {
|
|
653
|
+
query = query.offset(foundOffset)
|
|
654
|
+
}
|
|
655
|
+
// add sorting to pre-query
|
|
656
|
+
// no point in sorting when counting
|
|
657
|
+
query = this.addSorting(query, json)
|
|
607
658
|
}
|
|
659
|
+
// add filters to the query (where)
|
|
608
660
|
query = this.addFilters(query, filters, json.meta.table, {
|
|
609
661
|
aliases: tableAliases,
|
|
610
662
|
})
|
|
611
663
|
|
|
612
|
-
// add sorting to pre-query
|
|
613
|
-
query = this.addSorting(query, json)
|
|
614
664
|
const alias = tableAliases?.[tableName] || tableName
|
|
615
|
-
let preQuery = knex({
|
|
616
|
-
|
|
617
|
-
|
|
665
|
+
let preQuery: Knex.QueryBuilder = knex({
|
|
666
|
+
// the typescript definition for the knex constructor doesn't support this
|
|
667
|
+
// syntax, but it is the only way to alias a pre-query result as part of
|
|
668
|
+
// a query - there is an alias dictionary type, but it assumes it can only
|
|
669
|
+
// be a table name, not a pre-query
|
|
670
|
+
[alias]: query as any,
|
|
671
|
+
})
|
|
672
|
+
// if counting, use distinct count, else select
|
|
673
|
+
preQuery = !counting
|
|
674
|
+
? preQuery.select(generateSelectStatement(json, knex))
|
|
675
|
+
: this.addDistinctCount(preQuery, json)
|
|
618
676
|
// have to add after as well (this breaks MS-SQL)
|
|
619
|
-
if (this.client !== SqlClient.MS_SQL) {
|
|
677
|
+
if (this.client !== SqlClient.MS_SQL && !counting) {
|
|
620
678
|
preQuery = this.addSorting(preQuery, json)
|
|
621
679
|
}
|
|
622
680
|
// handle joins
|
|
@@ -627,6 +685,13 @@ class InternalBuilder {
|
|
|
627
685
|
endpoint.schema,
|
|
628
686
|
tableAliases
|
|
629
687
|
)
|
|
688
|
+
|
|
689
|
+
// add a base limit over the whole query
|
|
690
|
+
// if counting we can't set this limit
|
|
691
|
+
if (limits?.base) {
|
|
692
|
+
query = query.limit(limits.base)
|
|
693
|
+
}
|
|
694
|
+
|
|
630
695
|
return this.addFilters(query, filters, json.meta.table, {
|
|
631
696
|
relationship: true,
|
|
632
697
|
aliases: tableAliases,
|
|
@@ -671,6 +736,19 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
|
|
671
736
|
this.limit = limit
|
|
672
737
|
}
|
|
673
738
|
|
|
739
|
+
private convertToNative(query: Knex.QueryBuilder, opts: QueryOptions = {}) {
|
|
740
|
+
const sqlClient = this.getSqlClient()
|
|
741
|
+
if (opts?.disableBindings) {
|
|
742
|
+
return { sql: query.toString() }
|
|
743
|
+
} else {
|
|
744
|
+
let native = getNativeSql(query)
|
|
745
|
+
if (sqlClient === SqlClient.SQL_LITE) {
|
|
746
|
+
native = convertBooleans(native)
|
|
747
|
+
}
|
|
748
|
+
return native
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
674
752
|
/**
|
|
675
753
|
* @param json The JSON query DSL which is to be converted to SQL.
|
|
676
754
|
* @param opts extra options which are to be passed into the query builder, e.g. disableReturning
|
|
@@ -694,7 +772,16 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
|
|
694
772
|
query = builder.create(client, json, opts)
|
|
695
773
|
break
|
|
696
774
|
case Operation.READ:
|
|
697
|
-
query = builder.read(client, json,
|
|
775
|
+
query = builder.read(client, json, {
|
|
776
|
+
limits: {
|
|
777
|
+
query: this.limit,
|
|
778
|
+
base: BASE_LIMIT,
|
|
779
|
+
},
|
|
780
|
+
})
|
|
781
|
+
break
|
|
782
|
+
case Operation.COUNT:
|
|
783
|
+
// read without any limits to count
|
|
784
|
+
query = builder.read(client, json)
|
|
698
785
|
break
|
|
699
786
|
case Operation.UPDATE:
|
|
700
787
|
query = builder.update(client, json, opts)
|
|
@@ -705,6 +792,9 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
|
|
705
792
|
case Operation.BULK_CREATE:
|
|
706
793
|
query = builder.bulkCreate(client, json)
|
|
707
794
|
break
|
|
795
|
+
case Operation.BULK_UPSERT:
|
|
796
|
+
query = builder.bulkUpsert(client, json)
|
|
797
|
+
break
|
|
708
798
|
case Operation.CREATE_TABLE:
|
|
709
799
|
case Operation.UPDATE_TABLE:
|
|
710
800
|
case Operation.DELETE_TABLE:
|
|
@@ -713,15 +803,7 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
|
|
713
803
|
throw `Operation type is not supported by SQL query builder`
|
|
714
804
|
}
|
|
715
805
|
|
|
716
|
-
|
|
717
|
-
return { sql: query.toString() }
|
|
718
|
-
} else {
|
|
719
|
-
let native = getNativeSql(query)
|
|
720
|
-
if (sqlClient === SqlClient.SQL_LITE) {
|
|
721
|
-
native = convertBooleans(native)
|
|
722
|
-
}
|
|
723
|
-
return native
|
|
724
|
-
}
|
|
806
|
+
return this.convertToNative(query, opts)
|
|
725
807
|
}
|
|
726
808
|
|
|
727
809
|
async getReturningRow(queryFn: QueryFunction, json: QueryJson) {
|
|
@@ -797,6 +879,9 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
|
|
797
879
|
await this.getReturningRow(queryFn, this.checkLookupKeys(id, json))
|
|
798
880
|
)
|
|
799
881
|
}
|
|
882
|
+
if (operation === Operation.COUNT) {
|
|
883
|
+
return results
|
|
884
|
+
}
|
|
800
885
|
if (operation !== Operation.READ) {
|
|
801
886
|
return row
|
|
802
887
|
}
|
package/src/tenancy/db.ts
CHANGED
|
@@ -9,8 +9,13 @@ export function getTenantDB(tenantId: string) {
|
|
|
9
9
|
export async function saveTenantInfo(tenantInfo: TenantInfo) {
|
|
10
10
|
const db = getTenantDB(tenantInfo.tenantId)
|
|
11
11
|
// save the tenant info to db
|
|
12
|
-
return
|
|
12
|
+
return db.put({
|
|
13
13
|
_id: "tenant_info",
|
|
14
14
|
...tenantInfo,
|
|
15
15
|
})
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
export async function getTenantInfo(tenantId: string): Promise<TenantInfo> {
|
|
19
|
+
const db = getTenantDB(tenantId)
|
|
20
|
+
return db.get("tenant_info")
|
|
21
|
+
}
|