Drizzle ORM `0.29.0` 版本将要求 Drizzle Kit 的最低版本为 `0.20.0`,反之亦然。因此,在升级到 Drizzle ORM 的新版本时,您还需要升级 Drizzle Kit。这可能会导致版本之间的一些破坏性更改,特别是如果您的 Drizzle ORM 版本早于 `<0.28.0` 并且需要升级 Drizzle Kit 的情况。
新功能
🎉 MySQL `bigint` 的 `unsigned` 选项
您现在可以指定 `bigint unsigned` 类型
const table = mysqlTable('table', {
id: bigint('id', { mode: 'number', unsigned: true }),
});
在文档中了解更多
🎉 改进的查询构建器类型
从 `0.29.0` 版本开始,默认情况下,由于 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
这种行为对于传统的查询构建很有用,即当您一次性创建整个查询时。但是,当您想要动态构建查询时,例如,如果您有一个共享函数接受查询构建器并对其进行增强时,这就会成为一个问题。为了解决这个问题,Drizzle 为查询构建器提供了一种特殊的“动态”模式,它消除了只能调用一次方法的限制。要启用它,您需要在查询构建器上调用 `.$dynamic()`。
让我们通过实现一个简单的 `withPagination` 函数来看看它是如何工作的,该函数根据提供的页码和可选的页面大小向查询添加 `LIMIT` 和 `OFFSET` 子句。
function withPagination<T extends PgSelect>(
qb: T,
page: number,
pageSize: number = 10,
) {
return qb.limit(pageSize).offset(page * 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);
在文档中了解更多
🎉 可以为主键和外键指定名称
当约束名称超出数据库的 64 字符限制时,会出现问题。这会导致数据库引擎截断名称,可能导致问题。从 `0.29.0` 版本开始,您可以选择为 `primaryKey()` 和 `foreignKey()` 指定自定义名称。我们还已弃用旧的 `primaryKey()` 语法,该语法仍可使用,但将在未来版本中移除。
const table = pgTable('table', {
id: integer('id'),
name: text('name'),
}, (table) => ({
cpk: primaryKey({ name: 'composite_key', columns: [table.id, table.name] }),
cfk: foreignKey({
name: 'fkName',
columns: [table.id],
foreignColumns: [table.name],
}),
}));
在文档中了解更多
🎉 支持读副本
您现在可以使用 Drizzle 的 `withReplica` 函数为读副本指定不同的数据库连接,并为主实例指定用于写入操作的连接。默认情况下,`withReplicas` 将为读取操作使用随机读副本,并为主实例使用所有其他数据修改操作。您还可以指定自定义逻辑来选择要使用的读副本连接。您可以自由地为此做出任何加权、自定义的决定。以下是一些使用示例:
const primaryDb = drizzle({ client });
const read1 = drizzle({ client });
const read2 = drizzle({ client });
const db = withReplicas(primaryDb, [read1, read2]);
// read from primary
db.$primary.select().from(usersTable);
// read from either read1 connection or read2 connection
db.select().from(usersTable)
// use primary database for delete operation
db.delete(usersTable).where(eq(usersTable.id, 1))
自定义逻辑选择读副本的实现示例,其中第一个副本有 70% 的几率被选中,第二个副本有 30% 的几率被选中。请注意,您可以为读副本实现任何类型的随机选择。
const db = withReplicas(primaryDb, [read1, read2], (replicas) => {
const weight = [0.7, 0.3];
let cumulativeProbability = 0;
const rand = Math.random();
for (const [i, replica] of replicas.entries()) {
cumulativeProbability += weight[i]!;
if (rand < cumulativeProbability) return replica;
}
return replicas[0]!
});
`withReplicas` 函数适用于 Drizzle ORM 中的所有方言。
在文档中了解更多
🎉 支持集合操作符(UNION, UNION ALL, INTERSECT, INTERSECT ALL, EXCEPT, EXCEPT ALL)
非常感谢 @Angelelz 所做出的重大贡献,从 API 讨论到适当的类型检查和运行时逻辑,以及大量的测试。这极大地帮助我们在此版本中交付了此功能。
用法示例:所有集合操作符都可以通过两种方式使用:`导入方式` 或 `构建器方式`。
// Import approach
import { union } from 'drizzle-orm/pg-core'
const allUsersQuery = db.select().from(users);
const allCustomersQuery = db.select().from(customers);
const result = await union(allUsersQuery, allCustomersQuery)
// Builder approach
const result = await db.select().from(users).union(db.select().from(customers));
在文档中了解更多
🎉 新的 MySQL 代理驱动
发布了一个新的驱动,允许您使用 MySQL 数据库创建自己的 HTTP 驱动实现。您可以在 `./examples/mysql-proxy` 文件夹中找到使用示例。
您需要在服务器上实现两个端点,分别用于查询和迁移(迁移端点是可选的,仅当您想使用 Drizzle 迁移时才需要)。服务器和驱动的实现都由您决定,因此您不受任何限制。您可以添加自定义映射、日志记录等等。
您可以在 `./examples/mysql-proxy` 文件夹中找到服务器和驱动的实现示例。
// Driver
import axios from 'axios';
import { eq } from 'drizzle-orm/expressions';
import { drizzle } from 'drizzle-orm/mysql-proxy';
import { migrate } from 'drizzle-orm/mysql-proxy/migrator';
import { cities, users } from './schema';
async function main() {
const db = drizzle(async (sql, params, method) => {
try {
const rows = await axios.post(`${process.env.REMOTE_DRIVER}/query`, {
sql,
params,
method,
});
return { rows: rows.data };
} catch (e: any) {
console.error('Error from pg proxy server:', e.response.data);
return { rows: [] };
}
});
await migrate(db, async (queries) => {
try {
await axios.post(`${process.env.REMOTE_DRIVER}/migrate`, { queries });
} catch (e) {
console.log(e);
throw new Error('Proxy server cannot run migrations');
}
}, { migrationsFolder: 'drizzle' });
await db.insert(cities).values({ id: 1, name: 'name' });
await db.insert(users).values({
id: 1,
name: 'name',
email: 'email',
cityId: 1,
});
const usersToCityResponse = await db.select().from(users).leftJoin(
cities,
eq(users.cityId, cities.id),
);
}
在文档中了解更多
🎉 新的 PostgreSQL 代理驱动
与 MySQL 类似,您现在可以为 PostgreSQL 数据库实现自己的 HTTP 驱动。您可以在 `./examples/pg-proxy` 文件夹中找到使用示例。
您需要在服务器上实现两个端点,分别用于查询和迁移(迁移端点是可选的,仅当您想使用 Drizzle 迁移时才需要)。服务器和驱动的实现都由您决定,因此您不受任何限制。您可以添加自定义映射、日志记录等等。
您可以在 `./examples/pg-proxy` 文件夹中找到服务器和驱动的实现示例。
import axios from 'axios';
import { eq } from 'drizzle-orm/expressions';
import { drizzle } from 'drizzle-orm/pg-proxy';
import { migrate } from 'drizzle-orm/pg-proxy/migrator';
import { cities, users } from './schema';
async function main() {
const db = drizzle(async (sql, params, method) => {
try {
const rows = await axios.post(`${process.env.REMOTE_DRIVER}/query`, { sql, params, method });
return { rows: rows.data };
} catch (e: any) {
console.error('Error from pg proxy server:', e.response.data);
return { rows: [] };
}
});
await migrate(db, async (queries) => {
try {
await axios.post(`${process.env.REMOTE_DRIVER}/query`, { queries });
} catch (e) {
console.log(e);
throw new Error('Proxy server cannot run migrations');
}
}, { migrationsFolder: 'drizzle' });
const insertedCity = await db.insert(cities).values({ id: 1, name: 'name' }).returning();
const insertedUser = await db.insert(users).values({ id: 1, name: 'name', email: 'email', cityId: 1 });
const usersToCityResponse = await db.select().from(users).leftJoin(cities, eq(users.cityId, cities.id));
}
在文档中了解更多
🎉 支持 `D1` 批处理 API
参考:https://developers.cloudflare.com/d1/platform/client-api/#dbbatch
批处理 API 使用示例
const batchResponse = await db.batch([
db.insert(usersTable).values({ id: 1, name: 'John' }).returning({
id: usersTable.id,
}),
db.update(usersTable).set({ name: 'Dan' }).where(eq(usersTable.id, 1)),
db.query.usersTable.findMany({}),
db.select().from(usersTable).where(eq(usersTable.id, 1)),
db.select({ id: usersTable.id, invitedBy: usersTable.invitedBy }).from(
usersTable,
),
]);
type BatchResponse = [
{
id: number;
}[],
D1Result,
{
id: number;
name: string;
verified: number;
invitedBy: number | null;
}[],
{
id: number;
name: string;
verified: number;
invitedBy: number | null;
}[],
{
id: number;
invitedBy: number | null;
}[],
];
所有可以在 db.batch
中使用的可能构建器
`db.all()`,
`db.get()`,
`db.values()`,
`db.run()`,
`db.query.<table>.findMany()`,
`db.query.<table>.findFirst()`,
`db.select()...`,
`db.update()...`,
`db.delete()...`,
`db.insert()...`,
更多使用示例请参见此处:integration-tests/tests/d1-batch.test.ts 和 文档
Drizzle Kit 0.20.0
- 使用 `defineConfig` 函数定义 drizzle.config 的新方式
- 可以使用 wrangler.toml 文件通过 Drizzle Studio 访问 Cloudflare D1
- Drizzle Studio 正在迁移到 https://local.drizzle.studio/
- 支持 `bigint unsigned`
- `primaryKeys` 和 `foreignKeys` 现在可以拥有自定义名称
- 环境变量现在会自动获取
- 一些错误修复和改进
您可以在此处阅读更多关于 drizzle-kit 更新的信息