Drizzle 软关系
Drizzle 关系的唯一目的是让你以最简单、最简洁的方式查询关系数据:
import * as schema from './schema';
import { drizzle } from 'drizzle-orm/…';
const db = drizzle(client, { schema });
const result = 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
关系。
用户与用户之间 one-to-one
关系的示例,其中一个用户可以邀请另一个用户(此示例使用自引用):
import { pgTable, serial, text, integer, boolean } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name'),
invitedBy: integer('invited_by'),
});
export const usersRelations = relations(users, ({ one }) => ({
invitee: one(users, {
fields: [users.invitedBy],
references: [users.id],
}),
}));
另一个示例是将用户的个人资料信息存储在单独的表中。在这种情况下,由于外键存储在 “profile_info” 表中,因此用户关系既没有字段也没有引用。这会告诉 Typescript user.profileInfo
可空:
import { pgTable, serial, text, integer, jsonb } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name'),
});
export const usersRelations = relations(users, ({ one }) => ({
profileInfo: one(profileInfo),
}));
export const profileInfo = pgTable('profile_info', {
id: serial('id').primaryKey(),
userId: integer('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
运算符定义表之间的 one-to-many
关系。
用户和他们所写帖子之间 one-to-many
关系示例:
import { pgTable, serial, text, integer } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name'),
});
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
content: text('content'),
authorId: integer('author_id'),
});
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
现在让我们在帖子中添加评论:
...
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
content: text('content'),
authorId: integer('author_id'),
});
export const postsRelations = relations(posts, ({ one, many }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
comments: many(comments)
}));
export const comments = pgTable('comments', {
id: serial('id').primaryKey(),
text: text('text'),
authorId: integer('author_id'),
postId: integer('post_id'),
});
export const commentsRelations = relations(comments, ({ one }) => ({
post: one(posts, {
fields: [comments.postId],
references: [posts.id],
}),
}));
多对多
Drizzle ORM 为你提供了一个 API,用于通过所谓的 junction
或 join
表定义表之间的 many-to-many
关系,这些表必须明确定义并存储相关表之间的关联。
用户和组之间 many-to-many
关系示例:
import { relations } from 'drizzle-orm';
import { integer, pgTable, primaryKey, serial, text } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name'),
});
export const usersRelations = relations(users, ({ many }) => ({
usersToGroups: many(usersToGroups),
}));
export const groups = pgTable('groups', {
id: serial('id').primaryKey(),
name: text('name'),
});
export const groupsRelations = relations(groups, ({ many }) => ({
usersToGroups: many(usersToGroups),
}));
export const usersToGroups = pgTable(
'users_to_groups',
{
userId: integer('user_id')
.notNull()
.references(() => users.id),
groupId: integer('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
是更高级别的抽象,它们仅用于在应用级别定义表之间的关系。它们不会以任何方式影响数据库模式,也不会隐式创建外键。
这意味着 relations
和外键可以一起使用,但它们彼此不依赖。你可以在不使用外键的情况下定义 relations
(反之亦然),这使得它们可以与不支持外键的数据库一起使用。
以下两个示例在使用 Drizzle 关系查询数据方面完全相同。
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name'),
});
export const usersRelations = relations(users, ({ one, many }) => ({
profileInfo: one(users, {
fields: [profileInfo.userId],
references: [users.id],
}),
}));
export const profileInfo = pgTable('profile_info', {
id: serial('id').primaryKey(),
userId: integer("user_id"),
metadata: jsonb("metadata"),
});
外键操作
更多信息请查看 postgres 外键文档
你可以指定在父表中引用的数据被修改时应执行的操作。这些操作称为 “外键操作。“。PostgreSQL 为这些操作提供了多种选项。
删除/更新操作
-
CASCADE
:当父表中的一行被删除时,子表中所有对应的行也将被删除。这确保了子表中不存在孤立行。 -
NO ACTION
:这是默认操作。如果子表中存在相关行,它可以防止删除父表中的行。父表中的 DELETE 操作将失败。 -
RESTRICT
:与 NO ACTION 类似,如果子表中存在依赖行,它会阻止删除父行。它本质上与 NO ACTION 相同,出于兼容性原因而包含。 -
SET DEFAULT
:如果父表中的一行被删除,子表中的外键列将被设置为其默认值(如果有)。如果没有默认值,则 DELETE 操作将失败。 -
SET NULL
:当父表中的一行被删除时,子表中的外键列将被设置为 NULL。此操作假定子表中的外键列允许 NULL 值。
与 ON DELETE 类似,还有 ON UPDATE,当引用列发生更改(更新)时会调用该函数。可能的操作相同,但 SET NULL 和 SET DEFAULT 不能指定列列表。在这种情况下,CASCADE 表示引用列的更新值应复制到引用行中。在 Drizzle 中,你可以使用
references()
的第二个参数添加外键操作。
操作类型
export type UpdateDeleteAction = 'cascade' | 'restrict' | 'no action' | 'set null' | 'set default';
// second argument of references interface
actions?: {
onUpdate?: UpdateDeleteAction;
onDelete?: UpdateDeleteAction;
} | undefined
在下面的示例中,将 onDelete: 'cascade'
添加到 posts
模式的 author 字段意味着删除 user
也会删除所有相关的 Post 记录。
import { pgTable, serial, text, integer } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name'),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
name: text('name'),
author: integer('author').references(() => users.id, {onDelete: 'cascade'}).notNull(),
});
对于使用 foreignKey
运算符指定的约束,外键操作使用以下语法定义:
import { foreignKey, pgTable, serial, text, integer } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name'),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
name: text('name'),
author: integer('author').notNull(),
}, (table) => [
foreignKey({
name: "author_fk",
columns: [table.author],
foreignColumns: [users.id],
})
.onDelete('cascade')
.onUpdate('cascade')
]);
消除歧义关系
Drizzle 还提供了 relationName
选项,以便在你在相同的两个表之间定义多个关系时消除关系歧义。例如,如果你定义了一个包含 author
和 reviewer
关系的 posts
表。
import { pgTable, serial, text, integer } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name'),
});
export const usersRelations = relations(users, ({ many }) => ({
author: many(posts, { relationName: 'author' }),
reviewer: many(posts, { relationName: 'reviewer' }),
}));
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
content: text('content'),
authorId: integer('author_id'),
reviewerId: integer('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',
}),
}));