Skip to content
Open
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
4 changes: 3 additions & 1 deletion drizzle-orm/src/libsql/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,9 @@ export class LibSQLPreparedQuery<T extends PreparedQueryConfig = PreparedQueryCo
this.logger.logQuery(this.query.sql, params);
return await this.queryWithCache(this.query.sql, params, async () => {
const stmt: InStatement = { sql: this.query.sql, args: params as InArgs };
return (this.tx ? this.tx.execute(stmt) : this.client.execute(stmt)).then(({ rows }) => rows) as Promise<
return (this.tx ? this.tx.execute(stmt) : this.client.execute(stmt)).then(({ rows }) => {
return rows.map((row) => Array.prototype.slice.call(row));
}) as Promise<
T['values']
>;
});
Expand Down
78 changes: 78 additions & 0 deletions drizzle-orm/tests/libsql-cache-roundtrip.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { Client, InStatement } from '@libsql/client';
import { webcrypto } from 'node:crypto';
import { expect, test, vi } from 'vitest';

import { Cache, type MutationOption } from '~/cache/core/index.ts';
import { drizzle } from '~/libsql/index.ts';
import { sqliteTable, text } from '~/sqlite-core/index.ts';

if (!globalThis.crypto) {
Object.defineProperty(globalThis, 'crypto', {
value: webcrypto,
configurable: true,
});
}

// eslint-disable-next-line drizzle-internal/require-entity-kind
class JsonRoundTripCache extends Cache {
private data = new Map<string, string>();

override strategy(): 'explicit' | 'all' {
return 'explicit';
}

override async get(key: string): Promise<any[] | undefined> {
const stored = this.data.get(key);
return stored === undefined ? undefined : JSON.parse(stored);
}

override async put(key: string, response: any): Promise<void> {
this.data.set(key, JSON.stringify(response));
}

override async onMutate(_params: MutationOption): Promise<void> {}
}

function createArrayLikeLibSqlRow(payloadJson: string): Record<string, unknown> {
const row: Record<string, unknown> = {
payload: payloadJson,
};

Object.defineProperty(row, '0', {
value: payloadJson,
enumerable: false,
writable: false,
});
Object.defineProperty(row, 'length', {
value: 1,
enumerable: false,
writable: false,
});

return row;
}

test('libsql cached values survive JSON roundtrip', async () => {
const table = sqliteTable('cache_roundtrip_users', {
payload: text('payload', { mode: 'json' }).$type<{ a: number }>(),
});

const execute = vi.fn(async (_statement: InStatement) => {
return {
rows: [createArrayLikeLibSqlRow('{"a":1}')],
};
});

const client = {
execute,
} as unknown as Client;

const db = drizzle(client, { cache: new JsonRoundTripCache() });

const first = await db.select().from(table).$withCache();
const second = await db.select().from(table).$withCache();

expect(first).toEqual([{ payload: { a: 1 } }]);
expect(second).toEqual(first);
expect(execute).toHaveBeenCalledTimes(1);
});
54 changes: 54 additions & 0 deletions integration-tests/tests/sqlite/libsql.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { type Client, createClient } from '@libsql/client';
import retry from 'async-retry';
import type { MutationOption } from 'drizzle-orm/cache/core';
import { Cache } from 'drizzle-orm/cache/core';
import { sql } from 'drizzle-orm';
import { drizzle, type LibSQLDatabase } from 'drizzle-orm/libsql';
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { migrate } from 'drizzle-orm/libsql/migrator';
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
import { skipTests } from '~/common';
Expand All @@ -14,8 +17,34 @@ const ENABLE_LOGGING = false;
let db: LibSQLDatabase;
let dbGlobalCached: LibSQLDatabase;
let cachedDb: LibSQLDatabase;
let roundTripCachedDb: LibSQLDatabase;
let client: Client;

// eslint-disable-next-line drizzle-internal/require-entity-kind
class JsonRoundTripCache extends Cache {
private data = new Map<string, string>();

override strategy(): 'explicit' | 'all' {
return 'explicit';
}

override async get(key: string): Promise<any[] | undefined> {
const stored = this.data.get(key);
return stored === undefined ? undefined : JSON.parse(stored);
}

override async put(key: string, response: any): Promise<void> {
this.data.set(key, JSON.stringify(response));
}

override async onMutate(_params: MutationOption): Promise<void> {}
}

const cacheRoundTripUsers = sqliteTable('cache_roundtrip_users', {
id: integer('id').primaryKey({ autoIncrement: true }),
payload: text('payload', { mode: 'json' }).$type<{ a: number }>().notNull(),
});

beforeAll(async () => {
const url = process.env['LIBSQL_URL'];
const authToken = process.env['LIBSQL_AUTH_TOKEN'];
Expand All @@ -38,6 +67,7 @@ beforeAll(async () => {
db = drizzle(client, { logger: ENABLE_LOGGING });
cachedDb = drizzle(client, { logger: ENABLE_LOGGING, cache: new TestCache() });
dbGlobalCached = drizzle(client, { logger: ENABLE_LOGGING, cache: new TestGlobalCache() });
roundTripCachedDb = drizzle(client, { logger: ENABLE_LOGGING, cache: new JsonRoundTripCache() });
});

afterAll(async () => {
Expand Down Expand Up @@ -97,6 +127,30 @@ test('migrator : migrate with custom table', async () => {
await db.run(sql`drop table ${sql.identifier(customTable)}`);
});

test('libsql cache hit should keep row values after JSON roundtrip', async () => {
await db.run(sql`drop table if exists cache_roundtrip_users`);
await db.run(
sql`
create table cache_roundtrip_users (
id integer primary key AUTOINCREMENT,
payload text not null
)
`,
);

await db.insert(cacheRoundTripUsers).values({
payload: { a: 1 },
});

const first = await roundTripCachedDb.select().from(cacheRoundTripUsers).$withCache();
const second = await roundTripCachedDb.select().from(cacheRoundTripUsers).$withCache();

expect(first).toEqual([{ id: 1, payload: { a: 1 } }]);
expect(second).toEqual(first);

await db.run(sql`drop table if exists cache_roundtrip_users`);
});

skipTests([
'delete with limit and order by',
'update with limit and order by',
Expand Down