1076 lines
37 KiB
Python
1076 lines
37 KiB
Python
"""
|
|
Android Database 组件 — Cursor, MatrixCursor, ContentObserver 等
|
|
方法签名完全对齐 Android SDK
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from typing import Any, Callable, 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,
|
|
)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# SQLException hierarchy
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class SQLException(RuntimeError):
|
|
"""SQLException — 数据库异常基类"""
|
|
|
|
def __init__(self, message: str = ""):
|
|
super().__init__(message)
|
|
self._error_message = message
|
|
|
|
def getMessage(self) -> str:
|
|
return self._error_message
|
|
|
|
def toString(self) -> str:
|
|
return f"SQLException: {self._error_message}"
|
|
|
|
|
|
class CursorIndexOutOfBoundsException(SQLException):
|
|
"""CursorIndexOutOfBoundsException — 游标越界"""
|
|
|
|
def __init__(self, message: str = ""):
|
|
super().__init__(message)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# ContentObserver
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class ContentObserver(Stub):
|
|
"""ContentObserver — 内容观察者"""
|
|
|
|
def __init__(self, handler: Any = None):
|
|
super().__init__()
|
|
self._handler = handler
|
|
self._notify_for_descendants = False
|
|
|
|
def onChange(self, self_change: bool = True):
|
|
pass
|
|
|
|
def onChange(self, self_change: bool, uri: Any = None): # type: ignore
|
|
pass
|
|
|
|
def deliverSelfNotifications(self) -> bool:
|
|
return True
|
|
|
|
def getSelfChangeNotify(self) -> bool:
|
|
return True
|
|
|
|
def dispatchChange(self, self_change: bool, uri: Any = None):
|
|
self.onChange(self_change, uri)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# DataSetObserver
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class DataSetObserver(Stub):
|
|
"""DataSetObserver — 数据集观察者"""
|
|
|
|
def onChanged(self):
|
|
pass
|
|
|
|
def onInvalidated(self):
|
|
pass
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# ContentObservable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class ContentObservable(Stub):
|
|
"""ContentObservable — 内容可观察对象"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._observers: List[ContentObserver] = []
|
|
|
|
def registerObserver(self, observer: ContentObserver):
|
|
if observer not in self._observers:
|
|
self._observers.append(observer)
|
|
|
|
def unregisterObserver(self, observer: ContentObserver):
|
|
self._observers = [o for o in self._observers if o is not observer]
|
|
|
|
def dispatchChange(self, self_change: bool, uri: Any = None):
|
|
for observer in list(self._observers):
|
|
observer.dispatchChange(self_change, uri)
|
|
|
|
def notifyChange(self, self_change: bool):
|
|
for observer in list(self._observers):
|
|
observer.onChange(self_change)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# DataSetObservable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class DataSetObservable(Stub):
|
|
"""DataSetObservable — 数据集可观察对象"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._observers: List[DataSetObserver] = []
|
|
|
|
def registerObserver(self, observer: DataSetObserver):
|
|
if observer not in self._observers:
|
|
self._observers.append(observer)
|
|
|
|
def unregisterObserver(self, observer: DataSetObserver):
|
|
self._observers = [o for o in self._observers if o is not observer]
|
|
|
|
def notifyChanged(self):
|
|
for observer in list(self._observers):
|
|
observer.onChanged()
|
|
|
|
def notifyInvalidated(self):
|
|
for observer in list(self._observers):
|
|
observer.onInvalidated()
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# CharArrayBuffer
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class CharArrayBuffer(Stub):
|
|
"""CharArrayBuffer — 字符数组缓冲区"""
|
|
|
|
def __init__(self, size: int = 0):
|
|
super().__init__()
|
|
self.data: List[str] = [""] * size
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# DatabaseErrorHandler
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class DatabaseErrorHandler(Stub):
|
|
"""DatabaseErrorHandler — 数据库错误处理器"""
|
|
|
|
def onCorruption(self, db_obj: Any):
|
|
pass
|
|
|
|
|
|
class DefaultDatabaseErrorHandler(DatabaseErrorHandler):
|
|
"""DefaultDatabaseErrorHandler — 默认数据库错误处理器"""
|
|
|
|
def onCorruption(self, db_obj: Any):
|
|
import sys
|
|
sys.stderr.write(f"[DatabaseError] Corruption detected for {db_obj}\n")
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# CursorWindow
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class CursorWindow(Stub):
|
|
"""CursorWindow — 游标窗口"""
|
|
|
|
def __init__(self, name: str = "unnamed"):
|
|
super().__init__()
|
|
self._name = name
|
|
self._start_position: int = 0
|
|
self._num_rows: int = 0
|
|
self._num_columns: int = 0
|
|
self._data: List[List[Optional[Any]]] = []
|
|
|
|
def setStartPosition(self, pos: int):
|
|
self._start_position = pos
|
|
|
|
def getStartPosition(self) -> int:
|
|
return self._start_position
|
|
|
|
def allocRow(self) -> bool:
|
|
self._data.append([None] * self._num_columns)
|
|
self._num_rows = len(self._data)
|
|
return True
|
|
|
|
def freeRow(self, row: int):
|
|
if 0 <= row < len(self._data):
|
|
self._data[row] = [None] * self._num_columns
|
|
|
|
def freeLastRow(self):
|
|
if self._data:
|
|
self._data.pop()
|
|
self._num_rows = len(self._data)
|
|
|
|
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 writeString(self, value: str, row: int, col: int):
|
|
while len(self._data) <= row:
|
|
self._data.append([None] * self._num_columns)
|
|
while len(self._data[row]) <= col:
|
|
self._data[row].append(None)
|
|
self._data[row][col] = value
|
|
|
|
def writeLong(self, value: int, row: int, col: int):
|
|
while len(self._data) <= row:
|
|
self._data.append([None] * self._num_columns)
|
|
while len(self._data[row]) <= col:
|
|
self._data[row].append(None)
|
|
self._data[row][col] = value
|
|
|
|
def writeDouble(self, value: float, row: int, col: int):
|
|
while len(self._data) <= row:
|
|
self._data.append([None] * self._num_columns)
|
|
while len(self._data[row]) <= col:
|
|
self._data[row].append(None)
|
|
self._data[row][col] = value
|
|
|
|
def writeBlob(self, value: bytes, row: int, col: int):
|
|
while len(self._data) <= row:
|
|
self._data.append([None] * self._num_columns)
|
|
while len(self._data[row]) <= col:
|
|
self._data[row].append(None)
|
|
self._data[row][col] = value
|
|
|
|
def writeNull(self, row: int, col: int):
|
|
while len(self._data) <= row:
|
|
self._data.append([None] * self._num_columns)
|
|
while len(self._data[row]) <= col:
|
|
self._data[row].append(None)
|
|
self._data[row][col] = None
|
|
|
|
def getString(self, row: int, col: int) -> Optional[str]:
|
|
try:
|
|
v = self._data[row][col]
|
|
return str(v) if v is not None else None
|
|
except (IndexError, TypeError):
|
|
return None
|
|
|
|
def getLong(self, row: int, col: int) -> int:
|
|
try:
|
|
v = self._data[row][col]
|
|
return int(v) if v is not None else 0
|
|
except (IndexError, ValueError, TypeError):
|
|
return 0
|
|
|
|
def getDouble(self, row: int, col: int) -> float:
|
|
try:
|
|
v = self._data[row][col]
|
|
return float(v) if v is not None else 0.0
|
|
except (IndexError, ValueError, TypeError):
|
|
return 0.0
|
|
|
|
def getBlob(self, row: int, col: int) -> Optional[bytes]:
|
|
try:
|
|
v = self._data[row][col]
|
|
if isinstance(v, bytes):
|
|
return v
|
|
if isinstance(v, str):
|
|
return v.encode("utf-8")
|
|
return None
|
|
except IndexError:
|
|
return None
|
|
|
|
def isNull(self, row: int, col: int) -> bool:
|
|
try:
|
|
return self._data[row][col] is None
|
|
except IndexError:
|
|
return True
|
|
|
|
def clear(self):
|
|
self._data.clear()
|
|
self._num_rows = 0
|
|
|
|
def toString(self) -> str:
|
|
return f"CursorWindow(name={self._name}, rows={self._num_rows}, cols={self._num_columns})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# Cursor interface
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class Cursor(Stub):
|
|
"""Cursor — 游标接口"""
|
|
|
|
FIELD_TYPE_NULL = 0
|
|
FIELD_TYPE_INTEGER = 1
|
|
FIELD_TYPE_FLOAT = 2
|
|
FIELD_TYPE_STRING = 3
|
|
FIELD_TYPE_BLOB = 4
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._pos: int = -1
|
|
self._closed: bool = False
|
|
|
|
def getCount(self) -> int:
|
|
return 0
|
|
|
|
def getPosition(self) -> int:
|
|
return self._pos
|
|
|
|
def moveToFirst(self) -> bool:
|
|
return self.moveToPosition(0)
|
|
|
|
def moveToLast(self) -> bool:
|
|
return self.moveToPosition(self.getCount() - 1)
|
|
|
|
def moveToNext(self) -> bool:
|
|
return self.moveToPosition(self._pos + 1)
|
|
|
|
def moveToPrevious(self) -> bool:
|
|
return self.moveToPosition(self._pos - 1)
|
|
|
|
def move(self, offset: int) -> bool:
|
|
return self.moveToPosition(self._pos + offset)
|
|
|
|
def moveToPosition(self, position: int) -> bool:
|
|
if position < -1 or position >= self.getCount():
|
|
return False
|
|
self._pos = position
|
|
return True
|
|
|
|
def isFirst(self) -> bool:
|
|
return self._pos == 0
|
|
|
|
def isLast(self) -> bool:
|
|
return self._pos == self.getCount() - 1
|
|
|
|
def isBeforeFirst(self) -> bool:
|
|
return self._pos < 0
|
|
|
|
def isAfterLast(self) -> bool:
|
|
return self._pos >= self.getCount()
|
|
|
|
def getColumnIndex(self, column_name: str) -> int:
|
|
names = self.getColumnNames()
|
|
try:
|
|
return names.index(column_name)
|
|
except ValueError:
|
|
return -1
|
|
|
|
def getColumnIndexOrThrow(self, column_name: str) -> int:
|
|
idx = self.getColumnIndex(column_name)
|
|
if idx < 0:
|
|
raise IllegalArgumentException(f"Column '{column_name}' does not exist")
|
|
return idx
|
|
|
|
def getColumnName(self, column_index: int) -> str:
|
|
names = self.getColumnNames()
|
|
if 0 <= column_index < len(names):
|
|
return names[column_index]
|
|
return ""
|
|
|
|
def getColumnNames(self) -> List[str]:
|
|
return []
|
|
|
|
def getType(self, column_index: int) -> int:
|
|
return self.FIELD_TYPE_NULL
|
|
|
|
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 getShort(self, column_index: int) -> int:
|
|
return 0
|
|
|
|
def getBlob(self, column_index: int) -> Optional[bytes]:
|
|
return None
|
|
|
|
def getBytes(self, column_index: int) -> Optional[bytes]:
|
|
return self.getBlob(column_index)
|
|
|
|
def close(self):
|
|
self._closed = True
|
|
|
|
def isClosed(self) -> bool:
|
|
return self._closed
|
|
|
|
def requery(self) -> bool:
|
|
return False
|
|
|
|
def deactivate(self):
|
|
pass
|
|
|
|
def registerContentObserver(self, observer: ContentObserver):
|
|
pass
|
|
|
|
def unregisterContentObserver(self, observer: ContentObserver):
|
|
pass
|
|
|
|
def registerDataSetObserver(self, observer: DataSetObserver):
|
|
pass
|
|
|
|
def unregisterDataSetObserver(self, observer: DataSetObserver):
|
|
pass
|
|
|
|
def getWantsAllOnMoveCalls(self) -> bool:
|
|
return False
|
|
|
|
def getExtras(self) -> Any:
|
|
return None
|
|
|
|
def respond(self, extras: Any) -> Any:
|
|
return None
|
|
|
|
def setNotificationUri(self, cr: Any, uri: Any):
|
|
pass
|
|
|
|
def getNotificationUri(self) -> Any:
|
|
return None
|
|
|
|
def fillWindow(self, pos: int, window: CursorWindow):
|
|
pass
|
|
|
|
def copyStringToBuffer(self, column_index: int, buffer: CharArrayBuffer):
|
|
val = self.getString(column_index)
|
|
if val is not None:
|
|
buffer.data = list(val)
|
|
|
|
def isNull(self, column_index: int) -> bool:
|
|
return self.getType(column_index) == self.FIELD_TYPE_NULL
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# AbstractCursor
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class AbstractCursor(Cursor):
|
|
"""AbstractCursor — 游标抽象基类"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._content_observable = ContentObservable()
|
|
self._data_set_observable = DataSetObservable()
|
|
self._notification_uri: Any = None
|
|
self._self_observer: Optional[ContentObserver] = None
|
|
|
|
def getCount(self) -> int:
|
|
return 0
|
|
|
|
def getColumnNames(self) -> List[str]:
|
|
return []
|
|
|
|
def registerContentObserver(self, observer: ContentObserver):
|
|
self._content_observable.registerObserver(observer)
|
|
|
|
def unregisterContentObserver(self, observer: ContentObserver):
|
|
self._content_observable.unregisterObserver(observer)
|
|
|
|
def registerDataSetObserver(self, observer: DataSetObserver):
|
|
self._data_set_observable.registerObserver(observer)
|
|
|
|
def unregisterDataSetObserver(self, observer: DataSetObserver):
|
|
self._data_set_observable.unregisterObserver(observer)
|
|
|
|
def setNotificationUri(self, cr: Any, uri: Any):
|
|
self._notification_uri = uri
|
|
|
|
def getNotificationUri(self) -> Any:
|
|
return self._notification_uri
|
|
|
|
def onChange(self, self_change: bool):
|
|
self._content_observable.dispatchChange(self_change)
|
|
|
|
def getWantsAllOnMoveCalls(self) -> bool:
|
|
return False
|
|
|
|
def getExtras(self) -> Any:
|
|
return None
|
|
|
|
def respond(self, extras: Any) -> Any:
|
|
return None
|
|
|
|
def fillWindow(self, pos: int, window: CursorWindow):
|
|
pass
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# AbstractWindowedCursor
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class AbstractWindowedCursor(AbstractCursor):
|
|
"""AbstractWindowedCursor — 带窗口的游标"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._window: Optional[CursorWindow] = None
|
|
|
|
def getWindow(self) -> Optional[CursorWindow]:
|
|
return self._window
|
|
|
|
def setWindow(self, window: CursorWindow):
|
|
self._window = window
|
|
|
|
def hasWindow(self) -> bool:
|
|
return self._window is not None
|
|
|
|
def clearBlobCache(self):
|
|
pass
|
|
|
|
def fillWindow(self, pos: int, window: CursorWindow):
|
|
if self._window:
|
|
self._window.clear()
|
|
|
|
def getString(self, column_index: int) -> Optional[str]:
|
|
if self._window and self._pos >= 0:
|
|
return self._window.getString(self._pos, column_index)
|
|
return None
|
|
|
|
def getInt(self, column_index: int) -> int:
|
|
if self._window and self._pos >= 0:
|
|
return self._window.getLong(self._pos, column_index)
|
|
return 0
|
|
|
|
def getLong(self, column_index: int) -> int:
|
|
if self._window and self._pos >= 0:
|
|
return self._window.getLong(self._pos, column_index)
|
|
return 0
|
|
|
|
def getFloat(self, column_index: int) -> float:
|
|
if self._window and self._pos >= 0:
|
|
return self._window.getDouble(self._pos, column_index)
|
|
return 0.0
|
|
|
|
def getDouble(self, column_index: int) -> float:
|
|
if self._window and self._pos >= 0:
|
|
return self._window.getDouble(self._pos, column_index)
|
|
return 0.0
|
|
|
|
def getBlob(self, column_index: int) -> Optional[bytes]:
|
|
if self._window and self._pos >= 0:
|
|
return self._window.getBlob(self._pos, column_index)
|
|
return None
|
|
|
|
def isNull(self, column_index: int) -> bool:
|
|
if self._window and self._pos >= 0:
|
|
return self._window.isNull(self._pos, column_index)
|
|
return True
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# CursorWrapper
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class CursorWrapper(Cursor):
|
|
"""CursorWrapper — 游标包装器"""
|
|
|
|
def __init__(self, cursor: Cursor):
|
|
super().__init__()
|
|
self._wrapped_cursor = cursor
|
|
|
|
def getWrappedCursor(self) -> Cursor:
|
|
return self._wrapped_cursor
|
|
|
|
def getCount(self) -> int:
|
|
return self._wrapped_cursor.getCount()
|
|
|
|
def getPosition(self) -> int:
|
|
return self._wrapped_cursor.getPosition()
|
|
|
|
def moveToFirst(self) -> bool:
|
|
return self._wrapped_cursor.moveToFirst()
|
|
|
|
def moveToLast(self) -> bool:
|
|
return self._wrapped_cursor.moveToLast()
|
|
|
|
def moveToNext(self) -> bool:
|
|
return self._wrapped_cursor.moveToNext()
|
|
|
|
def moveToPrevious(self) -> bool:
|
|
return self._wrapped_cursor.moveToPrevious()
|
|
|
|
def move(self, offset: int) -> bool:
|
|
return self._wrapped_cursor.move(offset)
|
|
|
|
def moveToPosition(self, position: int) -> bool:
|
|
return self._wrapped_cursor.moveToPosition(position)
|
|
|
|
def getColumnNames(self) -> List[str]:
|
|
return self._wrapped_cursor.getColumnNames()
|
|
|
|
def getColumnIndex(self, column_name: str) -> int:
|
|
return self._wrapped_cursor.getColumnIndex(column_name)
|
|
|
|
def getColumnIndexOrThrow(self, column_name: str) -> int:
|
|
return self._wrapped_cursor.getColumnIndexOrThrow(column_name)
|
|
|
|
def getString(self, column_index: int) -> Optional[str]:
|
|
return self._wrapped_cursor.getString(column_index)
|
|
|
|
def getInt(self, column_index: int) -> int:
|
|
return self._wrapped_cursor.getInt(column_index)
|
|
|
|
def getLong(self, column_index: int) -> int:
|
|
return self._wrapped_cursor.getLong(column_index)
|
|
|
|
def getFloat(self, column_index: int) -> float:
|
|
return self._wrapped_cursor.getFloat(column_index)
|
|
|
|
def getDouble(self, column_index: int) -> float:
|
|
return self._wrapped_cursor.getDouble(column_index)
|
|
|
|
def getShort(self, column_index: int) -> int:
|
|
return self._wrapped_cursor.getShort(column_index)
|
|
|
|
def getBlob(self, column_index: int) -> Optional[bytes]:
|
|
return self._wrapped_cursor.getBlob(column_index)
|
|
|
|
def close(self):
|
|
self._wrapped_cursor.close()
|
|
|
|
def isClosed(self) -> bool:
|
|
return self._wrapped_cursor.isClosed()
|
|
|
|
def registerContentObserver(self, observer: ContentObserver):
|
|
self._wrapped_cursor.registerContentObserver(observer)
|
|
|
|
def unregisterContentObserver(self, observer: ContentObserver):
|
|
self._wrapped_cursor.unregisterContentObserver(observer)
|
|
|
|
def registerDataSetObserver(self, observer: DataSetObserver):
|
|
self._wrapped_cursor.registerDataSetObserver(observer)
|
|
|
|
def unregisterDataSetObserver(self, observer: DataSetObserver):
|
|
self._wrapped_cursor.unregisterDataSetObserver(observer)
|
|
|
|
def getWantsAllOnMoveCalls(self) -> bool:
|
|
return self._wrapped_cursor.getWantsAllOnMoveCalls()
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# CrossProcessCursor / CrossProcessCursorWrapper
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class CrossProcessCursor(Cursor):
|
|
"""CrossProcessCursor — 跨进程游标"""
|
|
|
|
def fillWindow(self, pos: int, window: CursorWindow):
|
|
pass
|
|
|
|
def getWindow(self) -> Optional[CursorWindow]:
|
|
return None
|
|
|
|
def onMove(self, old_position: int, new_position: int) -> bool:
|
|
return True
|
|
|
|
|
|
class CrossProcessCursorWrapper(CursorWrapper):
|
|
"""CrossProcessCursorWrapper — 跨进程游标包装器"""
|
|
|
|
def __init__(self, cursor: Cursor):
|
|
super().__init__(cursor)
|
|
self._cursor = cursor
|
|
|
|
def fillWindow(self, pos: int, window: CursorWindow):
|
|
if isinstance(self._cursor, CrossProcessCursor):
|
|
self._cursor.fillWindow(pos, window)
|
|
|
|
def getWindow(self) -> Optional[CursorWindow]:
|
|
if isinstance(self._cursor, CrossProcessCursor):
|
|
return self._cursor.getWindow()
|
|
return None
|
|
|
|
def onMove(self, old_position: int, new_position: int) -> bool:
|
|
if isinstance(self._cursor, CrossProcessCursor):
|
|
return self._cursor.onMove(old_position, new_position)
|
|
return True
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# MatrixCursor
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class MatrixCursor(AbstractCursor):
|
|
"""MatrixCursor — 内存矩阵游标"""
|
|
|
|
class RowBuilder(Stub):
|
|
"""RowBuilder — 行构建器"""
|
|
|
|
def __init__(self, cursor: "MatrixCursor", row_index: int):
|
|
super().__init__()
|
|
self._cursor = cursor
|
|
self._row_index = row_index
|
|
|
|
def add(self, column_value: Any) -> "MatrixCursor.RowBuilder":
|
|
self._cursor._add_row_value(self._row_index, column_value)
|
|
return self
|
|
|
|
def __init__(self, column_names: List[str], initial_capacity: int = 0):
|
|
super().__init__()
|
|
self._column_names = list(column_names)
|
|
self._rows: List[List[Optional[Any]]] = []
|
|
self._num_columns = len(column_names)
|
|
|
|
def _add_row_value(self, row_index: int, value: Any):
|
|
while len(self._rows) <= row_index:
|
|
self._rows.append([])
|
|
self._rows[row_index].append(value)
|
|
|
|
def addRow(self, values: Union[List[Any], Tuple[Any, ...]]) -> None:
|
|
row = list(values) + [None] * (self._num_columns - len(values))
|
|
self._rows.append(row[:self._num_columns])
|
|
|
|
def newRow(self) -> RowBuilder:
|
|
row_index = len(self._rows)
|
|
self._rows.append([])
|
|
return MatrixCursor.RowBuilder(self, row_index)
|
|
|
|
def getCount(self) -> int:
|
|
return len(self._rows)
|
|
|
|
def getColumnNames(self) -> List[str]:
|
|
return list(self._column_names)
|
|
|
|
def getString(self, column_index: int) -> Optional[str]:
|
|
if self._pos < 0 or self._pos >= len(self._rows):
|
|
return None
|
|
if column_index < 0 or column_index >= len(self._rows[self._pos]):
|
|
return None
|
|
v = self._rows[self._pos][column_index]
|
|
return str(v) if v is not None else None
|
|
|
|
def getInt(self, column_index: int) -> int:
|
|
v = self.getString(column_index)
|
|
if v is None:
|
|
return 0
|
|
try:
|
|
return int(float(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:
|
|
v = self.getString(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 getBlob(self, column_index: int) -> Optional[bytes]:
|
|
if self._pos < 0 or self._pos >= len(self._rows):
|
|
return None
|
|
if column_index < 0 or column_index >= len(self._rows[self._pos]):
|
|
return None
|
|
v = self._rows[self._pos][column_index]
|
|
if isinstance(v, bytes):
|
|
return v
|
|
if isinstance(v, str):
|
|
return v.encode("utf-8")
|
|
return None
|
|
|
|
def getType(self, column_index: int) -> int:
|
|
v = None
|
|
if 0 <= self._pos < len(self._rows):
|
|
row = self._rows[self._pos]
|
|
if 0 <= column_index < len(row):
|
|
v = 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 toString(self) -> str:
|
|
return f"MatrixCursor(cols={self._column_names}, rows={len(self._rows)})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# MergeCursor
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class MergeCursor(AbstractCursor):
|
|
"""MergeCursor — 合并多个游标"""
|
|
|
|
def __init__(self, cursors: List[Cursor]):
|
|
super().__init__()
|
|
self._cursors = cursors
|
|
self._cursor_index = 0
|
|
self._current_cursor: Optional[Cursor] = None
|
|
|
|
def getCount(self) -> int:
|
|
return sum(c.getCount() for c in self._cursors)
|
|
|
|
def getColumnNames(self) -> List[str]:
|
|
if self._cursors:
|
|
return self._cursors[0].getColumnNames()
|
|
return []
|
|
|
|
def moveToPosition(self, position: int) -> bool:
|
|
if position < -1:
|
|
return False
|
|
if position >= self.getCount():
|
|
return False
|
|
offset = 0
|
|
for i, c in enumerate(self._cursors):
|
|
count = c.getCount()
|
|
if position < offset + count:
|
|
self._cursor_index = i
|
|
self._current_cursor = c
|
|
return c.moveToPosition(position - offset)
|
|
offset += count
|
|
return False
|
|
|
|
def getString(self, column_index: int) -> Optional[str]:
|
|
if self._current_cursor and self._pos >= 0:
|
|
return self._current_cursor.getString(column_index)
|
|
return None
|
|
|
|
def getInt(self, column_index: int) -> int:
|
|
if self._current_cursor and self._pos >= 0:
|
|
return self._current_cursor.getInt(column_index)
|
|
return 0
|
|
|
|
def close(self):
|
|
for c in self._cursors:
|
|
c.close()
|
|
super().close()
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# CursorJoiner
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class CursorJoiner(Stub):
|
|
"""CursorJoiner — 游标连接器"""
|
|
|
|
class Result(Stub):
|
|
BOTH = 0
|
|
LEFT = 1
|
|
RIGHT = 2
|
|
|
|
def __init__(self, cursor_a: Cursor, column_names_a: List[str],
|
|
cursor_b: Cursor, column_names_b: List[str]):
|
|
super().__init__()
|
|
self._cursor_a = cursor_a
|
|
self._cursor_b = cursor_b
|
|
self._col_names_a = column_names_a
|
|
self._col_names_b = column_names_b
|
|
self._has_next = False
|
|
|
|
def hasNext(self) -> bool:
|
|
return self._has_next
|
|
|
|
def next(self) -> int:
|
|
return self.Result.BOTH
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# DatabaseUtils
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class DatabaseUtils(Stub):
|
|
"""DatabaseUtils — 数据库工具"""
|
|
|
|
STATEMENT_ABORT = "ABORT"
|
|
STATEMENT_FAIL = "FAIL"
|
|
STATEMENT_IGNORE = "IGNORE"
|
|
STATEMENT_REPLACE = "REPLACE"
|
|
STATEMENT_ROLLBACK = "ROLLBACK"
|
|
|
|
@staticmethod
|
|
def cursorFillWindow(cursor: Cursor, pos: int, window: CursorWindow):
|
|
cursor.fillWindow(pos, window)
|
|
|
|
@staticmethod
|
|
def cursorStringToContentValues(cursor: Cursor, field: str, values: Any):
|
|
pass
|
|
|
|
@staticmethod
|
|
def cursorIntToContentValues(cursor: Cursor, field: str, values: Any):
|
|
pass
|
|
|
|
@staticmethod
|
|
def cursorLongToContentValues(cursor: Cursor, field: str, values: Any):
|
|
pass
|
|
|
|
@staticmethod
|
|
def cursorDoubleToContentValues(cursor: Cursor, field: str, values: Any):
|
|
pass
|
|
|
|
@staticmethod
|
|
def cursorRowToContentValues(cursor: Cursor, values: Any):
|
|
for col_name in cursor.getColumnNames():
|
|
idx = cursor.getColumnIndex(col_name)
|
|
val = cursor.getString(idx)
|
|
if hasattr(values, "put"):
|
|
values.put(col_name, val)
|
|
|
|
@staticmethod
|
|
def dumpCursor(cursor: Cursor):
|
|
col_names = cursor.getColumnNames()
|
|
sys.stderr.write(f"Rows: {cursor.getCount()}\n")
|
|
sys.stderr.write(f"Columns: {col_names}\n")
|
|
|
|
@staticmethod
|
|
def dumpCursorToString(cursor: Cursor) -> str:
|
|
return f"Cursor(count={cursor.getCount()})"
|
|
|
|
@staticmethod
|
|
def bindObjectToProgram(program: Any, index: int, value: Any):
|
|
pass
|
|
|
|
@staticmethod
|
|
def cursorToContentValues(cursor: Cursor, values: Any):
|
|
DatabaseUtils.cursorRowToContentValues(cursor, values)
|
|
|
|
@staticmethod
|
|
def longForQuery(db: Any, query: str, args: Optional[List[str]] = None) -> int:
|
|
return 0
|
|
|
|
@staticmethod
|
|
def stringForQuery(db: Any, query: str, args: Optional[List[str]] = None) -> Optional[str]:
|
|
return None
|
|
|
|
@staticmethod
|
|
def blobForQuery(db: Any, query: str, args: Optional[List[str]] = None) -> Optional[bytes]:
|
|
return None
|
|
|
|
@staticmethod
|
|
def sqlEscapeString(value: str) -> str:
|
|
return value.replace("'", "''")
|
|
|
|
@staticmethod
|
|
def appendEscapedSQLString(sb: Any, value: str):
|
|
escaped = DatabaseUtils.sqlEscapeString(value)
|
|
sb.append(f"'{escaped}'")
|
|
|
|
@staticmethod
|
|
def appendValueToSql(sb: Any, value: Any):
|
|
if value is None:
|
|
sb.append("NULL")
|
|
elif isinstance(value, bool):
|
|
sb.append("1" if value else "0")
|
|
elif isinstance(value, (int, float)):
|
|
sb.append(str(value))
|
|
else:
|
|
DatabaseUtils.appendEscapedSQLString(sb, str(value))
|
|
|
|
@staticmethod
|
|
def appendSelectionArgs(original_values: List[str], new_values: List[str]) -> List[str]:
|
|
return list(original_values) + list(new_values)
|
|
|
|
@staticmethod
|
|
def getCollationKey(name: str) -> str:
|
|
return name.lower()
|
|
|
|
@staticmethod
|
|
def getHexCollationKey(name: str) -> str:
|
|
return name.lower().encode("utf-8").hex()
|
|
|
|
@staticmethod
|
|
def createDbFromSqlStatements(context: Any, db_name: str, db_version: int, sql_statements: str):
|
|
from .database_sqlite import SQLiteDatabase
|
|
db_path = os.path.join(_app_data_dir, "databases", db_name)
|
|
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
|
db = SQLiteDatabase.openOrCreateDatabase(db_path, None)
|
|
for stmt in sql_statements.split(";"):
|
|
stmt = stmt.strip()
|
|
if stmt:
|
|
try:
|
|
db.execSQL(stmt)
|
|
except Exception:
|
|
pass
|
|
return db
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# DatabaseUtilsInsertHelper
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class DatabaseUtilsInsertHelper(Stub):
|
|
"""DatabaseUtils.InsertHelper — 插入辅助类"""
|
|
|
|
TABLE_INFO_PRAGMA_COLUMNNAME_INDEX = 1
|
|
|
|
def __init__(self, db: Any, table_name: str):
|
|
super().__init__()
|
|
self._db = db
|
|
self._table_name = table_name
|
|
|
|
def getTableName(self) -> str:
|
|
return self._table_name
|
|
|
|
def prepareForInsert(self) -> int:
|
|
return 0
|
|
|
|
def prepareForReplace(self) -> int:
|
|
return 0
|
|
|
|
def bind(self, index: int, value: Any):
|
|
pass
|
|
|
|
def execute(self) -> int:
|
|
return 0
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# Helper
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class IllegalArgumentException(RuntimeError):
|
|
"""IllegalArgumentException — 非法参数异常"""
|
|
pass
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# __all__
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
__all__ = [
|
|
"Cursor",
|
|
"MatrixCursor",
|
|
"AbstractCursor",
|
|
"AbstractWindowedCursor",
|
|
"ContentObservable",
|
|
"ContentObserver",
|
|
"DataSetObservable",
|
|
"DatabaseErrorHandler",
|
|
"DatabaseUtils",
|
|
"DefaultDatabaseErrorHandler",
|
|
"CharArrayBuffer",
|
|
"CursorJoiner",
|
|
"CursorWindow",
|
|
"CursorWrapper",
|
|
"CrossProcessCursor",
|
|
"CrossProcessCursorWrapper",
|
|
"DataSetObserver",
|
|
"MergeCursor",
|
|
"SQLException",
|
|
"CursorIndexOutOfBoundsException",
|
|
"DatabaseUtilsInsertHelper",
|
|
] |