DrizzleORM v0.29.0 发布
Nov 9, 2023

Drizzle ORM 版本 0.29.0 将需要最低 Drizzle Kit 版本为 0.20.0 ,反之亦然。因此,当升级到更新版本的 Drizzle ORM 时,您也需要升级 Drizzle Kit。这可能会导致在版本之间产生一些破坏性变化,特别是如果您需要升级 Drizzle Kit,而您的 Drizzle ORM 版本旧于 <0.28.0

新功能

🎉 MySQL 的 unsigned 选项支持 bigint

现在您可以指定 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')); // ❌ 类型错误 - 只能调用 where() 一次

这个行为对于传统的查询构建很有用,即一次创建整个查询。然而,在您想动态构建查询时,它就成为一个问题,即如果您有一个共享函数,它接受一个查询构建器并增强它。为了解决这个问题,Drizzle 提供了一个特殊的 ‘dynamic’ 模式的查询构建器,它删除了仅能调用方法一次的限制。要启用它,您需要在查询构建器上调用 .$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); // ❌ 类型错误 - 查询构建器不处于动态模式下

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

请注意,withPagination 函数是泛型的,这允许您在其中修改查询构建器的结果类型,例如通过添加 join:

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]);

// 从主实例读取
db.$primary.select().from(usersTable);

// 从 read1 连接或 read2 连接读取
db.select().from(usersTable)

// 使用主数据库进行删除操作
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 approachbuilder approach

// 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

  1. 使用 defineConfig 函数定义 drizzle.config 的新方式
  2. 使用 wrangler.toml 文件可以在 Drizzle Studio 中访问 Cloudflare D1
  3. Drizzle Studio 迁移到 https://local.drizzle.studio/
  4. bigint unsigned 支持
  5. primaryKeysforeignKeys 现在可以有自定义名称
  6. 环境变量现在会自动获取
  7. 一些错误修复和改进

您可以在这里阅读有关 drizzle-kit 更新的更多信息:here

Become a Gold Sponsor