Drizzle 软关系

Drizzle 关系的唯一目的是让你以最简单、最简洁的方式查询关系数据:

关系查询
带连接的选择
import * as schema from './schema';
import { drizzle } from 'drizzle-orm/cockroach';

const db = drizzle({ client, schema });

const result = await db._query.users.findMany({
  with: {
    posts: true,
  },
});
[{
  id: 10,
  name: "Dan",
  posts: [
    {
      id: 1,
      content: "SQL is awesome",
      authorId: 10,
    },
    {
      id: 2,
      content: "But check relational queries",
      authorId: 10,
    }
  ]
}]

一对一

Drizzle ORM 为你提供了一个 API,可以使用 relations 操作符来定义表之间的 one-to-one 关系。

下面是一个 users 和 users 之间 one-to-one 关系的示例,其中一个用户可以邀请另一个用户(这个示例使用了自引用):

import { cockroachTable, int4, text, } from 'drizzle-orm/cockroach-core';
import { relations } from 'drizzle-orm/_relations';

export const users = cockroachTable('users', {
	id: int4().primaryKey(),
	name: text(),
	invitedBy: int4('invited_by'),
});

export const usersRelations = relations(users, ({ one }) => ({
	invitee: one(users, {
		fields: [users.invitedBy],
		references: [users.id],
	}),
}));

另一个例子是:用户的个人资料信息存储在单独的表中。在这种情况下,因为外键存储在 "profile_info" 表中,所以用户关系既没有 fields 也没有 references。这会告诉 Typescript,user.profileInfo 是可空的:

import { cockroachTable, int4, text, jsonb } from 'drizzle-orm/cockroach-core';
import { relations } from 'drizzle-orm/_relations';

export const users = cockroachTable('users', {
	id: int4('id').primaryKey(),
	name: text('name'),
});

export const usersRelations = relations(users, ({ one }) => ({
	profileInfo: one(profileInfo),
}));

export const profileInfo = cockroachTable('profile_info', {
	id: int4('id').primaryKey(),
	userId: int4('user_id').references(() => users.id),
	metadata: jsonb('metadata'),
});

export const profileInfoRelations = relations(profileInfo, ({ one }) => ({
	user: one(users, { fields: [profileInfo.userId], references: [users.id] }),
}));

const user = await queryUserWithProfileInfo();
//____^? type { id: number, profileInfo: { ... } | null  }

一对多

Drizzle ORM 提供了一个 API,使用 relations 操作符来定义表之间的 一对多 关系。

用户与他们撰写的帖子之间的一对多关系示例:

import { cockroachTable, int4, text } from 'drizzle-orm/cockroach-core';
import { relations } from 'drizzle-orm/_relations';

export const users = cockroachTable('users', {
	id: int4('id').primaryKey(),
	name: text('name'),
});

export const usersRelations = relations(users, ({ many }) => ({
	posts: many(posts),
}));

export const posts = cockroachTable('posts', {
	id: int4('id').primaryKey(),
	content: text('content'),
	authorId: int4('author_id'),
});

export const postsRelations = relations(posts, ({ one }) => ({
	author: one(users, {
		fields: [posts.authorId],
		references: [users.id],
	}),
}));

现在让我们给帖子添加评论:

...

export const posts = cockroachTable('posts', {
	id: int4('id').primaryKey(),
	content: text('content'),
	authorId: int4('author_id'),
});

export const postsRelations = relations(posts, ({ one, many }) => ({
	author: one(users, {
		fields: [posts.authorId],
		references: [users.id],
	}),
	comments: many(comments)
}));

export const comments = cockroachTable('comments', {
	id: int4('id').primaryKey(),
	text: text('text'),
	authorId: int4('author_id'),
	postId: int4('post_id'),
});

export const commentsRelations = relations(comments, ({ one }) => ({
	post: one(posts, {
		fields: [comments.postId],
		references: [posts.id],
	}),
}));

多对多

Drizzle ORM 为你提供了一个 API,用于通过所谓的 junctionjoin 表来定义表之间的 many-to-many 关系, 这些表必须显式定义,并用于存储相关表之间的关联。

users 和 groups 之间的 many-to-many 关系示例:

import { relations } from 'drizzle-orm/_relations';
import { int4, cockroachTable, primaryKey, text } from 'drizzle-orm/cockroach-core';

export const users = cockroachTable('users', {
  id: int4('id').primaryKey(),
  name: text('name'),
});

export const usersRelations = relations(users, ({ many }) => ({
  usersToGroups: many(usersToGroups),
}));

export const groups = cockroachTable('groups', {
  id: int4('id').primaryKey(),
  name: text('name'),
});

export const groupsRelations = relations(groups, ({ many }) => ({
  usersToGroups: many(usersToGroups),
}));

export const usersToGroups = cockroachTable(
  'users_to_groups',
  {
    userId: int4('user_id')
      .notNull()
      .references(() => users.id),
    groupId: int4('group_id')
      .notNull()
      .references(() => groups.id),
  },
  (t) => [
		primaryKey({ columns: [t.userId, t.groupId] })
	],
);

export const usersToGroupsRelations = relations(usersToGroups, ({ one }) => ({
  group: one(groups, {
    fields: [usersToGroups.groupId],
    references: [groups.id],
  }),
  user: one(users, {
    fields: [usersToGroups.userId],
    references: [users.id],
  }),
}));

外键

你可能已经注意到,relations 看起来和外键很相似——它们甚至都有一个 references 属性。那么区别是什么?

虽然外键也有类似的用途,用于定义表之间的关系,但它们工作的层级与 relations 不同。

外键是数据库层面的约束,它们会在每次 insert/update/delete 操作时进行检查,如果违反约束就会抛出错误。 而 relations 则是一种更高层级的抽象,它们只用于在应用层定义表之间的关系。 它们不会以任何方式影响数据库 schema,也不会隐式创建外键。

这意味着 relations 和外键可以一起使用,但它们彼此并不依赖。 你可以在不使用外键的情况下定义 relations(反之亦然),这使得它们也能用于不支持外键的数据库。

下面这两个示例在使用 Drizzle 关系查询来查询数据时,行为完全相同。

schema1.ts
schema2.ts
export const users = cockroachTable('users', {
	id: int4('id').primaryKey(),
	name: text('name'),
});

export const profileInfo = cockroachTable('profile_info', {
	id: int4('id').primaryKey(),
	userId: int4("user_id"),
	metadata: jsonb("metadata"),
});

export const profileInfoRelations = relations(profileInfo, ({ one }) => ({
  user: one(users, {
    fields: [profileInfo.userId],
    references: [users.id],
  }),
}));

外键动作

有关更多信息,请查看 CockroachDB 外键文档

你可以指定当父表中的被引用数据发生修改时应执行的动作。这些动作称为“外键动作”。CockroachDB 为这些动作提供了若干选项。

删除/更新动作

与 ON DELETE 类似,也有 ON UPDATE,它会在被引用列发生变化(更新)时触发。可用的动作相同,只是对于 SET NULL 和 SET DEFAULT 不能指定列列表。在这种情况下,CASCADE 表示应将被引用列的更新值复制到引用行中。 在 drizzle 中,你可以使用 references() 的第二个参数添加外键动作。

动作类型

export type UpdateDeleteAction = 'cascade' | 'restrict' | 'no action' | 'set null' | 'set default';

// references 接口的第二个参数
actions?: {
		onUpdate?: UpdateDeleteAction;
		onDelete?: UpdateDeleteAction;
	} | undefined

在下面的示例中,在 posts schema 的 author 字段上添加 onDelete: 'cascade' 意味着删除 user 时也会删除所有相关的 Post 记录。

import { cockroachTable, int4, text } from 'drizzle-orm/cockroach-core';

export const users = cockroachTable('users', {
	id: int4().primaryKey(),
	name: text(),
});

export const posts = cockroachTable('posts', {
	id: int4().primaryKey(),
	name: text(),
	author: int4().references(() => users.id, { onDelete: 'cascade' }).notNull(),
});

对于使用 foreignKey 操作符指定的约束,外键动作使用如下语法定义:

import { foreignKey, cockroachTable, int4, text } from 'drizzle-orm/cockroach-core';

export const users = cockroachTable('users', {
	id: int4().primaryKey(),
	name: text(),
});

export const posts = cockroachTable('posts', {
	id: int4().primaryKey(),
	name: text(),
	author: int4().notNull(),
}, (table) => [
	foreignKey({
		name: "author_fk",
		columns: [table.author],
		foreignColumns: [users.id],
	})
		.onDelete('cascade')
		.onUpdate('cascade')
]);

关系消歧

Drizzle 还提供了 relationName 选项,用于在你在同一对表之间定义多个关系时对这些关系进行消歧。例如,如果你定义了一个包含 authorreviewer 关系的 posts 表。

import { cockroachTable, int4, text } from 'drizzle-orm/cockroach-core';
import { relations } from 'drizzle-orm/_relations';
 
export const users = cockroachTable('users', {
	id: int4().primaryKey(),
	name: text(),
});
 
export const usersRelations = relations(users, ({ many }) => ({
	author: many(posts, { relationName: 'author' }),
	reviewer: many(posts, { relationName: 'reviewer' }),
}));
 
export const posts = cockroachTable('posts', {
	id: int4().primaryKey(),
	content: text(),
	authorId: int4('author_id'),
	reviewerId: int4('reviewer_id'),
});
 
export const postsRelations = relations(posts, ({ one }) => ({
	author: one(users, {
		fields: [posts.authorId],
		references: [users.id],
		relationName: 'author',
	}),
	reviewer: one(users, {
		fields: [posts.reviewerId],
		references: [users.id],
		relationName: 'reviewer',
	}),
}));