#!/usr/bin/env python2 # -*- coding: utf-8 -*- """SQLite wrapper with retry logic and WAL mode.""" import time import random import sys import sqlite3 class mysqlite(object): """SQLite connection wrapper with automatic retry on lock.""" def __init__(self, database, factory=None): self.handle = sqlite3.connect(database) if factory is not None: self.handle.text_factory = factory self.cursor = self.handle.cursor() self.dbname = database # Enable WAL mode for better concurrency self.cursor.execute('PRAGMA journal_mode=WAL') self.cursor.execute('PRAGMA synchronous=NORMAL') def _try_op(self, op, query, args=None, rmin=1.5, rmax=7.0): """Execute operation with retry on database lock.""" while True: try: if query is None: return op() elif args is None: return op(query) else: return op(query, args) except sqlite3.OperationalError as e: err_msg = str(e) if 'database is locked' in err_msg: sys.stderr.write('zzZzzZZ: db is locked (%s)\n' % self.dbname) time.sleep(random.uniform(rmin, rmax)) continue else: sys.stderr.write('%s\nquery: %s\nargs: %s\n' % ( str(sys.exc_info()), str(query), str(args))) raise def execute(self, query, args=None, rmin=1.5, rmax=7.0): """Execute a single query with retry.""" return self._try_op(self.cursor.execute, query, args, rmin, rmax) def executemany(self, query, args, rmin=1.5, rmax=7.0): """Execute query for multiple argument sets, batched.""" while args: batch = args[:500] self._try_op(self.cursor.executemany, query, batch, rmin, rmax) args = args[500:] def commit(self, rmin=1.5, rmax=7.0): """Commit transaction with retry.""" return self._try_op(self.handle.commit, None, None, rmin, rmax) def close(self): """Close database connection.""" self.handle.close()