Relevant source files
The following files were used as context for generating this wiki page:
- drizzle-orm/src/cache/upstash/cache.ts
- drizzle-orm/src/cache/upstash/index.ts
- drizzle-orm/src/singlestore-core/session.ts
- drizzle-orm/src/singlestore/session.ts
- drizzle-orm/src/mysql-core/session.ts
- drizzle-orm/src/planetscale-serverless/session.ts
- drizzle-orm/src/sqlite-core/session.ts
- drizzle-orm/src/sqlite-proxy/session.ts
- drizzle-orm/src/tidb-serverless/session.ts
- drizzle-orm/src/gel-core/session.ts
- drizzle-orm/src/neon-serverless/session.ts
- drizzle-orm/src/mysql2/session.ts
- drizzle-orm/src/cache/core/cache.ts
- drizzle-orm/src/libsql/session.ts
- drizzle-orm/src/node-postgres/session.ts
- drizzle-orm/src/vercel-postgres/session.ts
- drizzle-orm/src/bun-sql/session.ts
- drizzle-orm/src/pg-proxy/session.ts
- drizzle-orm/src/xata-http/session.ts
- drizzle-orm/src/pg-core/session.ts
- drizzle-orm/src/gel/session.ts
- drizzle-orm/src/pglite/session.ts
- drizzle-orm/src/mysql-proxy/session.ts
- drizzle-orm/src/op-sqlite/session.ts
- drizzle-orm/src/neon-http/session.ts
- drizzle-orm/src/singlestore-core/db.ts
- drizzle-orm/src/cache/core/index.ts
- drizzle-orm/src/aws-data-api/pg/session.ts
- drizzle-orm/src/cache/core/types.ts
- drizzle-orm/src/durable-sqlite/session.ts
Overview
Query caching in Drizzle provides an extensible caching infrastructure designed to intercept database execution, minimize redundant round-trips, and maintain synchronization between cached query results and underlying table mutations. By integrating caching abstractions directly into database sessions and prepared statements across supported SQL dialects, queries can be conditionally cached, retrieved via read-through patterns, and automatically invalidated when related tables undergo changes.
Sources: drizzle-orm/src/cache/core/cache.ts:5-42, drizzle-orm/src/singlestore-core/session.ts:68-152, drizzle-orm/src/mysql-core/session.ts:70-154, drizzle-orm/src/sqlite-core/session.ts:71-155, drizzle-orm/src/pg-core/session.ts:64-147
Core Cache Abstractions and Contract
Base Cache Class and Null Implementation
export abstract class Cache {
static readonly [entityKind]: string = 'Cache';
abstract strategy(): 'explicit' | 'all';
/**
* Invoked if we should check cache for cached response
* @param sql
* @param tables
*/
abstract get(
key: string,
tables: string[],
isTag: boolean,
isAutoInvalidate?: boolean,
): Promise<any[] | undefined>;
/**
* Invoked if new query should be inserted to cache
* @param sql
* @param tables
*/
abstract put(
hashedQuery: string,
response: any,
tables: string[],
isTag: boolean,
config?: CacheConfig,
): Promise<void>;
/**
* Invoked if insert, update, delete was invoked
* @param tables
*/
abstract onMutate(
params: MutationOption,
): Promise<void>;
}
export class NoopCache extends Cache {
override strategy() {
return 'all' as const;
}
static override readonly [entityKind]: string = 'NoopCache';
override async get(_key: string): Promise<any[] | undefined> {
return undefined;
}
override async put(
_hashedQuery: string,
_response: any,
_tables: string[],
_config?: any,
): Promise<void> {
// noop
}
override async onMutate(_params: MutationOption): Promise<void> {
// noop
}
}Sources: drizzle-orm/src/cache/core/cache.ts:5-65
Note
NoopCache inherits from the abstract Cache base class and resolves cache strategies as 'all' while returning undefined for get operations, effectively bypassing caching when no active provider is configured.
Mutation Options and Query Hashing
export type MutationOption = { tags?: string | string[]; tables?: Table<any> | Table<any>[] | string | string[] };
export async function hashQuery(sql: string, params?: any[]) {
const dataToHash = `${sql}-${JSON.stringify(params)}`;
const encoder = new TextEncoder();
const data = encoder.encode(dataToHash);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = [...new Uint8Array(hashBuffer)];
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
return hashHex;
}Sources: drizzle-orm/src/cache/core/cache.ts:67-78
Important
hashQuery serializes the SQL string and parameter array using JSON.stringify, encodes the combined payload via TextEncoder, and computes a SHA-256 digest using crypto.subtle.digest, converting the resulting buffer into a hexadecimal string representation.
Configuration Interfaces
export type CacheConfig = {
/**
* expire time, in seconds (a positive integer)
*/
ex?: number;
/**
* expire time, in milliseconds (a positive integer).
*/
px?: number;
/**
* Unix time at which the key will expire, in seconds (a positive integer).
*/
exat?: number;
/**
* Unix time at which the key will expire, in milliseconds (a positive integer)
*/
pxat?: number;
/**
* Retain the time to live associated with the key.
*/
keepTtl?: boolean;
/**
* Set an expiration (TTL or time to live) on one or more fields of a given hash key.
* Used for HEXPIRE command
*/
hexOptions?: 'NX' | 'nx' | 'XX' | 'xx' | 'GT' | 'gt' | 'LT' | 'lt';
};
export type WithCacheConfig = { enable: boolean; config?: CacheConfig; tag?: string; autoInvalidate?: boolean };Sources: drizzle-orm/src/cache/core/types.ts:1-29
| Configuration Property | Type | Description |
|---|---|---|
ex | number | Expire time in seconds (positive integer) |
px | number | Expire time in milliseconds (positive integer) |
exat | number | Unix time at which the key will expire, in seconds |
pxat | number | Unix time at which the key will expire, in milliseconds |
keepTtl | boolean | Retain the time to live associated with the key |
hexOptions | 'NX' | 'nx' | 'XX' | 'xx' | 'GT' | 'gt' | 'LT' | 'lt' | Expiration options used for HEXPIRE commands |
enable | boolean | Enables or disables query caching |
config | CacheConfig | Underlying expiration and TTL configuration |
tag | string | Custom cache tag for group invalidation |
autoInvalidate | boolean | Automatically invalidate entries upon table mutation |
Sources: drizzle-orm/src/cache/core/types.ts:1-29
Upstash Redis Storage Implementation
UpstashCache Storage Architecture and Key Prefixes
The UpstashCache class extends the core Cache abstraction, utilizing the `@upstash/redis client package to implement distributed query caching with Lua scripting support. It relies on fixed naming prefixes and global key structures to manage cache entries, non-auto-invalidated queries, and relational table mappings.
| Constant Name | Value / Prefix | Description |
|---|---|---|
compositeTableSetPrefix | '__CTS__' | Prefix for sets denoting composite table names for unique tables |
compositeTablePrefix | '__CT__' | Prefix for Redis hashes mapping hash keys or tags to cache values |
tagsMapKey | '__tagsMap__' | Redis key holding the mapping of tags to composite table names |
nonAutoInvalidateTablePrefix | '__nonAutoInvalidate__' | Hash prefix storing queries whose auto-invalidation is disabled |
Sources: drizzle-orm/src/cache/upstash/cache.ts:60-95
Note
Queries configured with auto-invalidation disabled bypass composite table hashes entirely, preventing them from being swept away during table mutation triggers and instead residing inside the __nonAutoInvalidate__ Redis hash.
Sources: drizzle-orm/src/cache/upstash/cache.ts:87-94
Lua Scripting for Retrieval and Mutation
const getByTagScript = `
local tagsMapKey = KEYS[1] -- tags map key
local tag = ARGV[1] -- tag
local compositeTableName = redis.call('HGET', tagsMapKey, tag)
if not compositeTableName then
return nil
end
local value = redis.call('HGET', compositeTableName, tag)
return value
`;
const onMutateScript = `
local tagsMapKey = KEYS[1] -- tags map key
local tables = {} -- initialize tables array
local tags = ARGV -- tags array
for i = 2, #KEYS do
tables[#tables + 1] = KEYS[i] -- add all keys except the first one to tables
end
if #tags > 0 then
for _, tag in ipairs(tags) do
if tag ~= nil and tag ~= '' then
local compositeTableName = redis.call('HGET', tagsMapKey, tag)
if compositeTableName then
redis.call('HDEL', compositeTableName, tag)
end
end
end
redis.call('HDEL', tagsMapKey, unpack(tags))
end
local keysToDelete = {}
if #tables > 0 then
local compositeTableNames = redis.call('SUNION', unpack(tables))
for _, compositeTableName in ipairs(compositeTableNames) do
keysToDelete[#keysToDelete + 1] = compositeTableName
end
for _, table in ipairs(tables) do
keysToDelete[#keysToDelete + 1] = table
end
redis.call('DEL', unpack(keysToDelete))
end
`;Sources: drizzle-orm/src/cache/upstash/cache.ts:8-54
Important
UpstashCache registers two compiled Lua scripts during initialization: getByTagScript (marked readonly) which looks up tag mappings and fetches cached values in a single atomic round-trip, and onMutateScript which executes set unions (SUNION), table deletions (DEL), and hash field removals (HDEL) when database modifications occur.
Sources: drizzle-orm/src/cache/upstash/cache.ts:8-54, drizzle-orm/src/cache/upstash/cache.ts:106-109
Session Cache Lifecycle Coordination
Execution Interception and Caching Mechanisms
Database sessions across SQLite, PostgreSQL, and MySQL proxy implementations coordinate caching through prepared query abstractions. When a query is executed via SQLiteRemoteSession, PgRemoteSession, or MySqlRemoteSession, parameters are filled, queries are logged via Logger, and execution flows through read-through wrapper methods like queryWithCache.
| Session Class | Driver Type | Cache Option Property | Default Cache Implementation |
|---|---|---|---|
SQLiteRemoteSession | SQLite Proxy | cache | NoopCache |
PgRemoteSession | PostgreSQL Proxy | cache | NoopCache |
MySqlRemoteSession | MySQL Proxy | cache | NoopCache |
Sources: drizzle-orm/src/sqlite-proxy/session.ts:22-41, drizzle-orm/src/pg-proxy/session.ts:18-41, drizzle-orm/src/mysql-proxy/session.ts:27-50
Note
Sessions initialize their internal logger and cache instances during construction, falling back to NoopLogger and NoopCache when options are omitted from the session configuration object.
Sources: drizzle-orm/src/sqlite-proxy/session.ts:38-41, drizzle-orm/src/pg-proxy/session.ts:32-41, drizzle-orm/src/mysql-proxy/session.ts:41-50
Prepared Query Coordination
Prepared query classes wrap execution commands (run, all, get, values, execute) and interact directly with session-level caching hooks by passing query strings, parameters, and execution callbacks.
async run(placeholderValues?: Record<string, unknown>): Promise<SqliteRemoteResult> {
const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
this.logger.logQuery(this.query.sql, params);
return await this.queryWithCache(this.query.sql, params, async () => {
return await (this.client as AsyncRemoteCallback)(this.query.sql, params, 'run');
});
}Sources: drizzle-orm/src/sqlite-proxy/session.ts:177-183
Warning
In PgRemoteSession, query execution wrapped with OpenTelemetry spans (drizzle.execute and drizzle.driver.execute) records query text and parameter payloads as span attributes before delegating to queryWithCache.
Sources: drizzle-orm/src/pg-proxy/session.ts:113-154
Cache Invalidation and Mutation Handling
Mutation Options and Invalidation Triggers
The core cache abstract contract defines the onMutate lifecycle hook to handle database modification operations such as inserts, updates, and deletes [Cache Invalidation and Mutation Handling], accepting a MutationOption parameter [Cache Invalidation and Mutation Handling]. This parameter structure supports optional tags and tables specified as single items or arrays of strings, table objects, or table instances [Cache Invalidation and Mutation Handling].
export type MutationOption = { tags?: string | string[]; tables?: Table<any> | Table<any>[] | string | string[] };Sources: drizzle-orm/src/cache/core/cache.ts:67-67
Note
The NoopCache implementation provides an empty implementation for onMutate, safely ignoring table mutation events when caching is disabled or unconfigured [Cache Invalidation and Mutation Handling].
Sources: drizzle-orm/src/cache/core/cache.ts:62-64
Upstash Mutation Execution via Lua
When database modifications trigger cache invalidation in UpstashCache, execution delegates to a specialized Lua script (onMutateScript) deployed to Redis [Cache Invalidation and Mutation Handling]. This script evaluates keys and arguments atomically to prune obsolete cache entries across composite table hashes and tag mapping structures [Cache Invalidation and Mutation Handling].
Sources: drizzle-orm/src/cache/upstash/cache.ts:21-54
Caution
The onMutateScript executes a set union (SUNION) across specified table keys to gather all composite table names linked to modified tables, subsequently issuing batch DEL commands to purge stale query results from Redis in a single atomic operation [Cache Invalidation and Mutation Handling].
Sources: drizzle-orm/src/cache/upstash/cache.ts:42-53
Driver and Serverless Runtime Adapters
Session Options and NoopCache Wiring
Serverless and lightweight driver sessions initialize their options by defaulting unconfigured cache properties to NoopCache. Across driver implementations including TiDBServerlessSession, NeonSession, XataHttpSession, GelDbSession, PgliteSession, OPSQLiteSession, and AwsDataApiSession, the session constructor extracts the optional cache from the session options object or instantiates a fresh NoopCache instance.
private options: TiDBServerlessSessionOptions = {},
) {
super(dialect);
this.client = tx ?? baseClient;
this.logger = options.logger ?? new NoopLogger();
this.cache = options.cache ?? new NoopCache();
}Sources: drizzle-orm/src/tidb-serverless/session.ts:124-130
constructor(
private client: XataHttpClient,
dialect: PgDialect,
private schema: RelationalSchemaConfig<TSchema> | undefined,
private options: XataHttpSessionOptions = {},
) {
super(dialect);
this.logger = options.logger ?? new NoopLogger();
this.cache = options.cache ?? new NoopCache();
}Sources: drizzle-orm/src/xata-http/session.ts:110-119
constructor(
private client: GelClient,
dialect: GelDialect,
private schema: RelationalSchemaConfig<TSchema> | undefined,
private options: GelSessionOptions = {},
) {
super(dialect);
this.logger = options.logger ?? new NoopLogger();
this.cache = options.cache ?? new NoopCache();
}Sources: drizzle-orm/src/gel/session.ts:117-126
constructor(
private client: PgliteClient | Transaction,
dialect: PgDialect,
private schema: RelationalSchemaConfig<TSchema> | undefined,
private options: PgliteSessionOptions = {},
) {
super(dialect);
this.logger = options.logger ?? new NoopLogger();
this.cache = options.cache ?? new NoopCache();
}Sources: drizzle-orm/src/pglite/session.ts:131-141
constructor(
private client: OPSQLiteConnection,
dialect: SQLiteAsyncDialect,
private schema: RelationalSchemaConfig<TSchema> | undefined,
options: OPSQLiteSessionOptions = {},
) {
super(dialect);
this.logger = options.logger ?? new NoopLogger();
this.cache = options.cache ?? new NoopCache();
}Sources: drizzle-orm/src/op-sqlite/session.ts:37-46
constructor(
/** @internal */
readonly client: AwsDataApiClient,
dialect: PgDialect,
private schema: RelationalSchemaConfig<TSchema> | undefined,
private options: AwsDataApiSessionOptions,
/** @internal */
readonly transactionId: string | undefined,
) {
super(dialect);
this.rawQuery = {
secretArn: options.secretArn,
resourceArn: options.resourceArn,
database: options.database,
};
this.cache = options.cache ?? new NoopCache();
}Sources: drizzle-orm/src/aws-data-api/pg/session.ts:175-191
constructor(
public client: TSQL,
dialect: PgDialect,
private schema: RelationalSchemaConfig<TSchema> | undefined,
/** @internal */
readonly options: BunSQLSessionOptions = {},
) {
super(dialect);
this.logger = options.logger ?? new NoopLogger();
this.cache = options.cache ?? new NoopCache();
}Sources: drizzle-orm/src/bun-sql/session.ts:120-130
Note
Synchronous storage environments such as SQLiteDOSession omit cache constructor arguments entirely within their prepared query initializers because synchronous runtime caching is unsupported.
Sources: drizzle-orm/src/durable-sqlite/session.ts:100-111
Prepared Query Cache Propagation
When sessions instantiate prepared queries, the session-level cache reference is passed directly into the query constructor. Prepared query classes such as TiDBServerlessPreparedQuery, NeonPreparedQuery, LibSQLPreparedQuery, VercelPgPreparedQuery, XataHttpPreparedQuery, GelDbPreparedQuery, PglitePreparedQuery, OPSQLitePreparedQuery, NeonHttpPreparedQuery, AwsDataApiPreparedQuery, and BunSQLPreparedQuery forward the cache instance, query metadata, and cache configuration objects up to base query classes.
| Driver Prepared Query | Client / Driver Type | Session Type | Cache Support |
|---|---|---|---|
TiDBServerlessPreparedQuery | Tx | Connection | TiDBServerlessSession | Yes |
NeonPreparedQuery | Pool | PoolClient | Client | NeonSession | Yes |
LibSQLPreparedQuery | Client | LibSQLSession | Yes |
VercelPgPreparedQuery | VercelPool | VercelClient | VercelPoolClient | VercelPgSession | Yes |
XataHttpPreparedQuery | XataHttpClient | XataHttpSession | Yes |
GelDbPreparedQuery | Client | Transaction | GelDbSession | Yes |
PglitePreparedQuery | PgliteClient | Transaction | PgliteSession | Yes |
OPSQLitePreparedQuery | OPSQLiteConnection | OPSQLiteSession | Yes |
NeonHttpPreparedQuery | NeonHttpClient | NeonHttpSession | Yes |
AwsDataApiPreparedQuery | AwsDataApiClient | AwsDataApiSession | Yes |
BunSQLPreparedQuery | SQL | BunSQLSession | Yes |
SQLiteDOPreparedQuery | DurableObjectStorage | SQLiteDOSession | No (undefined) |
Sources: drizzle-orm/src/tidb-serverless/session.ts:26-48, drizzle-orm/src/neon-serverless/session.ts:27-49, drizzle-orm/src/libsql/session.ts:165-192, drizzle-orm/src/vercel-postgres/session.ts:26-48, drizzle-orm/src/xata-http/session.ts:27-45, drizzle-orm/src/gel/session.ts:17-37, drizzle-orm/src/pglite/session.ts:19-42, drizzle-orm/src/op-sqlite/session.ts:112-133, drizzle-orm/src/neon-http/session.ts:29-48, drizzle-orm/src/aws-data-api/pg/session.ts:30-55, drizzle-orm/src/bun-sql/session.ts:19-38, drizzle-orm/src/durable-sqlite/session.ts:90-111
Warning
When transaction boundaries are opened in serverless sessions—such as TiDBServerlessSession.transaction, GelDbSession.transaction, PgliteSession.transaction, and AwsDataApiSession.transaction—new session instances are created with native transaction handles while preserving reference to the parent session's configured cache instance.
Sources: drizzle-orm/src/tidb-serverless/session.ts:173-191, drizzle-orm/src/gel/session.ts:154-162, drizzle-orm/src/pglite/session.ts:170-187, drizzle-orm/src/aws-data-api/pg/session.ts:238-256
Related
On This Page
OverviewCore Cache Abstractions and ContractBase Cache Class and Null ImplementationMutation Options and Query HashingConfiguration InterfacesUpstash Redis Storage ImplementationUpstashCache Storage Architecture and Key PrefixesLua Scripting for Retrieval and MutationSession Cache Lifecycle CoordinationExecution Interception and Caching MechanismsPrepared Query CoordinationCache Invalidation and Mutation HandlingMutation Options and Invalidation TriggersUpstash Mutation Execution via LuaDriver and Serverless Runtime AdaptersSession Options and NoopCache WiringPrepared Query Cache PropagationRelated