关于 database-schema
为 Claude 提供数据库开发检查流程,要求先核对模式、列与类型,再编写查询和代码。适合希望减少数据库字段错误的开发者。
主要功能
- 01编码前读取并验证数据库模式
- 02核对列、类型、关系与可空性
- 03使用 ORM 生成的类型
- 04提供迁移与类型生成流程
适用场景
- 01实现涉及数据库的功能
- 02检查查询中的字段和类型
- 03变更模式后生成迁移与类型
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ alinaqi/ maggy --skill database-schema安装后在 Claude Code 中输入/database-schema确认已加载。
详细介绍
数据库架构感知技能
问题: Claude 会在会话中途忘记架构细节——列名错误、字段缺失、类型不正确。TDD 能在运行时发现这些问题,但我们可以更早地预防。
核心规则:编写数据库代码前先阅读架构
强制要求:编写任何涉及数据库的代码之前:
┌─────────────────────────────────────────────────────────────┐
│ 1. READ the schema file (see locations below) │
│ 2. VERIFY columns/types you're about to use exist │
│ 3. REFERENCE schema in your response when writing queries │
│ 4. TYPE-CHECK using generated types (Drizzle/Prisma/etc) │
└─────────────────────────────────────────────────────────────┘
如果架构文件不存在 → 先创建,再继续。
架构文件位置(按技术栈)
| 技术栈 | 架构位置 | 类型生成 |
|---|---|---|
| Drizzle | src/db/schema.ts 或 drizzle/schema.ts | 内置 TypeScript |
| Prisma | prisma/schema.prisma | npx prisma generate |
| Supabase | supabase/migrations/*.sql + 类型 | supabase gen types typescript |
| SQLAlchemy | app/models/*.py 或 src/models.py | Pydantic 模型 |
| TypeORM | src/entities/*.ts | 装饰器 = 类型 |
| Raw SQL | schema.sql 或 migrations/ | 需要手动定义类型 |
架构参考文件(推荐)
创建 _project_specs/schema-reference.md,以便快速查阅:
# Database Schema Reference
*Auto-generated or manually maintained. Claude: READ THIS before database work.*
## Tables
### users
| Column | Type | Nullable | Default | Notes |
|--------|------|----------|---------|-------|
| id | uuid | NO | gen_random_uuid() | PK |
| email | text | NO | - | Unique |
| name | text | YES | - | Display name |
| created_at | timestamptz | NO | now() | - |
| updated_at | timestamptz | NO | now() | - |
### orders
| Column | Type | Nullable | Default | Notes |
|--------|------|----------|---------|-------|
| id | uuid | NO | gen_random_uuid() | PK |
| user_id | uuid | NO | - | FK → users.id |
| status | text | NO | 'pending' | enum: pending/paid/shipped/delivered |
| total_cents | integer | NO | - | Amount in cents |
| created_at | timestamptz | NO | now() | - |
## Relationships
- users 1:N orders (user_id)
## Enums
- order_status: pending, paid, shipped, delivered
编写代码前检查清单(数据库工作)
编写任何数据库代码之前,Claude 必须:
### Schema Verification Checklist
- [ ] Read schema file: `[path to schema]`
- [ ] Columns I'm using exist: [list columns]
- [ ] Types match my code: [list type mappings]
- [ ] Relationships are correct: [list FKs]
- [ ] Nullable fields handled: [list nullable columns]
实际示例:
### Schema Verification for TODO-042 (Add order history endpoint)
- [x] Read schema: `src/db/schema.ts`
- [x] Columns exist: orders.id, orders.user_id, orders.status, orders.total_cents, orders.created_at
- [x] Types: id=uuid→string, total_cents=integer→number, status=text→OrderStatus enum
- [x] Relationships: orders.user_id → users.id (many-to-one)
- [x] Nullable: none of these columns are nullable
类型生成命令
Drizzle(TypeScript)
// Schema defines types automatically
// src/db/schema.ts
import { pgTable, uuid, text, integer, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
email: text('email').notNull().unique(),
name: text('name'),
createdAt: timestamp('created_at').notNull().defaultNow(),
});
export const orders = pgTable('orders', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id').notNull().references(() => users.id),
status: text('status').notNull().default('pending'),
totalCents: integer('total_cents').notNull(),
createdAt: timestamp('created_at').notNull().defaultNow(),
});
// Inferred types - USE THESE
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Order = typeof orders.$inferSelect;
export type NewOrder = typeof orders.$inferInsert;
Prisma
// prisma/schema.prisma
model User {
id String @id @default(uuid())
email String @unique
name String?
orders Order[]
createdAt DateTime @default(now()) @map("created_at")
@@map("users")
}
model Order {
id String @id @default(uuid())
userId String @map("user_id")
user User @relation(fields: [userId], references: [id])
status String @default("pending")
totalCents Int @map("total_cents")
createdAt DateTime @default(now()) @map("created_at")
@@map("orders")
}
# Generate types after schema changes
npx prisma generate
Supabase
# Generate TypeScript types from live database
supabase gen types typescript --local > src/types/database.ts
# Or from remote
supabase gen types typescript --project-id your-project-id > src/types/database.ts
// Use generated types
import { Database } from '@/types/database';
type User = Database['public']['Tables']['users']['Row'];
type NewUser = Database['public']['Tables']['users']['Insert'];
type Order = Database['public']['Tables']['orders']['Row'];
SQLAlchemy(Python)
# app/models/user.py
from sqlalchemy import Column, String, DateTime
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.sql import func
from app.db import Base
import uuid
class User(Base):
__tablename__ = "users"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
email = Column(String, nullable=False, unique=True)
name = Column(String, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Relationships
orders = relationship("Order", back_populates="user")
# app/schemas/user.py - Pydantic for API validation
from pydantic import BaseModel, EmailStr
from uuid import UUID
from datetime import datetime
class UserBase(BaseModel):
email: EmailStr
name: str | None = None
class UserCreate(UserBase):
pass
class User(UserBase):
id: UUID
created_at: datetime
class Config:
from_attributes = True
感知架构的 TDD 工作流
将标准 TDD 工作流扩展到数据库工作:
┌─────────────────────────────────────────────────────────────┐
│ 0. SCHEMA: Read and verify schema before anything else │
│ └─ Read schema file │
│ └─ Complete Schema Verification Checklist │
│ └─ Note any missing columns/tables needed │
├─────────────────────────────────────────────────────────────┤
│ 1. RED: Write tests that use correct column names │
│ └─ Import generated types │
│ └─ Use type-safe queries in tests │
│ └─ Tests should fail on logic, NOT schema errors │
├─────────────────────────────────────────────────────────────┤
│ 2. GREEN: Implement with type-safe queries │
│ └─ Use ORM types, not raw strings │
│ └─ TypeScript/mypy catches column mismatches │
├─────────────────────────────────────────────────────────────┤
│ 3. VALIDATE: Type check catches schema drift │
│ └─ tsc --noEmit / mypy catches wrong columns │
│ └─ Tests validate runtime behavior │
└─────────────────────────────────────────────────────────────┘
常见架构错误(及预防方法)
| 错误 | 示例 | 预防方法 |
|---|---|---|
| 列名错误 | user.userName 与 user.name | 阅读架构,使用生成的类型 |
| 类型错误 | 将 totalCents 设为字符串 | 类型生成可以发现此问题 |
| 缺少可空检查 | 字段可为空时使用 user.name! | 架构会标明可空字段 |
| 外键关系错误 | order.userId 与 order.user_id | 检查架构中的列名 |
| 列缺失 | 使用不存在的 user.avatar | 编写代码前阅读架构 |
| 枚举值错误 | status: 'complete' 与 'completed' | 在架构参考中记录枚举值 |
类型安全的查询示例
Drizzle(编译时发现错误):
// ✅ Correct - uses schema-defined columns
const user = await db.select().from(users).where(eq(users.email, email));
// ❌ Wrong - TypeScript error: 'userName' doesn't exist
const user = await db.select().from(users).where(eq(users.userName, email));
Prisma(编译时发现错误):
// ✅ Correct
const user = await prisma.user.findUnique({ where: { email } });
// ❌ Wrong - TypeScript error
const user = await prisma.user.findUnique({ where: { userName: email } });
Raw SQL(无保护——避免使用):
// ❌ Dangerous - no type checking, easy to get wrong
const result = await db.query('SELECT * FROM users WHERE user_name = $1', [email]);
// Should be 'email' not 'user_name' - won't catch until runtime
迁移工作流
需要更改架构时:
┌─────────────────────────────────────────────────────────────┐
│ 1. Update schema file (Drizzle/Prisma/SQLAlchemy) │
├─────────────────────────────────────────────────────────────┤
│ 2. Generate migration │
│ └─ Drizzle: npx drizzle-kit generate │
│ └─ Prisma: npx prisma migrate dev --name add_column │
│ └─ Supabase: supabase migration new add_column │
├─────────────────────────────────────────────────────────────┤
│ 3. Regenerate types │
│ └─ Prisma: npx prisma generate │
│ └─ Supabase: supabase gen types typescript │
├─────────────────────────────────────────────────────────────┤
│ 4. Update schema-reference.md │
├─────────────────────────────────────────────────────────────┤
│ 5. Run type check - find all broken code │
│ └─ npm run typecheck │
├─────────────────────────────────────────────────────────────┤
│ 6. Fix type errors, update tests, run full validation │
└─────────────────────────────────────────────────────────────┘
会话开始流程
开始涉及数据库工作的会话时:
- 立即阅读架构文件
- 如果
_project_specs/schema-reference.md存在,则阅读该文件 - 在会话状态中记录相关的表和列
- 编写代码时明确参考架构
会话状态示例:
## Current Session - Database Context
**Schema read:** ✓ src/db/schema.ts
**Tables in scope:** users, orders, order_items
**Key columns:**
- users: id, email, name, created_at
- orders: id, user_id, status, total_cents
- order_items: id, order_id, product_id, quantity, price_cents
反模式
- ❌ 猜测列名——始终先阅读架构
- ❌ 使用原始 SQL 字符串——使用带类型生成的 ORM
- ❌ 未经验证就硬编码——使用任何列之前先检查架构
- ❌ 忽略类型错误——架构偏差会以类型错误的形式显现
- ❌ 不重新生成类型——迁移后务必重新生成
- ❌ 假定字段可为空——检查架构中的可空列
检查清单
设置
- 标准位置存在架构文件
- 已配置类型生成
- 已创建
_project_specs/schema-reference.md - 架构更改时会重新生成类型
每项任务
- 编写数据库代码前已阅读架构
- 已完成架构验证检查清单
- 使用生成的类型(而非原始字符串)
- 类型检查通过(可发现列错误)
- 测试使用正确的架构
评论
支持 Markdown