BareRTC/scripts/sqlite2psql.py
Noah Petherbridge 52df45b2e9 GORM for the database for Postgres support
* For the Direct Message History database, use gorm.io as ORM so that Postgres
  can be used instead of SQLite for bigger chat room instances.
* In settings.toml: the new DatabaseType field defaults to 'sqlite3' but can be
  set to 'postgres' and use the credentials in the new PostgresDatabase field.
* The DirectMessage table schema is also updated to deprecate the Timestamp int
  field in favor of a proper CreatedAt datetime field. Existing SQLite instances
  will upgrade their table in the background, converting Timestamp to CreatedAt
  and blanking out the legacy Timestamp column.
* Fix some DB queries so when paginating your DMs history username list, sorting
  it by timestamp now works reliably.
* For existing SQLite instances that want to switch to Postgres, use the
  scripts/sqlite2psql.py script to transfer your database over.
2025-07-06 11:56:20 -07:00

142 lines
3.7 KiB
Python

#!/usr/bin/env python
"""
SQLite to Postgres migration script.
BareRTC originally used SQLite directly for the DirectMessageHistory database,
but has switched to use GORM so that you can use Postgres instead of SQLite.
This script will migrate an existing BareRTC database.sqlite script over to
Postgres.
Instructions:
1. Create a Python virtualenv and install dependencies
mkvirtualenv barertc
pip install -r scripts/requirements.txt
2. Run this script
python3 scripts/sqlite2psql.py --sqlite database.sqlite \
--psql 'user=barertc password=barertc dbname=barertc sslmode=disable TimeZone=America/Los_Angeles'
Note: this script does not create the Postgres table. Run BareRTC in Postgres
mode and have it create the table first, then run this script to backfill your
old DMs from SQLite3.
"""
import argparse
import datetime
import sqlite3
import psycopg2
def main(args):
print("Opening SQLite DB")
sqlite = open_sqlite3(args.sqlite)
print("Connecting to Postgres")
psql = open_postgres(args.psql)
migrate(sqlite, psql)
def migrate(sqlite, psql):
print("Migrating direct messages")
counter = 0
for row in sqlite_paginate(sqlite):
counter += 1
# Migrate legacy Timestamp field.
if row['timestamp'] > 0:
dt = datetime.datetime.fromtimestamp(row['timestamp'])
row['timestamp'] = 0
row['created_at'] = dt.isoformat()
# No created_at? (shouldn't happen), use the message_id instead.
if not row['created_at']:
print("Missing created_at! Using message_id as timestamp!")
dt = datetime.datetime.fromtimestamp(row['message_id'])
row['created_at'] = dt.isoformat()
# Upsert it into Postgres.
psql_upsert(psql, row)
if counter % 500 == 0:
print(f"Migrated {counter} messages...")
psql.commit()
print("Finished!")
psql.commit()
def sqlite_paginate(sqlite):
"""Paginate over the DMs from SQLite as a generator."""
cur = sqlite.cursor()
after_id = 0
while True:
res = cur.execute(f"""
SELECT
message_id,
channel_id,
username,
message,
timestamp
FROM direct_messages
WHERE message_id > {after_id}
ORDER BY message_id ASC
LIMIT 500
""")
page = res.fetchall()
if len(page) == 0:
return
for row in page:
after_id = row[0]
yield dict(
message_id=row[0],
channel_id=row[1],
username=row[2],
message=row[3],
timestamp=row[4],
created_at=None,
)
def psql_upsert(psql, row):
cur = psql.cursor()
cur.execute("""
INSERT INTO direct_messages (message_id, channel_id, username, message, timestamp, created_at)
VALUES (%s, %s, %s, %s, 0, %s)
ON CONFLICT (message_id) DO NOTHING
""", (
row['message_id'], row['channel_id'], row['username'], row['message'], row['created_at'],
))
psql.commit()
def open_sqlite3(connstr):
conn = sqlite3.connect(connstr)
return conn
def open_postgres(connstr):
conn = psycopg2.connect(connstr)
return conn
if __name__ == "__main__":
parser = argparse.ArgumentParser("sqlite2psql")
parser.add_argument("--sqlite",
type=str,
required=True,
help="Path to your SQLite database",
)
parser.add_argument("--psql",
type=str,
required=True,
help="Your Postgres connection string",
)
args = parser.parse_args()
main(args)