2021-06-24 19:16:48 +02:00
|
|
|
import { Knex, knex } from "knex"
|
2023-09-14 17:53:36 +02:00
|
|
|
import { db as dbCore } from "@budibase/backend-core"
|
|
|
|
import { QueryOptions } from "../../definitions/datasource"
|
2024-04-04 19:16:23 +02:00
|
|
|
import {
|
|
|
|
isIsoDateString,
|
|
|
|
SqlClient,
|
|
|
|
isValidFilter,
|
|
|
|
getNativeSql,
|
|
|
|
} from "../utils"
|
2023-09-14 17:53:36 +02:00
|
|
|
import SqlTableQueryBuilder from "./sqlTable"
|
2021-06-24 19:17:26 +02:00
|
|
|
import {
|
2024-03-13 14:38:08 +01:00
|
|
|
BBReferenceFieldMetadata,
|
2024-03-12 16:27:34 +01:00
|
|
|
FieldSchema,
|
|
|
|
FieldSubtype,
|
|
|
|
FieldType,
|
2024-03-13 14:38:08 +01:00
|
|
|
JsonFieldMetadata,
|
2021-11-24 21:55:03 +01:00
|
|
|
Operation,
|
2021-06-24 19:17:26 +02:00
|
|
|
QueryJson,
|
2024-03-06 19:07:46 +01:00
|
|
|
SqlQuery,
|
2021-06-25 19:13:11 +02:00
|
|
|
RelationshipsJson,
|
2021-10-28 20:39:42 +02:00
|
|
|
SearchFilters,
|
2021-11-24 21:55:03 +01:00
|
|
|
SortDirection,
|
2024-03-06 19:07:46 +01:00
|
|
|
SqlQueryBinding,
|
2024-03-12 16:27:34 +01:00
|
|
|
Table,
|
2024-04-15 19:23:39 +02:00
|
|
|
TableSourceType,
|
|
|
|
INTERNAL_TABLE_SOURCE_ID,
|
2022-08-11 14:50:05 +02:00
|
|
|
} from "@budibase/types"
|
2022-03-21 18:44:43 +01:00
|
|
|
import environment from "../../environment"
|
2021-10-28 20:39:42 +02:00
|
|
|
|
2024-04-04 19:16:23 +02:00
|
|
|
type QueryFunction = (query: SqlQuery | SqlQuery[], operation: Operation) => any
|
2024-03-04 16:47:27 +01:00
|
|
|
|
2022-03-21 18:44:43 +01:00
|
|
|
const envLimit = environment.SQL_MAX_ROWS
|
|
|
|
? parseInt(environment.SQL_MAX_ROWS)
|
|
|
|
: null
|
|
|
|
const BASE_LIMIT = envLimit || 5000
|
2021-06-24 19:16:48 +02:00
|
|
|
|
2021-11-11 16:36:21 +01:00
|
|
|
// these are invalid dates sent by the client, need to convert them to a real max date
|
|
|
|
const MIN_ISO_DATE = "0000-00-00T00:00:00.000Z"
|
|
|
|
const MAX_ISO_DATE = "9999-00-00T00:00:00.000Z"
|
|
|
|
|
2022-05-04 17:13:54 +02:00
|
|
|
function likeKey(client: string, key: string): string {
|
|
|
|
let start: string, end: string
|
|
|
|
switch (client) {
|
2022-08-11 14:50:05 +02:00
|
|
|
case SqlClient.MY_SQL:
|
2022-05-04 17:13:54 +02:00
|
|
|
start = end = "`"
|
|
|
|
break
|
2022-08-11 14:50:05 +02:00
|
|
|
case SqlClient.ORACLE:
|
|
|
|
case SqlClient.POSTGRES:
|
2022-05-04 17:13:54 +02:00
|
|
|
start = end = '"'
|
|
|
|
break
|
2022-08-11 14:50:05 +02:00
|
|
|
case SqlClient.MS_SQL:
|
2022-05-04 17:13:54 +02:00
|
|
|
start = "["
|
|
|
|
end = "]"
|
|
|
|
break
|
2023-07-07 14:47:42 +02:00
|
|
|
case SqlClient.SQL_LITE:
|
|
|
|
start = end = "'"
|
|
|
|
break
|
2022-05-04 17:13:54 +02:00
|
|
|
default:
|
2023-07-07 14:47:42 +02:00
|
|
|
throw new Error("Unknown client generating like key")
|
2022-05-04 17:13:54 +02:00
|
|
|
}
|
|
|
|
const parts = key.split(".")
|
|
|
|
key = parts.map(part => `${start}${part}${end}`).join(".")
|
|
|
|
return key
|
|
|
|
}
|
|
|
|
|
2021-11-11 16:36:21 +01:00
|
|
|
function parse(input: any) {
|
|
|
|
if (Array.isArray(input)) {
|
|
|
|
return JSON.stringify(input)
|
|
|
|
}
|
2021-11-24 21:55:03 +01:00
|
|
|
if (input == undefined) {
|
|
|
|
return null
|
|
|
|
}
|
2021-11-11 16:36:21 +01:00
|
|
|
if (typeof input !== "string") {
|
|
|
|
return input
|
|
|
|
}
|
2022-03-10 11:18:03 +01:00
|
|
|
if (input === MAX_ISO_DATE || input === MIN_ISO_DATE) {
|
|
|
|
return null
|
2021-11-11 16:36:21 +01:00
|
|
|
}
|
|
|
|
if (isIsoDateString(input)) {
|
2023-09-11 22:35:51 +02:00
|
|
|
return new Date(input.trim())
|
2021-11-11 16:36:21 +01:00
|
|
|
}
|
2021-11-11 17:20:30 +01:00
|
|
|
return input
|
2021-11-11 16:36:21 +01:00
|
|
|
}
|
2021-06-03 17:31:24 +02:00
|
|
|
|
2021-07-12 11:51:30 +02:00
|
|
|
function parseBody(body: any) {
|
|
|
|
for (let [key, value] of Object.entries(body)) {
|
2021-11-11 16:36:21 +01:00
|
|
|
body[key] = parse(value)
|
2021-07-12 11:51:30 +02:00
|
|
|
}
|
|
|
|
return body
|
|
|
|
}
|
|
|
|
|
2021-11-24 21:55:03 +01:00
|
|
|
function parseFilters(filters: SearchFilters | undefined): SearchFilters {
|
|
|
|
if (!filters) {
|
|
|
|
return {}
|
|
|
|
}
|
2021-11-11 16:36:21 +01:00
|
|
|
for (let [key, value] of Object.entries(filters)) {
|
|
|
|
let parsed
|
|
|
|
if (typeof value === "object") {
|
|
|
|
parsed = parseFilters(value)
|
|
|
|
} else {
|
|
|
|
parsed = parse(value)
|
2021-06-03 17:31:24 +02:00
|
|
|
}
|
2021-11-11 16:36:21 +01:00
|
|
|
// @ts-ignore
|
|
|
|
filters[key] = parsed
|
2021-06-03 17:31:24 +02:00
|
|
|
}
|
2021-11-11 16:36:21 +01:00
|
|
|
return filters
|
2021-06-03 17:31:24 +02:00
|
|
|
}
|
|
|
|
|
2022-06-23 14:09:22 +02:00
|
|
|
function generateSelectStatement(
|
|
|
|
json: QueryJson,
|
2023-02-07 13:25:02 +01:00
|
|
|
knex: Knex
|
2023-02-07 13:29:58 +01:00
|
|
|
): (string | Knex.Raw)[] | "*" {
|
2022-06-22 16:58:15 +02:00
|
|
|
const { resource, meta } = json
|
2023-02-07 13:29:58 +01:00
|
|
|
|
|
|
|
if (!resource) {
|
|
|
|
return "*"
|
|
|
|
}
|
|
|
|
|
2022-06-22 16:58:15 +02:00
|
|
|
const schema = meta?.table?.schema
|
2023-02-07 13:29:58 +01:00
|
|
|
return resource.fields.map(field => {
|
2022-06-23 10:41:43 +02:00
|
|
|
const fieldNames = field.split(/\./g)
|
|
|
|
const tableName = fieldNames[0]
|
|
|
|
const columnName = fieldNames[1]
|
2022-07-05 17:59:32 +02:00
|
|
|
if (
|
|
|
|
columnName &&
|
|
|
|
schema?.[columnName] &&
|
2022-08-11 14:50:05 +02:00
|
|
|
knex.client.config.client === SqlClient.POSTGRES
|
2022-07-05 17:59:32 +02:00
|
|
|
) {
|
|
|
|
const externalType = schema[columnName].externalType
|
2022-06-22 16:58:15 +02:00
|
|
|
if (externalType?.includes("money")) {
|
2023-02-07 13:25:02 +01:00
|
|
|
return knex.raw(
|
|
|
|
`"${tableName}"."${columnName}"::money::numeric as "${field}"`
|
2022-06-22 16:58:15 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2023-02-07 13:25:02 +01:00
|
|
|
return `${field} as ${field}`
|
|
|
|
})
|
2022-06-22 16:58:15 +02:00
|
|
|
}
|
|
|
|
|
2024-04-16 12:38:00 +02:00
|
|
|
function getTableName(table?: Table): string | undefined {
|
2024-04-15 19:23:39 +02:00
|
|
|
// SQS uses the table ID rather than the table name
|
|
|
|
if (
|
2024-04-16 12:38:00 +02:00
|
|
|
table?.sourceType === TableSourceType.INTERNAL ||
|
|
|
|
table?.sourceId === INTERNAL_TABLE_SOURCE_ID
|
2024-04-15 19:23:39 +02:00
|
|
|
) {
|
2024-04-16 12:38:00 +02:00
|
|
|
return table?._id
|
2024-04-15 19:23:39 +02:00
|
|
|
} else {
|
2024-04-16 12:38:00 +02:00
|
|
|
return table?.name
|
2024-04-15 19:23:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-05 14:48:13 +01:00
|
|
|
class InternalBuilder {
|
|
|
|
private readonly client: string
|
|
|
|
|
|
|
|
constructor(client: string) {
|
|
|
|
this.client = client
|
2021-06-03 17:31:24 +02:00
|
|
|
}
|
|
|
|
|
2021-11-05 14:48:13 +01:00
|
|
|
// right now we only do filters on the specific table being queried
|
|
|
|
addFilters(
|
2024-02-28 16:18:07 +01:00
|
|
|
query: Knex.QueryBuilder,
|
2022-01-13 18:40:11 +01:00
|
|
|
filters: SearchFilters | undefined,
|
2024-04-16 18:28:21 +02:00
|
|
|
table: Table,
|
2024-01-30 18:57:10 +01:00
|
|
|
opts: { aliases?: Record<string, string>; relationship?: boolean }
|
2024-02-28 16:18:07 +01:00
|
|
|
): Knex.QueryBuilder {
|
2024-04-15 19:23:39 +02:00
|
|
|
function getTableAlias(name: string) {
|
2024-01-30 18:57:10 +01:00
|
|
|
const alias = opts.aliases?.[name]
|
|
|
|
return alias || name
|
|
|
|
}
|
2021-11-05 14:48:13 +01:00
|
|
|
function iterate(
|
|
|
|
structure: { [key: string]: any },
|
|
|
|
fn: (key: string, value: any) => void
|
|
|
|
) {
|
|
|
|
for (let [key, value] of Object.entries(structure)) {
|
2023-02-13 18:13:59 +01:00
|
|
|
const updatedKey = dbCore.removeKeyNumbering(key)
|
2022-08-02 19:34:58 +02:00
|
|
|
const isRelationshipField = updatedKey.includes(".")
|
2022-01-13 18:40:11 +01:00
|
|
|
if (!opts.relationship && !isRelationshipField) {
|
2024-04-16 18:36:51 +02:00
|
|
|
fn(`${getTableAlias(table.name)}.${updatedKey}`, value)
|
2022-01-13 18:40:11 +01:00
|
|
|
}
|
|
|
|
if (opts.relationship && isRelationshipField) {
|
2024-01-30 18:57:10 +01:00
|
|
|
const [filterTableName, property] = updatedKey.split(".")
|
2024-04-15 19:23:39 +02:00
|
|
|
fn(`${getTableAlias(filterTableName)}.${property}`, value)
|
2022-01-13 18:40:11 +01:00
|
|
|
}
|
2021-11-05 14:48:13 +01:00
|
|
|
}
|
|
|
|
}
|
2022-07-19 18:25:41 +02:00
|
|
|
|
|
|
|
const like = (key: string, value: any) => {
|
|
|
|
const fnc = allOr ? "orWhere" : "where"
|
|
|
|
// postgres supports ilike, nothing else does
|
2022-08-11 14:50:05 +02:00
|
|
|
if (this.client === SqlClient.POSTGRES) {
|
2022-07-19 18:25:41 +02:00
|
|
|
query = query[fnc](key, "ilike", `%${value}%`)
|
|
|
|
} else {
|
|
|
|
const rawFnc = `${fnc}Raw`
|
|
|
|
// @ts-ignore
|
|
|
|
query = query[rawFnc](`LOWER(${likeKey(this.client, key)}) LIKE ?`, [
|
2023-03-28 12:46:29 +02:00
|
|
|
`%${value.toLowerCase()}%`,
|
2022-07-19 18:25:41 +02:00
|
|
|
])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-27 14:19:47 +02:00
|
|
|
const contains = (mode: object, any: boolean = false) => {
|
2022-07-27 12:40:46 +02:00
|
|
|
const fnc = allOr ? "orWhere" : "where"
|
|
|
|
const rawFnc = `${fnc}Raw`
|
|
|
|
const not = mode === filters?.notContains ? "NOT " : ""
|
2022-07-27 17:37:29 +02:00
|
|
|
function stringifyArray(value: Array<any>, quoteStyle = '"'): string {
|
2022-07-27 12:40:46 +02:00
|
|
|
for (let i in value) {
|
|
|
|
if (typeof value[i] === "string") {
|
2022-07-27 17:37:29 +02:00
|
|
|
value[i] = `${quoteStyle}${value[i]}${quoteStyle}`
|
2022-07-27 12:40:46 +02:00
|
|
|
}
|
|
|
|
}
|
2022-07-27 17:37:29 +02:00
|
|
|
return `[${value.join(",")}]`
|
2022-07-27 12:40:46 +02:00
|
|
|
}
|
2022-08-15 13:24:41 +02:00
|
|
|
if (this.client === SqlClient.POSTGRES) {
|
2022-07-27 12:40:46 +02:00
|
|
|
iterate(mode, (key: string, value: Array<any>) => {
|
2022-07-27 17:37:29 +02:00
|
|
|
const wrap = any ? "" : "'"
|
|
|
|
const containsOp = any ? "\\?| array" : "@>"
|
2022-07-27 12:40:46 +02:00
|
|
|
const fieldNames = key.split(/\./g)
|
|
|
|
const tableName = fieldNames[0]
|
|
|
|
const columnName = fieldNames[1]
|
|
|
|
// @ts-ignore
|
2022-07-27 17:40:07 +02:00
|
|
|
query = query[rawFnc](
|
|
|
|
`${not}"${tableName}"."${columnName}"::jsonb ${containsOp} ${wrap}${stringifyArray(
|
|
|
|
value,
|
|
|
|
any ? "'" : '"'
|
2022-07-27 17:37:29 +02:00
|
|
|
)}${wrap}`
|
2022-07-27 12:40:46 +02:00
|
|
|
)
|
|
|
|
})
|
2022-08-15 13:24:41 +02:00
|
|
|
} else if (this.client === SqlClient.MY_SQL) {
|
2022-07-27 14:19:47 +02:00
|
|
|
const jsonFnc = any ? "JSON_OVERLAPS" : "JSON_CONTAINS"
|
2022-07-27 12:40:46 +02:00
|
|
|
iterate(mode, (key: string, value: Array<any>) => {
|
|
|
|
// @ts-ignore
|
|
|
|
query = query[rawFnc](
|
2022-07-27 17:37:29 +02:00
|
|
|
`${not}${jsonFnc}(${key}, '${stringifyArray(value)}')`
|
2022-07-27 12:40:46 +02:00
|
|
|
)
|
|
|
|
})
|
|
|
|
} else {
|
2022-07-28 10:20:00 +02:00
|
|
|
const andOr = mode === filters?.containsAny ? " OR " : " AND "
|
2022-07-27 12:40:46 +02:00
|
|
|
iterate(mode, (key: string, value: Array<any>) => {
|
2022-07-28 10:20:00 +02:00
|
|
|
let statement = ""
|
2022-07-27 12:40:46 +02:00
|
|
|
for (let i in value) {
|
|
|
|
if (typeof value[i] === "string") {
|
2023-03-28 12:46:29 +02:00
|
|
|
value[i] = `%"${value[i].toLowerCase()}"%`
|
2022-07-27 12:40:46 +02:00
|
|
|
} else {
|
|
|
|
value[i] = `%${value[i]}%`
|
|
|
|
}
|
2022-07-28 10:20:00 +02:00
|
|
|
statement +=
|
|
|
|
(statement ? andOr : "") +
|
2022-07-27 12:40:46 +02:00
|
|
|
`LOWER(${likeKey(this.client, key)}) LIKE ?`
|
|
|
|
}
|
2024-04-15 16:31:46 +02:00
|
|
|
|
|
|
|
if (statement === "") {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-07-27 12:40:46 +02:00
|
|
|
// @ts-ignore
|
2022-07-28 10:20:00 +02:00
|
|
|
query = query[rawFnc](`${not}(${statement})`, value)
|
2022-07-27 12:40:46 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-05 14:48:13 +01:00
|
|
|
if (!filters) {
|
|
|
|
return query
|
|
|
|
}
|
2021-11-11 16:36:21 +01:00
|
|
|
filters = parseFilters(filters)
|
2021-11-05 14:48:13 +01:00
|
|
|
// if all or specified in filters, then everything is an or
|
|
|
|
const allOr = filters.allOr
|
|
|
|
if (filters.oneOf) {
|
|
|
|
iterate(filters.oneOf, (key, array) => {
|
|
|
|
const fnc = allOr ? "orWhereIn" : "whereIn"
|
2021-11-26 18:08:56 +01:00
|
|
|
query = query[fnc](key, Array.isArray(array) ? array : [array])
|
2021-11-05 14:48:13 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
if (filters.string) {
|
|
|
|
iterate(filters.string, (key, value) => {
|
|
|
|
const fnc = allOr ? "orWhere" : "where"
|
|
|
|
// postgres supports ilike, nothing else does
|
2022-08-11 14:50:05 +02:00
|
|
|
if (this.client === SqlClient.POSTGRES) {
|
2021-11-05 14:48:13 +01:00
|
|
|
query = query[fnc](key, "ilike", `${value}%`)
|
|
|
|
} else {
|
|
|
|
const rawFnc = `${fnc}Raw`
|
|
|
|
// @ts-ignore
|
2023-01-20 15:03:14 +01:00
|
|
|
query = query[rawFnc](`LOWER(${likeKey(this.client, key)}) LIKE ?`, [
|
2023-03-28 12:46:29 +02:00
|
|
|
`${value.toLowerCase()}%`,
|
2023-01-20 15:03:14 +01:00
|
|
|
])
|
2021-11-05 14:48:13 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (filters.fuzzy) {
|
2022-07-19 18:25:41 +02:00
|
|
|
iterate(filters.fuzzy, like)
|
2021-11-05 14:48:13 +01:00
|
|
|
}
|
|
|
|
if (filters.range) {
|
|
|
|
iterate(filters.range, (key, value) => {
|
2024-04-16 18:28:21 +02:00
|
|
|
const fieldName = key.split(".")[1]
|
|
|
|
const field = table.schema[fieldName]
|
|
|
|
|
2023-02-15 16:10:02 +01:00
|
|
|
const isEmptyObject = (val: any) => {
|
|
|
|
return (
|
|
|
|
val &&
|
|
|
|
Object.keys(val).length === 0 &&
|
|
|
|
Object.getPrototypeOf(val) === Object.prototype
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if (isEmptyObject(value.low)) {
|
|
|
|
value.low = ""
|
|
|
|
}
|
|
|
|
if (isEmptyObject(value.high)) {
|
|
|
|
value.high = ""
|
|
|
|
}
|
2023-09-14 17:53:36 +02:00
|
|
|
const lowValid = isValidFilter(value.low),
|
|
|
|
highValid = isValidFilter(value.high)
|
|
|
|
if (lowValid && highValid) {
|
2022-03-10 11:18:03 +01:00
|
|
|
// Use a between operator if we have 2 valid range values
|
2024-04-16 18:28:21 +02:00
|
|
|
if (
|
|
|
|
field.type === FieldType.BIGINT &&
|
|
|
|
this.client === SqlClient.SQL_LITE
|
|
|
|
) {
|
|
|
|
query = query.whereRaw(
|
|
|
|
`CAST(${key} AS INTEGER) BETWEEN CAST(? AS INTEGER) AND CAST(? AS INTEGER)`,
|
|
|
|
[value.low, value.high]
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
const fnc = allOr ? "orWhereBetween" : "whereBetween"
|
|
|
|
query = query[fnc](key, [value.low, value.high])
|
|
|
|
}
|
2023-09-14 17:53:36 +02:00
|
|
|
} else if (lowValid) {
|
2022-03-10 11:18:03 +01:00
|
|
|
// Use just a single greater than operator if we only have a low
|
2024-04-16 18:28:21 +02:00
|
|
|
if (
|
|
|
|
field.type === FieldType.BIGINT &&
|
|
|
|
this.client === SqlClient.SQL_LITE
|
|
|
|
) {
|
|
|
|
query = query.whereRaw(
|
|
|
|
`CAST(${key} AS INTEGER) >= CAST(? AS INTEGER)`,
|
|
|
|
[value.low]
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
const fnc = allOr ? "orWhere" : "where"
|
|
|
|
query = query[fnc](key, ">=", value.low)
|
|
|
|
}
|
2023-09-14 17:53:36 +02:00
|
|
|
} else if (highValid) {
|
2022-03-10 11:18:03 +01:00
|
|
|
// Use just a single less than operator if we only have a high
|
2024-04-16 18:28:21 +02:00
|
|
|
if (
|
|
|
|
field.type === FieldType.BIGINT &&
|
|
|
|
this.client === SqlClient.SQL_LITE
|
|
|
|
) {
|
|
|
|
query = query.whereRaw(
|
|
|
|
`CAST(${key} AS INTEGER) <= CAST(? AS INTEGER)`,
|
|
|
|
[value.high]
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
const fnc = allOr ? "orWhere" : "where"
|
|
|
|
query = query[fnc](key, "<=", value.high)
|
|
|
|
}
|
2021-11-05 14:48:13 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (filters.equal) {
|
|
|
|
iterate(filters.equal, (key, value) => {
|
|
|
|
const fnc = allOr ? "orWhere" : "where"
|
|
|
|
query = query[fnc]({ [key]: value })
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (filters.notEqual) {
|
|
|
|
iterate(filters.notEqual, (key, value) => {
|
|
|
|
const fnc = allOr ? "orWhereNot" : "whereNot"
|
|
|
|
query = query[fnc]({ [key]: value })
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (filters.empty) {
|
|
|
|
iterate(filters.empty, key => {
|
|
|
|
const fnc = allOr ? "orWhereNull" : "whereNull"
|
|
|
|
query = query[fnc](key)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (filters.notEmpty) {
|
|
|
|
iterate(filters.notEmpty, key => {
|
|
|
|
const fnc = allOr ? "orWhereNotNull" : "whereNotNull"
|
|
|
|
query = query[fnc](key)
|
|
|
|
})
|
|
|
|
}
|
2022-07-19 18:25:41 +02:00
|
|
|
if (filters.contains) {
|
2022-07-27 12:40:46 +02:00
|
|
|
contains(filters.contains)
|
|
|
|
}
|
|
|
|
if (filters.notContains) {
|
|
|
|
contains(filters.notContains)
|
2022-07-19 18:25:41 +02:00
|
|
|
}
|
2022-07-27 14:19:47 +02:00
|
|
|
if (filters.containsAny) {
|
|
|
|
contains(filters.containsAny, true)
|
2024-04-11 10:53:54 +02:00
|
|
|
}
|
|
|
|
|
2021-06-23 20:05:32 +02:00
|
|
|
return query
|
|
|
|
}
|
2021-11-05 14:48:13 +01:00
|
|
|
|
2024-02-28 16:18:07 +01:00
|
|
|
addSorting(query: Knex.QueryBuilder, json: QueryJson): Knex.QueryBuilder {
|
2021-11-24 21:55:03 +01:00
|
|
|
let { sort, paginate } = json
|
|
|
|
const table = json.meta?.table
|
2024-04-15 19:23:39 +02:00
|
|
|
const tableName = getTableName(table)
|
2024-03-04 17:55:10 +01:00
|
|
|
const aliases = json.tableAliases
|
|
|
|
const aliased =
|
2024-04-16 12:38:00 +02:00
|
|
|
tableName && aliases?.[tableName] ? aliases[tableName] : table?.name
|
2023-08-04 14:53:30 +02:00
|
|
|
if (sort && Object.keys(sort || {}).length > 0) {
|
2022-02-04 17:17:36 +01:00
|
|
|
for (let [key, value] of Object.entries(sort)) {
|
2023-03-09 09:50:26 +01:00
|
|
|
const direction =
|
|
|
|
value.direction === SortDirection.ASCENDING ? "asc" : "desc"
|
2024-03-04 17:55:10 +01:00
|
|
|
query = query.orderBy(`${aliased}.${key}`, direction)
|
2022-02-04 17:17:36 +01:00
|
|
|
}
|
2022-08-11 14:50:05 +02:00
|
|
|
} else if (this.client === SqlClient.MS_SQL && paginate?.limit) {
|
2021-11-24 19:20:52 +01:00
|
|
|
// @ts-ignore
|
2024-03-04 17:55:10 +01:00
|
|
|
query = query.orderBy(`${aliased}.${table?.primary[0]}`)
|
2021-11-24 19:20:52 +01:00
|
|
|
}
|
|
|
|
return query
|
|
|
|
}
|
|
|
|
|
2023-11-28 19:43:38 +01:00
|
|
|
tableNameWithSchema(
|
|
|
|
tableName: string,
|
|
|
|
opts?: { alias?: string; schema?: string }
|
|
|
|
) {
|
|
|
|
let withSchema = opts?.schema ? `${opts.schema}.${tableName}` : tableName
|
|
|
|
if (opts?.alias) {
|
|
|
|
withSchema += ` as ${opts.alias}`
|
|
|
|
}
|
|
|
|
return withSchema
|
|
|
|
}
|
|
|
|
|
2021-11-05 14:48:13 +01:00
|
|
|
addRelationships(
|
2024-02-28 16:18:07 +01:00
|
|
|
query: Knex.QueryBuilder,
|
2021-11-05 14:48:13 +01:00
|
|
|
fromTable: string,
|
2022-12-19 19:12:05 +01:00
|
|
|
relationships: RelationshipsJson[] | undefined,
|
2024-01-30 18:57:10 +01:00
|
|
|
schema: string | undefined,
|
|
|
|
aliases?: Record<string, string>
|
2024-02-28 16:18:07 +01:00
|
|
|
): Knex.QueryBuilder {
|
2021-11-05 14:48:13 +01:00
|
|
|
if (!relationships) {
|
|
|
|
return query
|
|
|
|
}
|
2023-11-28 19:43:38 +01:00
|
|
|
const tableSets: Record<string, [RelationshipsJson]> = {}
|
2022-02-02 19:15:17 +01:00
|
|
|
// aggregate into table sets (all the same to tables)
|
2021-11-05 14:48:13 +01:00
|
|
|
for (let relationship of relationships) {
|
2022-02-02 19:15:17 +01:00
|
|
|
const keyObj: { toTable: string; throughTable: string | undefined } = {
|
|
|
|
toTable: relationship.tableName,
|
|
|
|
throughTable: undefined,
|
|
|
|
}
|
|
|
|
if (relationship.through) {
|
|
|
|
keyObj.throughTable = relationship.through
|
|
|
|
}
|
|
|
|
const key = JSON.stringify(keyObj)
|
|
|
|
if (tableSets[key]) {
|
|
|
|
tableSets[key].push(relationship)
|
|
|
|
} else {
|
|
|
|
tableSets[key] = [relationship]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (let [key, relationships] of Object.entries(tableSets)) {
|
|
|
|
const { toTable, throughTable } = JSON.parse(key)
|
2024-01-30 18:57:10 +01:00
|
|
|
const toAlias = aliases?.[toTable] || toTable,
|
|
|
|
throughAlias = aliases?.[throughTable] || throughTable,
|
|
|
|
fromAlias = aliases?.[fromTable] || fromTable
|
2023-11-28 19:43:38 +01:00
|
|
|
let toTableWithSchema = this.tableNameWithSchema(toTable, {
|
|
|
|
alias: toAlias,
|
|
|
|
schema,
|
|
|
|
})
|
|
|
|
let throughTableWithSchema = this.tableNameWithSchema(throughTable, {
|
|
|
|
alias: throughAlias,
|
|
|
|
schema,
|
|
|
|
})
|
2022-02-02 19:15:17 +01:00
|
|
|
if (!throughTable) {
|
2021-06-25 19:13:11 +02:00
|
|
|
// @ts-ignore
|
2022-12-19 19:12:05 +01:00
|
|
|
query = query.leftJoin(toTableWithSchema, function () {
|
2022-02-11 15:04:23 +01:00
|
|
|
for (let relationship of relationships) {
|
|
|
|
const from = relationship.from,
|
|
|
|
to = relationship.to
|
|
|
|
// @ts-ignore
|
2023-11-28 19:45:05 +01:00
|
|
|
this.orOn(`${fromAlias}.${from}`, "=", `${toAlias}.${to}`)
|
2022-02-11 15:04:23 +01:00
|
|
|
}
|
|
|
|
})
|
2021-11-05 14:48:13 +01:00
|
|
|
} else {
|
|
|
|
query = query
|
2022-02-02 19:18:53 +01:00
|
|
|
// @ts-ignore
|
2022-12-19 19:12:05 +01:00
|
|
|
.leftJoin(throughTableWithSchema, function () {
|
2022-02-11 15:04:23 +01:00
|
|
|
for (let relationship of relationships) {
|
|
|
|
const fromPrimary = relationship.fromPrimary
|
|
|
|
const from = relationship.from
|
|
|
|
// @ts-ignore
|
|
|
|
this.orOn(
|
2023-11-28 19:43:38 +01:00
|
|
|
`${fromAlias}.${fromPrimary}`,
|
2022-02-11 15:04:23 +01:00
|
|
|
"=",
|
2023-11-28 19:43:38 +01:00
|
|
|
`${throughAlias}.${from}`
|
2022-02-11 15:04:23 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
})
|
2022-12-19 19:12:05 +01:00
|
|
|
.leftJoin(toTableWithSchema, function () {
|
2022-02-11 15:04:23 +01:00
|
|
|
for (let relationship of relationships) {
|
|
|
|
const toPrimary = relationship.toPrimary
|
|
|
|
const to = relationship.to
|
|
|
|
// @ts-ignore
|
2023-11-28 19:43:38 +01:00
|
|
|
this.orOn(`${toAlias}.${toPrimary}`, `${throughAlias}.${to}`)
|
2022-02-11 15:04:23 +01:00
|
|
|
}
|
|
|
|
})
|
2021-11-05 14:48:13 +01:00
|
|
|
}
|
2021-06-23 20:05:32 +02:00
|
|
|
}
|
2021-11-05 14:48:13 +01:00
|
|
|
return query.limit(BASE_LIMIT)
|
2021-06-23 20:05:32 +02:00
|
|
|
}
|
|
|
|
|
2023-12-01 16:27:49 +01:00
|
|
|
knexWithAlias(
|
|
|
|
knex: Knex,
|
2024-01-30 18:57:10 +01:00
|
|
|
endpoint: QueryJson["endpoint"],
|
|
|
|
aliases?: QueryJson["tableAliases"]
|
2024-02-28 16:18:07 +01:00
|
|
|
): Knex.QueryBuilder {
|
2023-12-01 16:27:49 +01:00
|
|
|
const tableName = endpoint.entityId
|
2024-03-05 17:19:21 +01:00
|
|
|
const tableAlias = aliases?.[tableName]
|
|
|
|
let table: string | Record<string, string> = tableName
|
|
|
|
if (tableAlias) {
|
|
|
|
table = { [tableAlias]: tableName }
|
|
|
|
}
|
|
|
|
let query = knex(table)
|
2022-03-04 16:12:07 +01:00
|
|
|
if (endpoint.schema) {
|
|
|
|
query = query.withSchema(endpoint.schema)
|
|
|
|
}
|
2024-01-30 18:57:10 +01:00
|
|
|
return query
|
2023-12-01 16:27:49 +01:00
|
|
|
}
|
|
|
|
|
2024-02-28 16:18:07 +01:00
|
|
|
create(knex: Knex, json: QueryJson, opts: QueryOptions): Knex.QueryBuilder {
|
2023-12-01 16:27:49 +01:00
|
|
|
const { endpoint, body } = json
|
2024-01-30 18:57:10 +01:00
|
|
|
let query = this.knexWithAlias(knex, endpoint)
|
2021-11-05 14:48:13 +01:00
|
|
|
const parsedBody = parseBody(body)
|
|
|
|
// make sure no null values in body for creation
|
|
|
|
for (let [key, value] of Object.entries(parsedBody)) {
|
|
|
|
if (value == null) {
|
|
|
|
delete parsedBody[key]
|
|
|
|
}
|
|
|
|
}
|
2023-02-06 21:47:49 +01:00
|
|
|
|
2021-11-05 14:48:13 +01:00
|
|
|
// mysql can't use returning
|
|
|
|
if (opts.disableReturning) {
|
|
|
|
return query.insert(parsedBody)
|
|
|
|
} else {
|
2023-02-23 10:28:24 +01:00
|
|
|
return query.insert(parsedBody).returning("*")
|
2021-10-06 18:55:03 +02:00
|
|
|
}
|
|
|
|
}
|
2021-06-03 17:31:24 +02:00
|
|
|
|
2024-02-28 16:18:07 +01:00
|
|
|
bulkCreate(knex: Knex, json: QueryJson): Knex.QueryBuilder {
|
2021-11-12 20:24:56 +01:00
|
|
|
const { endpoint, body } = json
|
2024-01-30 18:57:10 +01:00
|
|
|
let query = this.knexWithAlias(knex, endpoint)
|
2021-11-12 20:24:56 +01:00
|
|
|
if (!Array.isArray(body)) {
|
|
|
|
return query
|
|
|
|
}
|
|
|
|
const parsedBody = body.map(row => parseBody(row))
|
2021-07-12 11:51:30 +02:00
|
|
|
return query.insert(parsedBody)
|
2021-06-18 14:14:45 +02:00
|
|
|
}
|
2021-06-03 17:31:24 +02:00
|
|
|
|
2024-02-28 16:18:07 +01:00
|
|
|
read(knex: Knex, json: QueryJson, limit: number): Knex.QueryBuilder {
|
2024-01-30 18:57:10 +01:00
|
|
|
let { endpoint, resource, filters, paginate, relationships, tableAliases } =
|
|
|
|
json
|
2023-11-24 19:11:53 +01:00
|
|
|
|
2021-11-05 14:48:13 +01:00
|
|
|
const tableName = endpoint.entityId
|
|
|
|
// select all if not specified
|
|
|
|
if (!resource) {
|
|
|
|
resource = { fields: [] }
|
2021-09-23 17:17:23 +02:00
|
|
|
}
|
2022-06-23 14:09:22 +02:00
|
|
|
let selectStatement: string | (string | Knex.Raw)[] = "*"
|
2021-11-05 14:48:13 +01:00
|
|
|
// handle select
|
|
|
|
if (resource.fields && resource.fields.length > 0) {
|
|
|
|
// select the resources as the format "table.columnName" - this is what is provided
|
|
|
|
// by the resource builder further up
|
2023-02-07 13:25:02 +01:00
|
|
|
selectStatement = generateSelectStatement(json, knex)
|
2021-11-05 14:48:13 +01:00
|
|
|
}
|
|
|
|
let foundLimit = limit || BASE_LIMIT
|
|
|
|
// handle pagination
|
|
|
|
let foundOffset: number | null = null
|
|
|
|
if (paginate && paginate.page && paginate.limit) {
|
|
|
|
// @ts-ignore
|
|
|
|
const page = paginate.page <= 1 ? 0 : paginate.page - 1
|
|
|
|
const offset = page * paginate.limit
|
|
|
|
foundLimit = paginate.limit
|
|
|
|
foundOffset = offset
|
|
|
|
} else if (paginate && paginate.limit) {
|
|
|
|
foundLimit = paginate.limit
|
|
|
|
}
|
|
|
|
// start building the query
|
2024-01-30 18:57:10 +01:00
|
|
|
let query = this.knexWithAlias(knex, endpoint, tableAliases)
|
2023-12-01 16:27:49 +01:00
|
|
|
query = query.limit(foundLimit)
|
2021-11-05 14:48:13 +01:00
|
|
|
if (foundOffset) {
|
|
|
|
query = query.offset(foundOffset)
|
|
|
|
}
|
2024-04-16 18:28:21 +02:00
|
|
|
query = this.addFilters(query, filters, json.meta?.table!, {
|
2024-01-30 18:57:10 +01:00
|
|
|
aliases: tableAliases,
|
|
|
|
})
|
2021-11-24 19:20:52 +01:00
|
|
|
// add sorting to pre-query
|
2021-11-24 21:55:03 +01:00
|
|
|
query = this.addSorting(query, json)
|
2024-01-30 18:57:10 +01:00
|
|
|
const alias = tableAliases?.[tableName] || tableName
|
|
|
|
let preQuery = knex({
|
|
|
|
[alias]: query,
|
|
|
|
} as any).select(selectStatement) as any
|
2021-11-24 19:20:52 +01:00
|
|
|
// have to add after as well (this breaks MS-SQL)
|
2022-08-11 14:50:05 +02:00
|
|
|
if (this.client !== SqlClient.MS_SQL) {
|
2021-11-24 21:55:03 +01:00
|
|
|
preQuery = this.addSorting(preQuery, json)
|
2021-11-24 19:20:52 +01:00
|
|
|
}
|
2021-11-05 14:48:13 +01:00
|
|
|
// handle joins
|
2022-12-19 19:12:05 +01:00
|
|
|
query = this.addRelationships(
|
|
|
|
preQuery,
|
|
|
|
tableName,
|
|
|
|
relationships,
|
2024-01-30 18:57:10 +01:00
|
|
|
endpoint.schema,
|
|
|
|
tableAliases
|
2022-12-19 19:12:05 +01:00
|
|
|
)
|
2024-04-16 18:28:21 +02:00
|
|
|
return this.addFilters(query, filters, json.meta?.table!, {
|
2024-01-30 18:57:10 +01:00
|
|
|
relationship: true,
|
|
|
|
aliases: tableAliases,
|
|
|
|
})
|
2021-11-05 14:48:13 +01:00
|
|
|
}
|
2021-06-03 17:31:24 +02:00
|
|
|
|
2024-02-28 16:18:07 +01:00
|
|
|
update(knex: Knex, json: QueryJson, opts: QueryOptions): Knex.QueryBuilder {
|
2024-01-30 18:57:10 +01:00
|
|
|
const { endpoint, body, filters, tableAliases } = json
|
|
|
|
let query = this.knexWithAlias(knex, endpoint, tableAliases)
|
2021-11-05 14:48:13 +01:00
|
|
|
const parsedBody = parseBody(body)
|
2024-04-16 18:28:21 +02:00
|
|
|
query = this.addFilters(query, filters, json.meta?.table!, {
|
2024-01-30 18:57:10 +01:00
|
|
|
aliases: tableAliases,
|
|
|
|
})
|
2021-11-05 14:48:13 +01:00
|
|
|
// mysql can't use returning
|
|
|
|
if (opts.disableReturning) {
|
|
|
|
return query.update(parsedBody)
|
|
|
|
} else {
|
2023-02-22 11:54:55 +01:00
|
|
|
return query.update(parsedBody).returning("*")
|
2021-11-05 14:48:13 +01:00
|
|
|
}
|
2021-06-18 14:14:45 +02:00
|
|
|
}
|
2021-06-03 17:31:24 +02:00
|
|
|
|
2024-02-28 16:18:07 +01:00
|
|
|
delete(knex: Knex, json: QueryJson, opts: QueryOptions): Knex.QueryBuilder {
|
2024-01-30 18:57:10 +01:00
|
|
|
const { endpoint, filters, tableAliases } = json
|
|
|
|
let query = this.knexWithAlias(knex, endpoint, tableAliases)
|
2024-04-16 18:28:21 +02:00
|
|
|
query = this.addFilters(query, filters, json.meta?.table!, {
|
2024-01-30 18:57:10 +01:00
|
|
|
aliases: tableAliases,
|
|
|
|
})
|
2021-11-05 14:48:13 +01:00
|
|
|
// mysql can't use returning
|
|
|
|
if (opts.disableReturning) {
|
|
|
|
return query.delete()
|
|
|
|
} else {
|
2023-02-07 13:25:02 +01:00
|
|
|
return query.delete().returning(generateSelectStatement(json, knex))
|
2021-11-05 14:48:13 +01:00
|
|
|
}
|
2021-06-18 14:14:45 +02:00
|
|
|
}
|
2021-06-03 17:31:24 +02:00
|
|
|
}
|
|
|
|
|
2021-10-28 20:39:42 +02:00
|
|
|
class SqlQueryBuilder extends SqlTableQueryBuilder {
|
2021-06-24 19:16:48 +02:00
|
|
|
private readonly limit: number
|
2021-06-03 17:31:24 +02:00
|
|
|
// pass through client to get flavour of SQL
|
2021-06-25 14:46:02 +02:00
|
|
|
constructor(client: string, limit: number = BASE_LIMIT) {
|
2021-10-28 20:39:42 +02:00
|
|
|
super(client)
|
2021-06-24 19:16:48 +02:00
|
|
|
this.limit = limit
|
2021-06-03 17:31:24 +02:00
|
|
|
}
|
|
|
|
|
2021-06-18 14:14:45 +02:00
|
|
|
/**
|
|
|
|
* @param json The JSON query DSL which is to be converted to SQL.
|
|
|
|
* @param opts extra options which are to be passed into the query builder, e.g. disableReturning
|
|
|
|
* which for the sake of mySQL stops adding the returning statement to inserts, updates and deletes.
|
2023-10-17 17:46:32 +02:00
|
|
|
* @return the query ready to be passed to the driver.
|
2021-06-18 14:14:45 +02:00
|
|
|
*/
|
2024-04-04 19:16:23 +02:00
|
|
|
_query(json: QueryJson, opts: QueryOptions = {}): SqlQuery | SqlQuery[] {
|
2021-10-28 20:39:42 +02:00
|
|
|
const sqlClient = this.getSqlClient()
|
2023-07-21 19:41:48 +02:00
|
|
|
const config: { client: string; useNullAsDefault?: boolean } = {
|
|
|
|
client: sqlClient,
|
|
|
|
}
|
2023-07-07 14:47:42 +02:00
|
|
|
if (sqlClient === SqlClient.SQL_LITE) {
|
|
|
|
config.useNullAsDefault = true
|
|
|
|
}
|
|
|
|
const client = knex(config)
|
2024-02-28 16:18:07 +01:00
|
|
|
let query: Knex.QueryBuilder
|
2021-11-05 14:48:13 +01:00
|
|
|
const builder = new InternalBuilder(sqlClient)
|
2021-06-03 19:48:04 +02:00
|
|
|
switch (this._operation(json)) {
|
2021-06-24 19:16:48 +02:00
|
|
|
case Operation.CREATE:
|
2021-11-05 14:48:13 +01:00
|
|
|
query = builder.create(client, json, opts)
|
2021-06-03 18:45:19 +02:00
|
|
|
break
|
2021-06-24 19:16:48 +02:00
|
|
|
case Operation.READ:
|
2021-11-05 14:48:13 +01:00
|
|
|
query = builder.read(client, json, this.limit)
|
2021-06-03 18:45:19 +02:00
|
|
|
break
|
2021-06-24 19:16:48 +02:00
|
|
|
case Operation.UPDATE:
|
2021-11-05 14:48:13 +01:00
|
|
|
query = builder.update(client, json, opts)
|
2021-06-03 18:45:19 +02:00
|
|
|
break
|
2021-06-24 19:16:48 +02:00
|
|
|
case Operation.DELETE:
|
2021-11-05 14:48:13 +01:00
|
|
|
query = builder.delete(client, json, opts)
|
2021-06-03 18:45:19 +02:00
|
|
|
break
|
2021-11-12 20:24:56 +01:00
|
|
|
case Operation.BULK_CREATE:
|
|
|
|
query = builder.bulkCreate(client, json)
|
2021-06-03 18:45:19 +02:00
|
|
|
break
|
2021-11-05 13:33:48 +01:00
|
|
|
case Operation.CREATE_TABLE:
|
|
|
|
case Operation.UPDATE_TABLE:
|
|
|
|
case Operation.DELETE_TABLE:
|
2021-10-28 20:39:42 +02:00
|
|
|
return this._tableQuery(json)
|
2021-06-03 17:31:24 +02:00
|
|
|
default:
|
2021-06-04 15:53:49 +02:00
|
|
|
throw `Operation type is not supported by SQL query builder`
|
2021-06-03 17:31:24 +02:00
|
|
|
}
|
2021-06-25 19:13:11 +02:00
|
|
|
|
2024-03-06 19:07:46 +01:00
|
|
|
if (opts?.disableBindings) {
|
|
|
|
return { sql: query.toString() }
|
2023-07-06 22:49:25 +02:00
|
|
|
} else {
|
2024-04-04 19:16:23 +02:00
|
|
|
return getNativeSql(query)
|
2023-07-06 22:49:25 +02:00
|
|
|
}
|
2021-06-03 17:31:24 +02:00
|
|
|
}
|
2021-11-05 19:55:36 +01:00
|
|
|
|
2024-03-04 16:47:27 +01:00
|
|
|
async getReturningRow(queryFn: QueryFunction, json: QueryJson) {
|
2021-11-05 19:55:36 +01:00
|
|
|
if (!json.extra || !json.extra.idFilter) {
|
|
|
|
return {}
|
|
|
|
}
|
|
|
|
const input = this._query({
|
|
|
|
endpoint: {
|
|
|
|
...json.endpoint,
|
|
|
|
operation: Operation.READ,
|
|
|
|
},
|
|
|
|
resource: {
|
|
|
|
fields: [],
|
|
|
|
},
|
2024-03-04 16:47:27 +01:00
|
|
|
filters: json.extra?.idFilter,
|
2021-11-05 19:55:36 +01:00
|
|
|
paginate: {
|
|
|
|
limit: 1,
|
|
|
|
},
|
|
|
|
meta: json.meta,
|
|
|
|
})
|
|
|
|
return queryFn(input, Operation.READ)
|
|
|
|
}
|
|
|
|
|
|
|
|
// when creating if an ID has been inserted need to make sure
|
|
|
|
// the id filter is enriched with it before trying to retrieve the row
|
|
|
|
checkLookupKeys(id: any, json: QueryJson) {
|
|
|
|
if (!id || !json.meta?.table || !json.meta.table.primary) {
|
|
|
|
return json
|
|
|
|
}
|
|
|
|
const primaryKey = json.meta.table.primary?.[0]
|
|
|
|
json.extra = {
|
|
|
|
idFilter: {
|
|
|
|
equal: {
|
|
|
|
[primaryKey]: id,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return json
|
|
|
|
}
|
|
|
|
|
|
|
|
// this function recreates the returning functionality of postgres
|
|
|
|
async queryWithReturning(
|
|
|
|
json: QueryJson,
|
2024-03-04 16:47:27 +01:00
|
|
|
queryFn: QueryFunction,
|
2021-11-05 19:55:36 +01:00
|
|
|
processFn: Function = (result: any) => result
|
|
|
|
) {
|
|
|
|
const sqlClient = this.getSqlClient()
|
|
|
|
const operation = this._operation(json)
|
|
|
|
const input = this._query(json, { disableReturning: true })
|
|
|
|
if (Array.isArray(input)) {
|
|
|
|
const responses = []
|
|
|
|
for (let query of input) {
|
|
|
|
responses.push(await queryFn(query, operation))
|
|
|
|
}
|
|
|
|
return responses
|
|
|
|
}
|
|
|
|
let row
|
|
|
|
// need to manage returning, a feature mySQL can't do
|
|
|
|
if (operation === Operation.DELETE) {
|
|
|
|
row = processFn(await this.getReturningRow(queryFn, json))
|
|
|
|
}
|
|
|
|
const response = await queryFn(input, operation)
|
|
|
|
const results = processFn(response)
|
|
|
|
// same as delete, manage returning
|
|
|
|
if (operation === Operation.CREATE || operation === Operation.UPDATE) {
|
|
|
|
let id
|
2022-08-11 14:50:05 +02:00
|
|
|
if (sqlClient === SqlClient.MS_SQL) {
|
2021-11-05 19:55:36 +01:00
|
|
|
id = results?.[0].id
|
2022-08-11 14:50:05 +02:00
|
|
|
} else if (sqlClient === SqlClient.MY_SQL) {
|
2021-11-05 19:55:36 +01:00
|
|
|
id = results?.insertId
|
|
|
|
}
|
|
|
|
row = processFn(
|
|
|
|
await this.getReturningRow(queryFn, this.checkLookupKeys(id, json))
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if (operation !== Operation.READ) {
|
|
|
|
return row
|
|
|
|
}
|
|
|
|
return results.length ? results : [{ [operation.toLowerCase()]: true }]
|
|
|
|
}
|
2024-01-18 19:13:11 +01:00
|
|
|
|
2024-03-12 16:27:34 +01:00
|
|
|
convertJsonStringColumns(
|
|
|
|
table: Table,
|
2024-03-19 17:28:25 +01:00
|
|
|
results: Record<string, any>[],
|
|
|
|
aliases?: Record<string, string>
|
2024-03-12 16:27:34 +01:00
|
|
|
): Record<string, any>[] {
|
2024-04-15 19:23:39 +02:00
|
|
|
const tableName = getTableName(table)
|
2024-03-12 16:27:34 +01:00
|
|
|
for (const [name, field] of Object.entries(table.schema)) {
|
|
|
|
if (!this._isJsonColumn(field)) {
|
|
|
|
continue
|
|
|
|
}
|
2024-04-16 12:38:00 +02:00
|
|
|
const aliasedTableName = (tableName && aliases?.[tableName]) || tableName
|
2024-04-15 19:23:39 +02:00
|
|
|
const fullName = `${aliasedTableName}.${name}`
|
2024-03-12 16:27:34 +01:00
|
|
|
for (let row of results) {
|
2024-03-19 17:28:25 +01:00
|
|
|
if (typeof row[fullName] === "string") {
|
|
|
|
row[fullName] = JSON.parse(row[fullName])
|
2024-03-12 16:27:34 +01:00
|
|
|
}
|
|
|
|
if (typeof row[name] === "string") {
|
|
|
|
row[name] = JSON.parse(row[name])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return results
|
|
|
|
}
|
|
|
|
|
2024-03-13 14:38:08 +01:00
|
|
|
_isJsonColumn(
|
|
|
|
field: FieldSchema
|
|
|
|
): field is JsonFieldMetadata | BBReferenceFieldMetadata {
|
2024-03-12 16:27:34 +01:00
|
|
|
return (
|
|
|
|
field.type === FieldType.JSON ||
|
|
|
|
(field.type === FieldType.BB_REFERENCE &&
|
|
|
|
field.subtype === FieldSubtype.USERS)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-03-06 19:07:46 +01:00
|
|
|
log(query: string, values?: SqlQueryBinding) {
|
2024-02-23 18:31:45 +01:00
|
|
|
if (!environment.SQL_LOGGING_ENABLE) {
|
2024-01-18 19:13:11 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
const sqlClient = this.getSqlClient()
|
|
|
|
let string = `[SQL] [${sqlClient.toUpperCase()}] query="${query}"`
|
|
|
|
if (values) {
|
|
|
|
string += ` values="${values.join(", ")}"`
|
|
|
|
}
|
|
|
|
console.log(string)
|
|
|
|
}
|
2021-06-03 17:31:24 +02:00
|
|
|
}
|
|
|
|
|
2021-11-05 14:56:54 +01:00
|
|
|
export default SqlQueryBuilder
|