@budibase/backend-core 2.22.17 → 2.23.0
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 +51 -15
- package/dist/index.js.map +2 -2
- 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/constants/db.d.ts +1 -0
- package/dist/src/constants/db.js +2 -1
- package/dist/src/constants/db.js.map +1 -1
- package/dist/src/db/couch/DatabaseImpl.d.ts +1 -0
- package/dist/src/db/couch/DatabaseImpl.js +17 -0
- package/dist/src/db/couch/DatabaseImpl.js.map +1 -1
- package/dist/src/db/couch/connections.d.ts +1 -0
- package/dist/src/db/couch/connections.js +1 -0
- package/dist/src/db/couch/connections.js.map +1 -1
- package/dist/src/db/couch/utils.js +8 -2
- package/dist/src/db/couch/utils.js.map +1 -1
- package/dist/src/db/instrumentation.d.ts +1 -0
- package/dist/src/db/instrumentation.js +6 -0
- package/dist/src/db/instrumentation.js.map +1 -1
- package/dist/src/db/lucene.d.ts +12 -71
- package/dist/src/db/lucene.js +10 -48
- package/dist/src/db/lucene.js.map +1 -1
- package/dist/src/index.d.ts +0 -1
- package/dist/src/index.js.map +1 -1
- package/dist/tests/core/utilities/testContainerUtils.js +5 -0
- package/dist/tests/core/utilities/testContainerUtils.js.map +1 -1
- package/package.json +4 -4
- package/src/constants/db.ts +1 -0
- package/src/db/couch/DatabaseImpl.ts +16 -0
- package/src/db/couch/connections.ts +1 -0
- package/src/db/couch/utils.ts +7 -2
- package/src/db/instrumentation.ts +7 -0
- package/src/db/lucene.ts +43 -78
- package/src/db/tests/lucene.spec.ts +47 -15
- package/src/index.ts +0 -1
- package/tests/core/utilities/testContainerUtils.ts +6 -0
package/src/db/lucene.ts
CHANGED
|
@@ -1,28 +1,16 @@
|
|
|
1
1
|
import fetch from "node-fetch"
|
|
2
2
|
import { getCouchInfo } from "./couch"
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
SearchFilters,
|
|
5
|
+
Row,
|
|
6
|
+
EmptyFilterOption,
|
|
7
|
+
SearchResponse,
|
|
8
|
+
SearchParams,
|
|
9
|
+
WithRequired,
|
|
10
|
+
} from "@budibase/types"
|
|
4
11
|
|
|
5
12
|
const QUERY_START_REGEX = /\d[0-9]*:/g
|
|
6
13
|
|
|
7
|
-
interface SearchResponse<T> {
|
|
8
|
-
rows: T[] | any[]
|
|
9
|
-
bookmark?: string
|
|
10
|
-
totalRows: number
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export type SearchParams<T> = {
|
|
14
|
-
tableId?: string
|
|
15
|
-
sort?: string
|
|
16
|
-
sortOrder?: string
|
|
17
|
-
sortType?: string
|
|
18
|
-
limit?: number
|
|
19
|
-
bookmark?: string
|
|
20
|
-
version?: string
|
|
21
|
-
indexer?: () => Promise<any>
|
|
22
|
-
disableEscaping?: boolean
|
|
23
|
-
rows?: T | Row[]
|
|
24
|
-
}
|
|
25
|
-
|
|
26
14
|
export function removeKeyNumbering(key: any): string {
|
|
27
15
|
if (typeof key === "string" && key.match(QUERY_START_REGEX) != null) {
|
|
28
16
|
const parts = key.split(":")
|
|
@@ -44,7 +32,7 @@ export class QueryBuilder<T> {
|
|
|
44
32
|
#query: SearchFilters
|
|
45
33
|
#limit: number
|
|
46
34
|
#sort?: string
|
|
47
|
-
#bookmark?: string
|
|
35
|
+
#bookmark?: string | number
|
|
48
36
|
#sortOrder: string
|
|
49
37
|
#sortType: string
|
|
50
38
|
#includeDocs: boolean
|
|
@@ -130,7 +118,7 @@ export class QueryBuilder<T> {
|
|
|
130
118
|
return this
|
|
131
119
|
}
|
|
132
120
|
|
|
133
|
-
setBookmark(bookmark?: string) {
|
|
121
|
+
setBookmark(bookmark?: string | number) {
|
|
134
122
|
if (bookmark != null) {
|
|
135
123
|
this.#bookmark = bookmark
|
|
136
124
|
}
|
|
@@ -226,14 +214,20 @@ export class QueryBuilder<T> {
|
|
|
226
214
|
}
|
|
227
215
|
}
|
|
228
216
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
217
|
+
preprocess(
|
|
218
|
+
value: any,
|
|
219
|
+
{
|
|
220
|
+
escape,
|
|
221
|
+
lowercase,
|
|
222
|
+
wrap,
|
|
223
|
+
type,
|
|
224
|
+
}: {
|
|
225
|
+
escape?: boolean
|
|
226
|
+
lowercase?: boolean
|
|
227
|
+
wrap?: boolean
|
|
228
|
+
type?: string
|
|
229
|
+
} = {}
|
|
230
|
+
): string | any {
|
|
237
231
|
const hasVersion = !!this.#version
|
|
238
232
|
// Determine if type needs wrapped
|
|
239
233
|
const originalType = typeof value
|
|
@@ -561,7 +555,7 @@ async function runQuery<T>(
|
|
|
561
555
|
url: string,
|
|
562
556
|
body: any,
|
|
563
557
|
cookie: string
|
|
564
|
-
): Promise<SearchResponse<T>> {
|
|
558
|
+
): Promise<WithRequired<SearchResponse<T>, "totalRows">> {
|
|
565
559
|
const response = await fetch(url, {
|
|
566
560
|
body: JSON.stringify(body),
|
|
567
561
|
method: "POST",
|
|
@@ -575,7 +569,7 @@ async function runQuery<T>(
|
|
|
575
569
|
}
|
|
576
570
|
const json = await response.json()
|
|
577
571
|
|
|
578
|
-
let output: SearchResponse<T> = {
|
|
572
|
+
let output: WithRequired<SearchResponse<T>, "totalRows"> = {
|
|
579
573
|
rows: [],
|
|
580
574
|
totalRows: 0,
|
|
581
575
|
}
|
|
@@ -613,63 +607,51 @@ async function recursiveSearch<T>(
|
|
|
613
607
|
dbName: string,
|
|
614
608
|
index: string,
|
|
615
609
|
query: any,
|
|
616
|
-
params:
|
|
610
|
+
params: SearchParams
|
|
617
611
|
): Promise<any> {
|
|
618
612
|
const bookmark = params.bookmark
|
|
619
613
|
const rows = params.rows || []
|
|
620
|
-
if (rows.length >= params.limit) {
|
|
614
|
+
if (params.limit && rows.length >= params.limit) {
|
|
621
615
|
return rows
|
|
622
616
|
}
|
|
623
617
|
let pageSize = QueryBuilder.maxLimit
|
|
624
|
-
if (rows.length > params.limit - QueryBuilder.maxLimit) {
|
|
618
|
+
if (params.limit && rows.length > params.limit - QueryBuilder.maxLimit) {
|
|
625
619
|
pageSize = params.limit - rows.length
|
|
626
620
|
}
|
|
627
|
-
const
|
|
621
|
+
const queryBuilder = new QueryBuilder<T>(dbName, index, query)
|
|
622
|
+
queryBuilder
|
|
628
623
|
.setVersion(params.version)
|
|
629
|
-
.setTable(params.tableId)
|
|
630
624
|
.setBookmark(bookmark)
|
|
631
625
|
.setLimit(pageSize)
|
|
632
626
|
.setSort(params.sort)
|
|
633
627
|
.setSortOrder(params.sortOrder)
|
|
634
628
|
.setSortType(params.sortType)
|
|
635
|
-
|
|
629
|
+
|
|
630
|
+
if (params.tableId) {
|
|
631
|
+
queryBuilder.setTable(params.tableId)
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const page = await queryBuilder.run()
|
|
636
635
|
if (!page.rows.length) {
|
|
637
636
|
return rows
|
|
638
637
|
}
|
|
639
638
|
if (page.rows.length < QueryBuilder.maxLimit) {
|
|
640
639
|
return [...rows, ...page.rows]
|
|
641
640
|
}
|
|
642
|
-
const newParams = {
|
|
641
|
+
const newParams: SearchParams = {
|
|
643
642
|
...params,
|
|
644
643
|
bookmark: page.bookmark,
|
|
645
|
-
rows: [...rows, ...page.rows],
|
|
644
|
+
rows: [...rows, ...page.rows] as Row[],
|
|
646
645
|
}
|
|
647
646
|
return await recursiveSearch(dbName, index, query, newParams)
|
|
648
647
|
}
|
|
649
648
|
|
|
650
|
-
/**
|
|
651
|
-
* Performs a paginated search. A bookmark will be returned to allow the next
|
|
652
|
-
* page to be fetched. There is a max limit off 200 results per page in a
|
|
653
|
-
* paginated search.
|
|
654
|
-
* @param dbName Which database to run a lucene query on
|
|
655
|
-
* @param index Which search index to utilise
|
|
656
|
-
* @param query The JSON query structure
|
|
657
|
-
* @param params The search params including:
|
|
658
|
-
* tableId {string} The table ID to search
|
|
659
|
-
* sort {string} The sort column
|
|
660
|
-
* sortOrder {string} The sort order ("ascending" or "descending")
|
|
661
|
-
* sortType {string} Whether to treat sortable values as strings or
|
|
662
|
-
* numbers. ("string" or "number")
|
|
663
|
-
* limit {number} The desired page size
|
|
664
|
-
* bookmark {string} The bookmark to resume from
|
|
665
|
-
* @returns {Promise<{hasNextPage: boolean, rows: *[]}>}
|
|
666
|
-
*/
|
|
667
649
|
export async function paginatedSearch<T>(
|
|
668
650
|
dbName: string,
|
|
669
651
|
index: string,
|
|
670
652
|
query: SearchFilters,
|
|
671
|
-
params: SearchParams
|
|
672
|
-
) {
|
|
653
|
+
params: SearchParams
|
|
654
|
+
): Promise<SearchResponse<T>> {
|
|
673
655
|
let limit = params.limit
|
|
674
656
|
if (limit == null || isNaN(limit) || limit < 0) {
|
|
675
657
|
limit = 50
|
|
@@ -713,29 +695,12 @@ export async function paginatedSearch<T>(
|
|
|
713
695
|
}
|
|
714
696
|
}
|
|
715
697
|
|
|
716
|
-
/**
|
|
717
|
-
* Performs a full search, fetching multiple pages if required to return the
|
|
718
|
-
* desired amount of results. There is a limit of 1000 results to avoid
|
|
719
|
-
* heavy performance hits, and to avoid client components breaking from
|
|
720
|
-
* handling too much data.
|
|
721
|
-
* @param dbName Which database to run a lucene query on
|
|
722
|
-
* @param index Which search index to utilise
|
|
723
|
-
* @param query The JSON query structure
|
|
724
|
-
* @param params The search params including:
|
|
725
|
-
* tableId {string} The table ID to search
|
|
726
|
-
* sort {string} The sort column
|
|
727
|
-
* sortOrder {string} The sort order ("ascending" or "descending")
|
|
728
|
-
* sortType {string} Whether to treat sortable values as strings or
|
|
729
|
-
* numbers. ("string" or "number")
|
|
730
|
-
* limit {number} The desired number of results
|
|
731
|
-
* @returns {Promise<{rows: *}>}
|
|
732
|
-
*/
|
|
733
698
|
export async function fullSearch<T>(
|
|
734
699
|
dbName: string,
|
|
735
700
|
index: string,
|
|
736
701
|
query: SearchFilters,
|
|
737
|
-
params: SearchParams
|
|
738
|
-
) {
|
|
702
|
+
params: SearchParams
|
|
703
|
+
): Promise<{ rows: Row[] }> {
|
|
739
704
|
let limit = params.limit
|
|
740
705
|
if (limit == null || isNaN(limit) || limit < 0) {
|
|
741
706
|
limit = 1000
|
|
@@ -1,23 +1,39 @@
|
|
|
1
1
|
import { newid } from "../../docIds/newid"
|
|
2
2
|
import { getDB } from "../db"
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
Database,
|
|
5
|
+
EmptyFilterOption,
|
|
6
|
+
SortOrder,
|
|
7
|
+
SortType,
|
|
8
|
+
DocumentType,
|
|
9
|
+
SEPARATOR,
|
|
10
|
+
} from "@budibase/types"
|
|
11
|
+
import { fullSearch, paginatedSearch, QueryBuilder } from "../lucene"
|
|
5
12
|
|
|
6
13
|
const INDEX_NAME = "main"
|
|
14
|
+
const TABLE_ID = DocumentType.TABLE + SEPARATOR + newid()
|
|
7
15
|
|
|
8
16
|
const index = `function(doc) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
if (!doc._id.startsWith("ro_")) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
let keys = Object.keys(doc).filter(key => !key.startsWith("_"))
|
|
21
|
+
for (let key of keys) {
|
|
22
|
+
const value = doc[key]
|
|
23
|
+
if (Array.isArray(value)) {
|
|
24
|
+
for (let val of value) {
|
|
13
25
|
index(key, val)
|
|
14
26
|
}
|
|
15
|
-
} else if (
|
|
16
|
-
index(key,
|
|
27
|
+
} else if (value) {
|
|
28
|
+
index(key, value)
|
|
17
29
|
}
|
|
18
30
|
}
|
|
19
31
|
}`
|
|
20
32
|
|
|
33
|
+
function rowId(id?: string) {
|
|
34
|
+
return DocumentType.ROW + SEPARATOR + (id || newid())
|
|
35
|
+
}
|
|
36
|
+
|
|
21
37
|
describe("lucene", () => {
|
|
22
38
|
let db: Database, dbName: string
|
|
23
39
|
|
|
@@ -25,10 +41,21 @@ describe("lucene", () => {
|
|
|
25
41
|
dbName = `db-${newid()}`
|
|
26
42
|
// create the DB for testing
|
|
27
43
|
db = getDB(dbName)
|
|
28
|
-
await db.put({ _id: newid(), property: "word", array: ["1", "4"] })
|
|
29
|
-
await db.put({ _id: newid(), property: "word2", array: ["3", "1"] })
|
|
30
44
|
await db.put({
|
|
31
|
-
_id:
|
|
45
|
+
_id: rowId(),
|
|
46
|
+
tableId: TABLE_ID,
|
|
47
|
+
property: "word",
|
|
48
|
+
array: ["1", "4"],
|
|
49
|
+
})
|
|
50
|
+
await db.put({
|
|
51
|
+
_id: rowId(),
|
|
52
|
+
tableId: TABLE_ID,
|
|
53
|
+
property: "word2",
|
|
54
|
+
array: ["3", "1"],
|
|
55
|
+
})
|
|
56
|
+
await db.put({
|
|
57
|
+
_id: rowId(),
|
|
58
|
+
tableId: TABLE_ID,
|
|
32
59
|
property: "word3",
|
|
33
60
|
number: 1,
|
|
34
61
|
array: ["1", "2"],
|
|
@@ -240,7 +267,8 @@ describe("lucene", () => {
|
|
|
240
267
|
docs = Array(QueryBuilder.maxLimit * 2.5)
|
|
241
268
|
.fill(0)
|
|
242
269
|
.map((_, i) => ({
|
|
243
|
-
_id: i.toString().padStart(3, "0"),
|
|
270
|
+
_id: rowId(i.toString().padStart(3, "0")),
|
|
271
|
+
tableId: TABLE_ID,
|
|
244
272
|
property: `value_${i.toString().padStart(3, "0")}`,
|
|
245
273
|
array: [],
|
|
246
274
|
}))
|
|
@@ -338,10 +366,11 @@ describe("lucene", () => {
|
|
|
338
366
|
},
|
|
339
367
|
},
|
|
340
368
|
{
|
|
369
|
+
tableId: TABLE_ID,
|
|
341
370
|
limit: 1,
|
|
342
371
|
sort: "property",
|
|
343
|
-
sortType:
|
|
344
|
-
sortOrder:
|
|
372
|
+
sortType: SortType.STRING,
|
|
373
|
+
sortOrder: SortOrder.DESCENDING,
|
|
345
374
|
}
|
|
346
375
|
)
|
|
347
376
|
expect(page.rows.length).toBe(1)
|
|
@@ -360,7 +389,10 @@ describe("lucene", () => {
|
|
|
360
389
|
property: "wo",
|
|
361
390
|
},
|
|
362
391
|
},
|
|
363
|
-
{
|
|
392
|
+
{
|
|
393
|
+
tableId: TABLE_ID,
|
|
394
|
+
query: {},
|
|
395
|
+
}
|
|
364
396
|
)
|
|
365
397
|
expect(page.rows.length).toBe(3)
|
|
366
398
|
})
|
package/src/index.ts
CHANGED
|
@@ -32,7 +32,6 @@ export { default as env } from "./environment"
|
|
|
32
32
|
export * as blacklist from "./blacklist"
|
|
33
33
|
export * as docUpdates from "./docUpdates"
|
|
34
34
|
export * from "./utils/Duration"
|
|
35
|
-
export { SearchParams } from "./db"
|
|
36
35
|
export * as docIds from "./docIds"
|
|
37
36
|
export * as security from "./security"
|
|
38
37
|
// Add context to tenancy for backwards compatibility
|
|
@@ -77,9 +77,15 @@ export function setupEnv(...envs: any[]) {
|
|
|
77
77
|
throw new Error("CouchDB port not found")
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
const couchSqlPort = getExposedV4Port(couch, 4984)
|
|
81
|
+
if (!couchSqlPort) {
|
|
82
|
+
throw new Error("CouchDB SQL port not found")
|
|
83
|
+
}
|
|
84
|
+
|
|
80
85
|
const configs = [
|
|
81
86
|
{ key: "COUCH_DB_PORT", value: `${couchPort}` },
|
|
82
87
|
{ key: "COUCH_DB_URL", value: `http://127.0.0.1:${couchPort}` },
|
|
88
|
+
{ key: "COUCH_DB_SQL_URL", value: `http://127.0.0.1:${couchSqlPort}` },
|
|
83
89
|
]
|
|
84
90
|
|
|
85
91
|
for (const config of configs.filter(x => !!x.value)) {
|