Forge
pythonf8555a15
1"""NativeDatabase — ctypes implementation of pywitdb.Database."""
2
3from __future__ import annotations
4
5from collections.abc import Iterator
6from contextlib import contextmanager
7from ctypes import (
8 POINTER,
9 addressof,
10 byref,
11 cast,
12 c_size_t,
13 c_uint8,
14 c_uint32,
15 create_string_buffer,
16 string_at,
17)
18
19from pywitdb._api import Database
20from pywitdb._native.lib import (
21 WITDB_OK,
22 WitDbNativeLib,
23 bytes_to_buf,
24 load_native_lib,
25)
26from pywitdb.exceptions import WitDbStoreError
27
28
29def native_available() -> bool:
30 return load_native_lib() is not None
31
32
33class NativeDatabase(Database):
34 def __init__(self, lib: WitDbNativeLib, handle: int, path: str) -> None:
35 self._lib = lib
36 self._handle = handle
37 self._path = path
38 self._closed = False
39
40 def _check(self, code: int) -> None:
41 if code == WITDB_OK:
42 return
43 msg = ""
44 try:
45 raw = self._lib.dll.witdb_last_error_message()
46 if raw:
47 msg = raw.decode("utf-8", errors="replace")
48 except Exception:
49 pass
50 raise WitDbStoreError(f"witdb status={code}" + (f": {msg}" if msg else ""))
51
52 def get(self, key: bytes) -> bytes | None:
53 key_buf, key_len = bytes_to_buf(key)
54 out_ptr = POINTER(c_uint8)()
55 out_len = c_uint32()
56 self._check(
57 self._lib.dll.witdb_get(
58 c_size_t(self._handle),
59 cast(addressof(key_buf), POINTER(c_uint8)),
60 c_uint32(key_len),
61 byref(out_ptr),
62 byref(out_len),
63 )
64 )
65 if not out_ptr:
66 return None
67 try:
68 return string_at(out_ptr, out_len.value)
69 finally:
70 self._lib.dll.witdb_buffer_free(out_ptr)
71
72 def put(self, key: bytes, value: bytes) -> None:
73 key_buf, key_len = bytes_to_buf(key)
74 val_buf, val_len = bytes_to_buf(value)
75 self._check(
76 self._lib.dll.witdb_put(
77 c_size_t(self._handle),
78 cast(addressof(key_buf), POINTER(c_uint8)),
79 c_uint32(key_len),
80 cast(addressof(val_buf), POINTER(c_uint8)),
81 c_uint32(val_len),
82 )
83 )
84
85 def delete(self, key: bytes) -> bool:
86 key_buf, key_len = bytes_to_buf(key)
87 deleted = c_uint8()
88 self._check(
89 self._lib.dll.witdb_delete(
90 c_size_t(self._handle),
91 cast(addressof(key_buf), POINTER(c_uint8)),
92 c_uint32(key_len),
93 byref(deleted),
94 )
95 )
96 return bool(deleted.value)
97
98 @contextmanager
99 def transaction(self) -> Iterator[None]:
100 txn_handle = c_size_t()
101 self._check(
102 self._lib.dll.witdb_txn_begin(c_size_t(self._handle), byref(txn_handle))
103 )
104 try:
105 yield None
106 self._check(self._lib.dll.witdb_txn_commit(txn_handle))
107 except Exception:
108 self._lib.dll.witdb_txn_rollback(txn_handle)
109 raise
110
111 def close(self) -> None:
112 if self._closed:
113 return
114 self._closed = True
115 self._check(self._lib.dll.witdb_close(c_size_t(self._handle)))
116
117
118def open_native(
119 path: str,
120 *,
121 password: str | None = None,
122 create: bool = True,
123) -> NativeDatabase:
124 lib = load_native_lib()
125 if lib is None:
126 raise WitDbStoreError(
127 "libwitdb not found; set WITDB_NATIVE_PATH or build OutWit.Database.Native"
128 )
129
130 handle = c_size_t()
131 code = lib.dll.witdb_open(
132 path.encode("utf-8"),
133 password.encode("utf-8") if password is not None else None,
134 c_uint8(1 if create else 0),
135 byref(handle),
136 )
137 if code != WITDB_OK:
138 msg = ""
139 raw = lib.dll.witdb_last_error_message()
140 if raw:
141 msg = raw.decode("utf-8", errors="replace")
142 raise WitDbStoreError(f"witdb_open failed ({code})" + (f": {msg}" if msg else ""))
143
144 return NativeDatabase(lib, int(handle.value), path)
145
View only · write via MCP/CIDE