database-schema について
データベースコードを書く前にスキーマを確認するための Claude 向けスキルです。カラムや型、リレーション、NULL 許容性、生成型を検証し、スキーマに起因するミスを減らします。
主な機能
- 01実装前のスキーマ確認
- 02カラム・型・関係・NULL許容性の検証
- 03ORM生成型の利用
- 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 を文字列として扱う | 型生成で検出できます |
| NULL 許容チェックの漏れ | NULL 許容の場合に user.name! を使用する | スキーマで NULL 許容フィールドを確認する |
| 外部キーのリレーションシップが誤っている | 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 を使う
- ❌ 確認せずにハードコードする - カラムを使用する前にスキーマを確認する
- ❌ 型エラーを無視する - スキーマの不整合は型エラーとして現れる
- ❌ 型を再生成しない - マイグレーション後は必ず再生成する
- ❌ NULL 許容だと思い込む - NULL 許容カラムかどうかをスキーマで確認する
チェックリスト
セットアップ
- 標準の場所にスキーマファイルがある
- 型生成が設定されている
-
_project_specs/schema-reference.mdが作成されている - スキーマ変更時に型が再生成される
タスクごと
- データベースコードを書く前にスキーマを読んだ
- スキーマ検証チェックリストを完了した
- 生成された型を使用している(生の文字列ではない)
- 型チェックに合格している(カラムのエラーを検出する)
- テストで正しいスキーマを使用している
コメント
Markdown 対応