Datenbank-Migrationen
Konvention
Migrationen werden von Drizzle Kit aus den Schema-Definitionen generiert und liegen unter drizzle/migrations/.
Zusätzlich existiert drizzle/deploy.sql — ein idempotentes Full-Schema-Script für Erstinstallationen.
Tools
- Drizzle Kit (v0.31.9) — Schema-Diff, Migration-Generation, Migration-Ausführung
- Drizzle ORM (v0.45.1) — Typsicherer DB-Client mit
postgresDriver
Workflow
Schema ändern
- Schema-Dateien unter
src/server/db/schema/bearbeiten:
| Datei | Inhalt |
|---|---|
auth.ts | Auth.js-Tabellen (users, accounts, sessions) |
tenant.ts | Tenants, Profiles, Settings, Invitations |
production.ts | Suppliers, Lots, Machines, Profiles, Barrels, etc. |
b2b.ts | Customers, Orders, Invoices, Pricing, Forecasts, Subscriptions |
shopify.ts | Shopify-Mirror-Tabellen |
shipping.ts | DHL Shipments, Labels, Tracking |
admin.ts | Platform Admin (Impersonation, Audit) |
order-form.ts | B2B-Dealer-Portal |
enums.ts | PostgreSQL Enum-Definitionen |
-
Migration generieren:
npx drizzle-kit generate -
Generierte SQL-Datei unter
drizzle/migrations/prüfen -
Migration anwenden (lokal oder beim Build):
npx drizzle-kit migrate
Build-Hook
Migrationen werden automatisch beim Build ausgeführt:
"build": "drizzle-kit migrate && next build"
Das stellt sicher, dass die Datenbank bei jedem Deployment aktuell ist.
Manuelle SQL (RLS, Trigger, Functions)
Für komplexe Migrationen, die Drizzle Kit nicht generieren kann (RLS-Policies, Trigger, Functions), manuell SQL in die generierte Migrationsdatei einfügen oder eine separate .sql-Datei unter drizzle/migrations/ anlegen.
Checkliste für neue Tabellen
Jede neue Tenant-Tabelle MUSS alle 6 Sicherheitsschichten haben:
-- 1. Tabelle erstellen
CREATE TABLE my_table (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
-- ... weitere Spalten
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ -- Falls Soft-Delete nötig
);
-- 2. RLS aktivieren
ALTER TABLE my_table ENABLE ROW LEVEL SECURITY;
-- 3. Policies
CREATE POLICY "select" ON my_table FOR SELECT
USING (tenant_id = current_setting('app.current_tenant_id')::uuid);
CREATE POLICY "insert" ON my_table FOR INSERT
WITH CHECK (tenant_id = current_setting('app.current_tenant_id')::uuid);
CREATE POLICY "update" ON my_table FOR UPDATE
USING (tenant_id = current_setting('app.current_tenant_id')::uuid)
WITH CHECK (tenant_id = current_setting('app.current_tenant_id')::uuid);
CREATE POLICY "delete" ON my_table FOR DELETE
USING (tenant_id = current_setting('app.current_tenant_id')::uuid);
-- 4. Immutable tenant_id
CREATE TRIGGER prevent_tenant_change
BEFORE UPDATE ON my_table
FOR EACH ROW
WHEN (OLD.tenant_id IS DISTINCT FROM NEW.tenant_id)
EXECUTE FUNCTION raise_immutable_error();
-- 5. Auto-Update updated_at
CREATE TRIGGER set_updated_at
BEFORE UPDATE ON my_table
FOR EACH ROW
EXECUTE FUNCTION update_updated_at();
-- 6. Indizes
CREATE INDEX idx_my_table_tenant ON my_table(tenant_id);
CREATE INDEX idx_my_table_active ON my_table(tenant_id)
WHERE deleted_at IS NULL; -- Falls Soft-Delete
-- 7. Cross-Tenant FK Trigger (falls FKs auf andere Domain-Tabellen)
CREATE OR REPLACE FUNCTION validate_my_table_fk_tenants()
RETURNS TRIGGER AS $$
BEGIN
-- Prüfe FK 1
IF EXISTS (SELECT 1 FROM other_table WHERE id = NEW.other_id AND tenant_id != NEW.tenant_id) THEN
RAISE EXCEPTION 'Cross-tenant FK violation';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER validate_fks
BEFORE INSERT OR UPDATE OF other_id ON my_table
FOR EACH ROW
EXECUTE FUNCTION validate_my_table_fk_tenants();
Datentyp-Regeln
| Regel | Richtig | Falsch |
|---|---|---|
| Geld / Gewichte | NUMERIC(10,2) | FLOAT, REAL, DOUBLE PRECISION |
| Zeitstempel | TIMESTAMPTZ | TIMESTAMP |
| IDs | UUID | SERIAL, INTEGER |
| Status-Werte | PostgreSQL ENUM | TEXT mit CHECK |
| Boolesche Flags | BOOLEAN NOT NULL DEFAULT false | TEXT ('yes'/'no') |
Rollback
Es gibt kein automatisches Rollback. Für destruktive Migrationen:
- Immer einen Rollback-Plan in der Migration als Kommentar dokumentieren
- Destruktive Operationen (DROP, ALTER TYPE) erfordern Nutzer-Bestätigung
- Daten-Migrationen: Zuerst additive Änderung, dann Daten migrieren, dann alte Spalte entfernen (in separaten Migrationen)