Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 29 additions & 29 deletions src/drivers/db0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const DEFAULT_TABLE_NAME = "unstorage";
const kExperimentalWarning = "__unstorage_db0_experimental_warning__";

const driver: DriverFactory<DB0DriverOptions, Database<Connector<unknown>>> = (opts) => {
opts.tableName = opts.tableName || DEFAULT_TABLE_NAME;
const tableName = opts.tableName || DEFAULT_TABLE_NAME;

let setupPromise: Promise<void> | undefined;
let setupDone = false;
Expand All @@ -40,7 +40,7 @@ const driver: DriverFactory<DB0DriverOptions, Database<Connector<unknown>>> = (o
);
(globalThis as any)[kExperimentalWarning] = true;
}
setupPromise = setupTable(opts).then(() => {
setupPromise = setupTable(opts.database, tableName).then(() => {
setupDone = true;
setupPromise = undefined;
});
Expand All @@ -52,71 +52,71 @@ const driver: DriverFactory<DB0DriverOptions, Database<Connector<unknown>>> = (o

return {
name: DRIVER_NAME,
options: opts,
options: { ...opts, tableName },
getInstance: () => opts.database,
async hasItem(key) {
await ensureTable();
const { rows } = isMysql
? await opts.database.sql<ResultSchema>
/* sql */ `SELECT EXISTS (SELECT 1 FROM {${opts.tableName}} WHERE \`key\` = ${key}) AS \`value\``
/* sql */ `SELECT EXISTS (SELECT 1 FROM {${tableName}} WHERE \`key\` = ${key}) AS \`value\``
: await opts.database.sql<ResultSchema>
/* sql */ `SELECT EXISTS (SELECT 1 FROM {${opts.tableName}} WHERE key = ${key}) AS value`;
/* sql */ `SELECT EXISTS (SELECT 1 FROM {${tableName}} WHERE key = ${key}) AS value`;
return rows?.[0]?.value == "1";
},
getItem: async (key) => {
await ensureTable();
const { rows } = isMysql
? await opts.database.sql<ResultSchema>
/* sql */ `SELECT value FROM {${opts.tableName}} WHERE \`key\` = ${key}`
/* sql */ `SELECT value FROM {${tableName}} WHERE \`key\` = ${key}`
: await opts.database.sql<ResultSchema>
/* sql */ `SELECT value FROM {${opts.tableName}} WHERE key = ${key}`;
/* sql */ `SELECT value FROM {${tableName}} WHERE key = ${key}`;
return rows?.[0]?.value ?? null;
},
getItemRaw: async (key) => {
await ensureTable();
const { rows } = isMysql
? await opts.database.sql<ResultSchema>
/* sql */ `SELECT \`blob\` as value FROM {${opts.tableName}} WHERE \`key\` = ${key}`
/* sql */ `SELECT \`blob\` as value FROM {${tableName}} WHERE \`key\` = ${key}`
: await opts.database.sql<ResultSchema>
/* sql */ `SELECT blob as value FROM {${opts.tableName}} WHERE key = ${key}`;
/* sql */ `SELECT blob as value FROM {${tableName}} WHERE key = ${key}`;
return rows?.[0]?.value ?? null;
},
setItem: async (key, value) => {
await ensureTable();
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`;
/* sql */ `INSERT INTO {${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`;
/* sql */ `INSERT INTO {${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 (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`;
/* sql */ `INSERT INTO {${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`;
/* sql */ `INSERT INTO {${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();
if (isMysql) {
await opts.database.sql /* sql */ `DELETE FROM {${opts.tableName}} WHERE \`key\`=${key}`;
await opts.database.sql /* sql */ `DELETE FROM {${tableName}} WHERE \`key\`=${key}`;
} else {
await opts.database.sql /* sql */ `DELETE FROM {${opts.tableName}} WHERE key=${key}`;
await opts.database.sql /* sql */ `DELETE FROM {${tableName}} WHERE key=${key}`;
}
},
getMeta: async (key) => {
await ensureTable();
const { rows } = isMysql
? await opts.database.sql<ResultSchema>
/* sql */ `SELECT created_at, updated_at FROM {${opts.tableName}} WHERE \`key\` = ${key}`
/* sql */ `SELECT created_at, updated_at FROM {${tableName}} WHERE \`key\` = ${key}`
: await opts.database.sql<ResultSchema>
/* sql */ `SELECT created_at, updated_at FROM {${opts.tableName}} WHERE key = ${key}`;
/* sql */ `SELECT created_at, updated_at FROM {${tableName}} WHERE key = ${key}`;

return {
birthtime: toDate(rows?.[0]?.created_at),
Expand All @@ -127,15 +127,15 @@ const driver: DriverFactory<DB0DriverOptions, Database<Connector<unknown>>> = (o
await ensureTable();
const { rows } = isMysql
? await opts.database.sql<ResultSchema>
/* sql */ `SELECT \`key\` FROM {${opts.tableName}} WHERE \`key\` LIKE ${base + "%"}`
/* sql */ `SELECT \`key\` FROM {${tableName}} WHERE \`key\` LIKE ${base + "%"}`
: await opts.database.sql<ResultSchema>
/* sql */ `SELECT key FROM {${opts.tableName}} WHERE key LIKE ${base + "%"}`;
/* sql */ `SELECT key FROM {${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 {${tableName}}`;
},
dispose: async () => {
await opts.database.dispose();
Expand All @@ -144,12 +144,12 @@ const driver: DriverFactory<DB0DriverOptions, Database<Connector<unknown>>> = (o
};

/** Run database init/migration once */
async function setupTable(opts: DB0DriverOptions) {
switch (opts.database.dialect) {
async function setupTable(database: Database, tableName: string) {
switch (database.dialect) {
case "sqlite":
case "libsql": {
await opts.database.sql /* sql */ `
CREATE TABLE IF NOT EXISTS {${opts.tableName}} (
await database.sql /* sql */ `
CREATE TABLE IF NOT EXISTS {${tableName}} (
key TEXT PRIMARY KEY,
value TEXT,
blob BLOB,
Expand All @@ -160,8 +160,8 @@ async function setupTable(opts: DB0DriverOptions) {
return;
}
case "postgresql": {
await opts.database.sql /* sql */ `
CREATE TABLE IF NOT EXISTS {${opts.tableName}} (
await database.sql /* sql */ `
CREATE TABLE IF NOT EXISTS {${tableName}} (
key VARCHAR(255) NOT NULL PRIMARY KEY,
value TEXT,
blob BYTEA,
Expand All @@ -172,8 +172,8 @@ async function setupTable(opts: DB0DriverOptions) {
return;
}
case "mysql": {
await opts.database.sql /* sql */ `
CREATE TABLE IF NOT EXISTS {${opts.tableName}} (
await database.sql /* sql */ `
CREATE TABLE IF NOT EXISTS {${tableName}} (
\`key\` VARCHAR(255) NOT NULL PRIMARY KEY,
\`value\` LONGTEXT,
\`blob\` BLOB,
Expand All @@ -184,7 +184,7 @@ async function setupTable(opts: DB0DriverOptions) {
return;
}
default: {
throw createError(DRIVER_NAME, `unsuppoted SQL dialect: ${opts.database.dialect}`);
throw createError(DRIVER_NAME, `unsuppoted SQL dialect: ${database.dialect}`);
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/drivers/fs-lite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@ const driver: DriverFactory<FSStorageOptions> = (opts = {}) => {
throw createRequiredError(DRIVER_NAME, "base");
}

opts.base = resolve(opts.base);
const base = resolve(opts.base);
const r = (key: string) => {
if (PATH_TRAVERSE_RE.test(key)) {
throw createError(
DRIVER_NAME,
`Invalid key: ${JSON.stringify(key)}. It should not contain .. segments`,
);
}
const resolved = join(opts.base!, key.replace(/:/g, "/"));
const resolved = join(base, key.replace(/:/g, "/"));
return resolved;
};

return {
name: DRIVER_NAME,
options: opts,
options: { ...opts, base },
flags: {
maxDepth: true,
},
Expand Down
19 changes: 10 additions & 9 deletions src/drivers/planetscale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ export const DRIVER_DEPENDENCIES: DriverDependencies = {
};

const DRIVER_NAME = "planetscale";
const DEFAULT_TABLE_NAME = "storage";

const driver: DriverFactory<PlanetscaleDriverOptions, Promise<Connection>> = (opts = {}) => {
opts.table = opts.table || "storage";
const table = opts.table || DEFAULT_TABLE_NAME;

let _connection: Promise<Connection> | undefined;
const getConnection = () =>
Expand Down Expand Up @@ -63,18 +64,18 @@ const driver: DriverFactory<PlanetscaleDriverOptions, Promise<Connection>> = (op

return {
name: DRIVER_NAME,
options: opts,
options: { ...opts, table },
getInstance: getConnection,
hasItem: async (key) => {
const res = await (
await getConnection()
).execute(`SELECT EXISTS (SELECT 1 FROM ${opts.table} WHERE id = :key) as value;`, { key });
).execute(`SELECT EXISTS (SELECT 1 FROM ${table} WHERE id = :key) as value;`, { key });
return rows<{ value: string }[]>(res)[0]?.value == "1";
},
getItem: async (key) => {
const res = await (
await getConnection()
).execute(`SELECT value from ${opts.table} WHERE id=:key;`, {
).execute(`SELECT value from ${table} WHERE id=:key;`, {
key,
});
return rows(res)[0]?.value ?? null;
Expand All @@ -83,17 +84,17 @@ const driver: DriverFactory<PlanetscaleDriverOptions, Promise<Connection>> = (op
await (
await getConnection()
).execute(
`INSERT INTO ${opts.table} (id, value) VALUES (:key, :value) ON DUPLICATE KEY UPDATE value = :value;`,
`INSERT INTO ${table} (id, value) VALUES (:key, :value) ON DUPLICATE KEY UPDATE value = :value;`,
{ key, value },
);
},
removeItem: async (key) => {
await (await getConnection()).execute(`DELETE FROM ${opts.table} WHERE id=:key;`, { key });
await (await getConnection()).execute(`DELETE FROM ${table} WHERE id=:key;`, { key });
},
getMeta: async (key) => {
const res = await (
await getConnection()
).execute(`SELECT created_at, updated_at from ${opts.table} WHERE id=:key;`, { key });
).execute(`SELECT created_at, updated_at from ${table} WHERE id=:key;`, { key });
return {
birthtime: rows(res)[0]?.created_at,
mtime: rows(res)[0]?.updated_at,
Expand All @@ -102,11 +103,11 @@ const driver: DriverFactory<PlanetscaleDriverOptions, Promise<Connection>> = (op
getKeys: async (base = "") => {
const res = await (
await getConnection()
).execute(`SELECT id from ${opts.table} WHERE id LIKE :base;`, { base: `${base}%` });
).execute(`SELECT id from ${table} WHERE id LIKE :base;`, { base: `${base}%` });
return rows(res).map((r) => r.id);
},
clear: async () => {
await (await getConnection()).execute(`DELETE FROM ${opts.table};`);
await (await getConnection()).execute(`DELETE FROM ${table};`);
},
};
};
Expand Down
6 changes: 6 additions & 0 deletions test/drivers/db0.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ for (const driver of drivers) {
testDriver({
driver: () => db0Driver({ database: db }),
additionalTests: (ctx) => {
it("does not mutate input options", () => {
const opts = { database: db };
const instance = db0Driver(opts);
expect(opts).toEqual({ database: db });
expect(instance.options?.tableName).toBe("unstorage");
});
it("meta", async () => {
await ctx.storage.setItem("meta:test", "test_data");

Expand Down
6 changes: 6 additions & 0 deletions test/drivers/fs-lite.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ describe("drivers: fs-lite", () => {
testDriver({
driver: driver({ base: dir }),
additionalTests(ctx) {
it("does not mutate input options", () => {
const opts = { base: "./tmp/fs-lite-opts" };
const instance = driver(opts);
expect(opts).toEqual({ base: "./tmp/fs-lite-opts" });
expect(instance.options?.base).toBe(resolve("./tmp/fs-lite-opts"));
});
it("check filesystem", async () => {
await ctx.storage.setItem("s1:a", "test_data");
expect(await readFile(resolve(dir, "s1/a"), "utf8")).toBe("test_data");
Expand Down
11 changes: 11 additions & 0 deletions test/drivers/planetscale.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { describe, it, expect } from "vitest";
import driver from "../../src/drivers/planetscale.ts";

describe("drivers: planetscale", () => {
it("does not mutate input options", () => {
const opts = { url: "mysql://user:pass@host/db" };
const instance = driver(opts);
expect(opts).toEqual({ url: "mysql://user:pass@host/db" });
expect(instance.options?.table).toBe("storage");
});
});