57 lines
1.8 KiB
Python
57 lines
1.8 KiB
Python
|
|
"""
|
||
|
|
run_migrations.py — fleettickets · apply SQL migrations in order.
|
||
|
|
|
||
|
|
Applies migrations/*.sql (lexical order) against DATABASE_URL, tracking applied
|
||
|
|
files in tickets.schema_migrations. Migrations are idempotent, so re-running is
|
||
|
|
safe. Run: `python run_migrations.py`.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import glob
|
||
|
|
import os
|
||
|
|
|
||
|
|
import psycopg2
|
||
|
|
|
||
|
|
MIG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "migrations")
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> None:
|
||
|
|
dsn = os.environ.get("DATABASE_URL")
|
||
|
|
if not dsn:
|
||
|
|
raise SystemExit("DATABASE_URL is not set")
|
||
|
|
conn = psycopg2.connect(dsn)
|
||
|
|
conn.autocommit = False
|
||
|
|
try:
|
||
|
|
with conn.cursor() as cur:
|
||
|
|
cur.execute("CREATE SCHEMA IF NOT EXISTS tickets")
|
||
|
|
cur.execute(
|
||
|
|
"CREATE TABLE IF NOT EXISTS tickets.schema_migrations "
|
||
|
|
"(filename text PRIMARY KEY, applied_at timestamptz NOT NULL DEFAULT now())"
|
||
|
|
)
|
||
|
|
conn.commit()
|
||
|
|
cur.execute("SELECT filename FROM tickets.schema_migrations")
|
||
|
|
applied = {r[0] for r in cur.fetchall()}
|
||
|
|
|
||
|
|
for path in sorted(glob.glob(os.path.join(MIG_DIR, "*.sql"))):
|
||
|
|
fn = os.path.basename(path)
|
||
|
|
if fn in applied:
|
||
|
|
print(f" skip {fn}")
|
||
|
|
continue
|
||
|
|
print(f" apply {fn}")
|
||
|
|
with open(path, encoding="utf-8") as f:
|
||
|
|
cur.execute(f.read())
|
||
|
|
cur.execute(
|
||
|
|
"INSERT INTO tickets.schema_migrations (filename) VALUES (%s) "
|
||
|
|
"ON CONFLICT DO NOTHING",
|
||
|
|
(fn,),
|
||
|
|
)
|
||
|
|
conn.commit()
|
||
|
|
print("migrations up to date.")
|
||
|
|
finally:
|
||
|
|
conn.close()
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|