动态查询构建

默认情况下,由于 Drizzle 中的所有查询构建器都尽可能遵循 SQL,因此大多数方法你只能调用一次。例如,在 SELECT 语句中可能只有一个 WHERE 子句,因此你只能调用 .where() 一次:

const query = db
	.select()
	.from(users)
	.where(eq(users.id, 1))
	.where(eq(users.name, 'John')); // ❌ Type error - where() can only be invoked once

在之前的 ORM 版本中,当这些限制尚未实现时,这个例子尤其让许多用户感到困惑,因为他们期望查询构建器将多个 .where() 调用合并为一个条件。

此行为对于常规查询构建非常有用,例如,当你一次创建整个查询时。但是,当你想要动态构建查询时,例如,如果你有一个共享函数,它接受查询构建器并对其进行增强,就会出现问题。为了解决这个问题,Drizzle 为查询构建器提供了一种特殊的 ‘dynamic’ 模式,它消除了只能调用一次方法的限制。要启用它,你需要在查询构建器上调用 .$dynamic()

让我们通过实现一个简单的 withPagination 函数来了解它的工作原理,该函数根据提供的页码和可选的页面大小,将 LIMITOFFSET 子句添加到查询中:

function withPagination<T extends PgSelect>(
	qb: T,
	page: number = 1,
	pageSize: number = 10,
) {
	return qb.limit(pageSize).offset((page - 1) * pageSize);
}

const query = db.select().from(users).where(eq(users.id, 1));
withPagination(query, 1); // ❌ Type error - the query builder is not in dynamic mode

const dynamicQuery = query.$dynamic();
withPagination(dynamicQuery, 1); // ✅ OK

请注意,withPagination 函数是通用函数,它允许你修改其中查询构建器的结果类型,例如通过添加连接:

function withFriends<T extends PgSelect>(qb: T) {
	return qb.leftJoin(friends, eq(friends.userId, users.id));
}

let query = db.select().from(users).where(eq(users.id, 1)).$dynamic();
query = withFriends(query);

这是可能的,因为 PgSelect 和其他类似类型是专门为动态查询构建而设计的。它们只能在动态模式下使用。

以下列出了动态查询构建中可用作泛型参数的所有类型:

DialectType
QuerySelectInsertUpdateDelete
PostgresPgSelectPgInsertPgUpdatePgDelete
PgSelectQueryBuilder
MySQLMySqlSelectMySqlInsertMySqlUpdateMySqlDelete
MySqlSelectQueryBuilder
SQLiteSQLiteSelectSQLiteInsertSQLiteUpdateSQLiteDelete
SQLiteSelectQueryBuilder

...QueryBuilder 类型用于 独立查询构建器实例。数据库查询构建器是它们的子类,因此你也可以使用它们。

	import { QueryBuilder } from 'drizzle-orm/pg-core';

	function withFriends<T extends PgSelectQueryBuilder>(qb: T) {
		return qb.leftJoin(friends, eq(friends.userId, users.id));
	}

	const qb = new QueryBuilder();
	let query = qb.select().from(users).where(eq(users.id, 1)).$dynamic();
	query = withFriends(query);