drizzle-seed는 drizzle-orm@0.36.4 이상에서만 사용할 수 있습니다. 이보다 낮은 버전은 런타임에서 작동할 수 있지만 타입 문제 및 identity 컬럼 문제가 발생할 수 있습니다. 이 패치는 drizzle-orm@0.36.4에서 도입되었습니다.
Drizzle Seed
drizzle-seed는 결정론적이면서도 현실적인 가짜 데이터를 생성하여 데이터베이스를 채우는 데 도움이 되는 TypeScript 라이브러리입니다. 시드 가능한 의사 난수 생성기(pRNG)를 활용하여 생성하는 데이터가 여러 실행에서 일관되고 재현 가능하도록 보장합니다.
이는 테스트, 개발 및 디버깅 목적에 특히 유용합니다.
결정론적 데이터 생성이란?
결정론적 데이터 생성은 동일한 입력이 항상 동일한 출력을 생성한다는 것을 의미합니다.
drizzle-seed의 맥락에서, 동일한 시드 번호로 라이브러리를 초기화하면
매번 동일한 시퀀스의 가짜 데이터를 생성합니다. 이를 통해 예측 가능하고 반복 가능한 데이터 세트를 얻을 수 있습니다.
의사 난수 생성기(pRNG)
의사 난수 생성기는 난수의 속성에 근사한 숫자 시퀀스를 생성하는 알고리즘입니다. 하지만 시드라고 하는 초기 값을 기반으로 하기 때문에 무작위성을 제어할 수 있습니다. 동일한 시드를 사용하면 pRNG는 동일한 숫자 시퀀스를 생성하여 데이터 생성 프로세스를 재현 가능하게 만듭니다.
pRNG 사용의 이점:
- 일관성: 테스트가 매번 동일한 데이터에서 실행되도록 보장합니다.
- 디버깅: 일관된 데이터 세트를 제공하여 버그를 재현하고 수정하기 쉽게 만듭니다.
- 협업: 팀 구성원이 시드 번호를 공유하여 동일한 데이터 세트로 작업할 수 있습니다.
drizzle-seed를 사용하면 현실적인 가짜 데이터를 생성하는 능력과 필요할 때마다 이를 재현할 수 있는 제어력이라는 두 가지 장점을 모두 얻을 수 있습니다.
설치
npm i drizzle-seed
기본 사용법
이 예제에서는 무작위 이름과 ID를 가진 10명의 사용자를 생성합니다
import { pgTable, integer, text } from "drizzle-orm/pg-core";
import { drizzle } from "drizzle-orm/node-postgres";
import { seed } from "drizzle-seed";
const users = pgTable("users", {
id: integer().primaryKey(),
name: text().notNull(),
});
async function main() {
const db = drizzle(process.env.DATABASE_URL!);
await seed(db, { users });
}
main();옵션
count
기본적으로 seed 함수는 10개의 엔터티를 생성합니다.
그러나 테스트를 위해 더 많이 필요한 경우 시드 옵션 객체에서 이를 지정할 수 있습니다
await seed(db, schema, { count: 1000 });seed
모든 후속 실행에 대해 다른 값 세트를 생성하기 위해 시드가 필요한 경우 seed 옵션에서 다른 번호를 정의할 수 있습니다. 새로운 번호는 고유한 값 세트를 생성합니다
await seed(db, schema, { seed: 12345 });데이터베이스 재설정
drizzle-seed를 사용하면 테스트 스위트에서와 같이 데이터베이스를 쉽게 재설정하고 새 값으로 시드할 수 있습니다
// path to a file with schema you want to reset
import * as schema from "./schema.ts";
import { reset } from "drizzle-seed";
async function main() {
const db = drizzle(process.env.DATABASE_URL!);
await reset(db, schema);
}
main();다른 방언(dialect)은 데이터베이스 재설정에 대해 다른 전략을 사용합니다
PostgreSQL의 경우 drizzle-seed 패키지는 재설정 함수 실행 후 모든 테이블이 비어 있도록 보장하기 위해 CASCADE 옵션과 함께 TRUNCATE 문을 생성합니다
TRUNCATE tableName1, tableName2, ... CASCADE;세분화
drizzle-seed가 기본적으로 사용하는 시드 생성기 함수의 동작을 변경해야 하는 경우 자체 구현을 지정하고 시딩 프로세스에 자체 값 목록을 사용할 수도 있습니다
.refine은 drizzle-seed에서 사용 가능한 모든 생성기 함수 목록을 받는 콜백입니다. 세분화하려는 테이블을 나타내는 키가 있는 객체를 반환하여 필요에 따라 동작을 정의해야 합니다.
각 테이블은 데이터베이스 시딩을 간소화하기 위해 여러 속성을 지정할 수 있습니다:
columns: 필요한 생성기 함수를 지정하여 각 컬럼의 기본 동작을 세분화합니다.count: 데이터베이스에 삽입할 행 수를 지정합니다. 기본적으로 10입니다.seed()옵션에서 전역 카운트가 정의된 경우, 여기에 정의된 카운트가 이 특정 테이블에 대해 이를 재정의합니다.with: 연관된 엔터티를 생성하려는 경우 각 부모 테이블에 대해 생성할 참조된 엔터티의 수를 정의합니다.
생성하려는 참조된 값의 수에 대해 가중 무작위 분포를 지정할 수도 있습니다. 이 API에 대한 자세한 내용은 가중 무작위 문서 섹션을 참조하세요
API
await seed(db, schema).refine((f) => ({
users: {
columns: {},
count: 10,
with: {
posts: 10
}
},
}));몇 가지 예제와 함께 어떤 일이 일어날지 설명을 확인해 보겠습니다:
import { pgTable, integer, text } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: integer().primaryKey(),
name: text().notNull(),
});
export const posts = pgTable("posts", {
id: integer().primaryKey(),
description: text(),
userId: integer().references(() => users.id),
});예제 1: name 컬럼에 대해 세분화된 시드 로직을 사용하여 20개의 엔터티로 users 테이블만 시딩합니다
import { drizzle } from "drizzle-orm/node-postgres";
import { seed } from "drizzle-seed";
import * as schema from './schema.ts'
async function main() {
const db = drizzle(process.env.DATABASE_URL!);
await seed(db, { users: schema.users }).refine((f) => ({
users: {
columns: {
name: f.fullName(),
},
count: 20
}
}));
}
main();예제 2: users 테이블을 20개의 엔터티로 시딩하고 posts 테이블을 시딩하여 posts에서 users로의 참조를 생성함으로써 각 user에 대해 10개의 posts를 추가합니다
import { drizzle } from "drizzle-orm/node-postgres";
import { seed } from "drizzle-seed";
import * as schema from './schema.ts'
async function main() {
const db = drizzle(process.env.DATABASE_URL!);
await seed(db, schema).refine((f) => ({
users: {
count: 20,
with: {
posts: 10
}
}
}));
}
main();예제 3: users 테이블을 5개의 엔터티로 시딩하고 users 엔터티에 연결하지 않고 데이터베이스를 100개의 posts로 채웁니다. users에 대한 id 생성을 세분화하여 10000에서 20000 사이의 정수를 제공하고 고유하게 유지하며, posts를 세분화하여 자체 정의된 배열에서 값을 검색합니다
import { drizzle } from "drizzle-orm/node-postgres";
import { seed } from "drizzle-seed";
import * as schema from './schema.ts'
async function main() {
const db = drizzle(process.env.DATABASE_URL!);
await seed(db, schema).refine((f) => ({
users: {
count: 5,
columns: {
id: f.int({
minValue: 10000,
maxValue: 20000,
isUnique: true,
}),
}
},
posts: {
count: 100,
columns: {
description: f.valuesFromArray({
values: [
"The sun set behind the mountains, painting the sky in hues of orange and purple",
"I can't believe how good this homemade pizza turned out!",
"Sometimes, all you need is a good book and a quiet corner.",
"Who else thinks rainy days are perfect for binge-watching old movies?",
"Tried a new hiking trail today and found the most amazing waterfall!",
// ...
],
})
}
}
}));
}
main();가중 무작위
시드 단계에서 데이터베이스에 삽입해야 하는 우선순위가 다른 여러 데이터 세트를 사용해야 하는 경우가 있을 수 있습니다. 이러한 경우를 위해 drizzle-seed는 가중 무작위라는 API를 제공합니다
Drizzle Seed 패키지에는 가중 무작위를 사용할 수 있는 몇 가지 위치가 있습니다:
- 각 테이블 세분화 내부의 컬럼
- 생성될 관련 엔터티의 양을 결정하는
with속성
두 가지에 대한 예제를 확인해 보겠습니다:
import { pgTable, integer, text, varchar, doublePrecision } from "drizzle-orm/pg-core";
export const orders = pgTable(
"orders",
{
id: integer().primaryKey(),
name: text().notNull(),
quantityPerUnit: varchar().notNull(),
unitPrice: doublePrecision().notNull(),
unitsInStock: integer().notNull(),
unitsOnOrder: integer().notNull(),
reorderLevel: integer().notNull(),
discontinued: integer().notNull(),
}
);
export const details = pgTable(
"details",
{
unitPrice: doublePrecision().notNull(),
quantity: integer().notNull(),
discount: doublePrecision().notNull(),
orderId: integer()
.notNull()
.references(() => orders.id, { onDelete: "cascade" }),
}
);예제 1: unitPrice 생성 로직을 세분화하여 10-100 사이의 가격에 대해 30%의 확률과 100-300 사이의 가격에 대해 70%의 확률로 5000개의 무작위 가격을 생성합니다
import { drizzle } from "drizzle-orm/node-postgres";
import { seed } from "drizzle-seed";
import * as schema from './schema.ts'
async function main() {
const db = drizzle(process.env.DATABASE_URL!);
await seed(db, schema).refine((f) => ({
orders: {
count: 5000,
columns: {
unitPrice: f.weightedRandom(
[
{
weight: 0.3,
value: funcs.int({ minValue: 10, maxValue: 100 })
},
{
weight: 0.7,
value: funcs.number({ minValue: 100, maxValue: 300, precision: 100 })
}
]
),
}
}
}));
}
main();예제 2: 각 주문에 대해 60%의 확률로 13개의 상세 정보, 30%의 확률로 57개의 상세 정보, 10%의 확률로 8~10개의 상세 정보를 생성합니다
import { drizzle } from "drizzle-orm/node-postgres";
import { seed } from "drizzle-seed";
import * as schema from './schema.ts'
async function main() {
const db = drizzle(process.env.DATABASE_URL!);
await seed(db, schema).refine((f) => ({
orders: {
with: {
details:
[
{ weight: 0.6, count: [1, 2, 3] },
{ weight: 0.3, count: [5, 6, 7] },
{ weight: 0.1, count: [8, 9, 10] },
]
}
}
}));
}
main();복잡한 예제
import { seed } from "drizzle-seed";
import * as schema from "./schema.ts";
const main = async () => {
const titlesOfCourtesy = ["Ms.", "Mrs.", "Dr."];
const unitsOnOrders = [0, 10, 20, 30, 50, 60, 70, 80, 100];
const reorderLevels = [0, 5, 10, 15, 20, 25, 30];
const quantityPerUnit = [
"100 - 100 g pieces",
"100 - 250 g bags",
"10 - 200 g glasses",
"10 - 4 oz boxes",
"10 - 500 g pkgs.",
"10 - 500 g pkgs."
];
const discounts = [0.05, 0.15, 0.2, 0.25];
await seed(db, schema).refine((funcs) => ({
customers: {
count: 10000,
columns: {
companyName: funcs.companyName(),
contactName: funcs.fullName(),
contactTitle: funcs.jobTitle(),
address: funcs.streetAddress(),
city: funcs.city(),
postalCode: funcs.postcode(),
region: funcs.state(),
country: funcs.country(),
phone: funcs.phoneNumber({ template: "(###) ###-####" }),
fax: funcs.phoneNumber({ template: "(###) ###-####" })
}
},
employees: {
count: 200,
columns: {
firstName: funcs.firstName(),
lastName: funcs.lastName(),
title: funcs.jobTitle(),
titleOfCourtesy: funcs.valuesFromArray({ values: titlesOfCourtesy }),
birthDate: funcs.date({ minDate: "2010-12-31", maxDate: "2010-12-31" }),
hireDate: funcs.date({ minDate: "2010-12-31", maxDate: "2024-08-26" }),
address: funcs.streetAddress(),
city: funcs.city(),
postalCode: funcs.postcode(),
country: funcs.country(),
homePhone: funcs.phoneNumber({ template: "(###) ###-####" }),
extension: funcs.int({ minValue: 428, maxValue: 5467 }),
notes: funcs.loremIpsum()
}
},
orders: {
count: 50000,
columns: {
shipVia: funcs.int({ minValue: 1, maxValue: 3 }),
freight: funcs.number({ minValue: 0, maxValue: 1000, precision: 100 }),
shipName: funcs.streetAddress(),
shipCity: funcs.city(),
shipRegion: funcs.state(),
shipPostalCode: funcs.postcode(),
shipCountry: funcs.country()
},
with: {
details:
[
{ weight: 0.6, count: [1, 2, 3, 4] },
{ weight: 0.2, count: [5, 6, 7, 8, 9, 10] },
{ weight: 0.15, count: [11, 12, 13, 14, 15, 16, 17] },
{ weight: 0.05, count: [18, 19, 20, 21, 22, 23, 24, 25] },
]
}
},
suppliers: {
count: 1000,
columns: {
companyName: funcs.companyName(),
contactName: funcs.fullName(),
contactTitle: funcs.jobTitle(),
address: funcs.streetAddress(),
city: funcs.city(),
postalCode: funcs.postcode(),
region: funcs.state(),
country: funcs.country(),
phone: funcs.phoneNumber({ template: "(###) ###-####" })
}
},
products: {
count: 5000,
columns: {
name: funcs.companyName(),
quantityPerUnit: funcs.valuesFromArray({ values: quantityPerUnit }),
unitPrice: funcs.weightedRandom(
[
{
weight: 0.5,
value: funcs.int({ minValue: 3, maxValue: 300 })
},
{
weight: 0.5,
value: funcs.number({ minValue: 3, maxValue: 300, precision: 100 })
}
]
),
unitsInStock: funcs.int({ minValue: 0, maxValue: 125 }),
unitsOnOrder: funcs.valuesFromArray({ values: unitsOnOrders }),
reorderLevel: funcs.valuesFromArray({ values: reorderLevels }),
discontinued: funcs.int({ minValue: 0, maxValue: 1 })
}
},
details: {
columns: {
unitPrice: funcs.number({ minValue: 10, maxValue: 130 }),
quantity: funcs.int({ minValue: 1, maxValue: 130 }),
discount: funcs.weightedRandom(
[
{ weight: 0.5, value: funcs.valuesFromArray({ values: discounts }) },
{ weight: 0.5, value: funcs.default({ defaultValue: 0 }) }
]
)
}
}
}));
}
main();
제한 사항
with에 대한 타입 제한
특정 TypeScript 제한 사항과 Drizzle의 현재 API로 인해 테이블 간 참조를 제대로 추론할 수 없습니다. 특히 테이블 간 순환 종속성이 있는 경우 더욱 그렇습니다.
이는 with 옵션이 스키마의 모든 테이블을 표시하며 일대다 관계가 있는 테이블을 수동으로 선택해야 함을 의미합니다
with 옵션은 일대다 관계에서 작동합니다. 예를 들어, 하나의 user와 여러 posts가 있는 경우 users with posts를 사용할 수 있지만 posts with users는 사용할 수 없습니다
Drizzle 테이블의 세 번째 매개변수에 대한 타입 제한:
현재 Drizzle 테이블의 세 번째 매개변수에 대한 타입 지원이 없습니다. 런타임에서는 작동하지만 타입 수준에서는 제대로 작동하지 않습니다