이 페이지는 drizzle 버전 1.0.0-beta.1 이상에서 사용 가능한 개념을 설명합니다.
Relational Queries 버전 2로 마이그레이션
npm i drizzle-orm@beta
npm i drizzle-kit@beta -D
아래는 목차입니다. 항목을 클릭하여 해당 섹션으로 이동할 수 있습니다:
API 변경사항
v1에서 달라진 동작 방식
가장 큰 업데이트 중 하나는 Relations 스키마 정의 부분입니다.
첫 번째 차이점은 더 이상 각 테이블에 대한 relations을 서로 다른 객체에서 개별적으로 지정한 다음
스키마와 함께 drizzle()에 모두 전달할 필요가 없다는 것입니다. Relational Queries v2에서는 이제
필요한 모든 테이블의 관계를 지정할 수 있는 전용 장소가 하나 있습니다.
콜백의 r 매개변수는 스키마의 모든 테이블과 one, many, through와 같은 함수를 포함하여
포괄적인 자동 완성 기능을 제공합니다. 본질적으로 관계를 지정하는 데 필요한 모든 것을 제공합니다.
// relations.ts
import * as schema from "./schema"
import { defineRelations } from "drizzle-orm"
export const relations = defineRelations(schema, (r) => ({
...
}));// index.ts
import { relations } from "./relations"
import { drizzle } from "drizzle-orm/..."
const db = drizzle(process.env.DATABASE_URL, { relations })무엇이 다른가요?
스키마 정의
import * as p from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
export const users = p.pgTable('users', {
id: p.integer().primaryKey(),
name: p.text(),
invitedBy: p.integer('invited_by'),
});
export const posts = p.pgTable('posts', {
id: p.integer().primaryKey(),
content: p.text(),
authorId: p.integer('author_id'),
});모든 관계를 한 곳에서 정의
import { relations } from "drizzle-orm/_relations";
import { users, posts } from './schema';
export const usersRelation = relations(users, ({ one, many }) => ({
invitee: one(users, {
fields: [users.invitedBy],
references: [users.id],
}),
posts: many(posts),
}));
export const postsRelation = relations(posts, ({ one, many }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));import { defineRelations } from "drizzle-orm";
import * as schema from "./schema";
export const relations = defineRelations(schema, (r) => ({
users: {
invitee: r.one.users({
from: r.users.invitedBy,
to: r.users.id,
}),
posts: r.many.posts(),
},
posts: {
author: r.one.users({
from: r.posts.authorId,
to: r.users.id,
}),
},
}));여전히 여러 parts로 분리할 수 있으며, 원하는 크기로 만들 수 있습니다
import { defineRelations, defineRelationsPart } from 'drizzle-orm';
import * as schema from "./schema";
export const relations = defineRelations(schema, (r) => ({
users: {
invitee: r.one.users({
from: r.users.invitedBy,
to: r.users.id,
}),
posts: r.many.posts(),
}
}));
export const part = defineRelationsPart(schema, (r) => ({
posts: {
author: r.one.users({
from: r.posts.authorId,
to: r.users.id,
}),
}
}));그런 다음 db 인스턴스에 제공할 수 있습니다
const db = drizzle(process.env.DB_URL, { relations: { ...relations, ...part } })one 없이 many 정의하기
v1에서는 관계의 many 측면만 원하는 경우, 반대편에서 one 측면을 지정해야 했으며,
이는 좋지 않은 개발자 경험을 제공했습니다.
v2에서는 추가 단계 없이 many 측면만 사용할 수 있습니다
import { relations } from "drizzle-orm/_relations";
import { users, posts } from './schema';
export const usersRelation = relations(users, ({ one, many }) => ({
posts: many(posts),
}));
export const postsRelation = relations(posts, ({ one, many }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));import { defineRelations } from "drizzle-orm";
import * as schema from "./schema";
export const relations = defineRelations(schema, (r) => ({
users: {
posts: r.many.posts({
from: r.users.id,
to: r.posts.authorId,
}),
},
}));새로운 optional 옵션
타입 레벨에서 optional: false는 posts 객체의 author 키를 필수로 만듭니다.
특정 엔티티가 항상 존재할 것이 확실할 때 사용해야 합니다.
v1에서는 지원되지 않았습니다
import { defineRelations } from "drizzle-orm";
import * as schema from "./schema";
export const relations = defineRelations(schema, (r) => ({
users: {
posts: r.many.posts({
from: r.users.id,
to: r.posts.authorId,
optional: false,
}),
},
}));drizzle()에 모드 지정 불필요
모든 MySQL dialect에 대해 동일한 전략을 사용하는 방법을 찾았으므로, 더 이상 지정할 필요가 없습니다
import * as schema from './schema'
const db = drizzle(process.env.DATABASE_URL, { mode: "planetscale", schema });
// or
const db = drizzle(process.env.DATABASE_URL, { mode: "default", schema });import { relations } from './relations'
const db = drizzle(process.env.DATABASE_URL, { relations });from과 to 개선
fields를 from으로, references를 to로 이름을 변경했으며, 둘 다 단일 값 또는 배열을 허용하도록 했습니다
...
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
......
author: r.one.users({
from: r.posts.authorId,
to: r.users.id,
}),
......
author: r.one.users({
from: [r.posts.authorId],
to: [r.users.id],
}),
...relationName -> alias
import { relations } from "drizzle-orm/_relations";
import { users, posts } from './schema';
export const postsRelation = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
relationName: "author_post",
}),
}));import { defineRelations } from "drizzle-orm";
import * as schema from "./schema";
export const relations = defineRelations(schema, (r) => ({
posts: {
author: r.one.users({
from: r.posts.authorId,
to: r.users.id,
alias: "author_post",
}),
},
}));custom types의 새로운 함수
Relational Queries v2에서 데이터 매핑 방식을 제어할 수 있도록 custom types에 몇 가지 새로운 함수가 추가되었습니다:
fromJson
데이터베이스에서 JSON으로 변환된 데이터를 원하는 형식으로 변환하는 데 사용되는 선택적 매핑 함수입니다. 예를 들어, RQB 또는 JSON functions를 통해 bigint 컬럼을 쿼리할 때, 일반 쿼리의 bigint와 달리 결과 필드가 문자열 표현으로 반환됩니다. 이를 처리하기 위해 해당 필드의 매핑을 처리하는 별도의 함수가 필요합니다:
fromJson(value: string): bigint {
return BigInt(value);
},반환되는 데이터가 다음과 같이 변경됩니다:
{
customField: "5044565289845416380";
}에서:
{
customField: 5044565289845416380n;
}forJsonSelect
JSON functions 내에서 컬럼 선택을 수정하는 데 사용되는 선택적 선택 수정자 함수입니다. 이러한 시나리오에 필요할 수 있는 추가 매핑은 fromJson 함수를 사용하여 처리할 수 있습니다. relational queries에서 사용됩니다.
예를 들어, bigint를 사용할 때 데이터 무결성을 보존하기 위해 필드를 text로 캐스팅해야 합니다
forJsonSelect(identifier: SQL, sql: SQLGenerator, arrayDimensions?: number): SQL {
return sql`${identifier}::text`
},쿼리가 다음과 같이 변경됩니다:
SELECT
row_to_json("t".*)
FROM
(
SELECT
"table"."custom_bigint" AS "bigint"
FROM
"table"
) AS "t"에서:
SELECT
row_to_json("t".*)
FROM
(
SELECT
"table"."custom_bigint"::text AS "bigint"
FROM
"table"
) AS "t"쿼리로 반환되는 객체가 다음과 같이 변경됩니다:
{
bigint: 5044565289845416000; // JSON 형식으로 직접 변환하여 부분적인 데이터 손실 발생
}에서:
{
bigint: "5044565289845416380"; // JSON 변환 전에 필드를 text로 변환하여 데이터 보존
}const customBytes = customType<{
data: Buffer;
driverData: Buffer;
jsonData: string;
}>({
dataType: () => 'bytea',
fromJson: (value) => {
return Buffer.from(value.slice(2, value.length), 'hex');
},
forJsonSelect: (identifier, sql, arrayDimensions) =>
sql`${identifier}::text${sql.raw('[]'.repeat(arrayDimensions ?? 0))}`,
});새로운 기능은 무엇인가요?
다대다 관계를 위한 through
이전에는 junction 테이블을 통해 쿼리한 다음 모든 응답에 대해 매핑해야 했습니다.
이제는 그럴 필요가 없습니다!
스키마
import * as p from "drizzle-orm/pg-core";
export const users = p.pgTable("users", {
id: p.integer().primaryKey(),
name: p.text(),
verified: p.boolean().notNull(),
});
export const groups = p.pgTable("groups", {
id: p.integer().primaryKey(),
name: p.text(),
});
export const usersToGroups = p.pgTable(
"users_to_groups",
{
userId: p
.integer("user_id")
.notNull()
.references(() => users.id),
groupId: p
.integer("group_id")
.notNull()
.references(() => groups.id),
},
(t) => [p.primaryKey({ columns: [t.userId, t.groupId] })]
);export const usersRelations = relations(users, ({ many }) => ({
usersToGroups: many(usersToGroups),
}));
export const groupsRelations = relations(groups, ({ many }) => ({
usersToGroups: many(usersToGroups),
}));
export const usersToGroupsRelations = relations(usersToGroups, ({ one }) => ({
group: one(groups, {
fields: [usersToGroups.groupId],
references: [groups.id],
}),
user: one(users, {
fields: [usersToGroups.userId],
references: [users.id],
}),
}));// Query example
const response = await db.query.users.findMany({
with: {
usersToGroups: {
columns: {},
with: {
group: true,
},
},
},
});import * as schema from './schema';
import { defineRelations } from 'drizzle-orm';
export const relations = defineRelations(schema, (r) => ({
users: {
groups: r.many.groups({
from: r.users.id.through(r.usersToGroups.userId),
to: r.groups.id.through(r.usersToGroups.groupId),
}),
},
groups: {
participants: r.many.users(),
},
}));// Query example
const response = await db.query.users.findMany({
with: {
groups: true,
},
});사전 정의된 필터
v1에서는 지원되지 않았습니다
import * as schema from './schema';
import { defineRelations } from 'drizzle-orm';
export const relations = defineRelations(schema,
(r) => ({
groups: {
verifiedUsers: r.many.users({
from: r.groups.id.through(r.usersToGroups.groupId),
to: r.users.id.through(r.usersToGroups.userId),
where: {
verified: true,
},
}),
},
})
);// 쿼리 예제: 인증된 모든 사용자가 있는 그룹 가져오기
const response = await db.query.groups.findMany({
with: {
verifiedUsers: true,
},
});where가 이제 객체입니다
const response = db._query.users.findMany({
where: (users, { eq }) => eq(users.id, 1),
});const response = db.query.users.findMany({
where: {
id: 1,
},
});전체 API 레퍼런스는 Select Filters 문서를 확인하세요.
RAW를 사용한 복잡한 필터 예제
// schema.ts
import { integer, jsonb, pgTable, text, timestamp } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: integer("id").primaryKey(),
name: text("name"),
email: text("email").notNull(),
age: integer("age"),
createdAt: timestamp("created_at").defaultNow(),
lastLogin: timestamp("last_login"),
subscriptionEnd: timestamp("subscription_end"),
lastActivity: timestamp("last_activity"),
preferences: jsonb("preferences"), // JSON column for user settings/preferences
interests: text("interests").array(), // Array column for user interests
});const response = db.query.users.findMany({
where: {
AND: [
{
OR: [
{ RAW: (table) => sql`LOWER(${table.name}) LIKE 'john%'` },
{ name: { ilike: "jane%" } },
],
},
{
OR: [
{ RAW: (table) => sql`${table.preferences}->>'theme' = 'dark'` },
{ RAW: (table) => sql`${table.preferences}->>'theme' IS NULL` },
],
},
{ RAW: (table) => sql`${table.age} BETWEEN 25 AND 35` },
],
},
});orderBy가 이제 객체입니다
const response = db._query.users.findMany({
orderBy: (users, { asc }) => [asc(users.id)],
});const response = db.query.users.findMany({
orderBy: { id: "asc" },
});관계로 필터링하기
v1에서는 지원되지 않았습니다
예제: ID가 10보다 크고 “M”으로 시작하는 콘텐츠가 있는 게시물을 하나 이상 가진 모든 사용자 가져오기
const usersWithPosts = await db.query.usersTable.findMany({
where: {
id: {
gt: 10
},
posts: {
content: {
like: 'M%'
}
}
},
});관련 객체에서 offset 사용하기
v1에서는 지원되지 않았습니다
await db.query.posts.findMany({
limit: 5,
offset: 2, // correct ✅
with: {
comments: {
offset: 3, // correct ✅
limit: 3,
},
},
});v1에서 v2로 관계 스키마 정의 마이그레이션하기
옵션 1: drizzle-kit pull 사용하기
새 버전의 drizzle-kit pull은 새로운 구문으로 relations.ts 파일을 가져오는 것을 지원합니다:
1단계
npx drizzle-kit pull
2단계
생성된 관계 코드를 drizzle/relations.ts에서 관계를 지정하는 데 사용하는 파일로 복사합니다
├ 📂 drizzle
│ ├ 📂 meta
│ ├ 📜 migration.sql
│ ├ 📜 relations.ts ────────┐
│ └ 📜 schema.ts |
├ 📂 src │
│ ├ 📂 db │
│ │ ├ 📜 relations.ts <─────┘
│ │ └ 📜 schema.ts
│ └ 📜 index.ts
└ …drizzle/relations.ts는 drizzle/schema.ts에서 모든 테이블의 import를 포함하며, 다음과 같이 보입니다:
import * as schema from './schema'모든 스키마 테이블이 있는 파일로 이 import를 변경해야 할 수 있습니다.
여러 스키마 파일이 있는 경우 다음과 같이 할 수 있습니다:
import * as schema1 from './schema1'
import * as schema2 from './schema2'
...3단계
drizzle 데이터베이스 인스턴스 생성을 변경하고 schema 대신 relations 객체를 제공합니다
import * as schema from './schema'
import { drizzle } from 'drizzle-orm/...'
const db = drizzle('<url>', { schema })// 2단계의 파일에서 import해야 합니다
import { relations } from './relations'
import { drizzle } from 'drizzle-orm/...'
const db = drizzle('<url>', { relations })MySQL dialect를 사용했다면 버전 2에서는 필요하지 않으므로 drizzle()에서 mode를 제거할 수 있습니다
수동 마이그레이션
수동으로 마이그레이션하려면 일대일, 일대다, 다대다 관계의 완전한 API 레퍼런스와 예제를 보려면 Drizzle Relations 섹션을 확인하세요.
v1에서 v2로 쿼리 마이그레이션하기
where 문 마이그레이션
예제와 완전한 API 레퍼런스를 보려면 Select Filters 문서를 확인하세요.
새로운 구문으로 AND, OR, NOT, RAW를 사용할 수 있으며,
Relations v1에서 이전에 사용 가능했던 모든 필터링 연산자를 사용할 수 있습니다.
예제
const response = db.query.users.findMany({
where: {
age: 15,
},
});select "users"."id" as "id", "users"."name" as "name"
from "users"
where ("users"."age" = $1)orderBy 문 마이그레이션
Order by는 컬럼과 정렬 방향(asc 또는 desc)을 지정하는 단일 객체로 단순화되었습니다
const response = db._query.users.findMany({
orderBy: (users, { asc }) => [asc(users.id)],
});const response = db.query.users.findMany({
orderBy: { id: "asc" },
});다대다 쿼리 마이그레이션
Relational Queries v1은 다대다 쿼리를 관리하는 매우 복잡한 방법을 가지고 있었습니다. junction 테이블을 통해 명시적으로 쿼리한 다음 해당 테이블을 매핑해야 했습니다:
const response = await db.query.users.findMany({
with: {
usersToGroups: {
columns: {},
with: {
group: true,
},
},
},
});Relational Queries v2로 업그레이드한 후 다대다 관계는 다음과 같이 보입니다:
import * as schema from './schema';
import { defineRelations } from 'drizzle-orm';
export const relations = defineRelations(schema, (r) => ({
users: {
groups: r.many.groups({
from: r.users.id.through(r.usersToGroups.userId),
to: r.groups.id.through(r.usersToGroups.groupId),
}),
},
groups: {
participants: r.many.users(),
},
}));쿼리를 마이그레이션하면 다음과 같이 됩니다:
// 쿼리 예제
const response = await db.query.users.findMany({
with: {
groups: true,
},
});부분 업그레이드 또는 업그레이드 후에도 RQB v1 유지하기
모든 이전 쿼리와 관계 정의가 여전히 사용 가능하도록 업그레이드를 만들었습니다. 이 경우 대규모 리팩토링 없이 쿼리별로 코드베이스를 마이그레이션할 수 있습니다
1단계: 관계 import 변경
Relational Queries v1을 사용하여 관계를 정의하려면 drizzle-orm에서 import해야 합니다
import { relations } from 'drizzle-orm';Relational Queries v2에서는 마이그레이션을 위한 시간을 주기 위해 drizzle-orm/_relations로 이동했습니다
import { relations } from "drizzle-orm/_relations";2단계: 쿼리를 ._query로 교체
Relational Queries v1을 사용하려면 db.query.를 작성해야 했습니다
await db.query.users.findMany();Relational Queries v2에서는 db.query를 새로운 구문에 사용할 수 있도록 db._query로 이동했으며,
여전히 db._query를 통해 이전 구문을 사용할 수 있는 옵션을 제공합니다.
db.query를 단순히 지원 중단하고 db.query2 또는 db.queryV2와 같은 것으로 교체해야 하는지에 대해
오랜 논의를 했습니다. 결국 모든 새로운 API는 db.query처럼 간단하게 유지되어야 하며,
이전 구문을 계속 사용하려면 모든 쿼리를 db._query로 교체하도록 요구하는 것이
향후 모든 사람이 db.queryV2, db.queryV3, db.queryV4 등을 사용하도록 강제하는 것보다
낫다고 결정했습니다.
// RQBv1 사용
await db._query.users.findMany();
// RQBv2 사용
await db.query.users.findMany();3단계
이 가이드를 사용하여 새로운 관계를 정의하거나 가져온 다음, 새 쿼리에서 사용하거나 기존 쿼리를 하나씩 마이그레이션하세요.
내부 변경사항
- 모든
drizzledatabase,session,migrator,transaction인스턴스가 RQB v2 쿼리를 위한 2개의 추가 제네릭 인자를 얻었습니다
예제
migrator
export async function migrate<
TSchema extends Record<string, unknown>
>(
db: NodePgDatabase<TSchema>,
config: MigrationConfig,
) {
...
}export async function migrate<
TSchema extends Record<string, unknown>,
TRelations extends AnyRelations
>(
db: NodePgDatabase<TSchema, TRelations>,
config: MigrationConfig,
) {
...
}session
export class NodePgSession<
TFullSchema extends Record<string, unknown>,
TSchema extends V1.TablesRelationalConfig,
> extends PgSession<NodePgQueryResultHKT, TFullSchema, TSchema>export class NodePgSession<
TFullSchema extends Record<string, unknown>,
TRelations extends AnyRelations,
TTablesConfig extends TablesRelationalConfig,
TSchema extends V1.TablesRelationalConfig,
> extends PgSession<NodePgQueryResultHKT, TFullSchema, TRelations, TTablesConfig, TSchema>transaction
export class NodePgTransaction<
TFullSchema extends Record<string, unknown>,
TSchema extends V1.TablesRelationalConfig,
> extends PgTransaction<NodePgQueryResultHKT, TFullSchema, TSchema>export class NodePgTransaction<
TFullSchema extends Record<string, unknown>,
TRelations extends AnyRelations,
TTablesConfig extends TablesRelationalConfig,
TSchema extends V1.TablesRelationalConfig,
> extends PgTransaction<NodePgQueryResultHKT, TFullSchema, TRelations, TTablesConfig, TSchema>driver
export class NodePgDatabase<
TSchema extends Record<string, unknown> = Record<string, never>,
> extends PgDatabase<NodePgQueryResultHKT, TSchema>export class NodePgDatabase<
TSchema extends Record<string, unknown> = Record<string, never>,
TRelations extends AnyRelations = EmptyRelations,
> extends PgDatabase<NodePgQueryResultHKT, TSchema, TRelations>DrizzleConfig제네릭에TRelations인자와relations: TRelations필드가 추가되었습니다
예제
export interface DrizzleConfig<
TSchema extends Record<string, unknown> = Record<string, never>
> {
logger?: boolean | Logger;
schema?: TSchema;
casing?: Casing;
}export interface DrizzleConfig<
TSchema extends Record<string, unknown> = Record<string, never>,
TRelations extends AnyRelations = EmptyRelations,
> {
logger?: boolean | Logger;
schema?: TSchema;
casing?: Casing;
relations?: TRelations;
}- 다음 엔티티들이
drizzle-orm및drizzle-orm/relations에서drizzle-orm/_relations로 이동되었습니다. 원본 import는 이제 Relational Queries v2에서 사용하는 새로운 타입을 포함하므로, 이전 타입을 사용하려면 import를 업데이트해야 합니다:
이동된 모든 엔티티 목록
RelationRelationsOneManyTableRelationsKeysOnlyExtractTableRelationsFromSchemaExtractObjectValuesExtractRelationsFromTableExtraConfigSchemagetOperatorsOperatorsgetOrderByOperatorsOrderByOperatorsFindTableByDBNameDBQueryConfigTableRelationalConfigTablesRelationalConfigRelationalSchemaConfigExtractTablesWithRelationsReturnTypeOrValueBuildRelationResultNonUndefinedKeysOnlyBuildQueryResultRelationConfigextractTablesRelationalConfigrelationscreateOnecreateManyNormalizedRelationnormalizeRelationcreateTableRelationsHelpersTableRelationsHelpersBuildRelationalQueryResultmapRelationalRow
- 마찬가지로
${dialect}-core/query-builders/query파일이${dialect}-core/query-builders/_query로 이동되었으며 RQB v2의 대안이 그 자리에 배치되었습니다
예제
import { RelationalQueryBuilder, PgRelationalQuery } from './query-builders/query.ts';import { _RelationalQueryBuilder, _PgRelationalQuery } from './query-builders/_query.ts';