#!/usr/bin/env python3 """Snapshot a readable SQLite table to JSONL without modifying its source.""" import argparse import json import sqlite3 from pathlib import Path def export(source, table, output): source, output = Path(source).resolve(), Path(output).resolve() if source == output or output.exists(): raise ValueError('输出文件已存在或与源文件相同,请使用新的输出路径') with source.open('rb') as file: if file.read(16) != b'SQLite format 3\x00': raise ValueError('源文件不是明文 SQLite;此脚本不解密数据库') db = sqlite3.connect(source.as_uri() + '?mode=ro', uri=True, timeout=15) snapshot = sqlite3.connect(':memory:') created = False try: # Backup API includes committed WAL contents; never copy only a live .db. db.backup(snapshot) snapshot.row_factory = sqlite3.Row names = [r[0] for r in snapshot.execute("SELECT name FROM sqlite_master WHERE type='table'")] if table not in names: raise ValueError('消息表不存在,可用表:' + ', '.join(names)) quoted = '"' + table.replace('"', '""') + '"' with output.open('x', encoding='utf-8') as file: created = True try: output.chmod(0o600) except OSError: pass for row in snapshot.execute('SELECT * FROM ' + quoted): record = dict(row) if any(isinstance(v, bytes) for v in record.values()): raise ValueError('表中含二进制字段,需先解码/转换;不会把 BLOB 假装成消息正文') file.write(json.dumps(record, ensure_ascii=False) + '\n') except Exception: if created and output.exists(): output.unlink() # Only the output file exclusively created by this invocation. raise finally: db.close() snapshot.close() if __name__ == '__main__': parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('source', type=Path) parser.add_argument('table') parser.add_argument('output', type=Path) args = parser.parse_args() export(args.source, args.table, args.output) print('Readable JSONL written to:', args.output.resolve())