Merge branch 'master' into fix-oidc-error-logging
This commit is contained in:
commit
c190a9983f
|
@ -64,10 +64,11 @@ jobs:
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
|
|
||||||
# Run build all the projects
|
# Run build all the projects
|
||||||
- name: Build
|
- name: Build OSS
|
||||||
run: |
|
run: yarn build:oss
|
||||||
yarn build:oss
|
- name: Build account portal
|
||||||
yarn build:account-portal
|
run: yarn build:account-portal
|
||||||
|
if: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
||||||
# Check the types of the projects built via esbuild
|
# Check the types of the projects built via esbuild
|
||||||
- name: Check types
|
- name: Check types
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -51,11 +51,11 @@ http {
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
|
|
||||||
set $csp_default "default-src 'self'";
|
set $csp_default "default-src 'self'";
|
||||||
set $csp_script "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.budibase.net https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io https://d2l5prqdbvm3op.cloudfront.net";
|
set $csp_script "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.budibase.net https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io https://d2l5prqdbvm3op.cloudfront.net https://us-assets.i.posthog.com";
|
||||||
set $csp_style "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me https://maxcdn.bootstrapcdn.com";
|
set $csp_style "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me https://maxcdn.bootstrapcdn.com";
|
||||||
set $csp_object "object-src 'none'";
|
set $csp_object "object-src 'none'";
|
||||||
set $csp_base_uri "base-uri 'self'";
|
set $csp_base_uri "base-uri 'self'";
|
||||||
set $csp_connect "connect-src 'self' https://*.budibase.app https://*.budibaseqa.app https://*.budibase.net https://api-iam.intercom.io https://api-iam.intercom.io https://api-ping.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io https://nexus-websocket-a.intercom.io https://nexus-websocket-b.intercom.io https://uploads.intercomcdn.com https://uploads.intercomusercontent.com https://*.amazonaws.com https://*.s3.amazonaws.com https://*.s3.us-east-2.amazonaws.com https://*.s3.us-east-1.amazonaws.com https://*.s3.us-west-1.amazonaws.com https://*.s3.us-west-2.amazonaws.com https://*.s3.af-south-1.amazonaws.com https://*.s3.ap-east-1.amazonaws.com https://*.s3.ap-southeast-3.amazonaws.com https://*.s3.ap-south-1.amazonaws.com https://*.s3.ap-northeast-3.amazonaws.com https://*.s3.ap-northeast-2.amazonaws.com https://*.s3.ap-southeast-1.amazonaws.com https://*.s3.ap-southeast-2.amazonaws.com https://*.s3.ap-northeast-1.amazonaws.com https://*.s3.ca-central-1.amazonaws.com https://*.s3.cn-north-1.amazonaws.com https://*.s3.cn-northwest-1.amazonaws.com https://*.s3.eu-central-1.amazonaws.com https://*.s3.eu-west-1.amazonaws.com https://*.s3.eu-west-2.amazonaws.com https://*.s3.eu-south-1.amazonaws.com https://*.s3.eu-west-3.amazonaws.com https://*.s3.eu-north-1.amazonaws.com https://*.s3.sa-east-1.amazonaws.com https://*.s3.me-south-1.amazonaws.com https://*.s3.us-gov-east-1.amazonaws.com https://*.s3.us-gov-west-1.amazonaws.com https://api.github.com";
|
set $csp_connect "connect-src 'self' https://*.budibase.app https://*.budibaseqa.app https://*.budibase.net https://api-iam.intercom.io https://api-iam.intercom.io https://api-ping.intercom.io https://app.posthog.com https://us.i.posthog.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io https://nexus-websocket-a.intercom.io https://nexus-websocket-b.intercom.io https://uploads.intercomcdn.com https://uploads.intercomusercontent.com https://*.amazonaws.com https://*.s3.amazonaws.com https://*.s3.us-east-2.amazonaws.com https://*.s3.us-east-1.amazonaws.com https://*.s3.us-west-1.amazonaws.com https://*.s3.us-west-2.amazonaws.com https://*.s3.af-south-1.amazonaws.com https://*.s3.ap-east-1.amazonaws.com https://*.s3.ap-southeast-3.amazonaws.com https://*.s3.ap-south-1.amazonaws.com https://*.s3.ap-northeast-3.amazonaws.com https://*.s3.ap-northeast-2.amazonaws.com https://*.s3.ap-southeast-1.amazonaws.com https://*.s3.ap-southeast-2.amazonaws.com https://*.s3.ap-northeast-1.amazonaws.com https://*.s3.ca-central-1.amazonaws.com https://*.s3.cn-north-1.amazonaws.com https://*.s3.cn-northwest-1.amazonaws.com https://*.s3.eu-central-1.amazonaws.com https://*.s3.eu-west-1.amazonaws.com https://*.s3.eu-west-2.amazonaws.com https://*.s3.eu-south-1.amazonaws.com https://*.s3.eu-west-3.amazonaws.com https://*.s3.eu-north-1.amazonaws.com https://*.s3.sa-east-1.amazonaws.com https://*.s3.me-south-1.amazonaws.com https://*.s3.us-gov-east-1.amazonaws.com https://*.s3.us-gov-west-1.amazonaws.com https://api.github.com";
|
||||||
set $csp_font "font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me https://maxcdn.bootstrapcdn.com https://js.intercomcdn.com https://fonts.intercomcdn.com";
|
set $csp_font "font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me https://maxcdn.bootstrapcdn.com https://js.intercomcdn.com https://fonts.intercomcdn.com";
|
||||||
set $csp_frame "frame-src 'self' https:";
|
set $csp_frame "frame-src 'self' https:";
|
||||||
set $csp_img "img-src http: https: data: blob:";
|
set $csp_img "img-src http: https: data: blob:";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.23.0",
|
"version": "2.23.4",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
isDocument,
|
isDocument,
|
||||||
RowResponse,
|
RowResponse,
|
||||||
RowValue,
|
RowValue,
|
||||||
|
SqlQueryBinding,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { getCouchInfo } from "./connections"
|
import { getCouchInfo } from "./connections"
|
||||||
import { directCouchUrlCall } from "./utils"
|
import { directCouchUrlCall } from "./utils"
|
||||||
|
@ -248,14 +249,20 @@ export class DatabaseImpl implements Database {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async sql<T extends Document>(sql: string): Promise<T[]> {
|
async sql<T extends Document>(
|
||||||
|
sql: string,
|
||||||
|
parameters?: SqlQueryBinding
|
||||||
|
): Promise<T[]> {
|
||||||
const dbName = this.name
|
const dbName = this.name
|
||||||
const url = `/${dbName}/${SQLITE_DESIGN_DOC_ID}`
|
const url = `/${dbName}/${SQLITE_DESIGN_DOC_ID}`
|
||||||
const response = await directCouchUrlCall({
|
const response = await directCouchUrlCall({
|
||||||
url: `${this.couchInfo.sqlUrl}/${url}`,
|
url: `${this.couchInfo.sqlUrl}/${url}`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
cookie: this.couchInfo.cookie,
|
cookie: this.couchInfo.cookie,
|
||||||
body: sql,
|
body: {
|
||||||
|
query: sql,
|
||||||
|
args: parameters,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if (response.status > 300) {
|
if (response.status > 300) {
|
||||||
throw new Error(await response.text())
|
throw new Error(await response.text())
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
DatabaseQueryOpts,
|
DatabaseQueryOpts,
|
||||||
Document,
|
Document,
|
||||||
RowValue,
|
RowValue,
|
||||||
|
SqlQueryBinding,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import tracer from "dd-trace"
|
import tracer from "dd-trace"
|
||||||
import { Writable } from "stream"
|
import { Writable } from "stream"
|
||||||
|
@ -150,10 +151,13 @@ export class DDInstrumentedDatabase implements Database {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
sql<T extends Document>(sql: string): Promise<T[]> {
|
sql<T extends Document>(
|
||||||
|
sql: string,
|
||||||
|
parameters?: SqlQueryBinding
|
||||||
|
): Promise<T[]> {
|
||||||
return tracer.trace("db.sql", span => {
|
return tracer.trace("db.sql", span => {
|
||||||
span?.addTags({ db_name: this.name })
|
span?.addTags({ db_name: this.name })
|
||||||
return this.db.sql(sql)
|
return this.db.sql(sql, parameters)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,8 +137,12 @@
|
||||||
const activeTag = document.activeElement?.tagName.toLowerCase()
|
const activeTag = document.activeElement?.tagName.toLowerCase()
|
||||||
const inCodeEditor =
|
const inCodeEditor =
|
||||||
document.activeElement?.classList?.contains("cm-content")
|
document.activeElement?.classList?.contains("cm-content")
|
||||||
|
const inPosthogSurvey =
|
||||||
|
document.activeElement?.classList?.[0]?.startsWith("PostHogSurvey")
|
||||||
if (
|
if (
|
||||||
(inCodeEditor || ["input", "textarea"].indexOf(activeTag) !== -1) &&
|
(inCodeEditor ||
|
||||||
|
inPosthogSurvey ||
|
||||||
|
["input", "textarea"].indexOf(activeTag) !== -1) &&
|
||||||
e.key !== "Escape"
|
e.key !== "Escape"
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -853,6 +853,7 @@
|
||||||
"array",
|
"array",
|
||||||
"datetime",
|
"datetime",
|
||||||
"attachment",
|
"attachment",
|
||||||
|
"attachment_single",
|
||||||
"link",
|
"link",
|
||||||
"formula",
|
"formula",
|
||||||
"auto",
|
"auto",
|
||||||
|
@ -1059,6 +1060,7 @@
|
||||||
"array",
|
"array",
|
||||||
"datetime",
|
"datetime",
|
||||||
"attachment",
|
"attachment",
|
||||||
|
"attachment_single",
|
||||||
"link",
|
"link",
|
||||||
"formula",
|
"formula",
|
||||||
"auto",
|
"auto",
|
||||||
|
@ -1276,6 +1278,7 @@
|
||||||
"array",
|
"array",
|
||||||
"datetime",
|
"datetime",
|
||||||
"attachment",
|
"attachment",
|
||||||
|
"attachment_single",
|
||||||
"link",
|
"link",
|
||||||
"formula",
|
"formula",
|
||||||
"auto",
|
"auto",
|
||||||
|
@ -1752,7 +1755,7 @@
|
||||||
},
|
},
|
||||||
"fuzzy": {
|
"fuzzy": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "A fuzzy search, only supported by internal tables."
|
"description": "Searches for a sub-string within a string column, e.g. searching for 'dib' will match 'Budibase'."
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -1786,6 +1789,36 @@
|
||||||
"oneOf": {
|
"oneOf": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2]."
|
"description": "Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2]."
|
||||||
|
},
|
||||||
|
"contains": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Searches for a value, or set of values in array column types (such as a multi-select). If an array of search options is provided then it must match all.",
|
||||||
|
"example": {
|
||||||
|
"arrayColumn": [
|
||||||
|
"a",
|
||||||
|
"b"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notContains": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "The logical inverse of contains. Only works on array column types. If an array of values is passed, the row must not match any of them to be returned in the response.",
|
||||||
|
"example": {
|
||||||
|
"arrayColumn": [
|
||||||
|
"a",
|
||||||
|
"b"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"containsAny": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "As with the contains search, only works on array column types and searches for any of the provided values when given an array.",
|
||||||
|
"example": {
|
||||||
|
"arrayColumn": [
|
||||||
|
"a",
|
||||||
|
"b"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -775,6 +775,7 @@ components:
|
||||||
- array
|
- array
|
||||||
- datetime
|
- datetime
|
||||||
- attachment
|
- attachment
|
||||||
|
- attachment_single
|
||||||
- link
|
- link
|
||||||
- formula
|
- formula
|
||||||
- auto
|
- auto
|
||||||
|
@ -940,6 +941,7 @@ components:
|
||||||
- array
|
- array
|
||||||
- datetime
|
- datetime
|
||||||
- attachment
|
- attachment
|
||||||
|
- attachment_single
|
||||||
- link
|
- link
|
||||||
- formula
|
- formula
|
||||||
- auto
|
- auto
|
||||||
|
@ -1112,6 +1114,7 @@ components:
|
||||||
- array
|
- array
|
||||||
- datetime
|
- datetime
|
||||||
- attachment
|
- attachment
|
||||||
|
- attachment_single
|
||||||
- link
|
- link
|
||||||
- formula
|
- formula
|
||||||
- auto
|
- auto
|
||||||
|
@ -1492,7 +1495,8 @@ components:
|
||||||
description: The value to search for in the column.
|
description: The value to search for in the column.
|
||||||
fuzzy:
|
fuzzy:
|
||||||
type: object
|
type: object
|
||||||
description: A fuzzy search, only supported by internal tables.
|
description: Searches for a sub-string within a string column, e.g. searching
|
||||||
|
for 'dib' will match 'Budibase'.
|
||||||
range:
|
range:
|
||||||
type: object
|
type: object
|
||||||
description: Searches within a range, the format of this must be in the format
|
description: Searches within a range, the format of this must be in the format
|
||||||
|
@ -1524,6 +1528,32 @@ components:
|
||||||
description: Searches for rows which have a column value that is any of the
|
description: Searches for rows which have a column value that is any of the
|
||||||
specified values. The format of this must be columnName ->
|
specified values. The format of this must be columnName ->
|
||||||
[value1, value2].
|
[value1, value2].
|
||||||
|
contains:
|
||||||
|
type: object
|
||||||
|
description: Searches for a value, or set of values in array column types (such
|
||||||
|
as a multi-select). If an array of search options is provided
|
||||||
|
then it must match all.
|
||||||
|
example:
|
||||||
|
arrayColumn:
|
||||||
|
- a
|
||||||
|
- b
|
||||||
|
notContains:
|
||||||
|
type: object
|
||||||
|
description: The logical inverse of contains. Only works on array column types.
|
||||||
|
If an array of values is passed, the row must not match any of
|
||||||
|
them to be returned in the response.
|
||||||
|
example:
|
||||||
|
arrayColumn:
|
||||||
|
- a
|
||||||
|
- b
|
||||||
|
containsAny:
|
||||||
|
type: object
|
||||||
|
description: As with the contains search, only works on array column types and
|
||||||
|
searches for any of the provided values when given an array.
|
||||||
|
example:
|
||||||
|
arrayColumn:
|
||||||
|
- a
|
||||||
|
- b
|
||||||
paginate:
|
paginate:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Enables pagination, by default this is disabled.
|
description: Enables pagination, by default this is disabled.
|
||||||
|
|
|
@ -27,7 +27,8 @@ export default new Resource().setSchemas({
|
||||||
},
|
},
|
||||||
fuzzy: {
|
fuzzy: {
|
||||||
type: "object",
|
type: "object",
|
||||||
description: "A fuzzy search, only supported by internal tables.",
|
description:
|
||||||
|
"Searches for a sub-string within a string column, e.g. searching for 'dib' will match 'Budibase'.",
|
||||||
},
|
},
|
||||||
range: {
|
range: {
|
||||||
type: "object",
|
type: "object",
|
||||||
|
@ -67,6 +68,30 @@ export default new Resource().setSchemas({
|
||||||
description:
|
description:
|
||||||
"Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2].",
|
"Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2].",
|
||||||
},
|
},
|
||||||
|
contains: {
|
||||||
|
type: "object",
|
||||||
|
description:
|
||||||
|
"Searches for a value, or set of values in array column types (such as a multi-select). If an array of search options is provided then it must match all.",
|
||||||
|
example: {
|
||||||
|
arrayColumn: ["a", "b"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
notContains: {
|
||||||
|
type: "object",
|
||||||
|
description:
|
||||||
|
"The logical inverse of contains. Only works on array column types. If an array of values is passed, the row must not match any of them to be returned in the response.",
|
||||||
|
example: {
|
||||||
|
arrayColumn: ["a", "b"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
containsAny: {
|
||||||
|
type: "object",
|
||||||
|
description:
|
||||||
|
"As with the contains search, only works on array column types and searches for any of the provided values when given an array.",
|
||||||
|
example: {
|
||||||
|
arrayColumn: ["a", "b"],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
paginate: {
|
paginate: {
|
||||||
|
|
|
@ -17,11 +17,9 @@ import {
|
||||||
Row,
|
Row,
|
||||||
Table,
|
Table,
|
||||||
UserCtx,
|
UserCtx,
|
||||||
EmptyFilterOption,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import * as utils from "./utils"
|
import * as utils from "./utils"
|
||||||
import { dataFilters } from "@budibase/shared-core"
|
|
||||||
import {
|
import {
|
||||||
inputProcessing,
|
inputProcessing,
|
||||||
outputProcessing,
|
outputProcessing,
|
||||||
|
@ -33,17 +31,6 @@ export async function handleRequest<T extends Operation>(
|
||||||
tableId: string,
|
tableId: string,
|
||||||
opts?: RunConfig
|
opts?: RunConfig
|
||||||
): Promise<ExternalRequestReturnType<T>> {
|
): Promise<ExternalRequestReturnType<T>> {
|
||||||
// make sure the filters are cleaned up, no empty strings for equals, fuzzy or string
|
|
||||||
if (opts && opts.filters) {
|
|
||||||
opts.filters = sdk.rows.removeEmptyFilters(opts.filters)
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!dataFilters.hasFilters(opts?.filters) &&
|
|
||||||
opts?.filters?.onEmptyFilter === EmptyFilterOption.RETURN_NONE
|
|
||||||
) {
|
|
||||||
return [] as any
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ExternalRequest<T>(operation, tableId, opts?.datasource).run(
|
return new ExternalRequest<T>(operation, tableId, opts?.datasource).run(
|
||||||
opts || {}
|
opts || {}
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,9 +6,12 @@ import {
|
||||||
Datasource,
|
Datasource,
|
||||||
EmptyFilterOption,
|
EmptyFilterOption,
|
||||||
FieldType,
|
FieldType,
|
||||||
|
RowSearchParams,
|
||||||
SearchFilters,
|
SearchFilters,
|
||||||
Table,
|
Table,
|
||||||
|
TableSchema,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
import _ from "lodash"
|
||||||
|
|
||||||
jest.unmock("mssql")
|
jest.unmock("mssql")
|
||||||
|
|
||||||
|
@ -24,8 +27,8 @@ describe.each([
|
||||||
const config = setup.getConfig()
|
const config = setup.getConfig()
|
||||||
|
|
||||||
let envCleanup: (() => void) | undefined
|
let envCleanup: (() => void) | undefined
|
||||||
let table: Table
|
|
||||||
let datasource: Datasource | undefined
|
let datasource: Datasource | undefined
|
||||||
|
let table: Table
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
if (isSqs) {
|
if (isSqs) {
|
||||||
|
@ -46,237 +49,217 @@ describe.each([
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("strings", () => {
|
async function createTable(schema: TableSchema) {
|
||||||
beforeEach(async () => {
|
table = await config.api.table.save(
|
||||||
table = await config.api.table.save(
|
tableForDatasource(datasource, { schema })
|
||||||
tableForDatasource(datasource, {
|
)
|
||||||
schema: {
|
}
|
||||||
name: {
|
|
||||||
name: "name",
|
|
||||||
type: FieldType.STRING,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const rows = [{ name: "foo" }, { name: "bar" }]
|
async function createRows(rows: Record<string, any>[]) {
|
||||||
|
await Promise.all(rows.map(r => config.api.row.save(table._id!, r)))
|
||||||
|
}
|
||||||
|
|
||||||
interface StringSearchTest {
|
class SearchAssertion {
|
||||||
query: SearchFilters
|
constructor(private readonly query: RowSearchParams) {}
|
||||||
expected: (typeof rows)[number][]
|
|
||||||
}
|
|
||||||
|
|
||||||
const stringSearchTests: StringSearchTest[] = [
|
async toFind(expectedRows: any[]) {
|
||||||
{ query: {}, expected: rows },
|
const { rows: foundRows } = await config.api.row.search(table._id!, {
|
||||||
{
|
...this.query,
|
||||||
query: { onEmptyFilter: EmptyFilterOption.RETURN_ALL },
|
tableId: table._id!,
|
||||||
expected: rows,
|
})
|
||||||
},
|
|
||||||
{
|
|
||||||
query: { onEmptyFilter: EmptyFilterOption.RETURN_NONE },
|
|
||||||
expected: [],
|
|
||||||
},
|
|
||||||
{ query: { string: { name: "foo" } }, expected: [rows[0]] },
|
|
||||||
{ query: { string: { name: "none" } }, expected: [] },
|
|
||||||
{ query: { fuzzy: { name: "oo" } }, expected: [rows[0]] },
|
|
||||||
{ query: { equal: { name: "foo" } }, expected: [rows[0]] },
|
|
||||||
{ query: { notEqual: { name: "foo" } }, expected: [rows[1]] },
|
|
||||||
{ query: { oneOf: { name: ["foo"] } }, expected: [rows[0]] },
|
|
||||||
// { query: { contains: { name: "f" } }, expected: [0] },
|
|
||||||
// { query: { notContains: { name: ["f"] } }, expected: [1] },
|
|
||||||
// { query: { containsAny: { name: ["f"] } }, expected: [0] },
|
|
||||||
]
|
|
||||||
|
|
||||||
it.each(stringSearchTests)(
|
// eslint-disable-next-line jest/no-standalone-expect
|
||||||
`should be able to run query: $query`,
|
expect(foundRows).toHaveLength(expectedRows.length)
|
||||||
async ({ query, expected }) => {
|
// eslint-disable-next-line jest/no-standalone-expect
|
||||||
const savedRows = await Promise.all(
|
expect(foundRows).toEqual(
|
||||||
rows.map(r => config.api.row.save(table._id!, r))
|
expect.arrayContaining(
|
||||||
)
|
expectedRows.map((expectedRow: any) =>
|
||||||
const { rows: foundRows } = await config.api.row.search(table._id!, {
|
expect.objectContaining(
|
||||||
tableId: table._id!,
|
foundRows.find(foundRow => _.isMatch(foundRow, expectedRow))
|
||||||
query,
|
|
||||||
})
|
|
||||||
expect(foundRows).toEqual(
|
|
||||||
expect.arrayContaining(
|
|
||||||
expected.map(r =>
|
|
||||||
expect.objectContaining(savedRows.find(sr => sr.name === r.name)!)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
async toFindNothing() {
|
||||||
|
await this.toFind([])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectSearch(query: Omit<RowSearchParams, "tableId">) {
|
||||||
|
return new SearchAssertion({ ...query, tableId: table._id! })
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectQuery(query: SearchFilters) {
|
||||||
|
return expectSearch({ query })
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("strings", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await createTable({
|
||||||
|
name: { name: "name", type: FieldType.STRING },
|
||||||
|
})
|
||||||
|
await createRows([{ name: "foo" }, { name: "bar" }])
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("misc", () => {
|
||||||
|
it("should return all if no query is passed", () =>
|
||||||
|
expectSearch({} as RowSearchParams).toFind([
|
||||||
|
{ name: "foo" },
|
||||||
|
{ name: "bar" },
|
||||||
|
]))
|
||||||
|
|
||||||
|
it("should return all if empty query is passed", () =>
|
||||||
|
expectQuery({}).toFind([{ name: "foo" }, { name: "bar" }]))
|
||||||
|
|
||||||
|
it("should return all if onEmptyFilter is RETURN_ALL", () =>
|
||||||
|
expectQuery({
|
||||||
|
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
|
||||||
|
}).toFind([{ name: "foo" }, { name: "bar" }]))
|
||||||
|
|
||||||
|
it("should return nothing if onEmptyFilter is RETURN_NONE", () =>
|
||||||
|
expectQuery({
|
||||||
|
onEmptyFilter: EmptyFilterOption.RETURN_NONE,
|
||||||
|
}).toFindNothing())
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("equal", () => {
|
||||||
|
it("successfully finds a row", () =>
|
||||||
|
expectQuery({ equal: { name: "foo" } }).toFind([{ name: "foo" }]))
|
||||||
|
|
||||||
|
it("fails to find nonexistent row", () =>
|
||||||
|
expectQuery({ equal: { name: "none" } }).toFindNothing())
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("notEqual", () => {
|
||||||
|
it("successfully finds a row", () =>
|
||||||
|
expectQuery({ notEqual: { name: "foo" } }).toFind([{ name: "bar" }]))
|
||||||
|
|
||||||
|
it("fails to find nonexistent row", () =>
|
||||||
|
expectQuery({ notEqual: { name: "bar" } }).toFind([{ name: "foo" }]))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("oneOf", () => {
|
||||||
|
it("successfully finds a row", () =>
|
||||||
|
expectQuery({ oneOf: { name: ["foo"] } }).toFind([{ name: "foo" }]))
|
||||||
|
|
||||||
|
it("fails to find nonexistent row", () =>
|
||||||
|
expectQuery({ oneOf: { name: ["none"] } }).toFindNothing())
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("fuzzy", () => {
|
||||||
|
it("successfully finds a row", () =>
|
||||||
|
expectQuery({ fuzzy: { name: "oo" } }).toFind([{ name: "foo" }]))
|
||||||
|
|
||||||
|
it("fails to find nonexistent row", () =>
|
||||||
|
expectQuery({ fuzzy: { name: "none" } }).toFindNothing())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("number", () => {
|
describe("numbers", () => {
|
||||||
beforeEach(async () => {
|
beforeAll(async () => {
|
||||||
table = await config.api.table.save(
|
await createTable({
|
||||||
tableForDatasource(datasource, {
|
age: { name: "age", type: FieldType.NUMBER },
|
||||||
schema: {
|
})
|
||||||
age: {
|
await createRows([{ age: 1 }, { age: 10 }])
|
||||||
name: "age",
|
|
||||||
type: FieldType.NUMBER,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const rows = [{ age: 1 }, { age: 10 }]
|
describe("equal", () => {
|
||||||
|
it("successfully finds a row", () =>
|
||||||
|
expectQuery({ equal: { age: 1 } }).toFind([{ age: 1 }]))
|
||||||
|
|
||||||
interface NumberSearchTest {
|
it("fails to find nonexistent row", () =>
|
||||||
query: SearchFilters
|
expectQuery({ equal: { age: 2 } }).toFindNothing())
|
||||||
expected: (typeof rows)[number][]
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const numberSearchTests: NumberSearchTest[] = [
|
describe("notEqual", () => {
|
||||||
{ query: {}, expected: rows },
|
it("successfully finds a row", () =>
|
||||||
{
|
expectQuery({ notEqual: { age: 1 } }).toFind([{ age: 10 }]))
|
||||||
query: { onEmptyFilter: EmptyFilterOption.RETURN_ALL },
|
|
||||||
expected: rows,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
query: { onEmptyFilter: EmptyFilterOption.RETURN_NONE },
|
|
||||||
expected: [],
|
|
||||||
},
|
|
||||||
{ query: { equal: { age: 1 } }, expected: [rows[0]] },
|
|
||||||
{ query: { equal: { age: 2 } }, expected: [] },
|
|
||||||
{ query: { notEqual: { age: 1 } }, expected: [rows[1]] },
|
|
||||||
{ query: { oneOf: { age: [1] } }, expected: [rows[0]] },
|
|
||||||
{ query: { range: { age: { low: 1, high: 5 } } }, expected: [rows[0]] },
|
|
||||||
{ query: { range: { age: { low: 0, high: 1 } } }, expected: [rows[0]] },
|
|
||||||
{ query: { range: { age: { low: 3, high: 4 } } }, expected: [] },
|
|
||||||
{ query: { range: { age: { low: 0, high: 11 } } }, expected: rows },
|
|
||||||
]
|
|
||||||
|
|
||||||
it.each(numberSearchTests)(
|
it("fails to find nonexistent row", () =>
|
||||||
`should be able to run query: $query`,
|
expectQuery({ notEqual: { age: 10 } }).toFind([{ age: 1 }]))
|
||||||
async ({ query, expected }) => {
|
})
|
||||||
const savedRows = await Promise.all(
|
|
||||||
rows.map(r => config.api.row.save(table._id!, r))
|
describe("oneOf", () => {
|
||||||
)
|
it("successfully finds a row", () =>
|
||||||
const { rows: foundRows } = await config.api.row.search(table._id!, {
|
expectQuery({ oneOf: { age: [1] } }).toFind([{ age: 1 }]))
|
||||||
tableId: table._id!,
|
|
||||||
query,
|
it("fails to find nonexistent row", () =>
|
||||||
})
|
expectQuery({ oneOf: { age: [2] } }).toFindNothing())
|
||||||
expect(foundRows).toEqual(
|
})
|
||||||
expect.arrayContaining(
|
|
||||||
expected.map(r =>
|
describe("range", () => {
|
||||||
expect.objectContaining(savedRows.find(sr => sr.age === r.age)!)
|
it("successfully finds a row", () =>
|
||||||
)
|
expectQuery({
|
||||||
)
|
range: { age: { low: 1, high: 5 } },
|
||||||
)
|
}).toFind([{ age: 1 }]))
|
||||||
}
|
|
||||||
)
|
it("successfully finds multiple rows", () =>
|
||||||
|
expectQuery({
|
||||||
|
range: { age: { low: 1, high: 10 } },
|
||||||
|
}).toFind([{ age: 1 }, { age: 10 }]))
|
||||||
|
|
||||||
|
it("successfully finds a row with a high bound", () =>
|
||||||
|
expectQuery({
|
||||||
|
range: { age: { low: 5, high: 10 } },
|
||||||
|
}).toFind([{ age: 10 }]))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("dates", () => {
|
describe("dates", () => {
|
||||||
beforeEach(async () => {
|
const JAN_1ST = "2020-01-01T00:00:00.000Z"
|
||||||
table = await config.api.table.save(
|
const JAN_2ND = "2020-01-02T00:00:00.000Z"
|
||||||
tableForDatasource(datasource, {
|
const JAN_5TH = "2020-01-05T00:00:00.000Z"
|
||||||
schema: {
|
const JAN_10TH = "2020-01-10T00:00:00.000Z"
|
||||||
dob: {
|
|
||||||
name: "dob",
|
beforeAll(async () => {
|
||||||
type: FieldType.DATETIME,
|
await createTable({
|
||||||
},
|
dob: { name: "dob", type: FieldType.DATETIME },
|
||||||
},
|
})
|
||||||
})
|
|
||||||
)
|
await createRows([{ dob: JAN_1ST }, { dob: JAN_10TH }])
|
||||||
})
|
})
|
||||||
|
|
||||||
const rows = [
|
describe("equal", () => {
|
||||||
{ dob: new Date("2020-01-01") },
|
it("successfully finds a row", () =>
|
||||||
{ dob: new Date("2020-01-10") },
|
expectQuery({ equal: { dob: JAN_1ST } }).toFind([{ dob: JAN_1ST }]))
|
||||||
]
|
|
||||||
|
|
||||||
interface DateSearchTest {
|
it("fails to find nonexistent row", () =>
|
||||||
query: SearchFilters
|
expectQuery({ equal: { dob: JAN_2ND } }).toFindNothing())
|
||||||
expected: (typeof rows)[number][]
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const dateSearchTests: DateSearchTest[] = [
|
describe("notEqual", () => {
|
||||||
{ query: {}, expected: rows },
|
it("successfully finds a row", () =>
|
||||||
{
|
expectQuery({ notEqual: { dob: JAN_1ST } }).toFind([{ dob: JAN_10TH }]))
|
||||||
query: { onEmptyFilter: EmptyFilterOption.RETURN_ALL },
|
|
||||||
expected: rows,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
query: { onEmptyFilter: EmptyFilterOption.RETURN_NONE },
|
|
||||||
expected: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
query: { equal: { dob: new Date("2020-01-01") } },
|
|
||||||
expected: [rows[0]],
|
|
||||||
},
|
|
||||||
{ query: { equal: { dob: new Date("2020-01-02") } }, expected: [] },
|
|
||||||
{
|
|
||||||
query: { notEqual: { dob: new Date("2020-01-01") } },
|
|
||||||
expected: [rows[1]],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
query: { oneOf: { dob: [new Date("2020-01-01")] } },
|
|
||||||
expected: [rows[0]],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
query: {
|
|
||||||
range: {
|
|
||||||
dob: {
|
|
||||||
low: new Date("2020-01-01").toISOString(),
|
|
||||||
high: new Date("2020-01-05").toISOString(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: [rows[0]],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
query: {
|
|
||||||
range: {
|
|
||||||
dob: {
|
|
||||||
low: new Date("2020-01-01").toISOString(),
|
|
||||||
high: new Date("2020-01-10").toISOString(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: rows,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
query: {
|
|
||||||
range: {
|
|
||||||
dob: {
|
|
||||||
low: new Date("2020-01-05").toISOString(),
|
|
||||||
high: new Date("2020-01-10").toISOString(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: [rows[1]],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
it.each(dateSearchTests)(
|
it("fails to find nonexistent row", () =>
|
||||||
`should be able to run query: $query`,
|
expectQuery({ notEqual: { dob: JAN_10TH } }).toFind([{ dob: JAN_1ST }]))
|
||||||
async ({ query, expected }) => {
|
})
|
||||||
// TODO(samwho): most of these work for SQS, but not all. Fix 'em.
|
|
||||||
if (isSqs) {
|
describe("oneOf", () => {
|
||||||
return
|
it("successfully finds a row", () =>
|
||||||
}
|
expectQuery({ oneOf: { dob: [JAN_1ST] } }).toFind([{ dob: JAN_1ST }]))
|
||||||
const savedRows = await Promise.all(
|
|
||||||
rows.map(r => config.api.row.save(table._id!, r))
|
it("fails to find nonexistent row", () =>
|
||||||
)
|
expectQuery({ oneOf: { dob: [JAN_2ND] } }).toFindNothing())
|
||||||
const { rows: foundRows } = await config.api.row.search(table._id!, {
|
})
|
||||||
tableId: table._id!,
|
|
||||||
query,
|
describe("range", () => {
|
||||||
})
|
it("successfully finds a row", () =>
|
||||||
expect(foundRows).toEqual(
|
expectQuery({
|
||||||
expect.arrayContaining(
|
range: { dob: { low: JAN_1ST, high: JAN_5TH } },
|
||||||
expected.map(r =>
|
}).toFind([{ dob: JAN_1ST }]))
|
||||||
expect.objectContaining(
|
|
||||||
savedRows.find(sr => sr.dob === r.dob.toISOString())!
|
it("successfully finds multiple rows", () =>
|
||||||
)
|
expectQuery({
|
||||||
)
|
range: { dob: { low: JAN_1ST, high: JAN_10TH } },
|
||||||
)
|
}).toFind([{ dob: JAN_1ST }, { dob: JAN_10TH }]))
|
||||||
)
|
|
||||||
}
|
it("successfully finds a row with a high bound", () =>
|
||||||
)
|
expectQuery({
|
||||||
|
range: { dob: { low: JAN_5TH, high: JAN_10TH } },
|
||||||
|
}).toFind([{ dob: JAN_10TH }]))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -273,6 +273,7 @@ export interface components {
|
||||||
| "array"
|
| "array"
|
||||||
| "datetime"
|
| "datetime"
|
||||||
| "attachment"
|
| "attachment"
|
||||||
|
| "attachment_single"
|
||||||
| "link"
|
| "link"
|
||||||
| "formula"
|
| "formula"
|
||||||
| "auto"
|
| "auto"
|
||||||
|
@ -381,6 +382,7 @@ export interface components {
|
||||||
| "array"
|
| "array"
|
||||||
| "datetime"
|
| "datetime"
|
||||||
| "attachment"
|
| "attachment"
|
||||||
|
| "attachment_single"
|
||||||
| "link"
|
| "link"
|
||||||
| "formula"
|
| "formula"
|
||||||
| "auto"
|
| "auto"
|
||||||
|
@ -491,6 +493,7 @@ export interface components {
|
||||||
| "array"
|
| "array"
|
||||||
| "datetime"
|
| "datetime"
|
||||||
| "attachment"
|
| "attachment"
|
||||||
|
| "attachment_single"
|
||||||
| "link"
|
| "link"
|
||||||
| "formula"
|
| "formula"
|
||||||
| "auto"
|
| "auto"
|
||||||
|
@ -693,7 +696,7 @@ export interface components {
|
||||||
* @example [object Object]
|
* @example [object Object]
|
||||||
*/
|
*/
|
||||||
string?: { [key: string]: string };
|
string?: { [key: string]: string };
|
||||||
/** @description A fuzzy search, only supported by internal tables. */
|
/** @description Searches for a sub-string within a string column, e.g. searching for 'dib' will match 'Budibase'. */
|
||||||
fuzzy?: { [key: string]: unknown };
|
fuzzy?: { [key: string]: unknown };
|
||||||
/**
|
/**
|
||||||
* @description Searches within a range, the format of this must be in the format of an object with a "low" and "high" property.
|
* @description Searches within a range, the format of this must be in the format of an object with a "low" and "high" property.
|
||||||
|
@ -713,6 +716,21 @@ export interface components {
|
||||||
notEmpty?: { [key: string]: unknown };
|
notEmpty?: { [key: string]: unknown };
|
||||||
/** @description Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2]. */
|
/** @description Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2]. */
|
||||||
oneOf?: { [key: string]: unknown };
|
oneOf?: { [key: string]: unknown };
|
||||||
|
/**
|
||||||
|
* @description Searches for a value, or set of values in array column types (such as a multi-select). If an array of search options is provided then it must match all.
|
||||||
|
* @example [object Object]
|
||||||
|
*/
|
||||||
|
contains?: { [key: string]: unknown };
|
||||||
|
/**
|
||||||
|
* @description The logical inverse of contains. Only works on array column types. If an array of values is passed, the row must not match any of them to be returned in the response.
|
||||||
|
* @example [object Object]
|
||||||
|
*/
|
||||||
|
notContains?: { [key: string]: unknown };
|
||||||
|
/**
|
||||||
|
* @description As with the contains search, only works on array column types and searches for any of the provided values when given an array.
|
||||||
|
* @example [object Object]
|
||||||
|
*/
|
||||||
|
containsAny?: { [key: string]: unknown };
|
||||||
};
|
};
|
||||||
/** @description Enables pagination, by default this is disabled. */
|
/** @description Enables pagination, by default this is disabled. */
|
||||||
paginate?: boolean;
|
paginate?: boolean;
|
||||||
|
|
|
@ -334,6 +334,7 @@ class InternalBuilder {
|
||||||
if (filters.containsAny) {
|
if (filters.containsAny) {
|
||||||
contains(filters.containsAny, true)
|
contains(filters.containsAny, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
EmptyFilterOption,
|
||||||
Row,
|
Row,
|
||||||
RowSearchParams,
|
RowSearchParams,
|
||||||
SearchFilters,
|
SearchFilters,
|
||||||
|
@ -11,6 +12,7 @@ import { NoEmptyFilterStrings } from "../../../constants"
|
||||||
import * as sqs from "./search/sqs"
|
import * as sqs from "./search/sqs"
|
||||||
import env from "../../../environment"
|
import env from "../../../environment"
|
||||||
import { ExportRowsParams, ExportRowsResult } from "./search/types"
|
import { ExportRowsParams, ExportRowsResult } from "./search/types"
|
||||||
|
import { dataFilters } from "@budibase/shared-core"
|
||||||
|
|
||||||
export { isValidFilter } from "../../../integrations/utils"
|
export { isValidFilter } from "../../../integrations/utils"
|
||||||
|
|
||||||
|
@ -60,6 +62,16 @@ export async function search(
|
||||||
options: RowSearchParams
|
options: RowSearchParams
|
||||||
): Promise<SearchResponse<Row>> {
|
): Promise<SearchResponse<Row>> {
|
||||||
const isExternalTable = isExternalTableID(options.tableId)
|
const isExternalTable = isExternalTableID(options.tableId)
|
||||||
|
options.query = removeEmptyFilters(options.query || {})
|
||||||
|
if (
|
||||||
|
!dataFilters.hasFilters(options.query) &&
|
||||||
|
options.query.onEmptyFilter === EmptyFilterOption.RETURN_NONE
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
rows: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isExternalTable) {
|
if (isExternalTable) {
|
||||||
return external.search(options)
|
return external.search(options)
|
||||||
} else if (env.SQS_SEARCH_ENABLE) {
|
} else if (env.SQS_SEARCH_ENABLE) {
|
||||||
|
|
|
@ -156,21 +156,21 @@ export async function search(
|
||||||
try {
|
try {
|
||||||
const query = builder._query(request, {
|
const query = builder._query(request, {
|
||||||
disableReturning: true,
|
disableReturning: true,
|
||||||
disableBindings: true,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (Array.isArray(query)) {
|
if (Array.isArray(query)) {
|
||||||
throw new Error("SQS cannot currently handle multiple queries")
|
throw new Error("SQS cannot currently handle multiple queries")
|
||||||
}
|
}
|
||||||
|
|
||||||
let sql = query.sql
|
let sql = query.sql,
|
||||||
|
bindings = query.bindings
|
||||||
|
|
||||||
// quick hack for docIds
|
// quick hack for docIds
|
||||||
sql = sql.replace(/`doc1`.`rowId`/g, "`doc1.rowId`")
|
sql = sql.replace(/`doc1`.`rowId`/g, "`doc1.rowId`")
|
||||||
sql = sql.replace(/`doc2`.`rowId`/g, "`doc2.rowId`")
|
sql = sql.replace(/`doc2`.`rowId`/g, "`doc2.rowId`")
|
||||||
|
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const rows = await db.sql<Row>(sql)
|
const rows = await db.sql<Row>(sql, bindings)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rows: await sqlOutputProcessing(
|
rows: await sqlOutputProcessing(
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
AnyDocument,
|
AnyDocument,
|
||||||
Document,
|
Document,
|
||||||
RowValue,
|
RowValue,
|
||||||
|
SqlQueryBinding,
|
||||||
ViewTemplateOpts,
|
ViewTemplateOpts,
|
||||||
} from "../"
|
} from "../"
|
||||||
import { Writable } from "stream"
|
import { Writable } from "stream"
|
||||||
|
@ -143,7 +144,10 @@ export interface Database {
|
||||||
opts?: DatabasePutOpts
|
opts?: DatabasePutOpts
|
||||||
): Promise<Nano.DocumentInsertResponse>
|
): Promise<Nano.DocumentInsertResponse>
|
||||||
bulkDocs(documents: AnyDocument[]): Promise<Nano.DocumentBulkResponse[]>
|
bulkDocs(documents: AnyDocument[]): Promise<Nano.DocumentBulkResponse[]>
|
||||||
sql<T extends Document>(sql: string): Promise<T[]>
|
sql<T extends Document>(
|
||||||
|
sql: string,
|
||||||
|
parameters?: SqlQueryBinding
|
||||||
|
): Promise<T[]>
|
||||||
allDocs<T extends Document | RowValue>(
|
allDocs<T extends Document | RowValue>(
|
||||||
params: DatabaseQueryOpts
|
params: DatabaseQueryOpts
|
||||||
): Promise<AllDocsResponse<T>>
|
): Promise<AllDocsResponse<T>>
|
||||||
|
|
Loading…
Reference in New Issue