feat: Android-pyApi v1.0 - 全面翻译 Android SDK API 为 Python
52 个模块覆盖 Android + Java + org.json 标准库
This commit is contained in:
commit
bfe27fdc08
104 files changed
+58750
No files matched your search
@@ -0,0 +1,965 @@
|
||||
"""
|
||||
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",
|
||||
]
|
||||
Reference in new issue
Block a user