@budibase/server
Version:
Budibase Web Server
113 lines (108 loc) • 3.72 kB
text/typescript
import { findHBSBlocks } from "@budibase/string-templates"
import { DatasourcePlus, SourceName } from "@budibase/types"
import sdk from "../../sdk"
const MYSQL_CONST_CHAR_REGEX = new RegExp(`"[^"]*"|'[^']*'`, "g")
const CONST_CHAR_REGEX = new RegExp(`'[^']*'`, "g")
function getConstCharRegex(sourceName: SourceName) {
// MySQL clients support ANSI_QUOTES mode off, this is by default
// but " and ' count as string literals
if (sourceName === SourceName.MYSQL) {
return MYSQL_CONST_CHAR_REGEX
} else {
return CONST_CHAR_REGEX
}
}
function getBindingWithinConstCharRegex(
sourceName: SourceName,
binding: string
) {
if (sourceName === SourceName.MYSQL) {
return new RegExp(`[^']*${binding}[^']*'|"[^"]*${binding}[^"]*"`, "g")
} else {
return new RegExp(`'[^']*${binding}[^']*'`)
}
}
export async function interpolateSQL(
sourceName: SourceName,
fields: { sql: string; bindings: any[] },
parameters: { [key: string]: any },
integration: DatasourcePlus,
opts: { nullDefaultSupport: boolean }
) {
let sql = fields.sql
if (!sql || typeof sql !== "string") {
return fields
}
const bindings = findHBSBlocks(sql)
let variables = [],
arrays = []
for (let binding of bindings) {
// look for array/list operations in the SQL statement, which will need handled later
const listRegexMatch = sql.match(
new RegExp(`(in|IN|In|iN)( )+[(]?${binding}[)]?`)
)
// check if the variable was used as part of a string concat e.g. 'Hello {{binding}}'
// start by finding all the instances of const character strings
const charConstMatch = sql.match(getConstCharRegex(sourceName)) || []
// now look within them to see if a binding is used
const charConstBindingMatch = charConstMatch.find((string: any) =>
string.match(getBindingWithinConstCharRegex(sourceName, binding))
)
if (charConstBindingMatch) {
let [part1, part2] = charConstBindingMatch.split(binding)
part1 = `'${part1.substring(1)}'`
part2 = `'${part2.substring(0, part2.length - 1)}'`
sql = sql.replace(
charConstBindingMatch,
integration.getStringConcat([
part1,
integration.getBindingIdentifier(),
part2,
])
)
}
// generate SQL parameterised array
else if (listRegexMatch) {
arrays.push(binding)
// determine the length of the array
const value = (await sdk.queries.enrichContext([binding], parameters))[0]
.split(",")
.map((val: string) => val.trim())
// build a string like ($1, $2, $3)
let replacement = `${Array.apply(null, Array(value.length))
.map(() => integration.getBindingIdentifier())
.join(",")}`
// check if parentheses are needed
if (!listRegexMatch[0].includes(`(${binding})`)) {
replacement = `(${replacement})`
}
sql = sql.replace(binding, replacement)
} else {
sql = sql.replace(binding, integration.getBindingIdentifier())
}
variables.push(binding)
}
// replicate the knex structure
fields.sql = sql
fields.bindings = await sdk.queries.enrichArrayContext(variables, parameters)
if (opts.nullDefaultSupport) {
for (let index in fields.bindings) {
if (fields.bindings[index] === "") {
fields.bindings[index] = null
}
}
}
// check for arrays in the data
let updated: string[] = []
for (let i = 0; i < variables.length; i++) {
if (arrays.includes(variables[i])) {
updated = updated.concat(
fields.bindings[i].split(",").map((val: string) => val.trim())
)
} else {
updated.push(fields.bindings[i])
}
}
fields.bindings = updated
return fields
}