Merge pull request #15795 from Budibase/fix/my-sql-ansi-quotes
MySQL ANSI quotes support for queries
This commit is contained in:
commit
449978d8f8
|
@ -16,7 +16,7 @@ const descriptions = datasourceDescribe({
|
|||
if (descriptions.length) {
|
||||
describe.each(descriptions)(
|
||||
"queries ($dbName)",
|
||||
({ config, dsProvider, isOracle, isMSSQL, isPostgres }) => {
|
||||
({ config, dsProvider, isOracle, isMSSQL, isPostgres, isMySQL }) => {
|
||||
let rawDatasource: Datasource
|
||||
let datasource: Datasource
|
||||
let client: Knex
|
||||
|
@ -217,6 +217,38 @@ if (descriptions.length) {
|
|||
expect(res).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
isMySQL &&
|
||||
it("should handle ANSI_QUOTE=off MySQL queries with bindings", async () => {
|
||||
const query = await createQuery({
|
||||
fields: {
|
||||
sql: client(tableName)
|
||||
.select("*")
|
||||
.where({
|
||||
name: client.raw("'{{ name }}'"),
|
||||
})
|
||||
.toString(),
|
||||
},
|
||||
parameters: [
|
||||
{
|
||||
name: "name",
|
||||
default: "",
|
||||
},
|
||||
],
|
||||
queryVerb: "read",
|
||||
})
|
||||
const res = await config.api.query.execute(
|
||||
query._id!,
|
||||
{
|
||||
parameters: { name: "one" },
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
}
|
||||
)
|
||||
expect(res.data.length).toEqual(1)
|
||||
expect(res.data[0].name).toEqual("one")
|
||||
})
|
||||
})
|
||||
|
||||
describe("preview", () => {
|
||||
|
|
|
@ -1,10 +1,30 @@
|
|||
import { findHBSBlocks } from "@budibase/string-templates"
|
||||
import { DatasourcePlus } from "@budibase/types"
|
||||
import { DatasourcePlus, SourceName } from "@budibase/types"
|
||||
import sdk from "../../sdk"
|
||||
|
||||
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 new RegExp(`"[^"]*"|'[^']*'`, "g")
|
||||
} else {
|
||||
return new RegExp(`'[^']*'`, "g")
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -24,10 +44,10 @@ export async function interpolateSQL(
|
|||
)
|
||||
// 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(CONST_CHAR_REGEX) || []
|
||||
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(new RegExp(`'[^']*${binding}[^']*'`))
|
||||
string.match(getBindingWithinConstCharRegex(sourceName, binding))
|
||||
)
|
||||
if (charConstBindingMatch) {
|
||||
let [part1, part2] = charConstBindingMatch.split(binding)
|
||||
|
|
|
@ -112,9 +112,15 @@ class QueryRunner {
|
|||
let query: Record<string, any>
|
||||
// handle SQL injections by interpolating the variables
|
||||
if (isSQL(datasourceClone)) {
|
||||
query = await interpolateSQL(fieldsClone, enrichedContext, integration, {
|
||||
query = await interpolateSQL(
|
||||
datasource.source,
|
||||
fieldsClone,
|
||||
enrichedContext,
|
||||
integration,
|
||||
{
|
||||
nullDefaultSupport,
|
||||
})
|
||||
}
|
||||
)
|
||||
} else {
|
||||
query = await sdk.queries.enrichContext(fieldsClone, enrichedContext)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue