Skip to content
Merged

Cache #4447

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4c990bf
Add Cache
AndriiSherman Feb 8, 2025
656bec7
Remove index.ts
AndriiSherman Feb 8, 2025
990beed
Up node
AndriiSherman Feb 10, 2025
4647df7
Upgrade better-sqlite in dev deps
AndriiSherman Feb 10, 2025
98994dc
Fix sessions
AndriiSherman Feb 10, 2025
610a691
Fix proxy driver for cache
AndriiSherman Feb 10, 2025
ffb171d
Update tests and proxy driver
AndriiSherman Feb 10, 2025
1177ad8
Fix
AndriiSherman Feb 10, 2025
168374f
Add db call before cache
AndriiSherman Feb 24, 2025
c76fe46
Merge branch 'beta' of github.com:drizzle-team/drizzle-orm into cache
AndriiSherman Mar 4, 2025
cd204a9
Add tables and tags params to upstash
AndriiSherman Mar 4, 2025
92cae77
dprint
AndriiSherman Mar 6, 2025
bbf78ae
Fix tests
AndriiSherman Mar 6, 2025
dafd14f
cache fixes
AndriiSherman Mar 21, 2025
ca1033e
oops
AndriiSherman Mar 25, 2025
9acf9da
make mutate queries running concurrently
AndriiSherman Apr 3, 2025
74ffdd5
Use lua scripts for get tag and onMutate (#4399)
CahidArda Apr 23, 2025
da9af74
Merge branch 'main' of github.com:drizzle-team/drizzle-orm into cache
AndriiSherman Apr 23, 2025
26a0497
Merge branch 'main' of github.com:drizzle-team/drizzle-orm into cache
AndriiSherman Apr 25, 2025
4aa795a
Fix optional cache
AndriiSherman Apr 25, 2025
5b76d0e
Merge branch 'main' of github.com:drizzle-team/drizzle-orm into cache
AndriiSherman May 5, 2025
5fb460b
Add autoInvalidate option to get
AndriiSherman May 5, 2025
8eb999a
Ex field and auto invalidation fixes for Upstash Cache (#4502)
CahidArda May 8, 2025
5cad19b
Merge branch 'main' of github.com:drizzle-team/drizzle-orm into cache
AndriiSherman May 13, 2025
d1468f0
Add drizzle query logger
AndriiSherman May 14, 2025
9506237
Merge branch 'main' of github.com:drizzle-team/drizzle-orm into cache
AndriiSherman May 21, 2025
581277d
Fix cache imports
AndriiSherman May 21, 2025
158b671
Fix: Tag Invalidation in non auto invalidated case (#4555)
CahidArda May 23, 2025
ec52620
Merge branch 'main' of github.com:drizzle-team/drizzle-orm into cache
AndriiSherman May 23, 2025
47df31f
Add tests, fix dprint
AndriiSherman May 23, 2025
a42461b
add release notes
AndriiSherman May 28, 2025
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
6 changes: 3 additions & 3 deletions .github/workflows/release-feature-branch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version: '18.18'
node-version: '22'
registry-url: 'https://registry.npmjs.org'

- uses: pnpm/action-setup@v3
Expand Down Expand Up @@ -334,7 +334,7 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version: '18.18'
node-version: '22'
registry-url: 'https://registry.npmjs.org'

- uses: pnpm/action-setup@v3
Expand Down Expand Up @@ -415,4 +415,4 @@ jobs:
working-directory: ${{ matrix.package }}
shell: bash
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/release-latest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version: '18.18'
node-version: '22'
registry-url: 'https://registry.npmjs.org'

- uses: pnpm/action-setup@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unpublish-release-feature-branch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version: '18.18'
node-version: '22'
registry-url: 'https://registry.npmjs.org'

- name: Unpublish
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18.18
22
91 changes: 91 additions & 0 deletions changelogs/drizzle-orm/0.44.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
## Error handling

Starting from this version, we’ve introduced a new `DrizzleQueryError` that wraps all errors from database drivers and provides a set of useful information:

1. A proper stack trace to identify which exact `Drizzle` query failed
2. The generated SQL string and its parameters
3. The original stack trace from the driver that caused the DrizzleQueryError

## Drizzle `cache` module

Drizzle sends every query straight to your database by default. There are no hidden actions, no automatic caching or invalidation - you’ll always see exactly what runs. If you want caching, you must opt in.

By default, Drizzle uses a explicit caching strategy (i.e. `global: false`), so nothing is ever cached unless you ask. This prevents surprises or hidden performance traps in your application. Alternatively, you can flip on all caching (global: true) so that every select will look in cache first.

Out first native integration was built together with Upstash team and let you natively use `upstash` as a cache for your drizzle queries

```ts
import { upstashCache } from "drizzle-orm/cache/upstash";
import { drizzle } from "drizzle-orm/...";

const db = drizzle(process.env.DB_URL!, {
cache: upstashCache({
// 👇 Redis credentials (optional — can also be pulled from env vars)
url: '<UPSTASH_URL>',
token: '<UPSTASH_TOKEN>',
// 👇 Enable caching for all queries by default (optional)
global: true,
// 👇 Default cache behavior (optional)
config: { ex: 60 }
})
});
```

You can also implement your own cache, as Drizzle exposes all the necessary APIs, such as get, put, mutate, etc.
You can find full implementation details on the [website](https://orm.drizzle.team/docs/cache#custom-cache)

```ts
import Keyv from "keyv";
export class TestGlobalCache extends Cache {
private globalTtl: number = 1000;
// This object will be used to store which query keys were used
// for a specific table, so we can later use it for invalidation.
private usedTablesPerKey: Record<string, string[]> = {};
constructor(private kv: Keyv = new Keyv()) {
super();
}
// For the strategy, we have two options:
// - 'explicit': The cache is used only when .$withCache() is added to a query.
// - 'all': All queries are cached globally.
// The default behavior is 'explicit'.
override strategy(): "explicit" | "all" {
return "all";
}
// This function accepts query and parameters that cached into key param,
// allowing you to retrieve response values for this query from the cache.
override async get(key: string): Promise<any[] | undefined> {
...
}
// This function accepts several options to define how cached data will be stored:
// - 'key': A hashed query and parameters.
// - 'response': An array of values returned by Drizzle from the database.
// - 'tables': An array of tables involved in the select queries. This information is needed for cache invalidation.
//
// For example, if a query uses the "users" and "posts" tables, you can store this information. Later, when the app executes
// any mutation statements on these tables, you can remove the corresponding key from the cache.
// If you're okay with eventual consistency for your queries, you can skip this option.
override async put(
key: string,
response: any,
tables: string[],
config?: CacheConfig,
): Promise<void> {
...
}
// This function is called when insert, update, or delete statements are executed.
// You can either skip this step or invalidate queries that used the affected tables.
//
// The function receives an object with two keys:
// - 'tags': Used for queries labeled with a specific tag, allowing you to invalidate by that tag.
// - 'tables': The actual tables affected by the insert, update, or delete statements,
// helping you track which tables have changed since the last cache update.
override async onMutate(params: {
tags: string | string[];
tables: string | string[] | Table<any> | Table<any>[];
}): Promise<void> {
...
}
}
```

For more usage example you can check our [docs](https://orm.drizzle.team/docs/cache#cache-usage-examples)
13 changes: 9 additions & 4 deletions drizzle-orm/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "drizzle-orm",
"version": "0.43.1",
"version": "0.44.0",
"description": "Drizzle ORM package for SQL databases",
"type": "module",
"scripts": {
Expand Down Expand Up @@ -64,14 +64,15 @@
"better-sqlite3": ">=7",
"bun-types": "*",
"expo-sqlite": ">=14.0.0",
"gel": ">=2",
"knex": "*",
"kysely": "*",
"mysql2": ">=2",
"pg": ">=8",
"postgres": ">=3",
"sql.js": ">=1",
"sqlite3": ">=5"
"sqlite3": ">=5",
"gel": ">=2",
"@upstash/redis": ">=1.34.7"
},
"peerDependenciesMeta": {
"mysql2": {
Expand Down Expand Up @@ -157,6 +158,9 @@
},
"@prisma/client": {
"optional": true
},
"@upstash/redis": {
"optional": true
}
},
"devDependencies": {
Expand All @@ -173,11 +177,12 @@
"@planetscale/database": "^1.16.0",
"@prisma/client": "5.14.0",
"@tidbcloud/serverless": "^0.1.1",
"@types/better-sqlite3": "^7.6.4",
"@types/better-sqlite3": "^7.6.12",
"@types/node": "^20.2.5",
"@types/pg": "^8.10.1",
"@types/react": "^18.2.45",
"@types/sql.js": "^1.4.4",
"@upstash/redis": "^1.34.3",
"@vercel/postgres": "^0.8.0",
"@xata.io/client": "^0.29.3",
"better-sqlite3": "^11.9.1",
Expand Down
7 changes: 6 additions & 1 deletion drizzle-orm/src/aws-data-api/pg/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { AwsDataApiSession } from './session.ts';

export interface PgDriverOptions {
logger?: Logger;
cache?: Cache;
database: string;
resourceArn: string;
secretArn: string;
Expand Down Expand Up @@ -118,9 +119,13 @@ function construct<TSchema extends Record<string, unknown> = Record<string, neve
};
}

const session = new AwsDataApiSession(client, dialect, schema, { ...config, logger }, undefined);
const session = new AwsDataApiSession(client, dialect, schema, { ...config, logger, cache: config.cache }, undefined);
const db = new AwsDataApiPgDatabase(dialect, session, schema as any);
(<any> db).$client = client;
(<any> db).$cache = config.cache;
if ((<any> db).$cache) {
(<any> db).$cache['invalidate'] = config.cache?.onMutate;
}

return db as any;
}
Expand Down
27 changes: 24 additions & 3 deletions drizzle-orm/src/aws-data-api/pg/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {
ExecuteStatementCommand,
RollbackTransactionCommand,
} from '@aws-sdk/client-rds-data';
import type { Cache } from '~/cache/core/cache.ts';
import { NoopCache } from '~/cache/core/cache.ts';
import type { WithCacheConfig } from '~/cache/core/types.ts';
import { entityKind } from '~/entity.ts';
import type { Logger } from '~/logger.ts';
import {
Expand Down Expand Up @@ -33,17 +36,23 @@ export class AwsDataApiPreparedQuery<

constructor(
private client: AwsDataApiClient,
queryString: string,
private queryString: string,
private params: unknown[],
private typings: QueryTypingsValue[],
private options: AwsDataApiSessionOptions,
cache: Cache,
queryMetadata: {
type: 'select' | 'update' | 'delete' | 'insert';
tables: string[];
} | undefined,
cacheConfig: WithCacheConfig | undefined,
private fields: SelectedFieldsOrdered | undefined,
/** @internal */
readonly transactionId: string | undefined,
private _isResponseInArrayMode: boolean,
private customResultMapper?: (rows: unknown[][]) => T['execute'],
) {
super({ sql: queryString, params });
super({ sql: queryString, params }, cache, queryMetadata, cacheConfig);
this.rawQuery = new ExecuteStatementCommand({
sql: queryString,
parameters: [],
Expand Down Expand Up @@ -108,7 +117,9 @@ export class AwsDataApiPreparedQuery<

this.options.logger?.logQuery(this.rawQuery.input.sql!, this.rawQuery.input.parameters);

const result = await this.client.send(this.rawQuery);
const result = await this.queryWithCache(this.queryString, params, async () => {
return await this.client.send(this.rawQuery);
});
const rows = result.records?.map((row) => {
return row.map((field) => getValueFromDataApi(field));
}) ?? [];
Expand Down Expand Up @@ -139,6 +150,7 @@ export class AwsDataApiPreparedQuery<

export interface AwsDataApiSessionOptions {
logger?: Logger;
cache?: Cache;
database: string;
resourceArn: string;
secretArn: string;
Expand All @@ -158,6 +170,7 @@ export class AwsDataApiSession<

/** @internal */
readonly rawQuery: AwsDataApiQueryBase;
private cache: Cache;

constructor(
/** @internal */
Expand All @@ -174,6 +187,7 @@ export class AwsDataApiSession<
resourceArn: options.resourceArn,
database: options.database,
};
this.cache = options.cache ?? new NoopCache();
}

prepareQuery<
Expand All @@ -188,6 +202,8 @@ export class AwsDataApiSession<
name: string | undefined,
isResponseInArrayMode: boolean,
customResultMapper?: (rows: unknown[][]) => T['execute'],
queryMetadata?: { type: 'select' | 'update' | 'delete' | 'insert'; tables: string[] },
cacheConfig?: WithCacheConfig,
transactionId?: string,
): AwsDataApiPreparedQuery<T> {
return new AwsDataApiPreparedQuery(
Expand All @@ -196,6 +212,9 @@ export class AwsDataApiSession<
query.params,
query.typings ?? [],
this.options,
this.cache,
queryMetadata,
cacheConfig,
fields,
transactionId ?? this.transactionId,
isResponseInArrayMode,
Expand All @@ -210,6 +229,8 @@ export class AwsDataApiSession<
undefined,
false,
undefined,
undefined,
undefined,
this.transactionId,
).execute();
}
Expand Down
6 changes: 5 additions & 1 deletion drizzle-orm/src/better-sqlite3/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class BetterSQLite3Database<TSchema extends Record<string, unknown> = Rec

function construct<TSchema extends Record<string, unknown> = Record<string, never>>(
client: Database,
config: DrizzleConfig<TSchema> = {},
config: Omit<DrizzleConfig<TSchema>, 'cache'> = {},
): BetterSQLite3Database<TSchema> & {
$client: Database;
} {
Expand Down Expand Up @@ -57,6 +57,10 @@ function construct<TSchema extends Record<string, unknown> = Record<string, neve
const session = new BetterSQLiteSession(client, dialect, schema, { logger });
const db = new BetterSQLite3Database('sync', dialect, session, schema);
(<any> db).$client = client;
// (<any> db).$cache = config.cache;
// if ((<any> db).$cache) {
// (<any> db).$cache['invalidate'] = config.cache?.onMutate;
// }

return db as any;
}
Expand Down
Loading