paper-dynasty-database/migrations/2026-03-12_add_evolution_tables.sql
Cal Corum 11a499c0ec feat: SQL migration for evolution tables and variant/image_url columns (#69)
Closes #69

- Create migrations/2026-03-12_add_evolution_tables.sql: idempotent
  PostgreSQL migration (BEGIN/COMMIT, all IF NOT EXISTS) that creates
  player_season_stats, evolution_track, evolution_card_state,
  evolution_tier_boost, and evolution_cosmetic tables; adds card.variant
  (INTEGER NULL DEFAULT NULL), battingcard.image_url (VARCHAR(500) NULL),
  and pitchingcard.image_url (VARCHAR(500) NULL).
- Add tests/test_evolution_migration.py: 16 unit tests validate SQL file
  structure (tables, columns, indexes, FK references, idempotency); 6
  integration tests annotated for PostgreSQL execution when POSTGRES_HOST
  is set.
- Add tests/__init__.py and tests/conftest.py (shared test infrastructure
  required for import isolation).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 18:08:31 -05:00

180 lines
7.0 KiB
PL/PgSQL

-- Migration: Add evolution tables and variant/image_url columns
-- Date: 2026-03-12
-- Issue: #69 (WP-04)
-- Dependencies: WP-01 (evolution models), WP-02 (PlayerSeasonStats model)
-- Purpose: Create player_season_stats, evolution_track, evolution_card_state,
-- evolution_tier_boost, evolution_cosmetic tables.
-- Add card.variant, battingcard.image_url, pitchingcard.image_url columns.
--
-- This migration is idempotent: all CREATE TABLE use IF NOT EXISTS,
-- ADD COLUMN uses IF NOT EXISTS, and all CREATE INDEX use IF NOT EXISTS.
--
-- Run on dev first, verify with:
-- SELECT table_name FROM information_schema.tables
-- WHERE table_schema = 'public'
-- AND table_name IN (
-- 'player_season_stats','evolution_track','evolution_card_state',
-- 'evolution_tier_boost','evolution_cosmetic'
-- );
--
-- Rollback: See DROP statements at bottom of file
-- ============================================
-- FORWARD MIGRATION
-- ============================================
BEGIN;
-- --------------------------------------------
-- 1. player_season_stats
-- --------------------------------------------
CREATE TABLE IF NOT EXISTS player_season_stats (
id SERIAL PRIMARY KEY,
player_id INTEGER NOT NULL REFERENCES player(player_id) ON DELETE CASCADE,
team_id INTEGER NOT NULL REFERENCES team(id) ON DELETE CASCADE,
season INTEGER NOT NULL,
-- Batting stats
games_batting INTEGER NOT NULL DEFAULT 0,
pa INTEGER NOT NULL DEFAULT 0,
ab INTEGER NOT NULL DEFAULT 0,
hits INTEGER NOT NULL DEFAULT 0,
hr INTEGER NOT NULL DEFAULT 0,
doubles INTEGER NOT NULL DEFAULT 0,
triples INTEGER NOT NULL DEFAULT 0,
bb INTEGER NOT NULL DEFAULT 0,
hbp INTEGER NOT NULL DEFAULT 0,
so INTEGER NOT NULL DEFAULT 0,
rbi INTEGER NOT NULL DEFAULT 0,
runs INTEGER NOT NULL DEFAULT 0,
sb INTEGER NOT NULL DEFAULT 0,
cs INTEGER NOT NULL DEFAULT 0,
-- Pitching stats
games_pitching INTEGER NOT NULL DEFAULT 0,
outs INTEGER NOT NULL DEFAULT 0,
k INTEGER NOT NULL DEFAULT 0, -- pitcher Ks (named k to avoid collision with batting so)
bb_allowed INTEGER NOT NULL DEFAULT 0,
hits_allowed INTEGER NOT NULL DEFAULT 0,
hr_allowed INTEGER NOT NULL DEFAULT 0,
wins INTEGER NOT NULL DEFAULT 0,
losses INTEGER NOT NULL DEFAULT 0,
saves INTEGER NOT NULL DEFAULT 0,
holds INTEGER NOT NULL DEFAULT 0,
blown_saves INTEGER NOT NULL DEFAULT 0,
-- Meta
last_game_id INTEGER REFERENCES stratgame(id) ON DELETE SET NULL,
last_updated_at TIMESTAMP NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS player_season_stats_player_team_season_uniq
ON player_season_stats (player_id, team_id, season);
CREATE INDEX IF NOT EXISTS player_season_stats_team_season_idx
ON player_season_stats (team_id, season);
CREATE INDEX IF NOT EXISTS player_season_stats_player_season_idx
ON player_season_stats (player_id, season);
-- --------------------------------------------
-- 2. evolution_track
-- --------------------------------------------
CREATE TABLE IF NOT EXISTS evolution_track (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
card_type VARCHAR(255) NOT NULL UNIQUE, -- batter / sp / rp
formula VARCHAR(255) NOT NULL,
t1_threshold INTEGER NOT NULL,
t2_threshold INTEGER NOT NULL,
t3_threshold INTEGER NOT NULL,
t4_threshold INTEGER NOT NULL
);
-- --------------------------------------------
-- 3. evolution_card_state
-- --------------------------------------------
CREATE TABLE IF NOT EXISTS evolution_card_state (
id SERIAL PRIMARY KEY,
player_id INTEGER NOT NULL REFERENCES player(player_id) ON DELETE CASCADE,
team_id INTEGER NOT NULL REFERENCES team(id) ON DELETE CASCADE,
track_id INTEGER NOT NULL REFERENCES evolution_track(id),
current_tier INTEGER NOT NULL DEFAULT 0, -- valid range: 0-4
current_value DOUBLE PRECISION NOT NULL DEFAULT 0.0,
fully_evolved BOOLEAN NOT NULL DEFAULT FALSE,
last_evaluated_at TIMESTAMP NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS evolution_card_state_player_team_uniq
ON evolution_card_state (player_id, team_id);
-- --------------------------------------------
-- 4. evolution_tier_boost (Phase 2 stub)
-- --------------------------------------------
CREATE TABLE IF NOT EXISTS evolution_tier_boost (
id SERIAL PRIMARY KEY,
card_state_id INTEGER NOT NULL REFERENCES evolution_card_state(id) ON DELETE CASCADE
);
-- --------------------------------------------
-- 5. evolution_cosmetic (Phase 2 stub)
-- --------------------------------------------
CREATE TABLE IF NOT EXISTS evolution_cosmetic (
id SERIAL PRIMARY KEY,
card_state_id INTEGER NOT NULL REFERENCES evolution_card_state(id) ON DELETE CASCADE
);
-- --------------------------------------------
-- 6. Add card.variant column
-- --------------------------------------------
ALTER TABLE card
ADD COLUMN IF NOT EXISTS variant INTEGER NULL DEFAULT NULL;
-- --------------------------------------------
-- 7. Add battingcard.image_url column
-- --------------------------------------------
ALTER TABLE battingcard
ADD COLUMN IF NOT EXISTS image_url VARCHAR(500) NULL;
-- --------------------------------------------
-- 8. Add pitchingcard.image_url column
-- --------------------------------------------
ALTER TABLE pitchingcard
ADD COLUMN IF NOT EXISTS image_url VARCHAR(500) NULL;
COMMIT;
-- ============================================
-- VERIFICATION QUERIES
-- ============================================
-- SELECT table_name FROM information_schema.tables
-- WHERE table_schema = 'public'
-- AND table_name IN (
-- 'player_season_stats','evolution_track','evolution_card_state',
-- 'evolution_tier_boost','evolution_cosmetic'
-- );
--
-- SELECT indexname, tablename FROM pg_indexes
-- WHERE tablename IN (
-- 'player_season_stats','evolution_card_state'
-- );
--
-- SELECT column_name FROM information_schema.columns
-- WHERE table_name = 'card' AND column_name = 'variant';
--
-- SELECT column_name FROM information_schema.columns
-- WHERE table_name IN ('battingcard','pitchingcard')
-- AND column_name = 'image_url';
-- ============================================
-- ROLLBACK (if needed)
-- ============================================
-- ALTER TABLE pitchingcard DROP COLUMN IF EXISTS image_url;
-- ALTER TABLE battingcard DROP COLUMN IF EXISTS image_url;
-- ALTER TABLE card DROP COLUMN IF EXISTS variant;
-- DROP TABLE IF EXISTS evolution_cosmetic CASCADE;
-- DROP TABLE IF EXISTS evolution_tier_boost CASCADE;
-- DROP TABLE IF EXISTS evolution_card_state CASCADE;
-- DROP TABLE IF EXISTS evolution_track CASCADE;
-- DROP TABLE IF EXISTS player_season_stats CASCADE;