""" Инициализация схемы БД — идемпотентно (CREATE IF NOT EXISTS). Вызывается при старте приложения (в app.py, до register_blueprint). Безопасно для нескольких gunicorn-воркеров — PostgreSQL корректно обрабатывает конкурентный DDL. Таблица runs — история запусков операций: - id, created_at - client_id, stand (изоляция по пользователю и стенду) - svc_id, svc_name, op_name, svc_op_id - instance_uid, display_name - status (OK/FAIL/TIMEOUT), duration_sec, error_log - params (JSONB), stages (JSONB) """ import os from db.pool import get_conn, put_conn SCHEMA_SQL = """ CREATE TABLE IF NOT EXISTS runs ( id SERIAL PRIMARY KEY, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), client_id VARCHAR(64) NOT NULL, stand VARCHAR(16) NOT NULL, svc_id INTEGER NOT NULL, svc_name VARCHAR(128), op_name VARCHAR(32) NOT NULL, svc_op_id INTEGER, instance_uid VARCHAR(64), display_name VARCHAR(255), status VARCHAR(16) NOT NULL, duration_sec DOUBLE PRECISION, error_log TEXT, params JSONB, stages JSONB ); CREATE INDEX IF NOT EXISTS idx_runs_client_stand ON runs (client_id, stand, created_at DESC); CREATE INDEX IF NOT EXISTS idx_runs_instance ON runs (instance_uid); CREATE INDEX IF NOT EXISTS idx_runs_created ON runs (created_at); """ def init_db(): """Применить схему — идемпотентно, безопасно для конкурентного вызова.""" if not os.getenv("DATABASE_URL"): print("[DB] DATABASE_URL not set — skipping init", flush=True) return conn = get_conn() if not conn: print("[DB] Failed to get connection — skipping init", flush=True) return try: cur = conn.cursor() cur.execute(SCHEMA_SQL) conn.commit() cur.close() print("[DB] Schema initialized", flush=True) except Exception as e: print(f"[DB] Init error: {e}", flush=True) conn.rollback() finally: put_conn(conn)