| 1 | """Load libwitdb and bind C ABI (ADR-0003).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | import os |
| 6 | import platform |
| 7 | import sys |
| 8 | from ctypes import ( |
| 9 | CDLL, |
| 10 | POINTER, |
| 11 | c_char_p, |
| 12 | c_int32, |
| 13 | c_int64, |
| 14 | c_size_t, |
| 15 | c_uint8, |
| 16 | c_uint32, |
| 17 | c_void_p, |
| 18 | create_string_buffer, |
| 19 | ) |
| 20 | from functools import lru_cache |
| 21 | from pathlib import Path |
| 22 | |
| 23 | WITDB_ABI_VERSION = 1 |
| 24 | |
| 25 | WITDB_OK = 0 |
| 26 | WITDB_INVALID_ARGUMENT = 1 |
| 27 | WITDB_NOT_FOUND = 2 |
| 28 | WITDB_PASSWORD_REQUIRED = 3 |
| 29 | WITDB_WRONG_PASSWORD = 4 |
| 30 | WITDB_CONFIG_MISMATCH = 5 |
| 31 | WITDB_UNKNOWN_PROVIDER = 6 |
| 32 | WITDB_TXN_NOT_SUPPORTED = 7 |
| 33 | WITDB_TXN_ACTIVE = 8 |
| 34 | WITDB_STORE_ERROR = 9 |
| 35 | WITDB_INVALID_HANDLE = 10 |
| 36 | WITDB_SQL_ERROR = 11 |
| 37 | |
| 38 | |
| 39 | class WitDbNativeLib: |
| 40 | def __init__(self, dll: CDLL) -> None: |
| 41 | self._dll = dll |
| 42 | self._configure() |
| 43 | |
| 44 | def _configure(self) -> None: |
| 45 | d = self._dll |
| 46 | d.witdb_abi_version.argtypes = [] |
| 47 | d.witdb_abi_version.restype = c_uint32 |
| 48 | |
| 49 | d.witdb_last_error_message.argtypes = [] |
| 50 | d.witdb_last_error_message.restype = c_char_p |
| 51 | |
| 52 | d.witdb_open.argtypes = [c_char_p, c_char_p, c_uint8, POINTER(c_size_t)] |
| 53 | d.witdb_open.restype = c_uint32 |
| 54 | |
| 55 | d.witdb_close.argtypes = [c_size_t] |
| 56 | d.witdb_close.restype = c_uint32 |
| 57 | |
| 58 | d.witdb_get.argtypes = [ |
| 59 | c_size_t, |
| 60 | POINTER(c_uint8), |
| 61 | c_uint32, |
| 62 | POINTER(POINTER(c_uint8)), |
| 63 | POINTER(c_uint32), |
| 64 | ] |
| 65 | d.witdb_get.restype = c_uint32 |
| 66 | |
| 67 | d.witdb_put.argtypes = [ |
| 68 | c_size_t, |
| 69 | POINTER(c_uint8), |
| 70 | c_uint32, |
| 71 | POINTER(c_uint8), |
| 72 | c_uint32, |
| 73 | ] |
| 74 | d.witdb_put.restype = c_uint32 |
| 75 | |
| 76 | d.witdb_delete.argtypes = [ |
| 77 | c_size_t, |
| 78 | POINTER(c_uint8), |
| 79 | c_uint32, |
| 80 | POINTER(c_uint8), |
| 81 | ] |
| 82 | d.witdb_delete.restype = c_uint32 |
| 83 | |
| 84 | d.witdb_txn_begin.argtypes = [c_size_t, POINTER(c_size_t)] |
| 85 | d.witdb_txn_begin.restype = c_uint32 |
| 86 | |
| 87 | d.witdb_txn_commit.argtypes = [c_size_t] |
| 88 | d.witdb_txn_commit.restype = c_uint32 |
| 89 | |
| 90 | d.witdb_txn_rollback.argtypes = [c_size_t] |
| 91 | d.witdb_txn_rollback.restype = c_uint32 |
| 92 | |
| 93 | d.witdb_txn_get.argtypes = [ |
| 94 | c_size_t, |
| 95 | POINTER(c_uint8), |
| 96 | c_uint32, |
| 97 | POINTER(POINTER(c_uint8)), |
| 98 | POINTER(c_uint32), |
| 99 | ] |
| 100 | d.witdb_txn_get.restype = c_uint32 |
| 101 | |
| 102 | d.witdb_txn_put.argtypes = [ |
| 103 | c_size_t, |
| 104 | POINTER(c_uint8), |
| 105 | c_uint32, |
| 106 | POINTER(c_uint8), |
| 107 | c_uint32, |
| 108 | ] |
| 109 | d.witdb_txn_put.restype = c_uint32 |
| 110 | |
| 111 | d.witdb_txn_delete.argtypes = [ |
| 112 | c_size_t, |
| 113 | POINTER(c_uint8), |
| 114 | c_uint32, |
| 115 | POINTER(c_uint8), |
| 116 | ] |
| 117 | d.witdb_txn_delete.restype = c_uint32 |
| 118 | |
| 119 | d.witdb_buffer_free.argtypes = [c_void_p] |
| 120 | d.witdb_buffer_free.restype = None |
| 121 | |
| 122 | if hasattr(d, "witdb_sql_exec"): |
| 123 | d.witdb_sql_exec.argtypes = [ |
| 124 | c_size_t, |
| 125 | c_char_p, |
| 126 | c_char_p, |
| 127 | POINTER(c_int64), |
| 128 | POINTER(c_int32), |
| 129 | ] |
| 130 | d.witdb_sql_exec.restype = c_uint32 |
| 131 | |
| 132 | d.witdb_sql_query.argtypes = [ |
| 133 | c_size_t, |
| 134 | c_char_p, |
| 135 | c_char_p, |
| 136 | POINTER(POINTER(c_uint8)), |
| 137 | POINTER(c_uint32), |
| 138 | ] |
| 139 | d.witdb_sql_query.restype = c_uint32 |
| 140 | |
| 141 | d.witdb_sql_commit.argtypes = [c_size_t] |
| 142 | d.witdb_sql_commit.restype = c_uint32 |
| 143 | |
| 144 | d.witdb_sql_rollback.argtypes = [c_size_t] |
| 145 | d.witdb_sql_rollback.restype = c_uint32 |
| 146 | |
| 147 | @property |
| 148 | def dll(self) -> CDLL: |
| 149 | return self._dll |
| 150 | |
| 151 | |
| 152 | def _default_search_paths() -> list[Path]: |
| 153 | root = Path(__file__).resolve().parents[4] |
| 154 | rid = _runtime_rid() |
| 155 | candidates: list[Path] = [] |
| 156 | env = os.environ.get("WITDB_NATIVE_PATH") |
| 157 | if env: |
| 158 | candidates.append(Path(env)) |
| 159 | for tfm in ("net10.0", "net9.0"): |
| 160 | candidates.append( |
| 161 | root |
| 162 | / "witdatabase" |
| 163 | / "Sources" |
| 164 | / "Core" |
| 165 | / "OutWit.Database.Native" |
| 166 | / "bin" |
| 167 | / "Release" |
| 168 | / tfm |
| 169 | / rid |
| 170 | / "publish" |
| 171 | / _dll_name() |
| 172 | ) |
| 173 | candidates.append(root / "native" / rid / _dll_name()) |
| 174 | return candidates |
| 175 | |
| 176 | |
| 177 | def _runtime_rid() -> str: |
| 178 | system = sys.platform |
| 179 | machine = platform.machine().lower() |
| 180 | if system == "win32": |
| 181 | return "win-x64" if machine in {"amd64", "x86_64"} else f"win-{machine}" |
| 182 | if system == "darwin": |
| 183 | return "osx-arm64" if machine == "arm64" else "osx-x64" |
| 184 | return "linux-x64" if machine in {"amd64", "x86_64"} else f"linux-{machine}" |
| 185 | |
| 186 | |
| 187 | def _dll_name() -> str: |
| 188 | if sys.platform == "win32": |
| 189 | return "witdb.dll" |
| 190 | if sys.platform == "darwin": |
| 191 | return "libwitdb.dylib" |
| 192 | return "libwitdb.so" |
| 193 | |
| 194 | |
| 195 | @lru_cache(maxsize=1) |
| 196 | def load_native_lib() -> WitDbNativeLib | None: |
| 197 | for path in _default_search_paths(): |
| 198 | try: |
| 199 | if path.is_file(): |
| 200 | lib = WitDbNativeLib(CDLL(str(path))) |
| 201 | elif path.is_dir(): |
| 202 | full = path / _dll_name() |
| 203 | if not full.is_file(): |
| 204 | continue |
| 205 | lib = WitDbNativeLib(CDLL(str(full))) |
| 206 | else: |
| 207 | continue |
| 208 | if lib.dll.witdb_abi_version() != WITDB_ABI_VERSION: |
| 209 | continue |
| 210 | return lib |
| 211 | except OSError: |
| 212 | continue |
| 213 | return None |
| 214 | |
| 215 | |
| 216 | def bytes_to_buf(data: bytes): |
| 217 | buf = create_string_buffer(data, len(data)) |
| 218 | return buf, len(data) |
| 219 | |