drizzle-orm-sqlite 0.12.0-beta.21 变更解析:raw 查询统一、查询构建器直传与 INSERT 生成优化
2026/9/19 20:50:42 网站建设 项目流程

drizzle-orm-sqlite 0.12.0-beta.21 变更解析:raw 查询统一、查询构建器直传与 INSERT 生成优化

【免费下载链接】drizzle-ormORM项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm

drizzle-orm-sqlite 0.12.0-beta.21 是 SQLite 方言包在 0.12.0 系列 beta 阶段的一次聚焦型发布,围绕四个内部行为收敛展开:修复各驱动db.all的执行逻辑、允许把查询构建器直接传给 raw 执行方法、优化单值 INSERT 的 SQL 生成、并暴露索引配置中的table属性。本文以该版本变更日志(changelogs/drizzle-orm-sqlite/0.12.0-beta.21.md)为核心骨架,结合当前仓库中 SQLite 方言与驱动层的源码实现,逐条剖析每个变更的动机、落点与对使用者的实际影响,帮助你在升级或使用该版本时准确理解行为差异。

1. 版本背景与定位

该文件位于changelogs/drizzle-orm-sqlite/目录,属于 drizzle-orm 早期分包的变更记录(与之相邻的还有0.12.0-beta.170.12.0-beta.21等多个 beta 版本,以及后续0.13.00.14.x等正式版记录)。beta 阶段的 changelog 通常以“修 bug + 内部重构”为主,本条四个条目全部属于运行时行为与 SQL 生成层面的改动,不涉及公开 API 的破坏性变更,因此对现有代码基本透明,但会影响生成的 SQL 文本与 raw 查询的返回结构。

从变更性质可以推断,0.12.0-beta.21 的目标是:统一各 SQLite 驱动的 raw 执行语义,并减少无意义 SQL 输出

2. 修复db.all逻辑:各驱动行为统一

2.1 变更内容

Fixeddb.alllogic for all drivers.

db.all是 SQLite 方言 DB 实例上的 raw 查询方法之一,用于执行原生 SQL 并返回多行结果。在 0.12.0-beta.21 中,该方法的执行逻辑在所有 SQLite 驱动(better-sqlite3、libsql、d1、bun-sqlite、op-sqlite、sqlite-proxy、expo-sqlite 等)下被统一修复。

2.2 源码印证:db.all的统一入口

在 drizzle-orm/src/sqlite-core/db.ts 中,all的定义为:

all<T = unknown>(query: SQLWrapper | string): DBResult<TResultKind, T[]> { const sequel = typeof query === 'string' ? sql.raw(query) : query.getSQL(); if (this.session) { return this.execute((session) => session.all(sequel), 'all') as DBResult<TResultKind, T[]>; } return this.session.all(sequel) as DBResult<TResultKind, T[]>; }

可见db.all本身只做两件事:把字符串包装为sql.raw(query)、把查询构建器转换为getSQL(),然后委托给session.all()。因此“修复 all 逻辑”的实质是让各个驱动会话(session)的all实现保持一致

以 better-sqlite3 驱动为例,drizzle-orm/src/better-sqlite3/session.ts 中PreparedQuery.all的逻辑是:

all(placeholderValues?: Record<string, unknown>): T['all'] { const { fields, joinsNotNullableMap, query, logger, stmt, customResultMapper } = this; if (!fields && !customResultMapper) { const params = fillPlaceholders(query.params, placeholderValues ?? {}); logger.logQuery(query.sql, params); return stmt.all(...params); } const rows = this.values(placeholderValues) as unknown[][]; if (customResultMapper) { return customResultMapper(rows) as T['all']; } return rows.map((row) => mapResultRow(fields!, row, joinsNotNullableMap)); }

关键点在于:当查询没有映射字段(fields为空)且没有自定义结果映射器时,直接透传底层驱动的stmt.all(),保持结果原样;否则走values()原始行路径再按字段映射。这一“分支一致化”正是 beta.21 修复的核心——确保每个驱动的 rawall返回结构对齐,不因驱动差异而出现字段映射或数组模式的偏差。

其他驱动同样实现了session.all的等价方法,例如 libsql/session.ts、d1/session.ts、sqlite-proxy/session.ts、op-sqlite/session.ts 等,此次变更的目的就是让这些实现的行为口径统一。

2.3 对使用者的影响

  • db.all('select * from users')的返回值在各驱动下行为一致,不再出现某些驱动返回原始行、某些驱动返回映射对象的不一致;
  • raw SQL 场景(无 schema 映射)下,返回的就是底层驱动原生行数据,便于与原生 API 兼容;
  • 配合查询构建器时(见下一节),返回结构受fields是否存在影响,需要留意。

3. 允许向 raw 执行方法直接传入查询构建器

3.1 变更内容

Allowed passing query builders to raw query execution methods.

此前 raw 执行方法(db.alldb.getdb.rundb.values)只接受stringSQLWrapper;本次变更允许直接传入 Drizzle 的查询构建器对象(如db.select().from(...)的产物)。

3.2 源码印证:SQLWrapper统一收口

在 drizzle-orm/src/sqlite-core/db.ts 中,四个 raw 方法全部采用同一套“字符串转sql.raw、其余取getSQL()”的收口逻辑:

run(query: SQLWrapper | string): DBResult<TResultKind, TRunResult> { const sequel = typeof query === 'string' ? sql.raw(query) : query.getSQL(); ... return this.session.run(sequel) as DBResult<TResultKind, TRunResult>; } all<T = unknown>(query: SQLWrapper | string): DBResult<TResultKind, T[]> { const sequel = typeof query === 'string' ? sql.raw(query) : query.getSQL(); ... } get<T = unknown>(query: SQLWrapper | string): DBResult<TResultKind, T> { const sequel = typeof query === 'string' ? sql.raw(query) : query.getSQL(); ... } values<T extends unknown[] = unknown[]>(query: SQLWrapper | string): DBResult<TResultKind, T[]> { const sequel = typeof query === 'string' ? sql.raw(query) : query.getSQL(); ... }

SQLite 的各类查询构建器(select/insert/update/delete 的 builder 与最终执行对象)都实现了SQLWrapper接口的getSQL()方法,因此传入构建器后会被转换为对应的 SQL 文本与参数列表再交给 session 执行。

3.3 实际用法示例

import { sql } from 'drizzle-orm/sqlite-core'; // 构造一个查询构建器 const qb = db.select({ id: users.id, name: users.name }).from(users).where(sql`age > 18`); // 直接传给 raw 执行方法 const rows = await db.all(qb); const first = await db.get(qb);

这一能力让“构建器生成的 SQL 再经 raw 通道执行”成为可能,适合需要对 SQL 做二次包装、拼接或复用预构建查询片段的场景。注意:传入构建器后,结果的字段映射行为遵循上一节描述的fields分支逻辑。

4. INSERT 生成优化:单值插入跳过无值列

4.1 变更内容

Optimized INSERT query generation for single values by skipping columns without values.

当插入的是单个值对象时,SQLite 方言在生成 INSERT 语句时跳过没有提供值的列,从而产出更精简、更贴近意图的 SQL。

4.2 源码印证:buildInsertQuery的列过滤

在 drizzle-orm/src/sqlite-core/dialect.ts 的buildInsertQuery中,参与 INSERT 的列先经过一次过滤:

const colEntries: [string, SQLiteColumn][] = Object.entries(columns).filter( ([_, col]) => !col.shouldDisableInsert(), ); const insertOrder = colEntries.map(([, column]) => sql.identifier(this.casing.getColumnCasing(column)));

随后在组装单行值时,对每个字段做“是否有值”的判断:值为undefined(或 Param 且值为undefined)时,按优先级回退到列的默认值(default)、默认函数(defaultFn)、更新函数(onUpdateFn),最终兜底为null

if ( colValue === undefined || (is(colValue, Param) && colValue.value === undefined) ) { let defaultValue; if (col.default !== null && col.default !== undefined) { defaultValue = is(col.default, SQL) ? col.default : sql.param(col.default, col); } else if (col.defaultFn !== undefined) { const defaultFnResult = col.defaultFn(); defaultValue = is(defaultFnResult, SQL) ? defaultFnResult : sql.param(defaultFnResult, col); } else if (!col.default && col.onUpdateFn !== undefined) { ... } else { defaultValue = sql`null`; } valueList.push(defaultValue); } else { valueList.push(colValue); }

结合 changelog 描述“skipping columns without values”,此次优化针对的是**单值插入(values.length === 1)**路径:在没有显式值的列上不再重复输出无意义的列项/null占位,而是让 SQL 更短、参数更少,减少客户端与数据库间的无效传输。代码中const isSingleValue = values.length === 1的注释(现被注释掉的default values分支)也印证了这一演进方向——单值且全空时理论上可退化为insert into t default values

4.3 行为对照

场景优化前(示意)优化后(示意)
db.insert(users).values({ name: 'a' })age无默认值可能输出age列并补null跳过无值列,仅输出有值列
多行批量插入values([...])每行补齐所有列保持每行列对齐语义,不做跳过

注意:该优化只针对单值插入路径;批量多行插入仍需保持列对齐,因此不适用于跳过策略。若你的业务依赖“未提供的列显式写入 NULL”而非依赖列默认值,请先确认目标列的default/defaultFn配置。

5. 从索引配置中暴露table属性

5.1 变更内容

Exposedtableproperty from index config.

SQLite 索引(Index)的配置对象(config)现在对外暴露table属性,标明该索引所属的表。

5.2 源码印证:Index构造时写入 table

在 drizzle-orm/src/sqlite-core/indexes.ts 中:

export class Index { static readonly [entityKind]: string = 'SQLiteIndex'; readonly config: IndexConfig & { table: SQLiteTable }; constructor(config: IndexConfig, table: SQLiteTable) { this.config = { ...config, table }; } }

IndexBuilder.build(table)在绑定表时调用new Index(this.config, table)(indexes.ts),从而把表引用写入config.tableIndexConfig本身包含namecolumnsuniquewhere四个字段(indexes.ts),其中where用于部分索引(partial index)条件;table是在Index层叠加的扩展属性。

5.3 使用场景

该暴露主要用于工具链与元数据场景:例如迁移生成器、schema 内省、文档生成器需要从索引对象反查其所属表(进而读取表的列定义、命名约定等)。此前config.table仅存在于内部构造流程,未对外可见;beta.21 之后可以直接读取:

const idx = index('users_email_idx').on(users.email); // 在绑定表之后(例如 table 定义完成时) console.log(idx.config.table); // SQLiteTable 实例 console.log(idx.config.columns); // 索引列 console.log(idx.config.unique); // 是否唯一索引 console.log(idx.config.where); // 部分索引条件,可为 undefined

注意:table只有在IndexBuilder.build(table)被调用(即索引真正绑定到某张表)之后才会存在,直接index(...).on(...)阶段的IndexBuilder.config中并没有table字段。

6. 总结与升级建议

drizzle-orm-sqlite 0.12.0-beta.21 的四项变更可以归纳为三个主题:

  1. raw 查询行为收敛(第 2、3 条):db.all在各驱动下语义统一,且all/get/run/values均接受查询构建器,统一以getSQL()收口;
  2. SQL 生成精简(第 4 条):单值 INSERT 跳过无值列,减少冗余输出;
  3. 元数据可访问性(第 5 条):索引配置暴露table,方便下游工具消费。

升级到该版本时,建议:

  • 若使用 raw SQL 多行查询,回归测试db.all在不同驱动下的返回结构;
  • 若使用db.insert(table).values(singleObject)且依赖未提供列写入 NULL,确认列定义中的默认值设置符合预期;
  • 工具类代码若需读取索引所属表,可直接访问index.config.table

对于更新版本的完整变更,可继续查看 changelogs/drizzle-orm-sqlite 目录下的0.13.00.14.x等后续记录,了解该分包随 drizzle-orm 主版本演进的完整脉络。

【免费下载链接】drizzle-ormORM项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询