Przejdź do głównej zawartości

Projektowanie schematu bazy danych z Cursorem

Projektowanie schematu bazy danych z Cursorem najlepiej działa jako pętla etapów: model danych uzgodniony w Plan Mode, schemat Drizzle wygenerowany przez agenta, testy ograniczeń wstawiające każdy zakazany wiersz, plany zapytań sprawdzone na danych testowych i migracje typu expand-and-contract. Każdy etap zostawia artefakt do sprawdzenia, więc o poprawności decydują testy i plany, a nie czytanie wygenerowanego SQL.

Ten tekst jest dla programistów, którzy projektują tabele pod nową funkcję, i dla tech leadów, którzy te projekty zatwierdzają. Zespół produktowy właśnie domknął specyfikację wielodostępnego systemu billingowego SaaS: użytkownicy, organizacje, plany, rejestrowanie zużycia, faktury i dziennik audytu. Nikt nie przesądził, czy użytkownik może należeć do kilku organizacji, czy faktury wiszą na subskrypcjach, czy na rekordach zużycia, ani jak dashboard ma się ładować poniżej 200 ms dla organizacji z 50 000 zdarzeń zużycia miesięcznie. Zła odpowiedź teraz oznacza później ALTER TABLE na gorącej tabeli produkcyjnej.

Przykłady używają PostgreSQL i Drizzle ORM. Z Prismą albo czystymi migracjami SQL pętla wygląda tak samo; zmień tylko nazwy plików w promptach. Decyzje architektoniczne, które poprzedzają model danych, opisuje planowanie architektury w Cursorze. Ten sam proces w Claude Code i Codeksie znajdziesz w tekście o pracy z bazami danych z agentami.

  • Dokument modelu danych (docs/data-model.md) z encjami, regułami biznesowymi i zapytaniami, które schemat musi obsłużyć, spisany, zanim powstanie jakakolwiek tabela
  • Cztery prompty do skopiowania: wyodrębnienie modelu, generowanie schematu, testy ograniczeń i przegląd planów zapytań
  • Zestaw testów ograniczeń, który dowodzi, że każdą regułę biznesową egzekwuje baza, a nie tylko kod aplikacji
  • Konfigurację serwera MCP dla Postgresa w trybie tylko do odczytu, dzięki której Cursor czyta prawdziwy wynik EXPLAIN, zamiast zgadywać
  • Wzorzec migracji expand-and-contract z ręcznie pisanym rollbackiem, bo Drizzle Kit nie generuje migracji w dół
  • Listę kontrolną, na podstawie której tech lead zatwierdza schemat bez czytania każdej wygenerowanej linii

Każdy etap zostawia plik, a każdy plik ma swoją bramkę. Większość plików pisze agent; decyzje i bramki należą do ciebie.

EtapTryb w CursorzeArtefaktBramka, która to potwierdza
1. ModelPlan Mode (bez kodu)docs/data-model.mdTy i product owner rozstrzygacie w nim każde otwarte pytanie
2. SchematAgentsrc/db/schema.ts, wygenerowana migracja SQLnpx drizzle-kit generate daje czysty diff; npx drizzle-kit check przechodzi; sprawdzanie typów przechodzi
3. RegułyAgenttests/db/constraints.test.tsKażda reguła biznesowa ma test, który wstawia zakazany wiersz i oczekuje odrzucenia przez bazę
4. ZapytaniaAgent + MCP Postgresa (tylko odczyt)tests/db/query-plans.test.ts albo raport planówŻadne zapytanie z dokumentu modelu nie robi Seq Scan na dużej tabeli
5. ZmianaAgentMigracje expand, backfill i contract oraz SQL rollbackuMigracje wchodzą na kopię danych produkcyjnych; rollback wykonuje się czysto

Plan Mode to tryb Cursora, który według dokumentacji „creates detailed implementation plans before writing any code”. Dzięki niemu etap 1 dotyczy decyzji, a nie plików. Różnice między trybami opisuje tekst o trybach agenta w Cursorze.

  1. Spisz model danych, zanim powstanie jakakolwiek tabela. Wklej surowe wymagania do docs/data-model.md i otwórz nowy czat w Plan Mode. Poniższy prompt prosi o encje, krotności i listę pytań, które specyfikacja zostawia otwarte, w postaci sekcji, którą sam zapisujesz w dokumencie (albo przełącz się na Agenta i poproś go o zapis pliku). Celowo zabrania SQL: pytanie „czy użytkownik może należeć do kilku organizacji?” kosztuje teraz jedną wiadomość, a później migrację.

    Odpowiedz na każde otwarte pytanie w samym dokumencie, z datą i nazwiskiem osoby, która zdecydowała. Dokument staje się specyfikacją, z którą porównuje się schemat, a recenzent czyta go w kilka minut.

  2. Wygeneruj schemat w trybie Agent. Przełącz się na Agenta, wskaż uzgodniony model i nazwij bazę, ORM oraz zasady usuwania i indeksowania. Jawnie podane konwencje usuwają zgadywanie, które agent inaczej wypełniłby własnymi domyślnymi wyborami.

    Komentarze // Rn i // Qn sprawiają, że wynik da się sprawdzić. Recenzent szuka R7 i znajduje albo ograniczenie, które tę regułę egzekwuje, albo jej wpis na liście reguł „niemożliwych do wymuszenia w bazie”.

  3. Udowodnij każdą regułę biznesową testem ograniczenia. Ograniczenie, którego żaden test nie sprawdza, to ograniczenie, o którym nikt nie wie, czy działa. Poproś agenta o jeden test na regułę: wstawia zakazany wiersz do prawdziwego Postgresa i oczekuje odrzucenia.

    drizzle-kit migrate odczytuje docelową bazę z drizzle.config.ts. Jeśli projekt jeszcze go nie ma, najpierw go utwórz, żeby polecenie użyło DATABASE_URL, a nie jakiejś innej bazy. drizzle-kit potrzebuje też sterownika Postgresa w projekcie: npm i pg (albo postgres).

    drizzle.config.ts
    import { defineConfig } from 'drizzle-kit';
    export default defineConfig({
    dialect: 'postgresql',
    schema: './src/db/schema.ts',
    out: './drizzle',
    dbCredentials: { url: process.env.DATABASE_URL! },
    });

    Potem uruchom jednorazową bazę i zastosuj migracje. Baza nie potrzebuje hasła, bo nasłuchuje tylko na twojej maszynie i znika po zatrzymaniu. Przy uwierzytelnianiu trust hasło jest lokalnie ignorowane; ustaw prawdziwe, gdy kierujesz tę konfigurację na współdzieloną bazę testową lub replikę.

    Okno terminala
    # Terminal: jednorazowy Postgres dla testów, usuwany po zatrzymaniu
    docker run --rm -d --name schema-test -p 127.0.0.1:55432:5432 \
    -e POSTGRES_HOST_AUTH_METHOD=trust postgres:17
    export DATABASE_URL=postgres://postgres@localhost:55432/postgres
    npx drizzle-kit migrate

    Uruchom npx vitest run tests/db. Czerwony test to tutaj dobry wynik: błąd schematu znaleziony przed produkcją. Popraw schemat w osobnym prompcie, wygeneruj migrację od nowa i uruchom testy ponownie. Zakaz dotykania schematu powstrzymuje agenta przed osłabieniem reguły tylko po to, żeby jego własny test przeszedł.

  4. Sprawdź plany zapytań na danych testowych. Schemat może spełniać każdą regułę, a mimo to skanować całą tabelę przy zapytaniu dashboardu. Wypełnij bazę testową danymi w realistycznej skali (liczby wierszy z listy Q), a potem pozwól Cursorowi przeczytać plany zapytań. Podłącz Postgres MCP Pro w trybie restricted, który wykonuje transakcje tylko do odczytu z limitami czasu. Dodaj to do ~/.cursor/mcp.json (globalnie, żeby dane dostępowe nie trafiły do repozytorium):

    {
    "mcpServers": {
    "postgres": {
    "command": "uvx",
    "args": ["postgres-mcp==0.3.0", "--access-mode=restricted"],
    "env": { "DATABASE_URI": "postgresql://agent_ro:CHANGE_ME@localhost:55432/postgres" }
    }
    }
    }

    Postgres MCP Pro to pakiet PyPI postgres-mcp (0.3.0, sprawdzone 2026-09-26), dlatego uruchamiasz go przez uvx; pakiet npm o tej samej nazwie to niezwiązany projekt. Ostatnie wydanie na PyPI pochodzi z 2025-05-16 (około 3,3 tys. gwiazdek na GitHubie, sprawdzone 2026-09-26), więc przypnij wersję (jak postgres-mcp==0.3.0 powyżej) i traktuj rolę tylko do odczytu jako właściwe zabezpieczenie. Starszy @modelcontextprotocol/server-postgres jest przestarzały i zarchiwizowany. Alternatywy dla Supabase, Neona i wielu silników opisuje strona o serwerach MCP dla baz danych, a położenie pliku wyjaśnia konfiguracja MCP w Cursorze.

    Zanim uruchomisz Cursora, utwórz rolę agent_ro z samym SELECT. Przy uwierzytelnianiu trust z kroku 3 jej hasło nie jest sprawdzane, więc gwarancję tylko do odczytu daje rola, na którą loguje się serwer MCP, a nie hasło:

    Okno terminala
    psql "$DATABASE_URL" -c "CREATE ROLE agent_ro LOGIN PASSWORD 'CHANGE_ME'; GRANT USAGE ON SCHEMA public TO agent_ro; GRANT SELECT ON ALL TABLES IN SCHEMA public TO agent_ro; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO agent_ro;"

    Tryb restricted odrzuca EXPLAIN ANALYZE (postgres-mcp 0.3.0, sprawdzone 2026-09-26), więc agent czyta plany szacunkowe: koszt i przewidywaną liczbę wierszy, a nie zmierzony czas. To wystarcza, żeby wychwycić Seq Scan. Gdy potrzebujesz rzeczywistego czasu wykonania, uruchom pomiar sam, poza MCP, na przykład psql "$DATABASE_URL" -c 'EXPLAIN (ANALYZE, BUFFERS) SELECT ...', albo zmierz go w query-plans.test.ts.

    Każdy przyjęty indeks dodaj do schema.ts z komentarzem // Qn i wygeneruj migrację od nowa. Żeby wynik się nie cofnął, poproś agenta o zamianę tabeli w tests/db/query-plans.test.ts: test uruchamia EXPLAIN (FORMAT JSON) dla każdego zapytania i kończy się błędem, jeśli plan zawiera Seq Scan na tabeli oznaczonej jako duża. Testy planów są zgrubne, ale łapią usunięty indeks, który umyka w code review.

  5. Zmieniaj schemat metodą expand, backfill i contract. Pierwszy schemat nigdy nie jest ostatni. Drizzle Kit generuje tylko migracje w przód: drizzle-kit 0.31.11 (sprawdzone 2026-09-26) nie ma polecenia migracji w dół ani rollbacku, a drop tylko usuwa plik migracji. Rollback piszesz sam, a podział ryzykownej zmiany na małe kroki sprawia, że każdy rollback jest prosty.

    Przed scaleniem zastosuj migracje na odtworzonej kopii danych produkcyjnych i uruchom na niej także pliki rollbacku. Rollback, którego nikt nie uruchomił, to zgadywanie. Zmiany nazw, zmiany typów i backfille na dużych tabelach szerzej opisuje proces migracji frameworków i baz danych.

Tech lead zatwierdza dowody, a nie każdą wygenerowaną linię. Opis pull requesta zawiera poniższe punkty, a każdy da się sprawdzić w kilka minut:

DowódGdzie jestCo znaczy „zatwierdzone”
Model danych z rozstrzygniętymi pytaniamidocs/data-model.mdKażde otwarte pytanie ma odpowiedź, datę i właściciela
Pokrycie regułkomentarze // Rn i constraints.test.tsKażdy numer R występuje w obu miejscach albo na liście reguł „niemożliwych do wymuszenia w bazie” ze wskazaniem kontroli po stronie aplikacji
Pokrycie zapytańquery-plans.test.ts albo tabela planówKażdy numer Q ma plan; brak Seq Scan na dużej tabeli
Zachowanie przy usuwaniugrep -n "onDelete" src/db/schema.tsKażde cascade ma komentarz z regułą własności
Bezpieczeństwo migracjidrizzle/ i drizzle/rollback/npx drizzle-kit check przechodzi; każda migracja ma SQL rollbacku uruchomiony na kopii danych produkcyjnych
Bramka CIlog pipeline’uSprawdzanie typów, vitest run tests/db i migracje na świeżej bazie są zielone

Konwencje z kroku 2 zapisz w regule projektu, żeby każdy kolejny prompt o schemat od nich zaczynał, a testy ograniczeń i planów uruchamiaj w CI zgodnie z tekstem o wzorcach testowania w Cursorze.

Agent denormalizuje za wcześnie. Modele często zapisują wartości wyliczane, na przykład invoices.total_amount, żeby uniknąć złączeń. W billingu zapisana suma niezgodna z pozycjami to błędna faktura. Naprawa: usuń kolumnę, licz sumę w widoku lub zapytaniu, a zapisaną kopię przywróć dopiero wtedy, gdy plan któregoś zapytania Q udowodni, że złączenie jest za wolne. Jeśli ją zostawiasz, dodaj test ograniczenia, który się wywraca, gdy suma i pozycje się rozjeżdżają.

Kaskada usuwa więcej, niż miała. ON DELETE CASCADE na złym kluczu obcym zamienia usunięcie jednej organizacji w pustą tabelę faktur. Naprawa: wyszukaj każde cascade, wymagaj komentarza o własności, a resztę zmień na restrict. Test „usuń organizację z niezapłaconą fakturą” z kroku 3 zaświeci się na czerwono, gdy problem wróci.

Agent zmienia schemat, żeby jego własne testy przeszły. Poproszony o „zielone testy” agent potrafi usunąć ograniczenie CHECK zamiast poprawić dane. Naprawa: zmiany schematu i zmiany testów rób w osobnych promptach i odrzucaj każdy diff, który dotyka jednocześnie schema.ts i constraints.test.ts bez odpowiadającej zmiany w docs/data-model.md.

Migracja pada w połowie. Backfill przekracza limit czasu albo CREATE INDEX CONCURRENTLY zostaje odrzucone, bo Postgres nie pozwala na nie w bloku transakcji, a twój mechanizm migracji opakowuje w nią pliki. Naprawa: odtwórz kopię, wydziel problematyczną instrukcję do osobnej migracji i sprawdź, jak twój mechanizm obsługuje transakcje, zanim zaczniesz tworzyć współbieżne indeksy na tabeli produkcyjnej.

Środowisko deweloperskie i produkcja używają różnych silników. Schemat testowany na SQLite i wdrożony na Postgresie różni się typami, indeksami częściowymi i zachowaniem ograniczeń. Naprawa: uruchamiaj testy ograniczeń i planów na tym samym silniku i tej samej wersji głównej, którą wdrażasz; dlatego krok 3 zaczyna się od kontenera z Postgresem.

Serwer MCP może zapisywać. Część serwerów MCP dla baz danych domyślnie ma pełny dostęp, a wygenerowane zapytanie „porządkujące” może zmienić dane. Naprawa: używaj --access-mode=restricted (albo przełącznika tylko do odczytu od dostawcy), łącz się jako rola z samym SELECT i kieruj serwer na bazę testową lub replikę do odczytu, nigdy na bazę główną.