- android/ 167个模块 (app/content/os/view/widget/graphics/media/hardware/net/telephony/provider等) - java/ 35个模块 (lang/io/util/net/nio/security/crypto/sql/beans/rmi/awt等) - javax/ 4个模块 (net/crypto/security/sql) - org/ 5个模块 (json/w3c_dom/xml_sax/xml_pull/apache_http) - dalvik/ 1个模块 (system) - ~3053个类/接口,方法签名完全对齐Android SDK - 桌面跨平台运行 (Windows/macOS/Linux) - 修复所有导入错误和Python关键字冲突
866 lines
28 KiB
Python
866 lines
28 KiB
Python
"""
|
|
Android Database Extension 组件 — ContentObserver, DataSetObserver, CrossProcessCursor, CursorWindow 等
|
|
方法签名完全对齐 Android SDK (android.database 包扩展)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
|
|
|
|
from .._core import Stub, AndroidOnlyError
|
|
from ..java.lang import RuntimeException
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# ContentObserver
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class ContentObserver(Stub):
|
|
"""ContentObserver — 内容观察者"""
|
|
|
|
NOTIFY_URIS = 0
|
|
NOTIFY_SYNC_TO_NETWORK = 1
|
|
NOTIFY_SKIP_SYNC = 2
|
|
|
|
def __init__(self, handler: Any = None):
|
|
super().__init__()
|
|
self._handler = handler
|
|
self._is_alive: bool = True
|
|
self._self_change_count: int = 0
|
|
self._change_count: int = 0
|
|
self._observed_uris: List[str] = []
|
|
|
|
def onChange(self, self_change: bool = False):
|
|
"""内容变化回调"""
|
|
pass
|
|
|
|
def onChange(self, self_change: bool, uri: Any = None):
|
|
"""内容变化回调 (带 URI)"""
|
|
pass
|
|
|
|
def onChange(self, self_change: bool, uri: Any, user_id: int = 0):
|
|
pass
|
|
|
|
def deliverSelfNotifications(self) -> bool:
|
|
"""是否传递自身通知"""
|
|
return False
|
|
|
|
def isAlive(self) -> bool:
|
|
return self._is_alive
|
|
|
|
def release(self):
|
|
"""释放观察者"""
|
|
self._is_alive = False
|
|
self._observed_uris.clear()
|
|
|
|
def dispatchChange(self, self_change: bool, uri: Any = None, user_id: int = 0):
|
|
"""调度变化"""
|
|
if self._is_alive:
|
|
if uri is not None:
|
|
self.onChange(self_change, uri, user_id)
|
|
else:
|
|
self.onChange(self_change)
|
|
|
|
def getHandler(self) -> Any:
|
|
return self._handler
|
|
|
|
def getChangeCount(self) -> int:
|
|
return self._change_count
|
|
|
|
def incrementChangeCount(self):
|
|
self._change_count += 1
|
|
|
|
def addObservedUri(self, uri: str):
|
|
if uri not in self._observed_uris:
|
|
self._observed_uris.append(uri)
|
|
|
|
def removeObservedUri(self, uri: str):
|
|
self._observed_uris = [u for u in self._observed_uris if u != uri]
|
|
|
|
def getObservedUris(self) -> List[str]:
|
|
return list(self._observed_uris)
|
|
|
|
def isObservingUri(self, uri: str) -> bool:
|
|
return uri in self._observed_uris
|
|
|
|
def toString(self) -> str:
|
|
return f"ContentObserver(alive={self._is_alive}, uris={len(self._observed_uris)})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# DataSetObserver
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class DataSetObserver(Stub):
|
|
"""DataSetObserver — 数据集观察者"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._is_alive: bool = True
|
|
self._notify_count: int = 0
|
|
|
|
def onChanged(self):
|
|
"""数据集变化回调"""
|
|
self._notify_count += 1
|
|
|
|
def onInvalidated(self):
|
|
"""数据集失效回调"""
|
|
pass
|
|
|
|
def isAlive(self) -> bool:
|
|
return self._is_alive
|
|
|
|
def release(self):
|
|
self._is_alive = False
|
|
|
|
def getNotifyCount(self) -> int:
|
|
return self._notify_count
|
|
|
|
def toString(self) -> str:
|
|
return f"DataSetObserver(alive={self._is_alive}, notified={self._notify_count})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# DataSetValidatable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class DataSetValidatable(Stub):
|
|
"""DataSetValidatable — 数据集可验证接口"""
|
|
|
|
def isValid(self) -> bool:
|
|
"""检查数据集是否有效"""
|
|
return True
|
|
|
|
def validate(self) -> bool:
|
|
"""验证数据集"""
|
|
return self.isValid()
|
|
|
|
def getValidationError(self) -> Optional[str]:
|
|
"""获取验证错误信息"""
|
|
return None
|
|
|
|
def resetValidation(self):
|
|
"""重置验证状态"""
|
|
pass
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# CrossProcessCursor
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class CrossProcessCursor(Stub):
|
|
"""CrossProcessCursor — 跨进程游标"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._window: Optional[CursorWindow] = None
|
|
self._position: int = -1
|
|
self._row_count: int = 0
|
|
self._column_count: int = 0
|
|
|
|
def getWindow(self) -> Optional[CursorWindow]:
|
|
return self._window
|
|
|
|
def setWindow(self, window: CursorWindow):
|
|
self._window = window
|
|
|
|
def fillWindow(self, position: int, window: CursorWindow):
|
|
"""填充窗口"""
|
|
self._window = window
|
|
self._position = position
|
|
|
|
def onMove(self, old_position: int, new_position: int) -> bool:
|
|
"""移动回调"""
|
|
return 0 <= new_position < self._row_count
|
|
|
|
def getPosition(self) -> int:
|
|
return self._position
|
|
|
|
def moveToPosition(self, position: int) -> bool:
|
|
if 0 <= position < self._row_count:
|
|
old = self._position
|
|
self._position = position
|
|
return self.onMove(old, position)
|
|
return False
|
|
|
|
def moveToFirst(self) -> bool:
|
|
return self.moveToPosition(0)
|
|
|
|
def moveToLast(self) -> bool:
|
|
return self.moveToPosition(self._row_count - 1)
|
|
|
|
def moveToNext(self) -> bool:
|
|
return self.moveToPosition(self._position + 1)
|
|
|
|
def moveToPrevious(self) -> bool:
|
|
return self.moveToPosition(self._position - 1)
|
|
|
|
def isFirst(self) -> bool:
|
|
return self._position == 0
|
|
|
|
def isLast(self) -> bool:
|
|
return self._position == self._row_count - 1
|
|
|
|
def isBeforeFirst(self) -> bool:
|
|
return self._position < 0
|
|
|
|
def isAfterLast(self) -> bool:
|
|
return self._position >= self._row_count
|
|
|
|
def getRowCount(self) -> int:
|
|
return self._row_count
|
|
|
|
def setRowCount(self, count: int):
|
|
self._row_count = count
|
|
|
|
def getColumnCount(self) -> int:
|
|
return self._column_count
|
|
|
|
def setColumnCount(self, count: int):
|
|
self._column_count = count
|
|
|
|
def getColumnIndex(self, column_name: str) -> int:
|
|
return -1
|
|
|
|
def getColumnName(self, index: int) -> str:
|
|
return ""
|
|
|
|
def getColumnNames(self) -> List[str]:
|
|
return []
|
|
|
|
def getString(self, column_index: int) -> Optional[str]:
|
|
return None
|
|
|
|
def getInt(self, column_index: int) -> int:
|
|
return 0
|
|
|
|
def getLong(self, column_index: int) -> int:
|
|
return 0
|
|
|
|
def getFloat(self, column_index: int) -> float:
|
|
return 0.0
|
|
|
|
def getDouble(self, column_index: int) -> float:
|
|
return 0.0
|
|
|
|
def isNull(self, column_index: int) -> bool:
|
|
return True
|
|
|
|
def close(self):
|
|
self._window = None
|
|
self._row_count = 0
|
|
|
|
def requery(self) -> bool:
|
|
return False
|
|
|
|
def toString(self) -> str:
|
|
return f"CrossProcessCursor(rows={self._row_count}, pos={self._position})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# CrossProcessCursorWrapper
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class CrossProcessCursorWrapper(CrossProcessCursor):
|
|
"""CrossProcessCursorWrapper — 跨进程游标包装器"""
|
|
|
|
def __init__(self, cursor: Optional[CrossProcessCursor] = None):
|
|
super().__init__()
|
|
self._wrapped_cursor = cursor
|
|
|
|
def getWrappedCursor(self) -> Optional[CrossProcessCursor]:
|
|
return self._wrapped_cursor
|
|
|
|
def setWrappedCursor(self, cursor: CrossProcessCursor):
|
|
self._wrapped_cursor = cursor
|
|
|
|
def getWindow(self) -> Optional[CursorWindow]:
|
|
if self._wrapped_cursor:
|
|
return self._wrapped_cursor.getWindow()
|
|
return self._window
|
|
|
|
def fillWindow(self, position: int, window: CursorWindow):
|
|
if self._wrapped_cursor:
|
|
self._wrapped_cursor.fillWindow(position, window)
|
|
else:
|
|
super().fillWindow(position, window)
|
|
|
|
def onMove(self, old_position: int, new_position: int) -> bool:
|
|
if self._wrapped_cursor:
|
|
return self._wrapped_cursor.onMove(old_position, new_position)
|
|
return super().onMove(old_position, new_position)
|
|
|
|
def getRowCount(self) -> int:
|
|
if self._wrapped_cursor:
|
|
return self._wrapped_cursor.getRowCount()
|
|
return self._row_count
|
|
|
|
def getColumnCount(self) -> int:
|
|
if self._wrapped_cursor:
|
|
return self._wrapped_cursor.getColumnCount()
|
|
return self._column_count
|
|
|
|
def getColumnNames(self) -> List[str]:
|
|
if self._wrapped_cursor:
|
|
return self._wrapped_cursor.getColumnNames()
|
|
return []
|
|
|
|
def getColumnIndex(self, column_name: str) -> int:
|
|
if self._wrapped_cursor:
|
|
return self._wrapped_cursor.getColumnIndex(column_name)
|
|
return -1
|
|
|
|
def getString(self, column_index: int) -> Optional[str]:
|
|
if self._wrapped_cursor:
|
|
return self._wrapped_cursor.getString(column_index)
|
|
return None
|
|
|
|
def getInt(self, column_index: int) -> int:
|
|
if self._wrapped_cursor:
|
|
return self._wrapped_cursor.getInt(column_index)
|
|
return 0
|
|
|
|
def getLong(self, column_index: int) -> int:
|
|
if self._wrapped_cursor:
|
|
return self._wrapped_cursor.getLong(column_index)
|
|
return 0
|
|
|
|
def getDouble(self, column_index: int) -> float:
|
|
if self._wrapped_cursor:
|
|
return self._wrapped_cursor.getDouble(column_index)
|
|
return 0.0
|
|
|
|
def moveToPosition(self, position: int) -> bool:
|
|
if self._wrapped_cursor:
|
|
return self._wrapped_cursor.moveToPosition(position)
|
|
return super().moveToPosition(position)
|
|
|
|
def close(self):
|
|
if self._wrapped_cursor:
|
|
self._wrapped_cursor.close()
|
|
super().close()
|
|
|
|
def toString(self) -> str:
|
|
return f"CrossProcessCursorWrapper(wrapped={self._wrapped_cursor})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# CursorWindow
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class CursorWindow(Stub):
|
|
"""CursorWindow — 游标窗口"""
|
|
|
|
WINDOW_SIZE_LIMIT = 2097152 # 2MB
|
|
|
|
TYPE_NULL = 0
|
|
TYPE_INTEGER = 1
|
|
TYPE_FLOAT = 2
|
|
TYPE_BLOB = 4
|
|
TYPE_STRING = 5
|
|
|
|
def __init__(self, name: str = "CursorWindow"):
|
|
super().__init__()
|
|
self._name = name
|
|
self._start_position: int = 0
|
|
self._num_rows: int = 0
|
|
self._is_open: bool = True
|
|
self._data: Dict[Tuple[int, int], Any] = {}
|
|
self._types: Dict[Tuple[int, int], int] = {}
|
|
self._allocated_memory: int = 0
|
|
self._max_allocated_memory: int = CursorWindow.WINDOW_SIZE_LIMIT
|
|
|
|
@staticmethod
|
|
def newFromParcel(data: Any) -> CursorWindow:
|
|
return CursorWindow("from_parcel")
|
|
|
|
def clear(self):
|
|
"""清空窗口"""
|
|
self._data.clear()
|
|
self._types.clear()
|
|
self._num_rows = 0
|
|
self._allocated_memory = 0
|
|
|
|
def getStartPosition(self) -> int:
|
|
return self._start_position
|
|
|
|
def setStartPosition(self, pos: int):
|
|
self._start_position = pos
|
|
|
|
def getNumRows(self) -> int:
|
|
return self._num_rows
|
|
|
|
def setNumColumns(self, num_columns: int):
|
|
self._num_columns = num_columns
|
|
|
|
def getNumColumns(self) -> int:
|
|
return self._num_columns
|
|
|
|
def allocRow(self) -> bool:
|
|
"""分配一行"""
|
|
self._num_rows += 1
|
|
return True
|
|
|
|
def freeLastRow(self):
|
|
"""释放最后一行"""
|
|
if self._num_rows > 0:
|
|
self._num_rows -= 1
|
|
keys_to_remove = [k for k in self._data if k[0] == self._num_rows]
|
|
for k in keys_to_remove:
|
|
del self._data[k]
|
|
del self._types[k]
|
|
|
|
def putString(self, value: str, row: int, col: int) -> bool:
|
|
self._data[(row, col)] = value
|
|
self._types[(row, col)] = CursorWindow.TYPE_STRING
|
|
self._allocated_memory += len(value)
|
|
return True
|
|
|
|
def putLong(self, value: int, row: int, col: int) -> bool:
|
|
self._data[(row, col)] = value
|
|
self._types[(row, col)] = CursorWindow.TYPE_INTEGER
|
|
self._allocated_memory += 8
|
|
return True
|
|
|
|
def putDouble(self, value: float, row: int, col: int) -> bool:
|
|
self._data[(row, col)] = value
|
|
self._types[(row, col)] = CursorWindow.TYPE_FLOAT
|
|
self._allocated_memory += 8
|
|
return True
|
|
|
|
def putBlob(self, value: bytes, row: int, col: int) -> bool:
|
|
self._data[(row, col)] = value
|
|
self._types[(row, col)] = CursorWindow.TYPE_BLOB
|
|
self._allocated_memory += len(value)
|
|
return True
|
|
|
|
def putNull(self, row: int, col: int) -> bool:
|
|
self._data[(row, col)] = None
|
|
self._types[(row, col)] = CursorWindow.TYPE_NULL
|
|
return True
|
|
|
|
def getString(self, row: int, col: int) -> Optional[str]:
|
|
val = self._data.get((row, col))
|
|
if isinstance(val, str):
|
|
return val
|
|
if val is None:
|
|
return None
|
|
return str(val)
|
|
|
|
def getLong(self, row: int, col: int) -> int:
|
|
val = self._data.get((row, col), 0)
|
|
if isinstance(val, int):
|
|
return val
|
|
if isinstance(val, float):
|
|
return int(val)
|
|
if isinstance(val, str):
|
|
try:
|
|
return int(val)
|
|
except ValueError:
|
|
return 0
|
|
return 0
|
|
|
|
def getDouble(self, row: int, col: int) -> float:
|
|
val = self._data.get((row, col), 0.0)
|
|
if isinstance(val, (int, float)):
|
|
return float(val)
|
|
if isinstance(val, str):
|
|
try:
|
|
return float(val)
|
|
except ValueError:
|
|
return 0.0
|
|
return 0.0
|
|
|
|
def getBlob(self, row: int, col: int) -> Optional[bytes]:
|
|
val = self._data.get((row, col))
|
|
if isinstance(val, bytes):
|
|
return val
|
|
if isinstance(val, str):
|
|
return val.encode("utf-8")
|
|
return None
|
|
|
|
def getType(self, row: int, col: int) -> int:
|
|
return self._types.get((row, col), CursorWindow.TYPE_NULL)
|
|
|
|
def isNull(self, row: int, col: int) -> bool:
|
|
return self.getType(row, col) == CursorWindow.TYPE_NULL
|
|
|
|
def isOpen(self) -> bool:
|
|
return self._is_open
|
|
|
|
def close(self):
|
|
"""关闭窗口"""
|
|
self._is_open = False
|
|
self.clear()
|
|
|
|
def toString(self) -> str:
|
|
return f"CursorWindow(name={self._name}, rows={self._num_rows}, open={self._is_open})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# DatabaseUtils
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class DatabaseUtils(Stub):
|
|
"""DatabaseUtils — 数据库工具"""
|
|
|
|
STATEMENT_ABORT = 0
|
|
STATEMENT_ATTACH = 1
|
|
STATEMENT_BEGIN = 2
|
|
STATEMENT_COMMIT = 3
|
|
STATEMENT_DELETE = 4
|
|
STATEMENT_DETACH = 5
|
|
STATEMENT_DROP_INDEX = 6
|
|
STATEMENT_DROP_TABLE = 7
|
|
STATEMENT_DROP_TRIGGER = 8
|
|
STATEMENT_DROP_VIEW = 9
|
|
STATEMENT_INSERT = 10
|
|
STATEMENT_PRAGMA = 11
|
|
STATEMENT_REINDEX = 12
|
|
STATEMENT_RELEASE = 13
|
|
STATEMENT_REPLACE = 14
|
|
STATEMENT_ROLLBACK = 15
|
|
STATEMENT_SAVEPOINT = 16
|
|
STATEMENT_SELECT = 17
|
|
STATEMENT_UPDATE = 18
|
|
STATEMENT_OTHER = 99
|
|
|
|
@staticmethod
|
|
def sqlEscapeString(value: str) -> str:
|
|
"""SQL 转义字符串"""
|
|
return "'" + value.replace("'", "''") + "'"
|
|
|
|
@staticmethod
|
|
def appendValueToSql(sql: str, value: Any) -> str:
|
|
"""将值附加到 SQL"""
|
|
if value is None:
|
|
return sql + " NULL"
|
|
if isinstance(value, bool):
|
|
return sql + (" 1" if value else " 0")
|
|
if isinstance(value, int):
|
|
return sql + f" {value}"
|
|
if isinstance(value, float):
|
|
return sql + f" {value}"
|
|
return sql + f" '{DatabaseUtils.sqlEscapeString(str(value))}'"
|
|
|
|
@staticmethod
|
|
def concatenateWhere(a: str, b: str) -> str:
|
|
"""连接 WHERE 子句"""
|
|
if a is None or a == "":
|
|
return b
|
|
if b is None or b == "":
|
|
return a
|
|
return f"({a}) AND ({b})"
|
|
|
|
@staticmethod
|
|
def getCollationKey(name: str) -> str:
|
|
"""获取排序键"""
|
|
return name.lower()
|
|
|
|
@staticmethod
|
|
def getHexCollationKey(name: str) -> str:
|
|
"""获取十六进制排序键"""
|
|
if not name:
|
|
return ""
|
|
return name.encode("utf-8").hex()
|
|
|
|
@staticmethod
|
|
def dumpCursor(cursor: CrossProcessCursor):
|
|
"""转储游标内容"""
|
|
rows = cursor.getRowCount()
|
|
cols = cursor.getColumnCount()
|
|
names = cursor.getColumnNames()
|
|
for r in range(rows):
|
|
cursor.moveToPosition(r)
|
|
row_data = {}
|
|
for c in range(cols):
|
|
name = names[c] if c < len(names) else f"col{c}"
|
|
row_data[name] = cursor.getString(c)
|
|
print(str(row_data))
|
|
|
|
@staticmethod
|
|
def cursorRowToContentValues(cursor: CrossProcessCursor) -> Dict[str, Any]:
|
|
"""将游标行转换为内容值"""
|
|
values = {}
|
|
for c in range(cursor.getColumnCount()):
|
|
name = cursor.getColumnName(c)
|
|
values[name] = cursor.getString(c)
|
|
return values
|
|
|
|
@staticmethod
|
|
def LongToHexString(value: int) -> str:
|
|
"""将长整型转换为十六进制字符串"""
|
|
return f"{value:#018x}"
|
|
|
|
@staticmethod
|
|
def bindObjectToProgram(statement: Any, index: int, value: Any):
|
|
"""绑定对象到 SQL 程序"""
|
|
pass
|
|
|
|
@staticmethod
|
|
def createDbFromSqlStatements(context: Any, name: str,
|
|
sql_statements: List[str]) -> Any:
|
|
"""从 SQL 语句创建数据库"""
|
|
return None
|
|
|
|
@staticmethod
|
|
def queryNumEntries(db: Any, table: str, selection: str = "") -> int:
|
|
"""查询条目数"""
|
|
return 0
|
|
|
|
@staticmethod
|
|
def stringOrEmpty(s: Optional[str]) -> str:
|
|
"""返回字符串或空"""
|
|
return s if s else ""
|
|
|
|
@staticmethod
|
|
def isTableExists(db: Any, table_name: str) -> bool:
|
|
"""检查表是否存在"""
|
|
return True
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# DefaultDatabaseErrorHandler
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class DefaultDatabaseErrorHandler(Stub):
|
|
"""DefaultDatabaseErrorHandler — 默认数据库错误处理器"""
|
|
|
|
ERROR_TYPE_CORRUPT = 1
|
|
ERROR_TYPE_FULL = 2
|
|
ERROR_TYPE_PERMISSION = 3
|
|
ERROR_TYPE_IO = 4
|
|
ERROR_TYPE_NOMEM = 5
|
|
ERROR_TYPE_TOOBIG = 6
|
|
ERROR_TYPE_CONSTRAINT = 7
|
|
ERROR_TYPE_MISMATCH = 8
|
|
ERROR_TYPE_MISUSE = 9
|
|
ERROR_TYPE_NOTADB = 10
|
|
ERROR_TYPE_INTERNAL = 11
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._error_count: int = 0
|
|
self._last_error: int = 0
|
|
self._error_message: str = ""
|
|
self._is_recovering: bool = False
|
|
|
|
def onCorruption(self, db_obj: Any):
|
|
"""数据库损坏回调"""
|
|
self._error_count += 1
|
|
self._last_error = DefaultDatabaseErrorHandler.ERROR_TYPE_CORRUPT
|
|
self._error_message = "Database corruption detected"
|
|
self._is_recovering = True
|
|
self._tryRecover(db_obj)
|
|
self._is_recovering = False
|
|
|
|
def onError(self, db_obj: Any, error_type: int, message: str = ""):
|
|
"""错误回调"""
|
|
self._error_count += 1
|
|
self._last_error = error_type
|
|
self._error_message = message
|
|
|
|
def _tryRecover(self, db_obj: Any):
|
|
"""尝试恢复数据库"""
|
|
pass
|
|
|
|
def getErrorCount(self) -> int:
|
|
return self._error_count
|
|
|
|
def getLastError(self) -> int:
|
|
return self._last_error
|
|
|
|
def getErrorMessage(self) -> str:
|
|
return self._error_message
|
|
|
|
def isRecovering(self) -> bool:
|
|
return self._is_recovering
|
|
|
|
def toString(self) -> str:
|
|
return f"DefaultDatabaseErrorHandler(errors={self._error_count})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# Extras (Database 扩展)
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class Extras(Stub):
|
|
"""Extras — 数据库扩展附加类"""
|
|
|
|
EXTRA_DATABASE_NAME = "database_name"
|
|
EXTRA_TABLE_NAME = "table_name"
|
|
EXTRA_SQL_COMMAND = "sql_command"
|
|
EXTRA_ERROR_DESCRIPTION = "error_description"
|
|
EXTRA_ROW_COUNT = "row_count"
|
|
EXTRA_PAGE_SIZE = "page_size"
|
|
EXTRA_PAGE_COUNT = "page_count"
|
|
EXTRA_DATABASE_SIZE = "database_size"
|
|
EXTRA_JOURNAL_MODE = "journal_mode"
|
|
EXTRA_SYNC_MODE = "sync_mode"
|
|
EXTRA_CACHE_SIZE = "cache_size"
|
|
EXTRA_FOREIGN_KEYS = "foreign_keys"
|
|
EXTRA_ENCODING = "encoding"
|
|
EXTRA_SCHEMA_VERSION = "schema_version"
|
|
EXTRA_USER_VERSION = "user_version"
|
|
|
|
FLAG_READ_ONLY = 1
|
|
FLAG_WRITE_AHEAD_LOGGING = 2
|
|
FLAG_ENABLE_FOREIGN_KEYS = 4
|
|
FLAG_CREATE_IF_NECESSARY = 8
|
|
FLAG_NO_LOCALIZED_COLLATORS = 16
|
|
FLAG_OPEN_READONLY = 32
|
|
FLAG_ENABLE_WRITE_AHEAD_LOGGING = 64
|
|
FLAG_DISABLE_COMPATIBILITY_WAL = 128
|
|
FLAG_OPEN_READ_WRITE = 256
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._extras: Dict[str, Any] = {}
|
|
self._flags: int = 0
|
|
|
|
def getString(self, key: str, default: str = "") -> str:
|
|
return str(self._extras.get(key, default))
|
|
|
|
def getInt(self, key: str, default: int = 0) -> int:
|
|
val = self._extras.get(key, default)
|
|
return int(val) if val is not None else default
|
|
|
|
def getBoolean(self, key: str, default: bool = False) -> bool:
|
|
val = self._extras.get(key, default)
|
|
return bool(val) if val is not None else default
|
|
|
|
def putString(self, key: str, value: str):
|
|
self._extras[key] = value
|
|
|
|
def putInt(self, key: str, value: int):
|
|
self._extras[key] = value
|
|
|
|
def putBoolean(self, key: str, value: bool):
|
|
self._extras[key] = value
|
|
|
|
def putAll(self, extras: Dict[str, Any]):
|
|
self._extras.update(extras)
|
|
|
|
def containsKey(self, key: str) -> bool:
|
|
return key in self._extras
|
|
|
|
def getKeys(self) -> List[str]:
|
|
return list(self._extras.keys())
|
|
|
|
def size(self) -> int:
|
|
return len(self._extras)
|
|
|
|
def isEmpty(self) -> bool:
|
|
return len(self._extras) == 0
|
|
|
|
def clear(self):
|
|
self._extras.clear()
|
|
|
|
def getFlags(self) -> int:
|
|
return self._flags
|
|
|
|
def setFlags(self, flags: int):
|
|
self._flags = flags
|
|
|
|
def hasFlag(self, flag: int) -> bool:
|
|
return bool(self._flags & flag)
|
|
|
|
def toString(self) -> str:
|
|
return f"Extras(entries={len(self._extras)}, flags={self._flags})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# BulkCursorDescriptor (helper)
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class BulkCursorDescriptor(Stub):
|
|
"""BulkCursorDescriptor — 批量游标描述符"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._cursor: Optional[CrossProcessCursor] = None
|
|
self._column_names: List[str] = []
|
|
self._wants_all_on_move_calls: bool = False
|
|
self._count: int = 0
|
|
self._window: Optional[CursorWindow] = None
|
|
|
|
def getCursor(self) -> Optional[CrossProcessCursor]:
|
|
return self._cursor
|
|
|
|
def setCursor(self, cursor: CrossProcessCursor):
|
|
self._cursor = cursor
|
|
|
|
def getColumnNames(self) -> List[str]:
|
|
return list(self._column_names)
|
|
|
|
def setColumnNames(self, names: List[str]):
|
|
self._column_names = list(names)
|
|
|
|
def wantsAllOnMoveCalls(self) -> bool:
|
|
return self._wants_all_on_move_calls
|
|
|
|
def setWantsAllOnMoveCalls(self, wants: bool):
|
|
self._wants_all_on_move_calls = wants
|
|
|
|
def getCount(self) -> int:
|
|
return self._count
|
|
|
|
def setCount(self, count: int):
|
|
self._count = count
|
|
|
|
def getWindow(self) -> Optional[CursorWindow]:
|
|
return self._window
|
|
|
|
def setWindow(self, window: CursorWindow):
|
|
self._window = window
|
|
|
|
def toString(self) -> str:
|
|
return f"BulkCursorDescriptor(columns={len(self._column_names)}, count={self._count})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# CursorJoiner (helper)
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class CursorJoiner(Stub):
|
|
"""CursorJoiner — 游标连接器"""
|
|
|
|
RESULT_BOTH = 0
|
|
RESULT_LEFT = 1
|
|
RESULT_RIGHT = 2
|
|
|
|
def __init__(self, cursor_left: CrossProcessCursor,
|
|
column_names_left: List[str],
|
|
cursor_right: CrossProcessCursor,
|
|
column_names_right: List[str]):
|
|
super().__init__()
|
|
self._left = cursor_left
|
|
self._right = cursor_right
|
|
self._left_cols = column_names_left
|
|
self._right_cols = column_names_right
|
|
self._left_pos: int = -1
|
|
self._right_pos: int = -1
|
|
self._result: int = CursorJoiner.RESULT_BOTH
|
|
|
|
def hasNext(self) -> bool:
|
|
return (self._left_pos + 1 < self._left.getRowCount() or
|
|
self._right_pos + 1 < self._right.getRowCount())
|
|
|
|
def next(self) -> int:
|
|
self._left_pos += 1
|
|
self._right_pos += 1
|
|
return self._result
|
|
|
|
def getLeftCursor(self) -> CrossProcessCursor:
|
|
return self._left
|
|
|
|
def getRightCursor(self) -> CrossProcessCursor:
|
|
return self._right
|
|
|
|
def reset(self):
|
|
self._left_pos = -1
|
|
self._right_pos = -1
|
|
|
|
def toString(self) -> str:
|
|
return f"CursorJoiner(left={self._left_pos}, right={self._right_pos})" |