Adding a fix for mysql, stopping coercion to dates in some cases, attempting to make this less all catching. Likely an area of concern, but there is currently no way to search for dates without this.

This commit is contained in:
mike12345567 2024-10-21 17:17:05 +01:00
parent 35238165d9
commit e69bfc2d71
4 changed files with 115 additions and 48 deletions

View File

@ -18,17 +18,18 @@ import { Knex } from "knex"
describe.each(
[
DatabaseName.POSTGRES,
// DatabaseName.POSTGRES,
DatabaseName.MYSQL,
DatabaseName.SQL_SERVER,
DatabaseName.MARIADB,
DatabaseName.ORACLE,
// DatabaseName.SQL_SERVER,
// DatabaseName.MARIADB,
// DatabaseName.ORACLE,
].map(name => [name, getDatasource(name)])
)("queries (%s)", (dbName, dsProvider) => {
const config = setup.getConfig()
const isOracle = dbName === DatabaseName.ORACLE
const isMsSQL = dbName === DatabaseName.SQL_SERVER
const isPostgres = dbName === DatabaseName.POSTGRES
const mainTableName = "test_table"
let rawDatasource: Datasource
let datasource: Datasource
@ -71,15 +72,15 @@ describe.each(
client = await knexClient(rawDatasource)
await client.schema.dropTableIfExists("test_table")
await client.schema.createTable("test_table", table => {
await client.schema.dropTableIfExists(mainTableName)
await client.schema.createTable(mainTableName, table => {
table.increments("id").primary()
table.string("name")
table.timestamp("birthday")
table.integer("number")
})
await client("test_table").insert([
await client(mainTableName).insert([
{ name: "one" },
{ name: "two" },
{ name: "three" },
@ -105,7 +106,7 @@ describe.each(
const query = await createQuery({
name: "New Query",
fields: {
sql: client("test_table").select("*").toString(),
sql: client(mainTableName).select("*").toString(),
},
})
@ -114,7 +115,7 @@ describe.each(
name: "New Query",
parameters: [],
fields: {
sql: client("test_table").select("*").toString(),
sql: client(mainTableName).select("*").toString(),
},
schema: {},
queryVerb: "read",
@ -133,7 +134,7 @@ describe.each(
it("should be able to update a query", async () => {
const query = await createQuery({
fields: {
sql: client("test_table").select("*").toString(),
sql: client(mainTableName).select("*").toString(),
},
})
@ -143,7 +144,7 @@ describe.each(
...query,
name: "Updated Query",
fields: {
sql: client("test_table").where({ id: 1 }).toString(),
sql: client(mainTableName).where({ id: 1 }).toString(),
},
})
@ -152,7 +153,7 @@ describe.each(
name: "Updated Query",
parameters: [],
fields: {
sql: client("test_table").where({ id: 1 }).toString(),
sql: client(mainTableName).where({ id: 1 }).toString(),
},
schema: {},
queryVerb: "read",
@ -169,7 +170,7 @@ describe.each(
it("should be able to delete a query", async () => {
const query = await createQuery({
fields: {
sql: client("test_table").select("*").toString(),
sql: client(mainTableName).select("*").toString(),
},
})
@ -188,7 +189,7 @@ describe.each(
it("should be able to list queries", async () => {
const query = await createQuery({
fields: {
sql: client("test_table").select("*").toString(),
sql: client(mainTableName).select("*").toString(),
},
})
@ -199,7 +200,7 @@ describe.each(
it("should strip sensitive fields for prod apps", async () => {
const query = await createQuery({
fields: {
sql: client("test_table").select("*").toString(),
sql: client(mainTableName).select("*").toString(),
},
})
@ -217,7 +218,7 @@ describe.each(
const jsonStatement = `COALESCE(json_build_object('name', name),'{"name":{}}'::json)`
const query = await createQuery({
fields: {
sql: client("test_table")
sql: client(mainTableName)
.select([
"*",
client.raw(
@ -245,7 +246,7 @@ describe.each(
datasourceId: datasource._id!,
queryVerb: "read",
fields: {
sql: client("test_table").where({ id: 1 }).toString(),
sql: client(mainTableName).where({ id: 1 }).toString(),
},
parameters: [],
transformer: "return data",
@ -391,7 +392,7 @@ describe.each(
it("should work with dynamic variables", async () => {
const basedOnQuery = await createQuery({
fields: {
sql: client("test_table").select("name").where({ id: 1 }).toString(),
sql: client(mainTableName).select("name").where({ id: 1 }).toString(),
},
})
@ -440,7 +441,7 @@ describe.each(
it("should handle the dynamic base query being deleted", async () => {
const basedOnQuery = await createQuery({
fields: {
sql: client("test_table").select("name").where({ id: 1 }).toString(),
sql: client(mainTableName).select("name").where({ id: 1 }).toString(),
},
})
@ -494,7 +495,7 @@ describe.each(
it("should be able to insert with bindings", async () => {
const query = await createQuery({
fields: {
sql: client("test_table").insert({ name: "{{ foo }}" }).toString(),
sql: client(mainTableName).insert({ name: "{{ foo }}" }).toString(),
},
parameters: [
{
@ -517,7 +518,7 @@ describe.each(
},
])
const rows = await client("test_table").where({ name: "baz" }).select()
const rows = await client(mainTableName).where({ name: "baz" }).select()
expect(rows).toHaveLength(1)
for (const row of rows) {
expect(row).toMatchObject({ name: "baz" })
@ -527,7 +528,7 @@ describe.each(
it("should not allow handlebars as parameters", async () => {
const query = await createQuery({
fields: {
sql: client("test_table").insert({ name: "{{ foo }}" }).toString(),
sql: client(mainTableName).insert({ name: "{{ foo }}" }).toString(),
},
parameters: [
{
@ -563,7 +564,7 @@ describe.each(
const date = new Date(datetimeStr)
const query = await createQuery({
fields: {
sql: client("test_table")
sql: client(mainTableName)
.insert({
name: "foo",
birthday: client.raw("{{ birthday }}"),
@ -585,7 +586,7 @@ describe.each(
expect(result.data).toEqual([{ created: true }])
const rows = await client("test_table")
const rows = await client(mainTableName)
.where({ birthday: datetimeStr })
.select()
expect(rows).toHaveLength(1)
@ -601,7 +602,7 @@ describe.each(
async notDateStr => {
const query = await createQuery({
fields: {
sql: client("test_table")
sql: client(mainTableName)
.insert({ name: client.raw("{{ name }}") })
.toString(),
},
@ -622,7 +623,7 @@ describe.each(
expect(result.data).toEqual([{ created: true }])
const rows = await client("test_table")
const rows = await client(mainTableName)
.where({ name: notDateStr })
.select()
expect(rows).toHaveLength(1)
@ -634,7 +635,7 @@ describe.each(
it("should execute a query", async () => {
const query = await createQuery({
fields: {
sql: client("test_table").select("*").orderBy("id").toString(),
sql: client(mainTableName).select("*").orderBy("id").toString(),
},
})
@ -677,7 +678,7 @@ describe.each(
it("should be able to transform a query", async () => {
const query = await createQuery({
fields: {
sql: client("test_table").where({ id: 1 }).select("*").toString(),
sql: client(mainTableName).where({ id: 1 }).select("*").toString(),
},
transformer: `
data[0].id = data[0].id + 1;
@ -700,7 +701,7 @@ describe.each(
it("should coerce numeric bindings", async () => {
const query = await createQuery({
fields: {
sql: client("test_table")
sql: client(mainTableName)
.where({ id: client.raw("{{ id }}") })
.select("*")
.toString(),
@ -734,7 +735,7 @@ describe.each(
it("should be able to update rows", async () => {
const query = await createQuery({
fields: {
sql: client("test_table")
sql: client(mainTableName)
.update({ name: client.raw("{{ name }}") })
.where({ id: client.raw("{{ id }}") })
.toString(),
@ -759,7 +760,7 @@ describe.each(
},
})
const rows = await client("test_table").where({ id: 1 }).select()
const rows = await client(mainTableName).where({ id: 1 }).select()
expect(rows).toEqual([
{ id: 1, name: "foo", birthday: null, number: null },
])
@ -768,7 +769,7 @@ describe.each(
it("should be able to execute an update that updates no rows", async () => {
const query = await createQuery({
fields: {
sql: client("test_table")
sql: client(mainTableName)
.update({ name: "updated" })
.where({ id: 100 })
.toString(),
@ -778,7 +779,7 @@ describe.each(
await config.api.query.execute(query._id!)
const rows = await client("test_table").select()
const rows = await client(mainTableName).select()
for (const row of rows) {
expect(row.name).not.toEqual("updated")
}
@ -787,14 +788,14 @@ describe.each(
it("should be able to execute a delete that deletes no rows", async () => {
const query = await createQuery({
fields: {
sql: client("test_table").where({ id: 100 }).delete().toString(),
sql: client(mainTableName).where({ id: 100 }).delete().toString(),
},
queryVerb: "delete",
})
await config.api.query.execute(query._id!)
const rows = await client("test_table").select()
const rows = await client(mainTableName).select()
expect(rows).toHaveLength(5)
})
})
@ -803,7 +804,7 @@ describe.each(
it("should be able to delete rows", async () => {
const query = await createQuery({
fields: {
sql: client("test_table")
sql: client(mainTableName)
.where({ id: client.raw("{{ id }}") })
.delete()
.toString(),
@ -823,7 +824,7 @@ describe.each(
},
})
const rows = await client("test_table").where({ id: 1 }).select()
const rows = await client(mainTableName).where({ id: 1 }).select()
expect(rows).toHaveLength(0)
})
})
@ -831,7 +832,7 @@ describe.each(
describe("query through datasource", () => {
it("should be able to query the datasource", async () => {
const entityId = "test_table"
const entityId = mainTableName
await config.api.datasource.update({
...datasource,
entities: {
@ -876,7 +877,7 @@ describe.each(
beforeAll(async () => {
queryParams = {
fields: {
sql: client("test_table")
sql: client(mainTableName)
.insert({
name: client.raw("{{ bindingName }}"),
number: client.raw("{{ bindingNumber }}"),
@ -929,4 +930,34 @@ describe.each(
})
})
})
describe("edge cases", () => {
it("should find rows with a binding containing a slash", async () => {
const slashValue = "1/10"
await client(mainTableName).insert([{ name: slashValue }])
const query = await createQuery({
fields: {
sql: client(mainTableName)
.select("*")
.where("name", "=", client.raw("{{ bindingName }}"))
.toString(),
},
parameters: [
{
name: "bindingName",
default: "",
},
],
queryVerb: "read",
})
const results = await config.api.query.execute(query._id!, {
parameters: {
bindingName: slashValue,
},
})
expect(results).toBeDefined()
expect(results.data.length).toEqual(1)
})
})
})

View File

@ -24,8 +24,7 @@ import {
checkExternalTables,
HOST_ADDRESS,
} from "./utils"
import dayjs from "dayjs"
import { NUMBER_REGEX } from "../utilities"
import { isDate, NUMBER_REGEX } from "../utilities"
import { MySQLColumn } from "./base/types"
import { getReadableErrorMessage } from "./base/errorMapping"
import { sql } from "@budibase/backend-core"
@ -129,11 +128,7 @@ export function bindingTypeCoerce(bindings: SqlQueryBinding) {
}
// if not a number, see if it is a date - important to do in this order as any
// integer will be considered a valid date
else if (
/^\d/.test(binding) &&
dayjs(binding).isValid() &&
!binding.includes(",")
) {
else if (isDate(binding)) {
let value: any
value = new Date(binding)
if (isNaN(value)) {
@ -439,8 +434,7 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
dumpContent.push(createTableStatement)
}
const schema = dumpContent.join("\n")
return schema
return dumpContent.join("\n")
} finally {
this.disconnect()
}

View File

@ -3,6 +3,7 @@ import { context } from "@budibase/backend-core"
import { generateMetadataID } from "../db/utils"
import { Document } from "@budibase/types"
import stream from "stream"
import dayjs from "dayjs"
const Readable = stream.Readable
@ -14,6 +15,17 @@ export const isDev = env.isDev
export const NUMBER_REGEX = /^[+-]?([0-9]*[.])?[0-9]+$/g
export function isDate(str: string) {
// checks for xx/xx/xx or ISO date timestamp formats
return (
/^\d{2}\/\d{2}\/\d{2,4}$|^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?)?$/.test(
str
) &&
dayjs(str).isValid() &&
!str.includes(",")
)
}
export function removeFromArray(array: any[], element: any) {
const index = array.indexOf(element)
if (index !== -1) {

View File

@ -0,0 +1,30 @@
import { isDate } from "../"
describe("isDate", () => {
it("should handle DD/MM/YYYY", () => {
expect(isDate("01/01/2001")).toEqual(true)
})
it("should handle DD/MM/YY", () => {
expect(isDate("01/01/01")).toEqual(true)
})
it("should handle ISO format YYYY-MM-DD", () => {
expect(isDate("2001-01-01")).toEqual(true)
})
it("should handle ISO format with time (YYYY-MM-DDTHH:MM)", () => {
expect(isDate("2001-01-01T12:30")).toEqual(true)
})
it("should handle ISO format with full timestamp (YYYY-MM-DDTHH:MM:SS)", () => {
expect(isDate("2001-01-01T12:30:45")).toEqual(true)
})
it("should return false for invalid formats", () => {
expect(isDate("")).toEqual(false)
expect(isDate("1/10")).toEqual(false)
expect(isDate("random string")).toEqual(false)
expect(isDate("123456")).toEqual(false)
})
})