From 6e834d32dd87c0ea0bd775eec49067557a8e813b Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 26 Dec 2024 13:21:28 +0100 Subject: [PATCH] fix(db): escape mysql queries --- src/drivers/db0.ts | 66 +++++++++++++++++++++++++++++++--------- test/drivers/db0.test.ts | 53 +++++++++++++++++++++----------- 2 files changed, 87 insertions(+), 32 deletions(-) diff --git a/src/drivers/db0.ts b/src/drivers/db0.ts index 2775e80e..098c95ca 100644 --- a/src/drivers/db0.ts +++ b/src/drivers/db0.ts @@ -44,13 +44,22 @@ 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 */ ` + 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} @@ -60,23 +69,31 @@ export default defineDriver((opts: DB0DriverOptions) => { }, getItem: async (key) => { await ensureTable(); - const { rows } = await opts.database.sql/* sql */ ` + 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 */ ` + 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") { + if (isMysql) { await opts.database.sql/* sql */ ` - INSERT INTO {${opts.tableName}} (key, value, created_at, updated_at) + 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 `; @@ -91,11 +108,12 @@ export default defineDriver((opts: DB0DriverOptions) => { }, async setItemRaw(key, value) { await ensureTable(); - if (opts.database.dialect === "mysql") { + 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}, ${value}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) - ON DUPLICATE KEY UPDATE blob = ${value}, updated_at = CURRENT_TIMESTAMP + 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 */ ` @@ -108,13 +126,25 @@ export default defineDriver((opts: DB0DriverOptions) => { }, removeItem: async (key) => { await ensureTable(); - await opts.database.sql/* sql */ ` + 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 */ ` + 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} @@ -127,7 +157,13 @@ export default defineDriver((opts: DB0DriverOptions) => { }, getKeys: async (base = "") => { await ensureTable(); - const { rows } = await opts.database.sql/* sql */ ` + 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 + "%"} @@ -175,9 +211,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), + }); }); - }); - }, - }); - }); + }, + }); + } + ); }