Refactor InternalBuilder to give me more access to query state.
This commit is contained in:
parent
ad414b982e
commit
e1ef66bf56
|
@ -34,6 +34,8 @@ import {
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import environment from "../environment"
|
import environment from "../environment"
|
||||||
import { helpers } from "@budibase/shared-core"
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
import { isPlainObject } from "lodash"
|
||||||
|
import { ColumnSplitter } from "@budibase/shared-core/src/filters"
|
||||||
|
|
||||||
type QueryFunction = (query: SqlQuery | SqlQuery[], operation: Operation) => any
|
type QueryFunction = (query: SqlQuery | SqlQuery[], operation: Operation) => any
|
||||||
|
|
||||||
|
@ -72,9 +74,15 @@ function convertBooleans(query: SqlQuery | SqlQuery[]): SqlQuery | SqlQuery[] {
|
||||||
|
|
||||||
class InternalBuilder {
|
class InternalBuilder {
|
||||||
private readonly client: SqlClient
|
private readonly client: SqlClient
|
||||||
|
private readonly query: QueryJson
|
||||||
|
|
||||||
constructor(client: SqlClient) {
|
constructor(client: SqlClient, query: QueryJson) {
|
||||||
this.client = client
|
this.client = client
|
||||||
|
this.query = query
|
||||||
|
}
|
||||||
|
|
||||||
|
get table(): Table {
|
||||||
|
return this.query.meta.table
|
||||||
}
|
}
|
||||||
|
|
||||||
// Takes a string like foo and returns a quoted string like [foo] for SQL Server
|
// Takes a string like foo and returns a quoted string like [foo] for SQL Server
|
||||||
|
@ -101,11 +109,8 @@ class InternalBuilder {
|
||||||
.join(".")
|
.join(".")
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateSelectStatement(
|
private generateSelectStatement(knex: Knex): (string | Knex.Raw)[] | "*" {
|
||||||
json: QueryJson,
|
const { resource, meta } = this.query
|
||||||
knex: Knex
|
|
||||||
): (string | Knex.Raw)[] | "*" {
|
|
||||||
const { resource, meta } = json
|
|
||||||
const client = knex.client.config.client as SqlClient
|
const client = knex.client.config.client as SqlClient
|
||||||
|
|
||||||
if (!resource || !resource.fields || resource.fields.length === 0) {
|
if (!resource || !resource.fields || resource.fields.length === 0) {
|
||||||
|
@ -183,10 +188,10 @@ class InternalBuilder {
|
||||||
// OracleDB can't use character-large-objects (CLOBs) in WHERE clauses,
|
// OracleDB can't use character-large-objects (CLOBs) in WHERE clauses,
|
||||||
// so when we use them we need to wrap them in to_char(). This function
|
// so when we use them we need to wrap them in to_char(). This function
|
||||||
// converts a field name to the appropriate identifier.
|
// converts a field name to the appropriate identifier.
|
||||||
private convertClobs(table: Table, field: string): string {
|
private convertClobs(field: string): string {
|
||||||
const parts = field.split(".")
|
const parts = field.split(".")
|
||||||
const col = parts.pop()!
|
const col = parts.pop()!
|
||||||
const schema = table.schema[col]
|
const schema = this.table.schema[col]
|
||||||
let identifier = this.quotedIdentifier(field)
|
let identifier = this.quotedIdentifier(field)
|
||||||
if (
|
if (
|
||||||
schema.type === FieldType.STRING ||
|
schema.type === FieldType.STRING ||
|
||||||
|
@ -201,54 +206,60 @@ class InternalBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private parse(input: any, schema: FieldSchema) {
|
private parse(input: any, schema: FieldSchema) {
|
||||||
|
if (input == undefined) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPlainObject(input)) {
|
||||||
|
for (const [key, value] of Object.entries(input)) {
|
||||||
|
input[key] = this.parse(value, schema)
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
if (schema.type === FieldType.DATETIME && schema.timeOnly) {
|
if (schema.type === FieldType.DATETIME && schema.timeOnly) {
|
||||||
if (this.client === SqlClient.ORACLE) {
|
if (this.client === SqlClient.ORACLE) {
|
||||||
return new Date(`1970-01-01 ${input}`)
|
return new Date(`1970-01-01 ${input}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(input)) {
|
if (typeof input === "string") {
|
||||||
return JSON.stringify(input)
|
|
||||||
}
|
|
||||||
if (input == undefined) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
if (typeof input !== "string") {
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
if (isInvalidISODateString(input)) {
|
if (isInvalidISODateString(input)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (isValidISODateString(input)) {
|
if (isValidISODateString(input)) {
|
||||||
return new Date(input.trim())
|
return new Date(input.trim())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseBody(body: any, table: Table) {
|
private parseBody(body: any) {
|
||||||
for (let [key, value] of Object.entries(body)) {
|
for (let [key, value] of Object.entries(body)) {
|
||||||
body[key] = this.parse(value, table.schema[key])
|
body[key] = this.parse(value, this.table.schema[key])
|
||||||
}
|
}
|
||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseFilters(
|
private parseFilters(filters: SearchFilters | undefined): SearchFilters {
|
||||||
filters: SearchFilters | undefined,
|
|
||||||
table: Table
|
|
||||||
): SearchFilters {
|
|
||||||
if (!filters) {
|
if (!filters) {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
for (let [key, value] of Object.entries(filters)) {
|
|
||||||
let parsed
|
for (const [_, filter] of Object.entries(filters)) {
|
||||||
if (typeof value === "object") {
|
for (const [key, value] of Object.entries(filter)) {
|
||||||
parsed = this.parseFilters(value, table)
|
const { column } = new ColumnSplitter([this.table]).run(key)
|
||||||
} else {
|
const schema = this.table.schema[column]
|
||||||
parsed = this.parse(value, table.schema[key])
|
if (!schema) {
|
||||||
|
throw new Error(
|
||||||
|
`Column ${key} does not exist in table ${this.table._id}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
filter[key] = this.parse(value, schema)
|
||||||
filters[key] = parsed
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return filters
|
return filters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,28 +267,26 @@ class InternalBuilder {
|
||||||
addFilters(
|
addFilters(
|
||||||
query: Knex.QueryBuilder,
|
query: Knex.QueryBuilder,
|
||||||
filters: SearchFilters | undefined,
|
filters: SearchFilters | undefined,
|
||||||
table: Table,
|
opts?: {
|
||||||
opts: {
|
|
||||||
aliases?: Record<string, string>
|
|
||||||
relationship?: boolean
|
relationship?: boolean
|
||||||
columnPrefix?: string
|
|
||||||
}
|
}
|
||||||
): Knex.QueryBuilder {
|
): Knex.QueryBuilder {
|
||||||
if (!filters) {
|
if (!filters) {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
filters = this.parseFilters(filters, table)
|
filters = this.parseFilters(filters)
|
||||||
|
const aliases = this.query.tableAliases
|
||||||
// if all or specified in filters, then everything is an or
|
// if all or specified in filters, then everything is an or
|
||||||
const allOr = filters.allOr
|
const allOr = filters.allOr
|
||||||
const sqlStatements = new SqlStatements(this.client, table, {
|
const sqlStatements = new SqlStatements(this.client, this.table, {
|
||||||
allOr,
|
allOr,
|
||||||
columnPrefix: opts.columnPrefix,
|
columnPrefix: this.query.meta.columnPrefix,
|
||||||
})
|
})
|
||||||
const tableName =
|
const tableName =
|
||||||
this.client === SqlClient.SQL_LITE ? table._id! : table.name
|
this.client === SqlClient.SQL_LITE ? this.table._id! : this.table.name
|
||||||
|
|
||||||
function getTableAlias(name: string) {
|
function getTableAlias(name: string) {
|
||||||
const alias = opts.aliases?.[name]
|
const alias = aliases?.[name]
|
||||||
return alias || name
|
return alias || name
|
||||||
}
|
}
|
||||||
function iterate(
|
function iterate(
|
||||||
|
@ -303,10 +312,10 @@ class InternalBuilder {
|
||||||
),
|
),
|
||||||
castedTypeValue.values
|
castedTypeValue.values
|
||||||
)
|
)
|
||||||
} else if (!opts.relationship && !isRelationshipField) {
|
} else if (!opts?.relationship && !isRelationshipField) {
|
||||||
const alias = getTableAlias(tableName)
|
const alias = getTableAlias(tableName)
|
||||||
fn(alias ? `${alias}.${updatedKey}` : updatedKey, value)
|
fn(alias ? `${alias}.${updatedKey}` : updatedKey, value)
|
||||||
} else if (opts.relationship && isRelationshipField) {
|
} else if (opts?.relationship && isRelationshipField) {
|
||||||
const [filterTableName, property] = updatedKey.split(".")
|
const [filterTableName, property] = updatedKey.split(".")
|
||||||
const alias = getTableAlias(filterTableName)
|
const alias = getTableAlias(filterTableName)
|
||||||
fn(alias ? `${alias}.${property}` : property, value)
|
fn(alias ? `${alias}.${property}` : property, value)
|
||||||
|
@ -394,7 +403,7 @@ class InternalBuilder {
|
||||||
filters.oneOf,
|
filters.oneOf,
|
||||||
(key: string, array) => {
|
(key: string, array) => {
|
||||||
if (this.client === SqlClient.ORACLE) {
|
if (this.client === SqlClient.ORACLE) {
|
||||||
key = this.convertClobs(table, key)
|
key = this.convertClobs(key)
|
||||||
array = Array.isArray(array) ? array : [array]
|
array = Array.isArray(array) ? array : [array]
|
||||||
const binding = new Array(array.length).fill("?").join(",")
|
const binding = new Array(array.length).fill("?").join(",")
|
||||||
query = query.whereRaw(`${key} IN (${binding})`, array)
|
query = query.whereRaw(`${key} IN (${binding})`, array)
|
||||||
|
@ -460,7 +469,7 @@ class InternalBuilder {
|
||||||
[value]
|
[value]
|
||||||
)
|
)
|
||||||
} else if (this.client === SqlClient.ORACLE) {
|
} else if (this.client === SqlClient.ORACLE) {
|
||||||
const identifier = this.convertClobs(table, key)
|
const identifier = this.convertClobs(key)
|
||||||
query = query[fnc](
|
query = query[fnc](
|
||||||
`(${identifier} IS NOT NULL AND ${identifier} = ?)`,
|
`(${identifier} IS NOT NULL AND ${identifier} = ?)`,
|
||||||
[value]
|
[value]
|
||||||
|
@ -482,7 +491,7 @@ class InternalBuilder {
|
||||||
[value]
|
[value]
|
||||||
)
|
)
|
||||||
} else if (this.client === SqlClient.ORACLE) {
|
} else if (this.client === SqlClient.ORACLE) {
|
||||||
const identifier = this.convertClobs(table, key)
|
const identifier = this.convertClobs(key)
|
||||||
query = query[fnc](
|
query = query[fnc](
|
||||||
`(${identifier} IS NOT NULL AND ${identifier} != ?)`,
|
`(${identifier} IS NOT NULL AND ${identifier} != ?)`,
|
||||||
[value]
|
[value]
|
||||||
|
@ -517,9 +526,9 @@ class InternalBuilder {
|
||||||
contains(filters.containsAny, true)
|
contains(filters.containsAny, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableRef = opts?.aliases?.[table._id!] || table._id
|
const tableRef = aliases?.[this.table._id!] || this.table._id
|
||||||
// when searching internal tables make sure long looking for rows
|
// when searching internal tables make sure long looking for rows
|
||||||
if (filters.documentType && !isExternalTable(table) && tableRef) {
|
if (filters.documentType && !isExternalTable(this.table) && tableRef) {
|
||||||
// has to be its own option, must always be AND onto the search
|
// has to be its own option, must always be AND onto the search
|
||||||
query.andWhereLike(
|
query.andWhereLike(
|
||||||
`${tableRef}._id`,
|
`${tableRef}._id`,
|
||||||
|
@ -530,29 +539,26 @@ class InternalBuilder {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
addDistinctCount(
|
addDistinctCount(query: Knex.QueryBuilder): Knex.QueryBuilder {
|
||||||
query: Knex.QueryBuilder,
|
const primary = this.table.primary
|
||||||
json: QueryJson
|
const aliases = this.query.tableAliases
|
||||||
): Knex.QueryBuilder {
|
|
||||||
const table = json.meta.table
|
|
||||||
const primary = table.primary
|
|
||||||
const aliases = json.tableAliases
|
|
||||||
const aliased =
|
const aliased =
|
||||||
table.name && aliases?.[table.name] ? aliases[table.name] : table.name
|
this.table.name && aliases?.[this.table.name]
|
||||||
|
? aliases[this.table.name]
|
||||||
|
: this.table.name
|
||||||
if (!primary) {
|
if (!primary) {
|
||||||
throw new Error("SQL counting requires primary key to be supplied")
|
throw new Error("SQL counting requires primary key to be supplied")
|
||||||
}
|
}
|
||||||
return query.countDistinct(`${aliased}.${primary[0]} as total`)
|
return query.countDistinct(`${aliased}.${primary[0]} as total`)
|
||||||
}
|
}
|
||||||
|
|
||||||
addSorting(query: Knex.QueryBuilder, json: QueryJson): Knex.QueryBuilder {
|
addSorting(query: Knex.QueryBuilder): Knex.QueryBuilder {
|
||||||
let { sort } = json
|
let { sort } = this.query
|
||||||
const table = json.meta.table
|
const primaryKey = this.table.primary
|
||||||
const primaryKey = table.primary
|
const tableName = getTableName(this.table)
|
||||||
const tableName = getTableName(table)
|
const aliases = this.query.tableAliases
|
||||||
const aliases = json.tableAliases
|
|
||||||
const aliased =
|
const aliased =
|
||||||
tableName && aliases?.[tableName] ? aliases[tableName] : table?.name
|
tableName && aliases?.[tableName] ? aliases[tableName] : this.table?.name
|
||||||
if (!Array.isArray(primaryKey)) {
|
if (!Array.isArray(primaryKey)) {
|
||||||
throw new Error("Sorting requires primary key to be specified for table")
|
throw new Error("Sorting requires primary key to be specified for table")
|
||||||
}
|
}
|
||||||
|
@ -667,26 +673,28 @@ class InternalBuilder {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
knexWithAlias(
|
qualifiedKnex(
|
||||||
knex: Knex,
|
knex: Knex,
|
||||||
endpoint: QueryJson["endpoint"],
|
opts?: { alias?: string | boolean }
|
||||||
aliases?: QueryJson["tableAliases"]
|
|
||||||
): Knex.QueryBuilder {
|
): Knex.QueryBuilder {
|
||||||
const tableName = endpoint.entityId
|
let alias = this.query.tableAliases?.[this.query.endpoint.entityId]
|
||||||
const tableAlias = aliases?.[tableName]
|
if (opts?.alias === false) {
|
||||||
|
alias = undefined
|
||||||
|
} else if (typeof opts?.alias === "string") {
|
||||||
|
alias = opts.alias
|
||||||
|
}
|
||||||
return knex(
|
return knex(
|
||||||
this.tableNameWithSchema(tableName, {
|
this.tableNameWithSchema(this.query.endpoint.entityId, {
|
||||||
alias: tableAlias,
|
alias,
|
||||||
schema: endpoint.schema,
|
schema: this.query.endpoint.schema,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
create(knex: Knex, json: QueryJson, opts: QueryOptions): Knex.QueryBuilder {
|
create(knex: Knex, opts: QueryOptions): Knex.QueryBuilder {
|
||||||
const { endpoint, body } = json
|
const { body } = this.query
|
||||||
let query = this.knexWithAlias(knex, endpoint)
|
let query = this.qualifiedKnex(knex, { alias: false })
|
||||||
const parsedBody = this.parseBody(body, json.meta.table)
|
const parsedBody = this.parseBody(body)
|
||||||
// make sure no null values in body for creation
|
// make sure no null values in body for creation
|
||||||
for (let [key, value] of Object.entries(parsedBody)) {
|
for (let [key, value] of Object.entries(parsedBody)) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
@ -702,29 +710,29 @@ class InternalBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bulkCreate(knex: Knex, json: QueryJson): Knex.QueryBuilder {
|
bulkCreate(knex: Knex): Knex.QueryBuilder {
|
||||||
const { endpoint, body } = json
|
const { body } = this.query
|
||||||
let query = this.knexWithAlias(knex, endpoint)
|
let query = this.qualifiedKnex(knex, { alias: false })
|
||||||
if (!Array.isArray(body)) {
|
if (!Array.isArray(body)) {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
const parsedBody = body.map(row => this.parseBody(row, json.meta.table))
|
const parsedBody = body.map(row => this.parseBody(row))
|
||||||
return query.insert(parsedBody)
|
return query.insert(parsedBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
bulkUpsert(knex: Knex, json: QueryJson): Knex.QueryBuilder {
|
bulkUpsert(knex: Knex): Knex.QueryBuilder {
|
||||||
const { endpoint, body } = json
|
const { body } = this.query
|
||||||
let query = this.knexWithAlias(knex, endpoint)
|
let query = this.qualifiedKnex(knex, { alias: false })
|
||||||
if (!Array.isArray(body)) {
|
if (!Array.isArray(body)) {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
const parsedBody = body.map(row => this.parseBody(row, json.meta.table))
|
const parsedBody = body.map(row => this.parseBody(row))
|
||||||
if (
|
if (
|
||||||
this.client === SqlClient.POSTGRES ||
|
this.client === SqlClient.POSTGRES ||
|
||||||
this.client === SqlClient.SQL_LITE ||
|
this.client === SqlClient.SQL_LITE ||
|
||||||
this.client === SqlClient.MY_SQL
|
this.client === SqlClient.MY_SQL
|
||||||
) {
|
) {
|
||||||
const primary = json.meta.table.primary
|
const primary = this.table.primary
|
||||||
if (!primary) {
|
if (!primary) {
|
||||||
throw new Error("Primary key is required for upsert")
|
throw new Error("Primary key is required for upsert")
|
||||||
}
|
}
|
||||||
|
@ -743,18 +751,18 @@ class InternalBuilder {
|
||||||
|
|
||||||
read(
|
read(
|
||||||
knex: Knex,
|
knex: Knex,
|
||||||
json: QueryJson,
|
|
||||||
opts: {
|
opts: {
|
||||||
limits?: { base: number; query: number }
|
limits?: { base: number; query: number }
|
||||||
} = {}
|
} = {}
|
||||||
): Knex.QueryBuilder {
|
): Knex.QueryBuilder {
|
||||||
let { endpoint, filters, paginate, relationships, tableAliases } = json
|
let { endpoint, filters, paginate, relationships, tableAliases } =
|
||||||
|
this.query
|
||||||
const { limits } = opts
|
const { limits } = opts
|
||||||
const counting = endpoint.operation === Operation.COUNT
|
const counting = endpoint.operation === Operation.COUNT
|
||||||
|
|
||||||
const tableName = endpoint.entityId
|
const tableName = endpoint.entityId
|
||||||
// start building the query
|
// start building the query
|
||||||
let query = this.knexWithAlias(knex, endpoint, tableAliases)
|
let query = this.qualifiedKnex(knex)
|
||||||
// handle pagination
|
// handle pagination
|
||||||
let foundOffset: number | null = null
|
let foundOffset: number | null = null
|
||||||
let foundLimit = limits?.query || limits?.base
|
let foundLimit = limits?.query || limits?.base
|
||||||
|
@ -782,13 +790,10 @@ class InternalBuilder {
|
||||||
}
|
}
|
||||||
// add sorting to pre-query
|
// add sorting to pre-query
|
||||||
// no point in sorting when counting
|
// no point in sorting when counting
|
||||||
query = this.addSorting(query, json)
|
query = this.addSorting(query)
|
||||||
}
|
}
|
||||||
// add filters to the query (where)
|
// add filters to the query (where)
|
||||||
query = this.addFilters(query, filters, json.meta.table, {
|
query = this.addFilters(query, filters)
|
||||||
columnPrefix: json.meta.columnPrefix,
|
|
||||||
aliases: tableAliases,
|
|
||||||
})
|
|
||||||
|
|
||||||
const alias = tableAliases?.[tableName] || tableName
|
const alias = tableAliases?.[tableName] || tableName
|
||||||
let preQuery: Knex.QueryBuilder = knex({
|
let preQuery: Knex.QueryBuilder = knex({
|
||||||
|
@ -800,11 +805,11 @@ class InternalBuilder {
|
||||||
})
|
})
|
||||||
// if counting, use distinct count, else select
|
// if counting, use distinct count, else select
|
||||||
preQuery = !counting
|
preQuery = !counting
|
||||||
? preQuery.select(this.generateSelectStatement(json, knex))
|
? preQuery.select(this.generateSelectStatement(knex))
|
||||||
: this.addDistinctCount(preQuery, json)
|
: this.addDistinctCount(preQuery)
|
||||||
// have to add after as well (this breaks MS-SQL)
|
// have to add after as well (this breaks MS-SQL)
|
||||||
if (this.client !== SqlClient.MS_SQL && !counting) {
|
if (this.client !== SqlClient.MS_SQL && !counting) {
|
||||||
preQuery = this.addSorting(preQuery, json)
|
preQuery = this.addSorting(preQuery)
|
||||||
}
|
}
|
||||||
// handle joins
|
// handle joins
|
||||||
query = this.addRelationships(
|
query = this.addRelationships(
|
||||||
|
@ -821,21 +826,14 @@ class InternalBuilder {
|
||||||
query = query.limit(limits.base)
|
query = query.limit(limits.base)
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.addFilters(query, filters, json.meta.table, {
|
return this.addFilters(query, filters, { relationship: true })
|
||||||
columnPrefix: json.meta.columnPrefix,
|
|
||||||
relationship: true,
|
|
||||||
aliases: tableAliases,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update(knex: Knex, json: QueryJson, opts: QueryOptions): Knex.QueryBuilder {
|
update(knex: Knex, opts: QueryOptions): Knex.QueryBuilder {
|
||||||
const { endpoint, body, filters, tableAliases } = json
|
const { body, filters } = this.query
|
||||||
let query = this.knexWithAlias(knex, endpoint, tableAliases)
|
let query = this.qualifiedKnex(knex)
|
||||||
const parsedBody = this.parseBody(body, json.meta.table)
|
const parsedBody = this.parseBody(body)
|
||||||
query = this.addFilters(query, filters, json.meta.table, {
|
query = this.addFilters(query, filters)
|
||||||
columnPrefix: json.meta.columnPrefix,
|
|
||||||
aliases: tableAliases,
|
|
||||||
})
|
|
||||||
// mysql can't use returning
|
// mysql can't use returning
|
||||||
if (opts.disableReturning) {
|
if (opts.disableReturning) {
|
||||||
return query.update(parsedBody)
|
return query.update(parsedBody)
|
||||||
|
@ -844,18 +842,15 @@ class InternalBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(knex: Knex, json: QueryJson, opts: QueryOptions): Knex.QueryBuilder {
|
delete(knex: Knex, opts: QueryOptions): Knex.QueryBuilder {
|
||||||
const { endpoint, filters, tableAliases } = json
|
const { filters } = this.query
|
||||||
let query = this.knexWithAlias(knex, endpoint, tableAliases)
|
let query = this.qualifiedKnex(knex)
|
||||||
query = this.addFilters(query, filters, json.meta.table, {
|
query = this.addFilters(query, filters)
|
||||||
columnPrefix: json.meta.columnPrefix,
|
|
||||||
aliases: tableAliases,
|
|
||||||
})
|
|
||||||
// mysql can't use returning
|
// mysql can't use returning
|
||||||
if (opts.disableReturning) {
|
if (opts.disableReturning) {
|
||||||
return query.delete()
|
return query.delete()
|
||||||
} else {
|
} else {
|
||||||
return query.delete().returning(this.generateSelectStatement(json, knex))
|
return query.delete().returning(this.generateSelectStatement(knex))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -899,13 +894,13 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
||||||
|
|
||||||
const client = knex(config)
|
const client = knex(config)
|
||||||
let query: Knex.QueryBuilder
|
let query: Knex.QueryBuilder
|
||||||
const builder = new InternalBuilder(sqlClient)
|
const builder = new InternalBuilder(sqlClient, json)
|
||||||
switch (this._operation(json)) {
|
switch (this._operation(json)) {
|
||||||
case Operation.CREATE:
|
case Operation.CREATE:
|
||||||
query = builder.create(client, json, opts)
|
query = builder.create(client, opts)
|
||||||
break
|
break
|
||||||
case Operation.READ:
|
case Operation.READ:
|
||||||
query = builder.read(client, json, {
|
query = builder.read(client, {
|
||||||
limits: {
|
limits: {
|
||||||
query: this.limit,
|
query: this.limit,
|
||||||
base: BASE_LIMIT,
|
base: BASE_LIMIT,
|
||||||
|
@ -914,19 +909,19 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
||||||
break
|
break
|
||||||
case Operation.COUNT:
|
case Operation.COUNT:
|
||||||
// read without any limits to count
|
// read without any limits to count
|
||||||
query = builder.read(client, json)
|
query = builder.read(client)
|
||||||
break
|
break
|
||||||
case Operation.UPDATE:
|
case Operation.UPDATE:
|
||||||
query = builder.update(client, json, opts)
|
query = builder.update(client, opts)
|
||||||
break
|
break
|
||||||
case Operation.DELETE:
|
case Operation.DELETE:
|
||||||
query = builder.delete(client, json, opts)
|
query = builder.delete(client, opts)
|
||||||
break
|
break
|
||||||
case Operation.BULK_CREATE:
|
case Operation.BULK_CREATE:
|
||||||
query = builder.bulkCreate(client, json)
|
query = builder.bulkCreate(client)
|
||||||
break
|
break
|
||||||
case Operation.BULK_UPSERT:
|
case Operation.BULK_UPSERT:
|
||||||
query = builder.bulkUpsert(client, json)
|
query = builder.bulkUpsert(client)
|
||||||
break
|
break
|
||||||
case Operation.CREATE_TABLE:
|
case Operation.CREATE_TABLE:
|
||||||
case Operation.UPDATE_TABLE:
|
case Operation.UPDATE_TABLE:
|
||||||
|
|
|
@ -40,14 +40,14 @@ import { structures } from "@budibase/backend-core/tests"
|
||||||
import { DEFAULT_EMPLOYEE_TABLE_SCHEMA } from "../../../db/defaultData/datasource_bb_default"
|
import { DEFAULT_EMPLOYEE_TABLE_SCHEMA } from "../../../db/defaultData/datasource_bb_default"
|
||||||
|
|
||||||
describe.each([
|
describe.each([
|
||||||
// ["in-memory", undefined],
|
["in-memory", undefined],
|
||||||
// ["lucene", undefined],
|
["lucene", undefined],
|
||||||
// ["sqs", undefined],
|
["sqs", undefined],
|
||||||
// [DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
|
[DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
|
||||||
// [DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
|
[DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
|
||||||
// [DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
|
[DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
|
||||||
// [DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)],
|
[DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)],
|
||||||
[DatabaseName.ORACLE, getDatasource(DatabaseName.ORACLE)],
|
// [DatabaseName.ORACLE, getDatasource(DatabaseName.ORACLE)],
|
||||||
])("search (%s)", (name, dsProvider) => {
|
])("search (%s)", (name, dsProvider) => {
|
||||||
const isSqs = name === "sqs"
|
const isSqs = name === "sqs"
|
||||||
const isLucene = name === "lucene"
|
const isLucene = name === "lucene"
|
||||||
|
@ -1318,7 +1318,7 @@ describe.each([
|
||||||
})
|
})
|
||||||
|
|
||||||
!isInternal &&
|
!isInternal &&
|
||||||
describe.only("datetime - time only", () => {
|
describe("datetime - time only", () => {
|
||||||
const T_1000 = "10:00:00"
|
const T_1000 = "10:00:00"
|
||||||
const T_1045 = "10:45:00"
|
const T_1045 = "10:45:00"
|
||||||
const T_1200 = "12:00:00"
|
const T_1200 = "12:00:00"
|
||||||
|
@ -2389,9 +2389,9 @@ describe.each([
|
||||||
|
|
||||||
describe.each([
|
describe.each([
|
||||||
{ low: "2024-07-03T00:00:00.000Z", high: "9999-00-00T00:00:00.000Z" },
|
{ low: "2024-07-03T00:00:00.000Z", high: "9999-00-00T00:00:00.000Z" },
|
||||||
{ low: "2024-07-03T00:00:00.000Z", high: "9998-00-00T00:00:00.000Z" },
|
// { low: "2024-07-03T00:00:00.000Z", high: "9998-00-00T00:00:00.000Z" },
|
||||||
{ low: "0000-00-00T00:00:00.000Z", high: "2024-07-04T00:00:00.000Z" },
|
// { low: "0000-00-00T00:00:00.000Z", high: "2024-07-04T00:00:00.000Z" },
|
||||||
{ low: "0001-00-00T00:00:00.000Z", high: "2024-07-04T00:00:00.000Z" },
|
// { low: "0001-00-00T00:00:00.000Z", high: "2024-07-04T00:00:00.000Z" },
|
||||||
])("date special cases", ({ low, high }) => {
|
])("date special cases", ({ low, high }) => {
|
||||||
const earlyDate = "2024-07-03T10:00:00.000Z",
|
const earlyDate = "2024-07-03T10:00:00.000Z",
|
||||||
laterDate = "2024-07-03T11:00:00.000Z"
|
laterDate = "2024-07-03T11:00:00.000Z"
|
||||||
|
@ -2405,7 +2405,7 @@ describe.each([
|
||||||
await createRows([{ date: earlyDate }, { date: laterDate }])
|
await createRows([{ date: earlyDate }, { date: laterDate }])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should be able to handle a date search", async () => {
|
it.only("should be able to handle a date search", async () => {
|
||||||
await expectSearch({
|
await expectSearch({
|
||||||
query: {
|
query: {
|
||||||
range: {
|
range: {
|
||||||
|
@ -2418,25 +2418,25 @@ describe.each([
|
||||||
|
|
||||||
describe.each([
|
describe.each([
|
||||||
"名前", // Japanese for "name"
|
"名前", // Japanese for "name"
|
||||||
// "Benutzer-ID", // German for "user ID", includes a hyphen
|
"Benutzer-ID", // German for "user ID", includes a hyphen
|
||||||
// "numéro", // French for "number", includes an accent
|
"numéro", // French for "number", includes an accent
|
||||||
// "år", // Swedish for "year", includes a ring above
|
"år", // Swedish for "year", includes a ring above
|
||||||
// "naïve", // English word borrowed from French, includes an umlaut
|
"naïve", // English word borrowed from French, includes an umlaut
|
||||||
// "الاسم", // Arabic for "name"
|
"الاسم", // Arabic for "name"
|
||||||
// "оплата", // Russian for "payment"
|
"оплата", // Russian for "payment"
|
||||||
// "पता", // Hindi for "address"
|
"पता", // Hindi for "address"
|
||||||
// "用戶名", // Chinese for "username"
|
"用戶名", // Chinese for "username"
|
||||||
// "çalışma_zamanı", // Turkish for "runtime", includes an underscore and a cedilla
|
"çalışma_zamanı", // Turkish for "runtime", includes an underscore and a cedilla
|
||||||
// "preço", // Portuguese for "price", includes a cedilla
|
"preço", // Portuguese for "price", includes a cedilla
|
||||||
// "사용자명", // Korean for "username"
|
"사용자명", // Korean for "username"
|
||||||
// "usuario_ñoño", // Spanish, uses an underscore and includes "ñ"
|
"usuario_ñoño", // Spanish, uses an underscore and includes "ñ"
|
||||||
// "файл", // Bulgarian for "file"
|
"файл", // Bulgarian for "file"
|
||||||
// "δεδομένα", // Greek for "data"
|
"δεδομένα", // Greek for "data"
|
||||||
// "geändert_am", // German for "modified on", includes an umlaut
|
"geändert_am", // German for "modified on", includes an umlaut
|
||||||
// "ব্যবহারকারীর_নাম", // Bengali for "user name", includes an underscore
|
"ব্যবহারকারীর_নাম", // Bengali for "user name", includes an underscore
|
||||||
// "São_Paulo", // Portuguese, includes an underscore and a tilde
|
"São_Paulo", // Portuguese, includes an underscore and a tilde
|
||||||
// "età", // Italian for "age", includes an accent
|
"età", // Italian for "age", includes an accent
|
||||||
// "ชื่อผู้ใช้", // Thai for "username"
|
"ชื่อผู้ใช้", // Thai for "username"
|
||||||
])("non-ascii column name: %s", name => {
|
])("non-ascii column name: %s", name => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
table = await createTable({
|
table = await createTable({
|
||||||
|
|
|
@ -123,7 +123,8 @@ export async function search(
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.message && err.message.includes("does not exist")) {
|
if (err.message && err.message.includes("does not exist")) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Table updated externally, please re-fetch - ${err.message}`
|
`Table updated externally, please re-fetch - ${err.message}`,
|
||||||
|
{ cause: err }
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
throw err
|
throw err
|
||||||
|
|
Loading…
Reference in New Issue