""" Android SQLite 组件 — SQLiteDatabase, SQLiteOpenHelper, SQLiteStatement 等 使用 Python sqlite3 模块作为后端 方法签名完全对齐 Android SDK """ import os import sys import threading import sqlite3 as _sqlite3 from typing import Any, Dict, List, Optional, Set, Tuple, Union from .._core import ( Stub, AndroidOnlyError, android_context, resolve_android_path, to_android_path, _sdcard_root, _app_data_dir, _package_name, ) from .database import ( Cursor, AbstractCursor, ContentObserver, DataSetObserver, SQLException as BaseSQLException, ) # ═══════════════════════════════════════════════════════════════ # SQLiteException hierarchy # ═══════════════════════════════════════════════════════════════ class SQLiteException(BaseSQLException): """SQLiteException — SQLite 异常基类""" def __init__(self, message: str = ""): super().__init__(message) self._error_message = message class SQLiteAbortException(SQLiteException): pass class SQLiteAccessPermException(SQLiteException): pass class SQLiteBindOrColumnIndexOutOfRangeException(SQLiteException): pass class SQLiteBlobTooBigException(SQLiteException): pass class SQLiteCantOpenDatabaseException(SQLiteException): pass class SQLiteConstraintException(SQLiteException): pass class SQLiteDatabaseCorruptException(SQLiteException): pass class SQLiteDatabaseLockedException(SQLiteException): pass class SQLiteDatatypeMismatchException(SQLiteException): pass class SQLiteDiskIOException(SQLiteException): pass class SQLiteDoneException(SQLiteException): pass class SQLiteException_General(SQLiteException): pass class SQLiteFullException(SQLiteException): pass class SQLiteMisuseException(SQLiteException): pass class SQLiteOutOfMemoryException(SQLiteException): pass class SQLiteReadOnlyDatabaseException(SQLiteException): pass class SQLiteTableLockedException(SQLiteException): pass class SQLiteUnsupportedOperationException(SQLiteException): pass # ═══════════════════════════════════════════════════════════════ # SQLiteClosable # ═══════════════════════════════════════════════════════════════ class SQLiteClosable(Stub): """SQLiteClosable — 可关闭资源基类""" def __init__(self): super().__init__() self._closed = False self._reference_count = 1 def acquireReference(self): if self._closed: raise IllegalStateException("already closed") self._reference_count += 1 def releaseReference(self): self._reference_count -= 1 if self._reference_count <= 0: self.onAllReferencesReleased() def releaseReferenceFromContainer(self): self.releaseReference() def onAllReferencesReleased(self): self.close() def close(self): self._closed = True def isClosed(self) -> bool: return self._closed class IllegalStateException(RuntimeError): pass # ═══════════════════════════════════════════════════════════════ # SQLiteProgram # ═══════════════════════════════════════════════════════════════ class SQLiteProgram(SQLiteClosable): """SQLiteProgram — SQL 程序基类""" def __init__(self, db: "SQLiteDatabase", sql: str): super().__init__() self._db = db self._sql = sql self._bindings: Dict[int, Any] = {} self._next_bind_index = 1 def getDatabase(self) -> "SQLiteDatabase": return self._db def getSql(self) -> str: return self._sql def bindNull(self, index: int): self._bindings[index] = None def bindLong(self, index: int, value: int): self._bindings[index] = value def bindDouble(self, index: int, value: float): self._bindings[index] = value def bindString(self, index: int, value: str): self._bindings[index] = value def bindBlob(self, index: int, value: bytes): self._bindings[index] = value def clearBindings(self): self._bindings.clear() def _ordered_bind_args(self) -> list: """按绑定索引升序返回参数列表(1-based 索引排序)。""" return [self._bindings[k] for k in sorted(self._bindings.keys())] def _bind_parameters(self, cursor: _sqlite3.Cursor): for idx in sorted(self._bindings.keys()): cursor.bind(idx, self._bindings[idx]) # ═══════════════════════════════════════════════════════════════ # SQLiteStatement # ═══════════════════════════════════════════════════════════════ class SQLiteStatement(SQLiteProgram): """SQLiteStatement — 预编译语句""" def __init__(self, db: "SQLiteDatabase", sql: str): super().__init__(db, sql) def execute(self): try: conn = self._db._acquire_connection() conn.execute(self._sql, self._ordered_bind_args()) if not self._db._in_transaction: conn.commit() except _sqlite3.DatabaseError as e: raise _map_sqlite_error(str(e), self._sql) def executeUpdateDelete(self) -> int: try: conn = self._db._acquire_connection() cursor = conn.execute(self._sql, self._ordered_bind_args()) if not self._db._in_transaction: conn.commit() return cursor.rowcount except _sqlite3.DatabaseError as e: raise _map_sqlite_error(str(e), self._sql) def executeInsert(self) -> int: try: conn = self._db._acquire_connection() cursor = conn.execute(self._sql, self._ordered_bind_args()) if not self._db._in_transaction: conn.commit() return cursor.lastrowid except _sqlite3.DatabaseError as e: raise _map_sqlite_error(str(e), self._sql) def simpleQueryForLong(self) -> int: try: conn = self._db._acquire_connection() cursor = conn.execute(self._sql, self._ordered_bind_args()) row = cursor.fetchone() if not self._db._in_transaction: conn.commit() if row: return int(row[0]) if row[0] is not None else 0 raise SQLiteDoneException("no results") except _sqlite3.DatabaseError as e: raise _map_sqlite_error(str(e), self._sql) def simpleQueryForString(self) -> Optional[str]: try: conn = self._db._acquire_connection() cursor = conn.execute(self._sql, self._ordered_bind_args()) row = cursor.fetchone() if not self._db._in_transaction: conn.commit() if row: return str(row[0]) if row[0] is not None else None raise SQLiteDoneException("no results") except _sqlite3.DatabaseError as e: raise _map_sqlite_error(str(e), self._sql) def simpleQueryForBlobString(self) -> Optional[bytes]: try: conn = self._db._acquire_connection() cursor = conn.execute(self._sql, self._ordered_bind_args()) row = cursor.fetchone() if not self._db._in_transaction: conn.commit() if row: v = row[0] if v is None: return None if isinstance(v, bytes): return v if isinstance(v, str): return v.encode("utf-8") return bytes(str(v), "utf-8") raise SQLiteDoneException("no results") except _sqlite3.DatabaseError as e: raise _map_sqlite_error(str(e), self._sql) def execQuery(self) -> "SQLiteCursor": """执行查询 SQL 并返回 SQLiteCursor。""" query = SQLiteQuery(self._db, self._sql) for idx, val in self._bindings.items(): query.bind(idx, val) return SQLiteCursor(self._db, query) # ═══════════════════════════════════════════════════════════════ # SQLiteQuery # ═══════════════════════════════════════════════════════════════ class SQLiteQuery(SQLiteProgram): """SQLiteQuery — 查询对象""" def __init__(self, db: "SQLiteDatabase", sql: str): super().__init__(db, sql) def toString(self) -> str: return f"SQLiteQuery: {self._sql}" def sql(self) -> str: return self._sql def bind(self, index: int, value: Any): self._bindings[index] = value # ═══════════════════════════════════════════════════════════════ # SQLiteCursor # ═══════════════════════════════════════════════════════════════ class SQLiteCursor(AbstractCursor): """SQLiteCursor — SQLite 游标实现""" def __init__(self, db: "SQLiteDatabase", query: SQLiteQuery): super().__init__() self._db = db self._query = query self._column_names: List[str] = [] self._rows: List[List[Any]] = [] self._current_row: Optional[List[Any]] = None self._loaded = False self._closed = False def _load(self): if self._loaded or self._closed: return self._loaded = True try: conn = self._db._acquire_connection() cursor = conn.execute(self._query.getSql(), list(self._query._bindings.values())) self._column_names = [desc[0] for desc in cursor.description] if cursor.description else [] self._rows = cursor.fetchall() except _sqlite3.DatabaseError as e: raise _map_sqlite_error(str(e), self._query.getSql()) def getCount(self) -> int: self._load() return len(self._rows) def getColumnNames(self) -> List[str]: self._load() return list(self._column_names) def getColumnIndex(self, column_name: str) -> int: self._load() try: return self._column_names.index(column_name) except ValueError: return -1 def moveToPosition(self, position: int) -> bool: self._load() count = len(self._rows) if position >= count: self._pos = count self._current_row = None return False if position < -1: self._pos = -1 self._current_row = None return False self._pos = position if 0 <= position < count: self._current_row = self._rows[position] else: self._current_row = None return True def getString(self, column_index: int) -> Optional[str]: if self._current_row is None or column_index < 0 or column_index >= len(self._current_row): return None v = self._current_row[column_index] return str(v) if v is not None else None def getInt(self, column_index: int) -> int: if self._current_row is None or column_index < 0 or column_index >= len(self._current_row): return 0 v = self._current_row[column_index] if v is None: return 0 try: return int(v) except (ValueError, TypeError): return 0 def getLong(self, column_index: int) -> int: return self.getInt(column_index) def getFloat(self, column_index: int) -> float: if self._current_row is None or column_index < 0 or column_index >= len(self._current_row): return 0.0 v = self._current_row[column_index] if v is None: return 0.0 try: return float(v) except (ValueError, TypeError): return 0.0 def getDouble(self, column_index: int) -> float: return self.getFloat(column_index) def getShort(self, column_index: int) -> int: return self.getInt(column_index) def getBlob(self, column_index: int) -> Optional[bytes]: if self._current_row is None or column_index < 0 or column_index >= len(self._current_row): return None v = self._current_row[column_index] if isinstance(v, bytes): return v if v is not None: return bytes(str(v), "utf-8") return None def getType(self, column_index: int) -> int: self._load() if self._current_row is None or column_index < 0 or column_index >= len(self._current_row): return Cursor.FIELD_TYPE_NULL v = self._current_row[column_index] if v is None: return Cursor.FIELD_TYPE_NULL if isinstance(v, int): return Cursor.FIELD_TYPE_INTEGER if isinstance(v, float): return Cursor.FIELD_TYPE_FLOAT if isinstance(v, bytes): return Cursor.FIELD_TYPE_BLOB return Cursor.FIELD_TYPE_STRING def isNull(self, column_index: int) -> bool: return self.getType(column_index) == Cursor.FIELD_TYPE_NULL def close(self): self._closed = True self._rows.clear() self._current_row = None def requery(self) -> bool: self._loaded = False self._rows.clear() self._current_row = None self._pos = -1 return True # ═══════════════════════════════════════════════════════════════ # SQLiteQueryBuilder # ═══════════════════════════════════════════════════════════════ class SQLiteQueryBuilder(Stub): """SQLiteQueryBuilder — 查询构建器""" def __init__(self): super().__init__() self._tables: str = "" self._distinct: bool = False self._where: Optional[str] = None def setTables(self, tables: str): self._tables = tables def getTables(self) -> str: return self._tables def appendWhere(self, where: str): if self._where: self._where = f"({self._where}) AND ({where})" else: self._where = where def appendWhereStandalone(self, where: str): self._where = where def setDistinct(self, distinct: bool): self._distinct = distinct def buildQueryString(self, distinct: bool, tables: str, columns: Optional[List[str]], where: Optional[str], group_by: Optional[str], having: Optional[str], order_by: Optional[str], limit: Optional[str]) -> str: sql = "SELECT " if distinct: sql += "DISTINCT " if columns and len(columns) > 0: sql += ", ".join(columns) else: sql += "*" sql += f" FROM {tables}" if where: sql += f" WHERE ({where})" if group_by: sql += f" GROUP BY {group_by}" if having: sql += f" HAVING {having}" if order_by: sql += f" ORDER BY {order_by}" if limit: sql += f" LIMIT {limit}" return sql def buildQuery(self, columns: Optional[List[str]], where: Optional[str], group_by: Optional[str], having: Optional[str], order_by: Optional[str], limit: Optional[str]) -> str: return self.buildQueryString(self._distinct, self._tables, columns, where or self._where, group_by, having, order_by, limit) def query(self, db: "SQLiteDatabase", columns: Optional[List[str]], where: Optional[str], group_by: Optional[str], having: Optional[str], order_by: Optional[str], limit: Optional[str]) -> SQLiteCursor: sql = self.buildQuery(columns, where, group_by, having, order_by, limit) return db.rawQuery(sql, None) # ═══════════════════════════════════════════════════════════════ # SQLiteDatabase # ═══════════════════════════════════════════════════════════════ def _map_sqlite_error(msg: str, sql: str = "") -> SQLiteException: msg_lower = msg.lower() if "constraint" in msg_lower: return SQLiteConstraintException(msg) if "locked" in msg_lower: return SQLiteDatabaseLockedException(msg) if "corrupt" in msg_lower: return SQLiteDatabaseCorruptException(msg) if "readonly" in msg_lower or "read only" in msg_lower: return SQLiteReadOnlyDatabaseException(msg) if "disk" in msg_lower or "full" in msg_lower: return SQLiteDiskIOException(msg) if "disk" in msg_lower else SQLiteFullException(msg) if "abort" in msg_lower: return SQLiteAbortException(msg) if "permission" in msg_lower: return SQLiteAccessPermException(msg) if "out of memory" in msg_lower: return SQLiteOutOfMemoryException(msg) if "not a database" in msg_lower or "cantopen" in msg_lower: return SQLiteCantOpenDatabaseException(msg) if "misuse" in msg_lower: return SQLiteMisuseException(msg) return SQLiteException(msg) def _extract_value_pairs(values: Any) -> List[Tuple[str, Any]]: """从 ContentValues / dict / 类似对象中提取 (列名, 值) 列表。 注意: 本实现中 ContentValues.valueSet() 返回的是键集合而非 (键, 值) 对, 因此需要通过 get() 取值。 """ if values is None: return [] if isinstance(values, dict): return list(values.items()) # ContentValues 内部使用 _values dict inner = getattr(values, "_values", None) if isinstance(inner, dict): return list(inner.items()) # ContentValues.valueSet() 在本实现中返回键集合 if hasattr(values, "valueSet") and hasattr(values, "get"): try: return [(str(k), values.get(k)) for k in values.valueSet()] except Exception: pass if hasattr(values, "keySet") and hasattr(values, "get"): try: return [(str(k), values.get(k)) for k in values.keySet()] except Exception: pass if hasattr(values, "items"): try: return [(str(k), v) for k, v in values.items()] except Exception: pass return [] class SQLiteDatabase(SQLiteClosable): """SQLiteDatabase — 数据库操作类""" CONFLICT_ROLLBACK = 1 CONFLICT_ABORT = 2 CONFLICT_FAIL = 3 CONFLICT_IGNORE = 4 CONFLICT_REPLACE = 5 OPEN_READWRITE = 0 OPEN_READONLY = 1 CREATE_IF_NECESSARY = 268435456 NO_LOCALIZED_COLLATORS = 16 _db_registry: Dict[str, "SQLiteDatabase"] = {} _lock = threading.Lock() def __init__(self, path: str, flags: int = 0): super().__init__() self._path = path self._flags = flags self._conn: Optional[_sqlite3.Connection] = None self._in_transaction = False self._transaction_successful = False self._version: int = 0 self._max_size: int = 0 self._page_size: int = 4096 self._enable_wal = False self._attached_dbs: List[Dict[str, Any]] = [] @staticmethod def openDatabase(path: str, cursor_factory: Any = None, flags: int = 0, error_handler: Any = None) -> "SQLiteDatabase": db = SQLiteDatabase(path, flags) try: db._conn = _sqlite3.connect(path) db._conn.execute("PRAGMA journal_mode=WAL") conn_cursor = db._conn.execute("PRAGMA user_version") row = conn_cursor.fetchone() if row: db._version = int(row[0]) except _sqlite3.DatabaseError as e: raise _map_sqlite_error(str(e)) SQLiteDatabase._db_registry[path] = db return db @staticmethod def openOrCreateDatabase(path: str, cursor_factory: Any = None, error_handler: Any = None) -> "SQLiteDatabase": return SQLiteDatabase.openDatabase(path, cursor_factory, SQLiteDatabase.CREATE_IF_NECESSARY, error_handler) @staticmethod def create(cursor_factory: Any = None, error_handler: Any = None) -> "SQLiteDatabase": import tempfile tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False) tmp.close() return SQLiteDatabase.openOrCreateDatabase(tmp.name, cursor_factory, error_handler) @staticmethod def deleteDatabase(path: str) -> bool: try: if path in SQLiteDatabase._db_registry: db = SQLiteDatabase._db_registry[path] db.close() del SQLiteDatabase._db_registry[path] os.remove(path) wal_path = path + "-wal" if os.path.exists(wal_path): os.remove(wal_path) shm_path = path + "-shm" if os.path.exists(shm_path): os.remove(shm_path) return True except (OSError, Exception): return False @staticmethod def databaseList() -> List[str]: return list(SQLiteDatabase._db_registry.keys()) def _acquire_connection(self) -> _sqlite3.Connection: if self._conn is None: raise SQLiteCantOpenDatabaseException("Database not open") return self._conn def execSQL(self, sql: str, bind_args: Optional[List[Any]] = None): self._ensure_open() try: conn = self._acquire_connection() args = bind_args or [] conn.execute(sql, args) if not self._in_transaction: conn.commit() except _sqlite3.DatabaseError as e: raise _map_sqlite_error(str(e), sql) def rawQuery(self, sql: str, selection_args: Optional[List[Any]] = None) -> SQLiteCursor: self._ensure_open() query = SQLiteQuery(self, sql) if selection_args: for i, arg in enumerate(selection_args): query.bind(i + 1, arg) cursor = SQLiteCursor(self, query) return cursor def insert(self, table: str, null_column_hack: Optional[str], values: Any) -> int: self._ensure_open() if values is None: return -1 items = _extract_value_pairs(values) if not items: cols = ["_id"] vals: List[Any] = [None] else: cols = [str(k) for k, _ in items] vals = [v for _, v in items] col_str = ", ".join(cols) placeholders = ", ".join(["?"] * len(vals)) sql = f"INSERT INTO {table} ({col_str}) VALUES ({placeholders})" try: conn = self._acquire_connection() cursor = conn.execute(sql, vals) if not self._in_transaction: conn.commit() return cursor.lastrowid except _sqlite3.DatabaseError: return -1 def insertOrThrow(self, table: str, null_column_hack: Optional[str], values: Any) -> int: self._ensure_open() if values is None: raise SQLiteException("null values not allowed") items = _extract_value_pairs(values) if not items: cols = ["_id"] vals: List[Any] = [None] else: cols = [str(k) for k, _ in items] vals = [v for _, v in items] col_str = ", ".join(cols) placeholders = ", ".join(["?"] * len(vals)) sql = f"INSERT INTO {table} ({col_str}) VALUES ({placeholders})" try: conn = self._acquire_connection() cursor = conn.execute(sql, vals) if not self._in_transaction: conn.commit() return cursor.lastrowid except _sqlite3.DatabaseError as e: raise _map_sqlite_error(str(e), sql) def replace(self, table: str, null_column_hack: Optional[str], values: Any) -> int: self._ensure_open() if values is None: return -1 items = _extract_value_pairs(values) if not items: cols = ["_id"] vals: List[Any] = [None] else: cols = [str(k) for k, _ in items] vals = [v for _, v in items] col_str = ", ".join(cols) placeholders = ", ".join(["?"] * len(vals)) sql = f"INSERT OR REPLACE INTO {table} ({col_str}) VALUES ({placeholders})" try: conn = self._acquire_connection() cursor = conn.execute(sql, vals) if not self._in_transaction: conn.commit() return cursor.lastrowid except _sqlite3.DatabaseError: return -1 def update(self, table: str, values: Any, where_clause: Optional[str] = None, where_args: Optional[List[Any]] = None) -> int: self._ensure_open() if values is None: return 0 items = _extract_value_pairs(values) if not items: return 0 set_pairs = [f"{k} = ?" for k, _ in items] vals: List[Any] = [v for _, v in items] set_str = ", ".join(set_pairs) sql = f"UPDATE {table} SET {set_str}" if where_clause: sql += f" WHERE {where_clause}" if where_args: vals.extend(where_args) try: conn = self._acquire_connection() cursor = conn.execute(sql, vals) if not self._in_transaction: conn.commit() return cursor.rowcount except _sqlite3.DatabaseError as e: raise _map_sqlite_error(str(e), sql) def delete(self, table: str, where_clause: Optional[str] = None, where_args: Optional[List[Any]] = None) -> int: self._ensure_open() sql = f"DELETE FROM {table}" vals: List[Any] = [] if where_clause: sql += f" WHERE {where_clause}" if where_args: vals = list(where_args) try: conn = self._acquire_connection() cursor = conn.execute(sql, vals) if not self._in_transaction: conn.commit() return cursor.rowcount except _sqlite3.DatabaseError as e: raise _map_sqlite_error(str(e), sql) def query(self, table: str, columns: Optional[List[str]] = None, selection: Optional[str] = None, selection_args: Optional[List[Any]] = None, group_by: Optional[str] = None, having: Optional[str] = None, order_by: Optional[str] = None, limit: Optional[str] = None) -> SQLiteCursor: col_str = ", ".join(columns) if columns else "*" sql = f"SELECT {col_str} FROM {table}" if selection: sql += f" WHERE {selection}" if group_by: sql += f" GROUP BY {group_by}" if having: sql += f" HAVING {having}" if order_by: sql += f" ORDER BY {order_by}" if limit: sql += f" LIMIT {limit}" return self.rawQuery(sql, selection_args) def compileStatement(self, sql: str) -> SQLiteStatement: return SQLiteStatement(self, sql) def beginTransaction(self): self._ensure_open() try: conn = self._acquire_connection() conn.execute("BEGIN TRANSACTION") self._in_transaction = True self._transaction_successful = False except _sqlite3.DatabaseError as e: raise _map_sqlite_error(str(e)) def endTransaction(self): if self._in_transaction and self._conn: try: if not self._transaction_successful: self._conn.rollback() else: self._conn.commit() except _sqlite3.DatabaseError: pass self._in_transaction = False def setTransactionSuccessful(self): if not self._in_transaction: raise IllegalStateException("Not in transaction") self._transaction_successful = True def inTransaction(self) -> bool: return self._in_transaction def isOpen(self) -> bool: return self._conn is not None def getPath(self) -> str: return self._path def getVersion(self) -> int: if self._conn: try: cursor = self._conn.execute("PRAGMA user_version") row = cursor.fetchone() if row: self._version = int(row[0]) return self._version except _sqlite3.DatabaseError: pass return self._version def setVersion(self, version: int): self._version = int(version) if self._conn: try: self._conn.execute(f"PRAGMA user_version = {int(version)}") if not self._in_transaction: self._conn.commit() except _sqlite3.DatabaseError: pass def onCreate(self, db: "SQLiteDatabase"): pass def onConfigure(self, db: "SQLiteDatabase"): pass def enableWriteAheadLogging(self) -> bool: self._ensure_open() try: self._conn.execute("PRAGMA journal_mode=WAL") self._enable_wal = True return True except _sqlite3.DatabaseError: return False def disableWriteAheadLogging(self): try: self._conn.execute("PRAGMA journal_mode=DELETE") self._enable_wal = False except _sqlite3.DatabaseError: pass def isWriteAheadLoggingEnabled(self) -> bool: return self._enable_wal def getMaximumSize(self) -> int: if self._conn: try: page_size = self.getPageSize() if page_size <= 0: page_size = 4096 cursor = self._conn.execute("PRAGMA max_page_count") row = cursor.fetchone() if row: self._max_size = int(row[0]) * page_size return self._max_size except _sqlite3.DatabaseError: pass return self._max_size def getPageSize(self) -> int: if self._conn: try: cursor = self._conn.execute("PRAGMA page_size") row = cursor.fetchone() if row: self._page_size = int(row[0]) return self._page_size except _sqlite3.DatabaseError: pass return self._page_size def setMaximumSize(self, size: int) -> int: self._max_size = int(size) if self._conn: try: page_size = self.getPageSize() if page_size <= 0: page_size = 4096 max_pages = max(1, int(size) // page_size) cursor = self._conn.execute(f"PRAGMA max_page_count = {max_pages}") row = cursor.fetchone() if row: self._max_size = int(row[0]) * page_size return self._max_size except _sqlite3.DatabaseError: pass return self._max_size def setPageSize(self, size: int): self._page_size = int(size) if self._conn: try: self._conn.execute(f"PRAGMA page_size = {int(size)}") except _sqlite3.DatabaseError: pass def getPageCount(self) -> int: if self._conn: try: cursor = self._conn.execute("PRAGMA page_count") row = cursor.fetchone() if row: return row[0] except _sqlite3.DatabaseError: pass return 0 def yieldIfContendedSafely(self, sleep_time: int = 0) -> bool: return True def needUpgrade(self, new_version: int) -> bool: return new_version > self._version def getAttachedDbs(self) -> Optional[List[Dict[str, Any]]]: return self._attached_dbs def isReadOnly(self) -> bool: return bool(self._flags & self.OPEN_READONLY) def close(self): if self._conn: try: self._conn.close() except Exception: pass self._conn = None super().close() def _ensure_open(self): if self._conn is None: raise SQLiteCantOpenDatabaseException("Database not open") def toString(self) -> str: return f"SQLiteDatabase(path={self._path})" # ═══════════════════════════════════════════════════════════════ # SQLiteOpenHelper # ═══════════════════════════════════════════════════════════════ class SQLiteOpenHelper(Stub): """SQLiteOpenHelper — 数据库打开辅助类""" def __init__(self, context: Any, name: Optional[str], factory: Any, version: int, error_handler: Any = None): super().__init__() self._context = context self._name = name self._factory = factory self._version = version self._error_handler = error_handler self._readable_db: Optional[SQLiteDatabase] = None self._writable_db: Optional[SQLiteDatabase] = None self._database_path: Optional[str] = None self._is_initialized = False def _get_database_path(self) -> str: if self._database_path: return self._database_path db_dir = os.path.join(_app_data_dir, "databases") os.makedirs(db_dir, exist_ok=True) if self._name: self._database_path = os.path.join(db_dir, self._name) else: import tempfile tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False) tmp.close() self._database_path = tmp.name return self._database_path def getWritableDatabase(self) -> SQLiteDatabase: if self._writable_db and self._writable_db.isOpen(): return self._writable_db path = self._get_database_path() db = SQLiteDatabase.openOrCreateDatabase(path, self._factory, self._error_handler) if not self._is_initialized: self._is_initialized = True current_version = db.getVersion() if current_version == 0: self.onCreate(db) db.setVersion(self._version) elif current_version < self._version: self.onUpgrade(db, current_version, self._version) elif current_version > self._version: self.onDowngrade(db, current_version, self._version) self.onOpen(db) self._writable_db = db self._readable_db = db return db def getReadableDatabase(self) -> SQLiteDatabase: return self.getWritableDatabase() def onCreate(self, db: SQLiteDatabase): pass def onUpgrade(self, db: SQLiteDatabase, old_version: int, new_version: int): pass def onDowngrade(self, db: SQLiteDatabase, old_version: int, new_version: int): pass def onOpen(self, db: SQLiteDatabase): pass def close(self): if self._writable_db: self._writable_db.close() self._writable_db = None if self._readable_db: self._readable_db = None def getDatabaseName(self) -> Optional[str]: return self._name def getReadableDB(self) -> Optional[SQLiteDatabase]: return self._readable_db def getWriteableDB(self) -> Optional[SQLiteDatabase]: return self._writable_db def toString(self) -> str: return f"SQLiteOpenHelper(name={self._name}, version={self._version})" # ═══════════════════════════════════════════════════════════════ # __all__ # ═══════════════════════════════════════════════════════════════ __all__ = [ "SQLiteDatabase", "SQLiteOpenHelper", "SQLiteStatement", "SQLiteCursor", "SQLiteQuery", "SQLiteQueryBuilder", "SQLiteClosable", "SQLiteProgram", "SQLiteAbortException", "SQLiteAccessPermException", "SQLiteBindOrColumnIndexOutOfRangeException", "SQLiteBlobTooBigException", "SQLiteCantOpenDatabaseException", "SQLiteConstraintException", "SQLiteDatabaseCorruptException", "SQLiteDatabaseLockedException", "SQLiteDatatypeMismatchException", "SQLiteDiskIOException", "SQLiteDoneException", "SQLiteException", "SQLiteFullException", "SQLiteMisuseException", "SQLiteOutOfMemoryException", "SQLiteReadOnlyDatabaseException", "SQLiteTableLockedException", "SQLiteUnsupportedOperationException", ]