Skip to content

Commit

Permalink
fix(db): escape mysql queries (#542)
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 authored Dec 26, 2024
1 parent ed6bcbf commit a015040
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 72 deletions.
104 changes: 49 additions & 55 deletions src/drivers/db0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ResultSchema>/* sql */ `
SELECT EXISTS (
SELECT 1 FROM {${opts.tableName}}
WHERE key = ${key}
) AS value
`;
const { rows } = isMysql
? await opts.database
.sql<ResultSchema>/* sql */ `SELECT EXISTS (SELECT 1 FROM {${opts.tableName}} WHERE \`key\` = ${key}) AS \`value\``
: await opts.database
.sql<ResultSchema>/* 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<ResultSchema>/* sql */ `
SELECT value FROM {${opts.tableName}} WHERE key = ${key}
`;
const { rows } = isMysql
? await opts.database
.sql<ResultSchema>/* sql */ `SELECT value FROM {${opts.tableName}} WHERE \`key\` = ${key}`
: await opts.database
.sql<ResultSchema>/* 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<ResultSchema>/* sql */ `
SELECT blob as value FROM {${opts.tableName}} WHERE key = ${key}
`;
const { rows } = isMysql
? await opts.database
.sql<ResultSchema>/* sql */ `SELECT \`blob\` as value FROM {${opts.tableName}} WHERE \`key\` = ${key}`
: await opts.database
.sql<ResultSchema>/* 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<ResultSchema>/* sql */ `
SELECT created_at, updated_at
FROM {${opts.tableName}}
WHERE key = ${key}
`;
const { rows } = isMysql
? await opts.database
.sql<ResultSchema>/* sql */ `SELECT created_at, updated_at FROM {${opts.tableName}} WHERE \`key\` = ${key}`
: await opts.database
.sql<ResultSchema>/* sql */ `SELECT created_at, updated_at FROM {${opts.tableName}} WHERE key = ${key}`;

return {
birthtime: toDate(rows?.[0]?.created_at),
Expand All @@ -127,19 +123,17 @@ export default defineDriver((opts: DB0DriverOptions) => {
},
getKeys: async (base = "") => {
await ensureTable();
const { rows } = await opts.database.sql<ResultSchema>/* sql */ `
SELECT key
FROM {${opts.tableName}}
WHERE key LIKE ${base + "%"}
`;
const { rows } = isMysql
? await opts.database
.sql<ResultSchema>/* sql */ `SELECT \`key\` FROM {${opts.tableName}} WHERE \`key\` LIKE ${base + "%"}`
: await opts.database
.sql<ResultSchema>/* 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}}`;
},
};
});
Expand Down Expand Up @@ -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
);
Expand Down
53 changes: 36 additions & 17 deletions test/drivers/db0.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
});
});
});
},
});
});
},
});
}
);
}

0 comments on commit a015040

Please sign in to comment.