Drizzle 模式

Drizzle 允许您使用 TypeScript 定义模式,支持底层数据库的各种模型和属性。当您定义模式时,它将作为未来查询修改(使用 Drizzle-ORM)和迁移(使用 Drizzle-Kit)的真相来源。

如果您正在使用 Drizzle-Kit 进行迁移过程,请确保导出模式文件中定义的所有模型,以便 Drizzle-Kit 可以导入它们并在迁移差异处理过程中使用它们。

组织模式文件

您可以直接在 TypeScript 中声明您的 SQL 模式,可以放在单个 schema.ts 文件中,也可以分散在各个文件中——随您喜欢,完全自由!

单个文件中的模式

使用 Drizzle 声明模式最常见的方式是将所有表都放在一个 schema.ts 文件中。

注意:您可以随意命名模式文件。例如,它可以是 models.ts,或者其他名称。

如果您的表模型不多,或者您不介意将它们都放在一个文件中,这种方法会很好用。

示例

📦 <project root>
└ 📂 src
└ 📂 db
   └ 📜 schema.ts

drizzle.config.ts 文件中,您需要指定模式文件的路径。通过此配置,Drizzle 将从 schema.ts 文件中读取信息,并在生成迁移时使用这些信息。有关 drizzle.config.ts 文件以及 Drizzle 迁移的更多信息,请查看:链接

import { defineConfig } from "drizzle-kit";

export default defineConfig({
  dialect: 'postgresql', // 'mysql' | 'sqlite' | 'turso'
  schema: './src/db/schema.ts'
})

多个文件中的模式

您可以将 Drizzle 模型(例如表、枚举、序列等)不仅放在一个文件中,还可以放在您喜欢的任何文件中。您唯一必须确保的是,从这些文件中导出所有模型,以便 Drizzle kit 可以导入它们并在迁移中使用它们。

一个用例是将每个表分离到其自己的文件中。

📦 <project root>
 └ 📂 src
    └ 📂 db
       └ 📂 schema
          ├ 📜 users.ts
          ├ 📜 countries.ts
          ├ 📜 cities.ts
          ├ 📜 products.ts
          ├ 📜 clients.ts
          └ 📜 etc.ts

drizzle.config.ts 文件中,您需要指定模式文件夹的路径。通过此配置,Drizzle 将从 schema 文件夹中递归读取所有文件,并从那里获取所有 drizzle 表。有关 drizzle.config.ts 文件以及 Drizzle 迁移的更多信息,请查看:链接

import { defineConfig } from "drizzle-kit";

export default defineConfig({
  dialect: 'postgresql', // 'mysql' | 'sqlite' | 'turso'
  schema: './src/db/schema'
})

您还可以按照自己喜欢的方式对它们进行分组,例如为用户相关表、消息相关表、产品相关表等创建分组。

📦 <project root>
 └ 📂 src
    └ 📂 db
       └ 📂 schema
          ├ 📜 users.ts
          ├ 📜 messaging.ts
          └ 📜 products.ts

drizzle.config.ts 文件中,您需要指定模式文件的路径。通过此配置,Drizzle 将从 schema.ts 文件中读取信息,并在生成迁移时使用这些信息。有关 drizzle.config.ts 文件以及 Drizzle 迁移的更多信息,请查看:链接

import { defineConfig } from "drizzle-kit";

export default defineConfig({
  dialect: 'postgresql', // 'mysql' | 'sqlite' | 'turso'
  schema: './src/db/schema'
})

塑造数据模式

Drizzle 模式由您正在使用的数据库中的几种模型类型组成。使用 Drizzle,您可以指定

让我们逐一查看如何在 Drizzle 中定义模式。

表和列声明

Drizzle 中的表应至少定义 1 列,这与数据库中的要求相同。有一点很重要,Drizzle 中没有通用的表对象。您需要选择正在使用的方言,PostgreSQL、MySQL 或 SQLite。

PostgreSQL 表
MySQL 表
SQLite 表
import { pgTable, integer } from "drizzle-orm/pg-core"

export const users = pgTable('users', {
  id: integer()
});

默认情况下,Drizzle 将在数据库查询中使用 TypeScript 键名作为列名。因此,示例中的模式和查询将生成如下所示的 SQL 查询:

本例使用了一个 db 对象,其初始化在此文档部分未涵盖。要了解如何连接到数据库,请参阅 连接文档


TypeScript 键 = 数据库键

// schema.ts
import { integer, pgTable, varchar } from "drizzle-orm/pg-core";

export const users = pgTable('users', {
  id: integer(),
  first_name: varchar()
})
// query.ts
await db.select().from(users);
SELECT "id", "first_name" from users;

如果希望在 TypeScript 代码和数据库中使用不同的名称,可以使用列别名。

// schema.ts
import { integer, pgTable, varchar } from "drizzle-orm/pg-core";

export const users = pgTable('users', {
  id: integer(),
  firstName: varchar('first_name')
})
// query.ts
await db.select().from(users);
SELECT "id", "first_name" from users;

驼峰命名与蛇形命名

数据库模型名称通常使用 snake_case 约定,而在 TypeScript 中,通常使用 camelCase 命名模型。这可能导致模式中出现大量别名定义。为解决此问题,Drizzle 提供了一种在 Drizzle 数据库初始化时包含一个可选参数来自动将 TypeScript 中的 camelCase 映射到数据库中的 snake_case 的方法。

对于这种映射,您可以在 Drizzle DB 声明中使用 casing 选项。此参数将帮助您指定数据库模型的命名约定,并将尝试相应地映射所有 JavaScript 键。

// schema.ts
import { drizzle } from "drizzle-orm/node-postgres";
import { integer, pgTable, varchar } from "drizzle-orm/pg-core";

export const users = pgTable('users', {
  id: integer(),
  firstName: varchar()
})
// db.ts
const db = drizzle({ connection: process.env.DATABASE_URL, casing: 'snake_case' })
// query.ts
await db.select().from(users);
SELECT "id", "first_name" from users;

高级

您可以使用 Drizzle ORM 实现一些技巧。由于 Drizzle 完全存在于 TypeScript 文件中,因此您基本上可以在简单的 TypeScript 项目中对代码执行任何操作。

一个常见功能是将列分离到不同的位置,然后重复使用它们。例如,考虑 updated_atcreated_atdeleted_at 列。许多表/模型可能需要这三个字段来跟踪和分析系统中实体的创建、删除和更新。

我们可以将这些列定义在一个单独的文件中,然后导入并将它们分散到您拥有的所有表对象中。

// columns.helpers.ts
const timestamps = {
  updated_at: timestamp(),
  created_at: timestamp().defaultNow().notNull(),
  deleted_at: timestamp(),
}
// users.sql.ts
export const users = pgTable('users', {
  id: integer(),
  ...timestamps
})
// posts.sql.ts
export const posts = pgTable('posts', {
  id: integer(),
  ...timestamps
})

模式

PostgreSQL
MySQL
SQLite


在 PostgreSQL 中,有一个实体称为 schema(我们认为应该称为 folders)。这在 PostgreSQL 中创建了一个结构。

您可以使用 pgSchema 管理您的 PostgreSQL 模式,并将任何其他模型放在其中。

定义您要使用 Drizzle 管理的模式。

import { pgSchema } from "drizzle-orm/pg-core"

export const customSchema = pgSchema('custom');

然后将表放置在模式对象中。

import { integer, pgSchema } from "drizzle-orm/pg-core";

export const customSchema = pgSchema('custom');

export const users = customSchema.table('users', {
  id: integer()
})

示例

掌握了基础知识后,让我们为一个真实项目定义一个模式示例,以便更好地了解和理解。

所有示例都将使用 generateUniqueString。它的实现将在所有模式示例之后提供。

PostgreSQL
MySQL
SQLite
import { AnyPgColumn } from "drizzle-orm/pg-core";
import { pgEnum, pgTable as table } from "drizzle-orm/pg-core";
import * as t from "drizzle-orm/pg-core";

export const rolesEnum = pgEnum("roles", ["guest", "user", "admin"]);

export const users = table(
  "users",
  {
    id: t.integer().primaryKey().generatedAlwaysAsIdentity(),
    firstName: t.varchar("first_name", { length: 256 }),
    lastName: t.varchar("last_name", { length: 256 }),
    email: t.varchar().notNull(),
    invitee: t.integer().references((): AnyPgColumn => users.id),
    role: rolesEnum().default("guest"),
  },
  (table) => [
    t.uniqueIndex("email_idx").on(table.email)
  ]
);

export const posts = table(
  "posts",
  {
    id: t.integer().primaryKey().generatedAlwaysAsIdentity(),
    slug: t.varchar().$default(() => generateUniqueString(16)),
    title: t.varchar({ length: 256 }),
    ownerId: t.integer("owner_id").references(() => users.id),
  },
  (table) => [
    t.uniqueIndex("slug_idx").on(table.slug),
    t.index("title_idx").on(table.title),
  ]
);

export const comments = table("comments", {
  id: t.integer().primaryKey().generatedAlwaysAsIdentity(),
  text: t.varchar({ length: 256 }),
  postId: t.integer("post_id").references(() => posts.id),
  ownerId: t.integer("owner_id").references(() => users.id),
});

generateUniqueString 的实现

function generateUniqueString(length: number = 12): string {
  const characters =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  let uniqueString = "";

  for (let i = 0; i < length; i++) {
    const randomIndex = Math.floor(Math.random() * characters.length);
    uniqueString += characters[randomIndex];
  }

  return uniqueString;
}

接下来是什么?