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μ relations μ°μ°μλ₯Ό μ¬μ©νμ¬ ν
μ΄λΈ κ°μ μΌλμΌ κ΄κ³λ₯Ό μ μνλ APIλ₯Ό μ 곡ν©λλ€.
μ¬μ©μκ° λ€λ₯Έ μ¬μ©μλ₯Ό μ΄λν μ μλ μ¬μ©μ κ° μΌλμΌ κ΄κ³μ μμμ
λλ€ (μ΄ μμλ μκΈ° μ°Έμ‘°λ₯Ό μ¬μ©ν©λλ€):
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β ν
μ΄λΈμ μ μ₯λμ΄ μκΈ° λλ¬Έμ μ¬μ©μ κ΄κ³μλ fieldsλ referencesκ° μμ΅λλ€. μ΄λ TypeScriptμκ² user.profileInfoκ° nullableμμ μλ €μ€λλ€:
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μ relations μ°μ°μλ₯Ό μ¬μ©νμ¬ ν
μ΄λΈ κ°μ μΌλλ€ κ΄κ³λ₯Ό μ μνλ APIλ₯Ό μ 곡ν©λλ€.
μ¬μ©μμ κ·Έλ€μ΄ μμ±ν κ²μλ¬Ό κ°μ μΌλλ€ κ΄κ³ μμ:
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μ μμ μ€κ°(junction) λλ μ‘°μΈ(join) ν
μ΄λΈμ ν΅ν΄ ν
μ΄λΈ κ°μ λ€λλ€ κ΄κ³λ₯Ό μ μνλ APIλ₯Ό μ 곡ν©λλ€.
μ΄λ€μ λͺ
μμ μΌλ‘ μ μλμ΄μΌ νλ©° κ΄λ ¨ ν
μ΄λΈ κ°μ μ°κ΄μ±μ μ μ₯ν©λλ€.
μ¬μ©μμ κ·Έλ£Ή κ°μ λ€λλ€ κ΄κ³ μμ:
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"),
});μΈλ ν€ μμ
μμΈν μ 보λ PostgreSQL μΈλ ν€ λ¬Έμλ₯Ό νμΈνμΈμ
λΆλͺ¨ ν μ΄λΈμ μ°Έμ‘°λ λ°μ΄ν°κ° μμ λ λ λ°μν΄μΌ νλ μμ μ μ§μ ν μ μμ΅λλ€. μ΄λ¬ν μμ μ βμΈλ ν€ μμ βμ΄λΌκ³ ν©λλ€. 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;
} | undefinedposts μ€ν€λ§μ author νλμ onDelete: 'cascade'λ₯Ό μΆκ°νλ©΄ 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',
}),
}));