965 lines
32 KiB
Python
965 lines
32 KiB
Python
"""
|
|
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 _bind_parameters(self, cursor: _sqlite3.Cursor):
|
|
for idx, val in sorted(self._bindings.items()):
|
|
cursor.execute(f"PRAGMA dummy={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()
|
|
cursor = conn.execute(self._sql, list(self._bindings.values()))
|
|
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, list(self._bindings.values()))
|
|
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, list(self._bindings.values()))
|
|
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, list(self._bindings.values()))
|
|
row = cursor.fetchone()
|
|
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, list(self._bindings.values()))
|
|
row = cursor.fetchone()
|
|
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)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# 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()
|
|
if position < -1 or position >= len(self._rows):
|
|
return False
|
|
self._pos = position
|
|
if 0 <= position < len(self._rows):
|
|
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)
|
|
|
|
|
|
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 schema_version")
|
|
row = conn_cursor.fetchone()
|
|
if row:
|
|
db._version = 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
|
|
cols = []
|
|
vals = []
|
|
if hasattr(values, "valueSet"):
|
|
items = list(values.valueSet())
|
|
elif hasattr(values, "key_set"):
|
|
items = [(k, getattr(values, f"get{k[0].upper()}{k[1:]}", lambda: None)())
|
|
for k in values.key_set()]
|
|
elif isinstance(values, dict):
|
|
items = list(values.items())
|
|
else:
|
|
return -1
|
|
for k, v in items:
|
|
cols.append(str(k))
|
|
vals.append(v)
|
|
if len(cols) == 0:
|
|
cols = ["_id"]
|
|
vals = [None]
|
|
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 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
|
|
set_pairs = []
|
|
vals = []
|
|
if isinstance(values, dict):
|
|
items = list(values.items())
|
|
elif hasattr(values, "valueSet"):
|
|
items = list(values.valueSet())
|
|
else:
|
|
return 0
|
|
for k, v in items:
|
|
set_pairs.append(f"{k} = ?")
|
|
vals.append(v)
|
|
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:
|
|
return self._version
|
|
|
|
def setVersion(self, version: int):
|
|
self._version = version
|
|
if self._conn:
|
|
try:
|
|
self._conn.execute(f"PRAGMA schema_version = {version}")
|
|
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:
|
|
return self._max_size
|
|
|
|
def getPageSize(self) -> int:
|
|
return self._page_size
|
|
|
|
def setMaximumSize(self, size: int) -> int:
|
|
self._max_size = size
|
|
return size
|
|
|
|
def setPageSize(self, size: int):
|
|
self._page_size = size
|
|
if self._conn:
|
|
try:
|
|
self._conn.execute(f"PRAGMA page_size = {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",
|
|
] |