drizzle-migrations
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDrizzle ORM Migrations
Drizzle ORM 迁移
This skill helps you manage database schema changes using Drizzle ORM with SQLite.
本技能可帮助你使用Drizzle ORM结合SQLite管理数据库Schema变更。
When to Use
适用场景
✅ USE this skill for:
- Adding new tables or modifying existing columns
- Generating and running database migrations
- Drizzle-specific query patterns and relations
- SQLite schema best practices with Drizzle
- Setting up Drizzle configuration
❌ DO NOT use for:
- Supabase/PostgreSQL → use skill
supabase-admin - Raw SQL without Drizzle → use standard SQL resources
- Prisma ORM → different syntax and patterns
- General database design theory → use database architecture resources
✅ 推荐使用本技能的场景:
- 添加新表或修改现有列
- 生成并执行数据库迁移
- Drizzle专属查询模式与关联关系
- 结合Drizzle的SQLite Schema最佳实践
- 配置Drizzle环境
❌ 请勿使用本技能的场景:
- Supabase/PostgreSQL → 请使用技能
supabase-admin - 不借助Drizzle的原生SQL → 请使用标准SQL资源
- Prisma ORM → 语法与模式不同
- 通用数据库设计理论 → 请使用数据库架构相关资源
Project Setup
项目设置
Configuration:
drizzle.config.tstypescript
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'sqlite',
dbCredentials: {
url: './data/app.db',
},
});Commands:
bash
npm run db:generate # Generate migration files
npm run db:push # Push schema directly (dev only)
npm run db:studio # Open Drizzle Studio GUI配置文件:
drizzle.config.tstypescript
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'sqlite',
dbCredentials: {
url: './data/app.db',
},
});命令:
bash
npm run db:generate # 生成迁移文件
npm run db:push # 直接推送Schema(仅开发环境使用)
npm run db:studio # 打开Drizzle Studio图形界面Schema Definition
Schema定义
Location:
src/db/schema.ts文件位置:
src/db/schema.tsTable Definition
表定义
typescript
import { sqliteTable, text, integer, real, blob } from 'drizzle-orm/sqlite-core';
import { relations } from 'drizzle-orm';
// Basic table
export const users = sqliteTable('users', {
id: text('id').primaryKey(),
email: text('email').notNull().unique(),
username: text('username').notNull(),
passwordHash: text('password_hash'),
createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
updatedAt: text('updated_at'),
});
// Table with foreign key
export const checkIns = sqliteTable('check_ins', {
id: text('id').primaryKey(),
userId: text('user_id').notNull().references(() => users.id, {
onDelete: 'cascade',
}),
mood: integer('mood').notNull(),
cravingLevel: integer('craving_level').notNull(),
sleepHours: real('sleep_hours'),
notes: text('notes'),
createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
});
// Table with composite index
export const auditLog = sqliteTable('audit_log', {
id: text('id').primaryKey(),
userId: text('user_id').notNull(),
action: text('action').notNull(),
targetType: text('target_type'),
targetId: text('target_id'),
details: text('details'), // JSON string
createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
}, (table) => ({
userActionIdx: index('idx_audit_user_action').on(table.userId, table.action),
createdAtIdx: index('idx_audit_created').on(table.createdAt),
}));typescript
import { sqliteTable, text, integer, real, blob } from 'drizzle-orm/sqlite-core';
import { relations } from 'drizzle-orm';
// 基础表
export const users = sqliteTable('users', {
id: text('id').primaryKey(),
email: text('email').notNull().unique(),
username: text('username').notNull(),
passwordHash: text('password_hash'),
createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
updatedAt: text('updated_at'),
});
// 带外键的表
export const checkIns = sqliteTable('check_ins', {
id: text('id').primaryKey(),
userId: text('user_id').notNull().references(() => users.id, {
onDelete: 'cascade',
}),
mood: integer('mood').notNull(),
cravingLevel: integer('craving_level').notNull(),
sleepHours: real('sleep_hours'),
notes: text('notes'),
createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
});
// 带复合索引的表
export const auditLog = sqliteTable('audit_log', {
id: text('id').primaryKey(),
userId: text('user_id').notNull(),
action: text('action').notNull(),
targetType: text('target_type'),
targetId: text('target_id'),
details: text('details'), // JSON字符串
createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
}, (table) => ({
userActionIdx: index('idx_audit_user_action').on(table.userId, table.action),
createdAtIdx: index('idx_audit_created').on(table.createdAt),
}));Relations
关联关系
typescript
export const usersRelations = relations(users, ({ many }) => ({
checkIns: many(checkIns),
sessions: many(sessions),
journalEntries: many(journalEntries),
}));
export const checkInsRelations = relations(checkIns, ({ one }) => ({
user: one(users, {
fields: [checkIns.userId],
references: [users.id],
}),
}));typescript
export const usersRelations = relations(users, ({ many }) => ({
checkIns: many(checkIns),
sessions: many(sessions),
journalEntries: many(journalEntries),
}));
export const checkInsRelations = relations(checkIns, ({ one }) => ({
user: one(users, {
fields: [checkIns.userId],
references: [users.id],
}),
}));Column Types
列类型
SQLite Types in Drizzle
Drizzle中的SQLite类型
typescript
import {
sqliteTable,
text, // TEXT - strings, JSON, dates
integer, // INTEGER - numbers, booleans (0/1)
real, // REAL - floating point
blob, // BLOB - binary data
} from 'drizzle-orm/sqlite-core';
const examples = sqliteTable('examples', {
// Strings
name: text('name').notNull(),
description: text('description'),
// Numbers
count: integer('count').notNull().default(0),
rating: real('rating'),
// Booleans (stored as 0/1)
isActive: integer('is_active', { mode: 'boolean' }).default(true),
// Dates (stored as ISO strings)
createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
expiresAt: text('expires_at'),
// JSON (stored as TEXT)
metadata: text('metadata', { mode: 'json' }),
// Enums (stored as TEXT)
status: text('status', { enum: ['pending', 'active', 'archived'] }),
});typescript
import {
sqliteTable,
text, // TEXT - 字符串、JSON、日期
integer, // INTEGER - 数字、布尔值(0/1)
real, // REAL - 浮点数
blob, // BLOB - 二进制数据
} from 'drizzle-orm/sqlite-core';
const examples = sqliteTable('examples', {
// 字符串
name: text('name').notNull(),
description: text('description'),
// 数字
count: integer('count').notNull().default(0),
rating: real('rating'),
// 布尔值(存储为0/1)
isActive: integer('is_active', { mode: 'boolean' }).default(true),
// 日期(存储为ISO字符串)
createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
expiresAt: text('expires_at'),
// JSON(存储为TEXT)
metadata: text('metadata', { mode: 'json' }),
// 枚举(存储为TEXT)
status: text('status', { enum: ['pending', 'active', 'archived'] }),
});Migration Strategies
迁移策略
Strategy 1: Push (Development Only)
策略1:直接推送(仅开发环境)
bash
npm run db:push- Directly applies schema changes
- Fast for development
- Never use in production
bash
npm run db:push- 直接应用Schema变更
- 开发环境下速度快
- 切勿在生产环境使用
Strategy 2: Generate & Migrate (Production)
策略2:生成并执行迁移(生产环境)
bash
undefinedbash
undefined1. Generate migration file
1. 生成迁移文件
npm run db:generate
npm run db:generate
2. Review generated SQL in /drizzle folder
2. 检查/drizzle文件夹中生成的SQL
3. Apply migration (in code or manually)
3. 执行迁移(代码中或手动执行)
undefinedundefinedApplying Migrations in Code
在代码中执行迁移
typescript
import { drizzle } from 'drizzle-orm/better-sqlite3';
import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
import Database from 'better-sqlite3';
const sqlite = new Database('./data/app.db');
const db = drizzle(sqlite);
// Run migrations
migrate(db, { migrationsFolder: './drizzle' });typescript
import { drizzle } from 'drizzle-orm/better-sqlite3';
import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
import Database from 'better-sqlite3';
const sqlite = new Database('./data/app.db');
const db = drizzle(sqlite);
// 执行迁移
migrate(db, { migrationsFolder: './drizzle' });Common Schema Changes
常见Schema变更
Adding a New Table
添加新表
typescript
// 1. Add to schema.ts
export const newFeature = sqliteTable('new_feature', {
id: text('id').primaryKey(),
userId: text('user_id').notNull().references(() => users.id),
name: text('name').notNull(),
createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
});
// 2. Add relations
export const newFeatureRelations = relations(newFeature, ({ one }) => ({
user: one(users, {
fields: [newFeature.userId],
references: [users.id],
}),
}));
// 3. Generate migration
// npm run db:generatetypescript
// 1. 添加到schema.ts
export const newFeature = sqliteTable('new_feature', {
id: text('id').primaryKey(),
userId: text('user_id').notNull().references(() => users.id),
name: text('name').notNull(),
createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
});
// 2. 添加关联关系
export const newFeatureRelations = relations(newFeature, ({ one }) => ({
user: one(users, {
fields: [newFeature.userId],
references: [users.id],
}),
}));
// 3. 生成迁移
// npm run db:generateAdding a Column
添加列
typescript
// In schema.ts, add the new column
export const users = sqliteTable('users', {
// existing columns...
newColumn: text('new_column'), // Add this
});
// Generate migration
// npm run db:generatetypescript
// 在schema.ts中添加新列
export const users = sqliteTable('users', {
// 现有列...
newColumn: text('new_column'), // 添加此行
});
// 生成迁移
// npm run db:generateAdding an Index
添加索引
typescript
export const messages = sqliteTable('messages', {
id: text('id').primaryKey(),
conversationId: text('conversation_id').notNull(),
createdAt: text('created_at').notNull(),
}, (table) => ({
// Add index
convCreatedIdx: index('idx_messages_conv_created')
.on(table.conversationId, table.createdAt),
}));typescript
export const messages = sqliteTable('messages', {
id: text('id').primaryKey(),
conversationId: text('conversation_id').notNull(),
createdAt: text('created_at').notNull(),
}, (table) => ({
// 添加索引
convCreatedIdx: index('idx_messages_conv_created')
.on(table.conversationId, table.createdAt),
}));Renaming (Requires Manual SQL)
重命名(需手动编写SQL)
SQLite doesn't support direct column renames in older versions. For complex changes:
sql
-- drizzle/XXXX_rename_column.sql
-- Manual migration for column rename
-- 1. Create new table with desired schema
CREATE TABLE users_new (
id TEXT PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
display_name TEXT NOT NULL, -- renamed from username
created_at TEXT NOT NULL
);
-- 2. Copy data
INSERT INTO users_new SELECT id, email, username, created_at FROM users;
-- 3. Drop old table
DROP TABLE users;
-- 4. Rename new table
ALTER TABLE users_new RENAME TO users;旧版本SQLite不支持直接重命名列。对于复杂变更:
sql
-- drizzle/XXXX_rename_column.sql
-- 用于列重命名的手动迁移
-- 1. 创建包含目标Schema的新表
CREATE TABLE users_new (
id TEXT PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
display_name TEXT NOT NULL, -- 从username重命名而来
created_at TEXT NOT NULL
);
-- 2. 复制数据
INSERT INTO users_new SELECT id, email, username, created_at FROM users;
-- 3. 删除旧表
DROP TABLE users;
-- 4. 重命名新表
ALTER TABLE users_new RENAME TO users;Query Patterns
查询模式
Basic Queries
基础查询
typescript
import { db } from '@/db';
import { eq, and, or, desc, asc, like, gte, lte } from 'drizzle-orm';
import { users, checkIns } from '@/db/schema';
// Select all
const allUsers = await db.select().from(users);
// Select with conditions
const activeUsers = await db
.select()
.from(users)
.where(eq(users.isActive, true));
// Select specific columns
const userEmails = await db
.select({ id: users.id, email: users.email })
.from(users);
// Complex where clause
const results = await db
.select()
.from(checkIns)
.where(
and(
eq(checkIns.userId, userId),
gte(checkIns.createdAt, startDate),
lte(checkIns.createdAt, endDate)
)
)
.orderBy(desc(checkIns.createdAt))
.limit(30);typescript
import { db } from '@/db';
import { eq, and, or, desc, asc, like, gte, lte } from 'drizzle-orm';
import { users, checkIns } from '@/db/schema';
// 查询所有数据
const allUsers = await db.select().from(users);
// 带条件查询
const activeUsers = await db
.select()
.from(users)
.where(eq(users.isActive, true));
// 查询指定列
const userEmails = await db
.select({ id: users.id, email: users.email })
.from(users);
// 复杂条件查询
const results = await db
.select()
.from(checkIns)
.where(
and(
eq(checkIns.userId, userId),
gte(checkIns.createdAt, startDate),
lte(checkIns.createdAt, endDate)
)
)
.orderBy(desc(checkIns.createdAt))
.limit(30);Insert
插入数据
typescript
// Single insert
const [newUser] = await db
.insert(users)
.values({
id: generateId(),
email: 'user@example.com',
username: 'newuser',
})
.returning();
// Bulk insert
await db.insert(checkIns).values([
{ id: '1', userId, mood: 7, cravingLevel: 2 },
{ id: '2', userId, mood: 8, cravingLevel: 1 },
]);
// Upsert (insert or update)
await db
.insert(users)
.values({ id: 'user-1', email: 'new@example.com' })
.onConflictDoUpdate({
target: users.id,
set: { email: 'new@example.com' },
});typescript
// 单条插入
const [newUser] = await db
.insert(users)
.values({
id: generateId(),
email: 'user@example.com',
username: 'newuser',
})
.returning();
// 批量插入
await db.insert(checkIns).values([
{ id: '1', userId, mood: 7, cravingLevel: 2 },
{ id: '2', userId, mood: 8, cravingLevel: 1 },
]);
// 插入或更新(Upsert)
await db
.insert(users)
.values({ id: 'user-1', email: 'new@example.com' })
.onConflictDoUpdate({
target: users.id,
set: { email: 'new@example.com' },
});Update
更新数据
typescript
await db
.update(users)
.set({ username: 'newname', updatedAt: new Date().toISOString() })
.where(eq(users.id, userId));typescript
await db
.update(users)
.set({ username: 'newname', updatedAt: new Date().toISOString() })
.where(eq(users.id, userId));Delete
删除数据
typescript
// Always use WHERE clause!
await db
.delete(checkIns)
.where(eq(checkIns.id, checkInId));
// Delete with multiple conditions
await db
.delete(sessions)
.where(
and(
eq(sessions.userId, userId),
lte(sessions.expiresAt, new Date().toISOString())
)
);typescript
// 务必使用WHERE子句!
await db
.delete(checkIns)
.where(eq(checkIns.id, checkInId));
// 多条件删除
await db
.delete(sessions)
.where(
and(
eq(sessions.userId, userId),
lte(sessions.expiresAt, new Date().toISOString())
)
);Joins
联表查询
typescript
const userWithCheckIns = await db
.select({
user: users,
checkIn: checkIns,
})
.from(users)
.leftJoin(checkIns, eq(users.id, checkIns.userId))
.where(eq(users.id, userId));typescript
const userWithCheckIns = await db
.select({
user: users,
checkIn: checkIns,
})
.from(users)
.leftJoin(checkIns, eq(users.id, checkIns.userId))
.where(eq(users.id, userId));Aggregations
聚合查询
typescript
import { count, avg, sum, max, min } from 'drizzle-orm';
const stats = await db
.select({
totalCheckIns: count(),
avgMood: avg(checkIns.mood),
maxStreak: max(checkIns.streak),
})
.from(checkIns)
.where(eq(checkIns.userId, userId));typescript
import { count, avg, sum, max, min } from 'drizzle-orm';
const stats = await db
.select({
totalCheckIns: count(),
avgMood: avg(checkIns.mood),
maxStreak: max(checkIns.streak),
})
.from(checkIns)
.where(eq(checkIns.userId, userId));Best Practices
最佳实践
- Always use transactions for related changes
typescript
await db.transaction(async (tx) => {
await tx.insert(users).values(userData);
await tx.insert(profiles).values(profileData);
});- Always include WHERE on DELETE/UPDATE
- Use indexes for frequently queried columns
- Store dates as ISO strings for SQLite
- Use to get inserted/updated rows
returning() - Generate migrations, don't push to production
- 关联变更务必使用事务
typescript
await db.transaction(async (tx) => {
await tx.insert(users).values(userData);
await tx.insert(profiles).values(profileData);
});- 删除/更新操作务必添加WHERE子句
- 为频繁查询的列添加索引
- 在SQLite中以ISO字符串格式存储日期
- 使用获取插入/更新后的行数据
returning() - 生成迁移文件,切勿直接推送到生产环境