你还可以为要创建的引用值数量指定加权随机分布。有关此 API 的详细信息,请参阅 加权随机文档 文档部分
Drizzle Seed
drizzle-seed 是一个 TypeScript 库,帮助你生成确定性但又逼真的假数据,用于填充你的数据库。通过利用可设种子的伪随机数生成器(pRNG),它确保你生成的数据在不同运行之间保持一致且可复现。这对于测试、开发和调试尤其有用。
什么是确定性数据生成?
确定性数据生成意味着相同的输入总会产生相同的输出。对于 drizzle-seed 来说,当你使用相同的种子数字初始化该库时,它每次都会生成相同的假数据序列。这使得数据集具有可预测性和可重复性。
伪随机数生成器(pRNG)
伪随机数生成器是一种算法,它生成的数字序列近似随机数的特性。然而,由于它基于一个称为种子的初始值,你可以控制它的随机性。通过使用相同的种子,pRNG 将产生相同的数字序列,从而使你的数据生成过程可复现。
使用 pRNG 的好处:
- 一致性:确保你的测试每次都在相同的数据上运行。
- 调试:通过提供一致的数据集,更容易复现和修复 bug。
- 协作:团队成员可以共享种子数字,以便使用相同的数据集进行工作。
使用 drizzle-seed,你可以兼得两全其美:既能生成逼真的假数据,又能在需要时控制并复现它。
安装
npm i drizzle-seed
基本用法
在这个示例中,我们将使用随机姓名和 id 创建 10 个用户
import { singlestoreTable, int, varchar } from "drizzle-orm/singlestore-core";
import { drizzle } from "drizzle-orm/singlestore";
import { seed } from "drizzle-seed";
const users = singlestoreTable("users", {
id: int().primaryKey().autoincrement(),
name: varchar({ length: 255 }).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,你可以轻松重置数据库并用新值重新填充,例如在测试套件中
// 需要重置的 schema 文件路径
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();不同的方言会采用不同的数据库重置策略。对于 SingleStore:
对于 SingleStore,drizzle-seed 包会首先禁用 FOREIGN_KEY_CHECKS 以确保下一步不会失败,然后生成 TRUNCATE 语句来清空所有表中的内容
SET FOREIGN_KEY_CHECKS = 0;
TRUNCATE tableName1;
TRUNCATE tableName2;
...
SET FOREIGN_KEY_CHECKS = 1;精炼
如果你需要更改 drizzle-seed 默认使用的种子生成器函数的行为,可以指定你自己的实现,甚至可以在播种过程中使用你自己的一组值。
.refine 是一个回调,它接收来自 drizzle-seed 的所有可用生成器函数列表。它应该返回一个对象,其中的键代表你想要精炼的表,并根据需要定义它们的行为。
每个表都可以指定多个属性,以简化数据库播种过程:
columns:通过指定所需的生成器函数来精炼每一列的默认行为,或者通过指定false将该列从播种中排除。count:指定要插入数据库的行数。默认值为 10。如果在seed()选项中定义了全局 count,这里定义的 count 将覆盖该特定表的全局值。with:如果你想生成关联实体,则定义每个父表要创建多少个引用实体。
API
await seed(db, schema).refine((f) => ({
users: {
columns: {},
count: 10,
with: {
posts: 10
}
},
}));让我们通过一些示例来看看会发生什么,并附带说明:
import { singlestoreTable, int, varchar } from "drizzle-orm/singlestore-core";
export const users = singlestoreTable("users", {
id: int().primaryKey().autoincrement(),
name: varchar({ length: 255 }).notNull(),
});
export const posts = singlestoreTable("posts", {
id: int().primaryKey().autoincrement(),
description: varchar({ length: 255 }),
userId: int().references(() => users.id),
});示例 1:仅为 users 表生成 20 条实体,并对 name 列使用精炼后的种子逻辑
import { drizzle } from "drizzle-orm/singlestore";
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 表生成数据,且将 name 列排除在播种之外
import { drizzle } from "drizzle-orm/singlestore";
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: false, // name 列不会被播种,从而允许数据库使用其默认值。
}
}
}));
}
main();示例 3:为 users 表生成 20 条实体,并通过播种 posts 表以及创建从 posts 到 users 的引用,为每个 user 添加 10 条 posts
import { drizzle } from "drizzle-orm/singlestore";
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();示例 4:为 users 表生成 5 条实体,并向数据库填充 100 条 posts,但不将它们与 users 实体关联。为 users 精炼 id 生成逻辑,使其生成 10000 到 20000 之间的任意整数且保持唯一,并为 posts 精炼为从自定义数组中取值
import { drizzle } from "drizzle-orm/singlestore";
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: [
"太阳落在群山之后,把天空染成了橙色和紫色的层层色调",
"我简直不敢相信这道自制披萨居然这么好吃!",
"有时候,你所需要的只是一本文好书和一个安静的角落。",
"还有谁觉得雨天特别适合刷老电影马拉松吗?",
"今天尝试了一条新的徒步路线,还发现了最惊艳的瀑布!",
// ...
],
})
}
}
}));
}
main();加权随机
有些情况下,您可能需要在 seed 阶段将多个具有不同优先级的数据集插入数据库。对于这类场景,drizzle-seed 提供了一个名为 weighted random 的 API。
Drizzle Seed 包在以下几个地方可以使用加权随机:
- 每个表 refine 中的列
with属性,用于决定要创建多少相关实体
下面我们来看两个示例:
import { singlestoreTable, int, varchar, double } from "drizzle-orm/singlestore-core";
export const orders = singlestoreTable(
"orders",
{
id: int().primaryKey().autoincrement(),
name: varchar({ length: 255 }).notNull(),
quantityPerUnit: varchar().notNull(),
unitPrice: double().notNull(),
unitsInStock: int().notNull(),
unitsOnOrder: int().notNull(),
reorderLevel: int().notNull(),
discontinued: int().notNull(),
}
);
export const details = singlestoreTable(
"details",
{
unitPrice: double().notNull(),
quantity: int().notNull(),
discount: double().notNull(),
orderId: int()
.notNull()
.references(() => orders.id, { onDelete: "cascade" }),
}
);示例 1:优化 unitPrice 的生成逻辑,生成 5000 个随机价格,其中 30% 的概率生成 10-100 之间的价格,70% 的概率生成 100-300 之间的价格
import { drizzle } from "drizzle-orm/singlestore";
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:对于每个订单,生成 1 到 3 条 details 的概率为 60%,生成 5 到 7 条 details 的概率为 30%,生成 8 到 10 条 details 的概率为 10%
import { drizzle } from "drizzle-orm/singlestore";
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 = ["女士", "夫人", "博士"];
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 选项会显示 schema 中的所有表,你需要手动选择其中具有一对多关系的表
with 选项适用于一对多关系。例如,如果你有一个 user 和多个 posts,你可以在 users 上使用 with 包含 posts,但不能在 posts 上使用 with 包含 users
Drizzle 表第三个参数的类型限制:
目前,我们不支持 Drizzle 表第三个参数的类型。虽然它在运行时可以工作,但在类型层面不会正常生效