@budibase/backend-core 2.29.0 → 2.29.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 +190 -146
- 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/constants/db.d.ts +1 -1
- package/dist/src/constants/db.js +2 -1
- package/dist/src/constants/db.js.map +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/sql/sql.d.ts +2 -1
- package/dist/src/sql/sql.js +87 -43
- package/dist/src/sql/sql.js.map +1 -1
- package/dist/src/sql/sqlTable.js +2 -2
- package/dist/src/sql/sqlTable.js.map +1 -1
- package/dist/src/sql/utils.d.ts +2 -5
- package/dist/src/sql/utils.js +3 -3
- package/dist/src/sql/utils.js.map +1 -1
- package/dist/tests/core/utilities/structures/accounts.js +1 -1
- package/dist/tests/core/utilities/structures/accounts.js.map +1 -1
- package/package.json +4 -4
- package/src/constants/db.ts +1 -1
- package/src/db/constants.ts +5 -14
- package/src/sql/sql.ts +113 -54
- package/src/sql/sqlTable.ts +4 -2
- package/src/sql/utils.ts +4 -4
- package/tests/core/utilities/structures/accounts.ts +0 -1
package/src/db/constants.ts
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
"updatedAt",
|
|
7
|
-
"tableId",
|
|
8
|
-
] as const
|
|
9
|
-
|
|
10
|
-
export const CONSTANT_EXTERNAL_ROW_COLS = ["_id", "_rev", "tableId"] as const
|
|
11
|
-
|
|
12
|
-
export function isInternalColumnName(name: string): boolean {
|
|
13
|
-
return (CONSTANT_INTERNAL_ROW_COLS as readonly string[]).includes(name)
|
|
14
|
-
}
|
|
1
|
+
export {
|
|
2
|
+
CONSTANT_INTERNAL_ROW_COLS,
|
|
3
|
+
CONSTANT_EXTERNAL_ROW_COLS,
|
|
4
|
+
isInternalColumnName,
|
|
5
|
+
} from "@budibase/shared-core"
|
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,9 +448,12 @@ class InternalBuilder {
|
|
|
429
448
|
|
|
430
449
|
query = query.orderBy(`${aliased}.${key}`, direction, nulls)
|
|
431
450
|
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// add sorting by the primary key if the result isn't already sorted by it,
|
|
454
|
+
// to make sure result is deterministic
|
|
455
|
+
if (!sort || sort[primaryKey[0]] === undefined) {
|
|
456
|
+
query = query.orderBy(`${aliased}.${primaryKey[0]}`)
|
|
435
457
|
}
|
|
436
458
|
return query
|
|
437
459
|
}
|
|
@@ -522,7 +544,7 @@ class InternalBuilder {
|
|
|
522
544
|
})
|
|
523
545
|
}
|
|
524
546
|
}
|
|
525
|
-
return query
|
|
547
|
+
return query
|
|
526
548
|
}
|
|
527
549
|
|
|
528
550
|
knexWithAlias(
|
|
@@ -533,13 +555,12 @@ class InternalBuilder {
|
|
|
533
555
|
const tableName = endpoint.entityId
|
|
534
556
|
const tableAlias = aliases?.[tableName]
|
|
535
557
|
|
|
536
|
-
|
|
558
|
+
return knex(
|
|
537
559
|
this.tableNameWithSchema(tableName, {
|
|
538
560
|
alias: tableAlias,
|
|
539
561
|
schema: endpoint.schema,
|
|
540
562
|
})
|
|
541
563
|
)
|
|
542
|
-
return query
|
|
543
564
|
}
|
|
544
565
|
|
|
545
566
|
create(knex: Knex, json: QueryJson, opts: QueryOptions): Knex.QueryBuilder {
|
|
@@ -587,7 +608,8 @@ class InternalBuilder {
|
|
|
587
608
|
if (!primary) {
|
|
588
609
|
throw new Error("Primary key is required for upsert")
|
|
589
610
|
}
|
|
590
|
-
|
|
611
|
+
const ret = query.insert(parsedBody).onConflict(primary).merge()
|
|
612
|
+
return ret
|
|
591
613
|
} else if (this.client === SqlClient.MS_SQL) {
|
|
592
614
|
// No upsert or onConflict support in MSSQL yet, see:
|
|
593
615
|
// https://github.com/knex/knex/pull/6050
|
|
@@ -596,25 +618,23 @@ class InternalBuilder {
|
|
|
596
618
|
return query.upsert(parsedBody)
|
|
597
619
|
}
|
|
598
620
|
|
|
599
|
-
read(
|
|
600
|
-
|
|
601
|
-
|
|
621
|
+
read(
|
|
622
|
+
knex: Knex,
|
|
623
|
+
json: QueryJson,
|
|
624
|
+
opts: {
|
|
625
|
+
limits?: { base: number; query: number }
|
|
626
|
+
} = {}
|
|
627
|
+
): Knex.QueryBuilder {
|
|
628
|
+
let { endpoint, filters, paginate, relationships, tableAliases } = json
|
|
629
|
+
const { limits } = opts
|
|
630
|
+
const counting = endpoint.operation === Operation.COUNT
|
|
602
631
|
|
|
603
632
|
const tableName = endpoint.entityId
|
|
604
|
-
//
|
|
605
|
-
|
|
606
|
-
resource = { fields: [] }
|
|
607
|
-
}
|
|
608
|
-
let selectStatement: string | (string | Knex.Raw)[] = "*"
|
|
609
|
-
// handle select
|
|
610
|
-
if (resource.fields && resource.fields.length > 0) {
|
|
611
|
-
// select the resources as the format "table.columnName" - this is what is provided
|
|
612
|
-
// by the resource builder further up
|
|
613
|
-
selectStatement = generateSelectStatement(json, knex)
|
|
614
|
-
}
|
|
615
|
-
let foundLimit = limit || BASE_LIMIT
|
|
633
|
+
// start building the query
|
|
634
|
+
let query = this.knexWithAlias(knex, endpoint, tableAliases)
|
|
616
635
|
// handle pagination
|
|
617
636
|
let foundOffset: number | null = null
|
|
637
|
+
let foundLimit = limits?.query || limits?.base
|
|
618
638
|
if (paginate && paginate.page && paginate.limit) {
|
|
619
639
|
// @ts-ignore
|
|
620
640
|
const page = paginate.page <= 1 ? 0 : paginate.page - 1
|
|
@@ -627,24 +647,39 @@ class InternalBuilder {
|
|
|
627
647
|
} else if (paginate && paginate.limit) {
|
|
628
648
|
foundLimit = paginate.limit
|
|
629
649
|
}
|
|
630
|
-
//
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
650
|
+
// counting should not sort, limit or offset
|
|
651
|
+
if (!counting) {
|
|
652
|
+
// add the found limit if supplied
|
|
653
|
+
if (foundLimit != null) {
|
|
654
|
+
query = query.limit(foundLimit)
|
|
655
|
+
}
|
|
656
|
+
// add overall pagination
|
|
657
|
+
if (foundOffset != null) {
|
|
658
|
+
query = query.offset(foundOffset)
|
|
659
|
+
}
|
|
660
|
+
// add sorting to pre-query
|
|
661
|
+
// no point in sorting when counting
|
|
662
|
+
query = this.addSorting(query, json)
|
|
635
663
|
}
|
|
664
|
+
// add filters to the query (where)
|
|
636
665
|
query = this.addFilters(query, filters, json.meta.table, {
|
|
637
666
|
aliases: tableAliases,
|
|
638
667
|
})
|
|
639
668
|
|
|
640
|
-
// add sorting to pre-query
|
|
641
|
-
query = this.addSorting(query, json)
|
|
642
669
|
const alias = tableAliases?.[tableName] || tableName
|
|
643
|
-
let preQuery = knex({
|
|
644
|
-
|
|
645
|
-
|
|
670
|
+
let preQuery: Knex.QueryBuilder = knex({
|
|
671
|
+
// the typescript definition for the knex constructor doesn't support this
|
|
672
|
+
// syntax, but it is the only way to alias a pre-query result as part of
|
|
673
|
+
// a query - there is an alias dictionary type, but it assumes it can only
|
|
674
|
+
// be a table name, not a pre-query
|
|
675
|
+
[alias]: query as any,
|
|
676
|
+
})
|
|
677
|
+
// if counting, use distinct count, else select
|
|
678
|
+
preQuery = !counting
|
|
679
|
+
? preQuery.select(generateSelectStatement(json, knex))
|
|
680
|
+
: this.addDistinctCount(preQuery, json)
|
|
646
681
|
// have to add after as well (this breaks MS-SQL)
|
|
647
|
-
if (this.client !== SqlClient.MS_SQL) {
|
|
682
|
+
if (this.client !== SqlClient.MS_SQL && !counting) {
|
|
648
683
|
preQuery = this.addSorting(preQuery, json)
|
|
649
684
|
}
|
|
650
685
|
// handle joins
|
|
@@ -655,6 +690,13 @@ class InternalBuilder {
|
|
|
655
690
|
endpoint.schema,
|
|
656
691
|
tableAliases
|
|
657
692
|
)
|
|
693
|
+
|
|
694
|
+
// add a base limit over the whole query
|
|
695
|
+
// if counting we can't set this limit
|
|
696
|
+
if (limits?.base) {
|
|
697
|
+
query = query.limit(limits.base)
|
|
698
|
+
}
|
|
699
|
+
|
|
658
700
|
return this.addFilters(query, filters, json.meta.table, {
|
|
659
701
|
relationship: true,
|
|
660
702
|
aliases: tableAliases,
|
|
@@ -699,6 +741,19 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
|
|
699
741
|
this.limit = limit
|
|
700
742
|
}
|
|
701
743
|
|
|
744
|
+
private convertToNative(query: Knex.QueryBuilder, opts: QueryOptions = {}) {
|
|
745
|
+
const sqlClient = this.getSqlClient()
|
|
746
|
+
if (opts?.disableBindings) {
|
|
747
|
+
return { sql: query.toString() }
|
|
748
|
+
} else {
|
|
749
|
+
let native = getNativeSql(query)
|
|
750
|
+
if (sqlClient === SqlClient.SQL_LITE) {
|
|
751
|
+
native = convertBooleans(native)
|
|
752
|
+
}
|
|
753
|
+
return native
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
702
757
|
/**
|
|
703
758
|
* @param json The JSON query DSL which is to be converted to SQL.
|
|
704
759
|
* @param opts extra options which are to be passed into the query builder, e.g. disableReturning
|
|
@@ -722,7 +777,16 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
|
|
722
777
|
query = builder.create(client, json, opts)
|
|
723
778
|
break
|
|
724
779
|
case Operation.READ:
|
|
725
|
-
query = builder.read(client, json,
|
|
780
|
+
query = builder.read(client, json, {
|
|
781
|
+
limits: {
|
|
782
|
+
query: this.limit,
|
|
783
|
+
base: BASE_LIMIT,
|
|
784
|
+
},
|
|
785
|
+
})
|
|
786
|
+
break
|
|
787
|
+
case Operation.COUNT:
|
|
788
|
+
// read without any limits to count
|
|
789
|
+
query = builder.read(client, json)
|
|
726
790
|
break
|
|
727
791
|
case Operation.UPDATE:
|
|
728
792
|
query = builder.update(client, json, opts)
|
|
@@ -744,15 +808,7 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
|
|
744
808
|
throw `Operation type is not supported by SQL query builder`
|
|
745
809
|
}
|
|
746
810
|
|
|
747
|
-
|
|
748
|
-
return { sql: query.toString() }
|
|
749
|
-
} else {
|
|
750
|
-
let native = getNativeSql(query)
|
|
751
|
-
if (sqlClient === SqlClient.SQL_LITE) {
|
|
752
|
-
native = convertBooleans(native)
|
|
753
|
-
}
|
|
754
|
-
return native
|
|
755
|
-
}
|
|
811
|
+
return this.convertToNative(query, opts)
|
|
756
812
|
}
|
|
757
813
|
|
|
758
814
|
async getReturningRow(queryFn: QueryFunction, json: QueryJson) {
|
|
@@ -828,6 +884,9 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
|
|
828
884
|
await this.getReturningRow(queryFn, this.checkLookupKeys(id, json))
|
|
829
885
|
)
|
|
830
886
|
}
|
|
887
|
+
if (operation === Operation.COUNT) {
|
|
888
|
+
return results
|
|
889
|
+
}
|
|
831
890
|
if (operation !== Operation.READ) {
|
|
832
891
|
return row
|
|
833
892
|
}
|
package/src/sql/sqlTable.ts
CHANGED
|
@@ -109,8 +109,10 @@ function generateSchema(
|
|
|
109
109
|
const { tableName } = breakExternalTableId(column.tableId)
|
|
110
110
|
// @ts-ignore
|
|
111
111
|
const relatedTable = tables[tableName]
|
|
112
|
-
if (!relatedTable) {
|
|
113
|
-
throw new Error(
|
|
112
|
+
if (!relatedTable || !relatedTable.primary) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
"Referenced table doesn't exist or has no primary keys"
|
|
115
|
+
)
|
|
114
116
|
}
|
|
115
117
|
const relatedPrimary = relatedTable.primary[0]
|
|
116
118
|
const externalType = relatedTable.schema[relatedPrimary].externalType
|
package/src/sql/utils.ts
CHANGED
|
@@ -55,10 +55,7 @@ export function buildExternalTableId(datasourceId: string, tableName: string) {
|
|
|
55
55
|
return `${datasourceId}${DOUBLE_SEPARATOR}${tableName}`
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
export function breakExternalTableId(tableId: string
|
|
59
|
-
if (!tableId) {
|
|
60
|
-
return {}
|
|
61
|
-
}
|
|
58
|
+
export function breakExternalTableId(tableId: string) {
|
|
62
59
|
const parts = tableId.split(DOUBLE_SEPARATOR)
|
|
63
60
|
let datasourceId = parts.shift()
|
|
64
61
|
// if they need joined
|
|
@@ -67,6 +64,9 @@ export function breakExternalTableId(tableId: string | undefined) {
|
|
|
67
64
|
if (tableName.includes(ENCODED_SPACE)) {
|
|
68
65
|
tableName = decodeURIComponent(tableName)
|
|
69
66
|
}
|
|
67
|
+
if (!datasourceId || !tableName) {
|
|
68
|
+
throw new Error("Unable to get datasource/table name from table ID")
|
|
69
|
+
}
|
|
70
70
|
return { datasourceId, tableName }
|
|
71
71
|
}
|
|
72
72
|
|