Drizzle ORM 0.37.0 版本详解:SingleStore 新方言与 SQLite Durable Objects 驱动实战
【免费下载链接】drizzle-ormORM项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm
本篇基于 Drizzle ORM 0.37.0 版本的官方变更记录,深入解读该版本的三项核心更新:全新的 SingleStore 方言、SQLite Durable Objects(Cloudflare DO)驱动,以及两个关键 Bug 修复。读者读完本文后,将掌握 SingleStore 数据库在 Drizzle 中的接入方式、在 Cloudflare Durable Objects 中构建同步 SQLite 数据层的完整流程,并能理解withReplicas与 Neon$withAuth两个修复背后的实现原理。
版本概览:0.37.0 带来了什么
Drizzle ORM 0.37.0 是一次以"新数据库接入能力"为主的版本迭代,主要变化集中在三方面:
- 新增 SingleStore 方言:支持在 Drizzle 中以类型安全的方式操作 MySQL 兼容的 SingleStore 数据库;
- 新增 SQLite Durable Objects 驱动:允许在 Cloudflare Durable Objects 的
DurableObjectStorage之上直接执行同步 SQLite 查询; - 修复两处已知缺陷:
withReplicas中$with未定义,以及 Neon serverless 驱动$withAuth不接受 Promise 类型 token。
下文分别展开。
一、SingleStore 方言:MySQL 兼容体系的新成员
1.1 能力来源与定位
SingleStore(原 MemSQL)是一款同时支持行存与列存的分布式数据库,其协议与 MySQL 高度兼容。0.37.0 版本中,SingleStore 团队为 Drizzle 提交了 PR,完成了对 SingleStore 中MySQL 兼容部分的完整支持,使得 Drizzle 的全部核心能力(类型安全的表定义、查询构建、关系查询等)可以直接作用于 SingleStore 实例。
从仓库源码结构看,SingleStore 支持被拆分为两层,与 MySQL/PostgreSQL 的既有架构完全一致:
singlestore-core(目录):与方言无关的核心层,包含表定义、列构建器、查询构建器、方言、会话抽象等;singlestore(目录):基于mysql2驱动的实现层,包含驱动、会话与迁移器。
singlestore-core的公开入口 index.ts 统一导出了singlestoreTable、全部列构建器、SingleStoreDialect、索引、主键、唯一约束、视图等符号,说明 SingleStore 从第一天起就享受与 MySQL 同等的完整能力面。
1.2 快速上手示例
以下是官方变更记录给出的最小可用示例,它完整覆盖了"定义表结构 → 建立连接 → 发起查询"三步:
import { int, singlestoreTable, varchar } from 'drizzle-orm/singlestore-core'; import { drizzle } from 'drizzle-orm/singlestore'; export const usersTable = singlestoreTable('users_table', { id: int().primaryKey(), name: varchar({ length: 255 }).notNull(), age: int().notNull(), email: varchar({ length: 255 }).notNull().unique(), }); // ... 其余代码 const db = drizzle(process.env.DATABASE_URL!); db.select()...几个值得注意的细节:
singlestoreTable与sqliteTable、pgTable一样,接收表名与列定义对象;- 列构建器(
int、varchar等)从drizzle-orm/singlestore-core导入,与驱动实现解耦; drizzle()直接接收连接字符串(DATABASE_URL),内部会自动创建连接池。
1.3 连接初始化:字符串、PoolOptions 与回调客户端
官方文档只展示了传入连接字符串的最简形式。从 singlestore/driver.ts 的源码可以看到,drizzle()实际支持三种调用形态:
| 调用形态 | 说明 |
|---|---|
drizzle(connectionString) | 传入连接字符串,内部调用createPool({ uri, connectAttributes })创建回调风格连接池,并自动转为 Promise 风格 |
drizzle(connectionString, config) | 连接字符串 + Drizzle 配置(logger、cache、schema、casing) |
drizzle({ connection \| client, ...config }) | 传入PoolOptions对象,或直接传入已有的mysql2客户端(Pool/Connection/ 回调版本) |
其中SingleStoreDriverOptions(源码)支持两个可选字段:
logger:传入自定义Logger实例,或设为true使用内置DefaultLogger;cache:传入缓存实例,用于查询结果缓存,且连接池会自动附带_connector_name: 'SingleStore Drizzle ORM Driver'与_connector_version两个连接属性(源码),便于 SingleStore 侧识别 Drizzle 客户端。
此外,drizzle.mock()(源码)同样适用于 SingleStore 驱动,可在不连接真实数据库的情况下构建查询、进行类型与 SQL 断言测试。
1.4 完整的列类型体系
SingleStore 方言并非只支持int与varchar。查看 singlestore-core/columns/all.ts,共注册了 28 种列构建器,覆盖数值、字符串、时间、二进制、JSON 与向量等类型:
- 数值:
bigint、int、tinyint、smallint、mediumint、decimal、double、float、real、serial、year - 字符串:
varchar、char、text、tinytext、mediumtext、longtext、binary、varbinary - 时间:
date、datetime、time、timestamp - 其他:
boolean、json、singlestoreEnum、customType、vector
其中特别值得一提的是vector列(vector.ts),其构建器接收dimensions与elementType配置,底层数据类型为Array<number>、驱动参数为字符串——这为在 SingleStore 上构建向量检索场景预留了直接的类型支持,是 MySQL 方言所不具备的 SingleStore 特色能力。
1.5 迁移与测试支持
- 集成测试目录 integration-tests/tests/singlestore/ 中包含了常规、自定义类型、前缀表名、代理(proxy)等全套测试用例;
- drizzle-kit 的迁移快照目录 integration-tests/drizzle2/singlestore/ 下存在
meta/0000_snapshot.json与_journal.json,说明drizzle-kit的 generate/push/migrate 流程对 SingleStore 已可用; - 关系查询测试(relational/singlestore.test.ts)与副本读写测试(replicas/singlestore.test.ts)也一并就绪。
注意:本版本先支持 SingleStore 与 MySQL 兼容的部分。SingleStore 官方团队将持续迭代,后续版本会逐步引入更多 SingleStore 特有的能力。
二、SQLite Durable Objects 驱动:在 Cloudflare DO 中直接查询 SQLite
2.1 什么是 SQLite Durable Objects
Cloudflare Durable Objects(DO)是 Workers 平台提供的强一致性状态存储原语,每个 DO 拥有一个DurableObjectStorage实例。0.37.0 之前,开发者需要自行在 SQL 与 DO Storage API 之间做适配;0.37.0 起,Drizzle 直接提供了drizzle-orm/durable-sqlite驱动,把 DO 的存储层封装为同步 SQLite 会话,开发者可以直接用 Drizzle 的类型安全 API 读写数据。
2.2 官方示例:一个完整的 Durable Object
以下代码来自官方变更记录,展示了一个完整的 DO 类——在构造函数中通过drizzle(this.storage)建立数据库实例,并对外暴露migrate、insert、select三个方法:
/// <reference types="@cloudflare/workers-types" /> import { drizzle, DrizzleSqliteDODatabase } from 'drizzle-orm/durable-sqlite'; import { DurableObject } from 'cloudflare:workers' import { migrate } from 'drizzle-orm/durable-sqlite/migrator'; import migrations from '../drizzle/migrations'; import { usersTable } from './db/schema'; export class MyDurableObject1 extends DurableObject { storage: DurableObjectStorage; db: DrizzleSqliteDODatabase<any>; constructor(ctx: DurableObjectState, env: Env) { super(ctx, env); this.storage = ctx.storage; this.db = drizzle(this.storage, { logger: false }); } async migrate() { migrate(this.db, migrations); } async insert(user: typeof usersTable.$inferInsert) { await this.db.insert(usersTable).values(user); } async select() { return this.db.select().from(usersTable); } } export default { /** * This is the standard fetch handler for a Cloudflare Worker * * @param request - The request submitted to the Worker from the client * @param env - The interface to reference bindings declared in wrangler.toml * @param ctx - The execution context of the Worker * @returns The response to be sent back to the client */ async fetch(request: Request, env: Env): Promise<Response> { const id: DurableObjectId = env.MY_DURABLE_OBJECT1.idFromName('durable-object'); const stub = env.MY_DURABLE_OBJECT1.get(id); await stub.migrate(); await stub.insert({ name: 'John', age: 30, email: 'john@example.com', }) console.log('New user created!') const users = await stub.select(); console.log('Getting all users from the database: ', users) return new Response(); } }示例中的关键信息:
- 入口类型
DrizzleSqliteDODatabase与工厂函数drizzle均从drizzle-orm/durable-sqlite导入; - 迁移器从
drizzle-orm/durable-sqlite/migrator导入,migrations是 drizzle-kit 生成后编译进 Worker 的迁移对象; - Worker 的
fetch处理器通过env.MY_DURABLE_OBJECT1.idFromName(...)获取 DO stub,随后依次执行迁移、插入与查询。
2.3 驱动实现原理:同步会话与底层存储
从源码看,这个驱动的设计非常克制:它完全复用了 SQLite 同步方言,只是替换了会话层。
- driver.ts 中,
drizzle(client, config)接收DurableObjectStorage作为客户端,构造SQLiteSyncDialect与SQLiteDOSession,并支持config.schema(关系查询)、config.casing、config.logger(true时使用DefaultLogger)等标准 Drizzle 配置,同时把原始客户端挂载到$client上; - session.ts 中的
SQLiteDOPreparedQuery是核心执行单元:run/all/get/values四个方法最终都落到this.client.sql.exec(...)上——即 DO 存储层内置的 SQLite 执行引擎,并且全部为同步 API,返回SqlStorageCursor后再经toArray()/mapResultRow映射为行对象; - 事务能力由
SQLiteDOTransaction提供(session.ts),底层调用client.transactionSync(() => ...)保证事务原子性,并支持嵌套事务(nestedIndex + 1)。
2.4 迁移器:__drizzle_migrations表与断点执行
durable-sqlite/migrator.ts 中的migrate(db, config)实现了与 SQLite 其他驱动一致的迁移协议:
- 读取
journal.entries,按序号拼接出每个迁移的 SQL(键名规则为m+ 4 位零填充序号,如m0000); - 按
--> statement-breakpoint将每个迁移文件拆分为多条语句; - 在事务内创建
__drizzle_migrations记录表(id、hash、created_at); - 读取最近一条已应用迁移的时间戳,仅执行比它更新的迁移,并逐条写入记录。
由于迁移文件最终会被打包进 Worker bundle(示例中的import migrations from '../drizzle/migrations'),这套机制让 DO 在首次初始化时即可自动完成建表。
2.5 集成测试佐证
仓库中 integration-tests/tests/sqlite/durable-objects/index.ts 是一份超过 3600 行的 DO 集成测试,覆盖了 DO 场景下的完整 Drizzle 能力面:
- 迁移流程(
migrate1)、插入/自增/默认值($default); - 全字段/部分字段/原始 SQL/类型化 SQL 查询;
inArray、notInArray、selectDistinct、returning、JOIN 与alias;blob的 JSON 与 bigint 模式、timestamp 模式等类型映射。
该测试文件同时配有 wrangler.toml 配置,说明整个 DO 数据层是通过真实 Cloudflare 运行环境验证的。
三、Bug 修复:两处一致性缺陷的源码级解读
3.1 修复一:withReplicas中$with未定义
问题背景(对应 issue #1834):withReplicas(primary, replicas)用于构建"主库写入、从库读取"的副本分离实例。此前其返回对象缺失$with/with(CTE 构建入口),导致在副本分离场景下使用 CTE 时抛出$with is undefined。
修复方式:在 mysql-core/db.ts 的withReplicas实现中,新增了$with与with的转发:
const $with: Q['with'] = (...args: []) => getReplica(replicas).with(...args); // ... return { ...primary, select, selectDistinct, $count, with: $with, // 只读查询转发到副本 // ... };与select、selectDistinct、$count一样,CTE 构建被归类为只读操作,因此统一转发到随机挑选的副本上执行;而update、insert、delete、execute、transaction依旧路由到主库。同样的修复模式也存在于pg-core、gel-core与singlestore-core各自的withReplicas实现中(可从各目录的db.ts中检索withReplicas确认)。
3.2 修复二:Neon$withAuth的 token 类型对齐
问题背景(对应 issue #3597):Neon serverless 驱动的连接选项中authToken支持传入 Promise,但数据库实例上的$withAuth()方法此前只接受字符串,造成类型不一致与使用困惑。
修复方式:在 neon-http/driver.ts 中,NeonHttpDatabase.$withAuth的入参类型被改为与HTTPQueryOptions['authToken']保持一致:
$withAuth( token: Exclude<HTTPQueryOptions<true, true>['authToken'], undefined>, ): Omit<this, /* 保留查询相关方法 */> { ... }同时,该方法内部通过 Proxy 包装(wrap(this, token, ...))实现 token 的透传:对with等链式返回的中间对象也会递归包裹,确保后续所有查询都携带新 token。修复后,$withAuth返回的类型被收窄为仅包含$count、delete、select、selectDistinct、selectDistinctOn、update、insert、with、query、execute、refreshMaterializedView等查询方法的实例,避免误用其他实例 API。
四、升级与使用建议
- 升级
drizzle-orm至 0.37.0 或更高版本即可获得上述能力;配套使用drizzle-kit时,SingleStore 项目的生成/推送流程可参考 drizzle2/singlestore 迁移快照 作为输出形态参考; - SingleStore 场景下建议优先从
drizzle-orm/singlestore-core导入表与列定义、从drizzle-orm/singlestore导入drizzle,保持核心层与驱动层的清晰边界; - Durable Objects 场景下需在
tsconfig中引入@cloudflare/workers-types(示例顶部有/// <reference types="@cloudflare/workers-types" />),且注意该驱动当前为同步 API,不适合在需要异步游标流式读取的超大结果集场景使用; - 若使用了
withReplicas或 Neon$withAuth,本次两个修复为向后兼容的缺陷修复,升级后无需改动业务代码,直接获得正确的$with行为与 Promise token 类型支持。
结语
0.37.0 通过 SingleStore 方言与 SQLite Durable Objects 驱动,将 Drizzle 的类型安全查询体验扩展到了"分布式分析型数据库"与"边缘状态存储"两个新领域,同时以两个针对性的 Bug 修复收紧了副本分离与 Neon 认证的类型边界。对希望在 Cloudflare Workers 上构建本地优先(local-first)应用的团队而言,durable-sqlite驱动提供了一条把 SQLite 迁移、CRUD 与关系查询全部纳入类型系统的捷径。
【免费下载链接】drizzle-ormORM项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考