1
0
Fork 0
mirror of https://github.com/EDeev/y.calendarkin.git synced 2026-10-08 00:39:27 +03:00
y.calendarkin/scripts/migrate_sqlite.py
Egor Deev abafd7a55d Хранилище — PostgreSQL вместо двух файлов SQLite
code/sql.py на psycopg 3 с пулом соединений (переподключается сам); таблицы users, urls,
alarms — в PostgreSQL user и url зарезервированы. Внутренние номера пользователей сохраняются:
на них ссылаются файлы календарей data/icals/<номер>.ics. scripts/migrate_sqlite.py переносит
старые базы со сверкой (проверено на копии рабочих: 25 пользователей, 9 подписок).
Тесты хранилища и переноса — на PostgreSQL в CI; compose поднимает бота вместе с базой.
2026-10-06 11:46:39 +00:00

60 lines
3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Перенос данных Я.Календаркина из SQLite (users.db, clock.db) в PostgreSQL.
python scripts/migrate_sqlite.py --sqlite-dir /path/to/db --dsn postgresql://…
Внутренние номера пользователей сохраняются: на них ссылаются файлы календарей data/icals/<номер>.ics.
"""
import argparse
import os
import sqlite3
import sys
import psycopg
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "code"))
from sql import SCHEMA # noqa: E402
def migrate(sqlite_dir, dsn, force=False):
users_db = sqlite3.connect(os.path.join(sqlite_dir, "users.db"))
clock_db = sqlite3.connect(os.path.join(sqlite_dir, "clock.db"))
users = users_db.execute("SELECT id, user_id FROM user ORDER BY id").fetchall()
urls = users_db.execute("SELECT user_id, status, url_ical, time_zone FROM url").fetchall()
alarms = clock_db.execute("SELECT user_id, daily, start, alarm_1, alarm_2, status_2 FROM alarm").fetchall()
known = {u[0] for u in users}
with psycopg.connect(dsn) as conn:
conn.execute(SCHEMA)
if conn.execute("SELECT count(*) FROM users").fetchone()[0]:
if not force:
raise SystemExit("В PostgreSQL уже есть данные — перенос остановлен (--force очистит таблицы)")
conn.execute("TRUNCATE users, urls, alarms RESTART IDENTITY CASCADE")
with conn.cursor() as cur:
cur.executemany("INSERT INTO users (id, tg_id) VALUES (%s, %s) ON CONFLICT DO NOTHING", users)
cur.executemany("INSERT INTO urls (user_id, status, url_ical, time_zone) VALUES (%s, %s, %s, %s) "
"ON CONFLICT DO NOTHING",
[(u, bool(s), url, tz) for u, s, url, tz in urls if u in known])
cur.executemany("INSERT INTO alarms (user_id, daily, start, alarm_1, alarm_2, status_2) "
"VALUES (%s, %s, %s, %s, %s, %s) ON CONFLICT DO NOTHING",
[(u, bool(d), bool(s), a1, a2, bool(s2)) for u, d, s, a1, a2, s2 in alarms if u in known])
cur.execute("SELECT setval(pg_get_serial_sequence('users', 'id'), GREATEST((SELECT max(id) FROM users), 1))")
report = {name: (src, conn.execute(f"SELECT count(*) FROM {name}").fetchone()[0])
for name, src in (("users", len(users)), ("urls", len(urls)), ("alarms", len(alarms)))}
for name, (src, dst) in report.items():
print(f"{name:7} SQLite {src:>5} PostgreSQL {dst:>5} {'ok' if src == dst else 'MISMATCH'}")
return all(a == b for a, b in report.values())
def main():
parser = argparse.ArgumentParser(description=__doc__.split("\n")[0])
parser.add_argument("--sqlite-dir", required=True)
parser.add_argument("--dsn", default=os.getenv("DATABASE_URL"))
parser.add_argument("--force", action="store_true")
args = parser.parse_args()
sys.exit(0 if migrate(args.sqlite_dir, args.dsn, args.force) else 1)
if __name__ == "__main__":
main()