From a015040dbe123a83d4afb39aec88071e73a7a0d1 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 26 Dec 2024 13:29:33 +0100 Subject: [PATCH] fix(db): escape mysql queries (#542) --- src/drivers/db0.ts | 104 ++++++++++++++++++--------------------- test/drivers/db0.test.ts | 53 +++++++++++++------- 2 files changed, 85 insertions(+), 72 deletions(-) diff --git a/src/drivers/db0.ts b/src/drivers/db0.ts index 2775e80e..c4fd86aa 100644 --- a/src/drivers/db0.ts +++ b/src/drivers/db0.ts @@ -44,81 +44,77 @@ export default defineDriver((opts: DB0DriverOptions) => { return setupPromise; }; + const isMysql = opts.database.dialect === "mysql"; + return { name: DRIVER_NAME, options: opts, getInstance: () => opts.database, async hasItem(key) { await ensureTable(); - const { rows } = await opts.database.sql/* sql */ ` - SELECT EXISTS ( - SELECT 1 FROM {${opts.tableName}} - WHERE key = ${key} - ) AS value - `; + const { rows } = isMysql + ? await opts.database + .sql/* sql */ `SELECT EXISTS (SELECT 1 FROM {${opts.tableName}} WHERE \`key\` = ${key}) AS \`value\`` + : await opts.database + .sql/* sql */ `SELECT EXISTS (SELECT 1 FROM {${opts.tableName}} WHERE key = ${key}) AS value`; return rows?.[0]?.value == "1"; }, getItem: async (key) => { await ensureTable(); - const { rows } = await opts.database.sql/* sql */ ` - SELECT value FROM {${opts.tableName}} WHERE key = ${key} - `; + const { rows } = isMysql + ? await opts.database + .sql/* sql */ `SELECT value FROM {${opts.tableName}} WHERE \`key\` = ${key}` + : await opts.database + .sql/* sql */ `SELECT value FROM {${opts.tableName}} WHERE key = ${key}`; return rows?.[0]?.value ?? null; }, getItemRaw: async (key) => { await ensureTable(); - const { rows } = await opts.database.sql/* sql */ ` - SELECT blob as value FROM {${opts.tableName}} WHERE key = ${key} - `; + const { rows } = isMysql + ? await opts.database + .sql/* sql */ `SELECT \`blob\` as value FROM {${opts.tableName}} WHERE \`key\` = ${key}` + : await opts.database + .sql/* sql */ `SELECT blob as value FROM {${opts.tableName}} WHERE key = ${key}`; return rows?.[0]?.value ?? null; }, setItem: async (key, value) => { await ensureTable(); - if (opts.database.dialect === "mysql") { - await opts.database.sql/* sql */ ` - INSERT INTO {${opts.tableName}} (key, value, created_at, updated_at) - VALUES (${key}, ${value}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) - ON DUPLICATE KEY UPDATE value = ${value}, updated_at = CURRENT_TIMESTAMP - `; + if (isMysql) { + await opts.database + .sql/* sql */ `INSERT INTO {${opts.tableName}} (\`key\`, \`value\`, created_at, updated_at) VALUES (${key}, ${value}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE value = ${value}, updated_at = CURRENT_TIMESTAMP`; } else { - await opts.database.sql/* sql */ ` - INSERT INTO {${opts.tableName}} (key, value, created_at, updated_at) - VALUES (${key}, ${value}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) - ON CONFLICT(key) DO UPDATE - SET value = ${value}, updated_at = CURRENT_TIMESTAMP - `; + await opts.database + .sql/* sql */ `INSERT INTO {${opts.tableName}} (key, value, created_at, updated_at) VALUES (${key}, ${value}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON CONFLICT(key) DO UPDATE SET value = ${value}, updated_at = CURRENT_TIMESTAMP`; } }, async setItemRaw(key, value) { await ensureTable(); - if (opts.database.dialect === "mysql") { - await opts.database.sql/* sql */ ` - INSERT INTO {${opts.tableName}} (key, blob, created_at, updated_at) - VALUES (${key}, ${value}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) - ON DUPLICATE KEY UPDATE blob = ${value}, updated_at = CURRENT_TIMESTAMP - `; + if (isMysql) { + const blob = Buffer.from(value) as any; + await opts.database + .sql/* sql */ `INSERT INTO {${opts.tableName}} (\`key\`, \`blob\`, created_at, updated_at) VALUES (${key}, ${blob}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE \`blob\` = ${blob}, updated_at = CURRENT_TIMESTAMP`; } else { - await opts.database.sql/* sql */ ` - INSERT INTO {${opts.tableName}} (key, blob, created_at, updated_at) - VALUES (${key}, ${value}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) - ON CONFLICT(key) DO UPDATE - SET blob = ${value}, updated_at = CURRENT_TIMESTAMP - `; + await opts.database + .sql/* sql */ `INSERT INTO {${opts.tableName}} (key, blob, created_at, updated_at) VALUES (${key}, ${value}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON CONFLICT(key) DO UPDATE SET blob = ${value}, updated_at = CURRENT_TIMESTAMP`; } }, removeItem: async (key) => { await ensureTable(); - await opts.database.sql/* sql */ ` - DELETE FROM {${opts.tableName}} WHERE key=${key} - `; + if (isMysql) { + await opts.database + .sql/* sql */ `DELETE FROM {${opts.tableName}} WHERE \`key\`=${key}`; + } else { + await opts.database + .sql/* sql */ `DELETE FROM {${opts.tableName}} WHERE key=${key}`; + } }, getMeta: async (key) => { await ensureTable(); - const { rows } = await opts.database.sql/* sql */ ` - SELECT created_at, updated_at - FROM {${opts.tableName}} - WHERE key = ${key} - `; + const { rows } = isMysql + ? await opts.database + .sql/* sql */ `SELECT created_at, updated_at FROM {${opts.tableName}} WHERE \`key\` = ${key}` + : await opts.database + .sql/* sql */ `SELECT created_at, updated_at FROM {${opts.tableName}} WHERE key = ${key}`; return { birthtime: toDate(rows?.[0]?.created_at), @@ -127,19 +123,17 @@ export default defineDriver((opts: DB0DriverOptions) => { }, getKeys: async (base = "") => { await ensureTable(); - const { rows } = await opts.database.sql/* sql */ ` - SELECT key - FROM {${opts.tableName}} - WHERE key LIKE ${base + "%"} - `; + const { rows } = isMysql + ? await opts.database + .sql/* sql */ `SELECT \`key\` FROM {${opts.tableName}} WHERE \`key\` LIKE ${base + "%"}` + : await opts.database + .sql/* sql */ `SELECT key FROM {${opts.tableName}} WHERE key LIKE ${base + "%"}`; return rows?.map((r) => r.key); }, clear: async () => { await ensureTable(); - await opts.database.sql/* sql */ ` - DELETE FROM {${opts.tableName}} - `; + await opts.database.sql/* sql */ `DELETE FROM {${opts.tableName}}`; }, }; }); @@ -175,9 +169,9 @@ async function setupTable(opts: DB0DriverOptions) { case "mysql": { await opts.database.sql/* sql */ ` CREATE TABLE IF NOT EXISTS {${opts.tableName}} ( - key VARCHAR(255) NOT NULL PRIMARY KEY, - value LONGTEXT, - blob BLOB, + \`key\` VARCHAR(255) NOT NULL PRIMARY KEY, + \`value\` LONGTEXT, + \`blob\` BLOB, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); diff --git a/test/drivers/db0.test.ts b/test/drivers/db0.test.ts index 0340813a..e142ca04 100644 --- a/test/drivers/db0.test.ts +++ b/test/drivers/db0.test.ts @@ -31,28 +31,47 @@ const drivers = [ return createDatabase(pglite()); }, }, + // docker run -it --rm --name mysql -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=unstorage -p 3306:3306 mysql + // VITEST_MYSQL_URI=mysql://root:root@localhost/unstorage pnpm vitest test/drivers/db0.test.ts -t mysql + { + name: "mysql", + enabled: !!process.env.VITEST_MYSQL_URI, + async getDB() { + const mysql = await import("db0/connectors/mysql2").then( + (m) => m.default + ); + return createDatabase( + mysql({ + uri: process.env.VITEST_MYSQL_URI, + }) + ); + }, + }, ]; for (const driver of drivers) { - describe(`drivers: db0 - ${driver.name}`, async () => { - const db = await driver.getDB(); + describe.skipIf(driver.enabled === false)( + `drivers: db0 - ${driver.name}`, + async () => { + const db = await driver.getDB(); - afterAll(async () => { - await db.sql`DROP TABLE IF EXISTS unstorage`; - }); + afterAll(async () => { + await db.sql`DROP TABLE IF EXISTS unstorage`; + }); - testDriver({ - driver: () => db0Driver({ database: db }), - additionalTests: (ctx) => { - it("meta", async () => { - await ctx.storage.setItem("meta:test", "test_data"); + testDriver({ + driver: () => db0Driver({ database: db }), + additionalTests: (ctx) => { + it("meta", async () => { + await ctx.storage.setItem("meta:test", "test_data"); - expect(await ctx.storage.getMeta("meta:test")).toMatchObject({ - birthtime: expect.any(Date), - mtime: expect.any(Date), + expect(await ctx.storage.getMeta("meta:test")).toMatchObject({ + birthtime: expect.any(Date), + mtime: expect.any(Date), + }); }); - }); - }, - }); - }); + }, + }); + } + ); }