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개의 엔티티를 생성합니다.
하지만 테스트를 위해 더 많이 필요한 경우, seed 옵션 객체에서 지정할 수 있습니다
await seed(db, schema, { count: 1000 });seed
모든 후속 실행에 대해 다른 값 세트를 생성하기 위해 시드가 필요한 경우, seed 옵션에서 다른 숫자를 정의할 수 있습니다. 새로운 숫자는 고유한 값 세트를 생성합니다
await seed(db, schema, { seed: 12345 });데이터베이스 초기화
drizzle-seed를 사용하면 데이터베이스를 쉽게 초기화하고 새로운 값으로 시드할 수 있습니다. 예를 들어 테스트 스위트에서 사용할 수 있습니다.
// 초기화할 스키마가 있는 파일 경로
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();데이터베이스 방언에 따라 다른 초기화 전략이 사용됩니다.
PostgreSQL의 경우, drizzle-seed 패키지는 CASCADE 옵션과 함께 TRUNCATE 문을 생성하여 reset 함수를 실행한 후 모든 테이블이 비어 있도록 보장합니다.
TRUNCATE tableName1, tableName2, ... CASCADE;Refinements
drizzle-seed가 기본적으로 사용하는 시드 생성기 함수의 동작을 변경해야 하는 경우, 자체 구현을 지정하고 시드 프로세스에 대한 자체 값 목록을 사용할 수도 있습니다.
.refine은 drizzle-seed의 모든 사용 가능한 생성기 함수 목록을 받는 콜백입니다. 필요에 따라 동작을 정의하면서 세분화하려는 테이블을 나타내는 키가 있는 객체를 반환해야 합니다.
각 테이블은 데이터베이스 시드를 단순화하기 위해 여러 속성을 지정할 수 있습니다.
columns: 필요한 생성기 함수를 지정하여 각 컬럼의 기본 동작을 세분화합니다.count: 데이터베이스에 삽입할 행 수를 지정합니다. 기본값은 10입니다.seed()옵션에서 전역 count가 정의된 경우, 여기에 정의된 count가 이 특정 테이블에 대해 재정의합니다.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: users 테이블만 20개의 엔티티로 시드하고 name 컬럼에 대한 세분화된 시드 로직 적용
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개의 엔티티로 시드하고 각 user에 대해 10개의 posts를 추가하여 posts 테이블을 시드하고 posts에서 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, schema).refine((f) => ({
users: {
count: 20,
with: {
posts: 10
}
}
}));
}
main();예제 3: users 테이블을 5개의 엔티티로 시드하고 데이터베이스를 100개의 posts로 채우되 users 엔티티와 연결하지 않습니다. 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 생성 로직을 세분화하여 5000개의 무작위 가격을 생성하되, 30% 확률로 10-100 사이의 가격을 생성하고 70% 확률로 100-300 사이의 가격을 생성합니다.
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 테이블의 세 번째 매개변수에 대한 타입 지원이 없습니다. 런타임에는 작동하지만 타입 수준에서는 올바르게 작동하지 않습니다.