sqlite7 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sqlite7/__init__.py ADDED
@@ -0,0 +1,87 @@
1
+ """
2
+ This module was written by Joumaico Maulas. It provides an SQL
3
+ interface inspired by the DB-API 2.0 specification described by PEP 249,
4
+ and requires the third-party SQLite library.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from ._native import (
13
+ Binary,
14
+ PARSE_COLNAMES,
15
+ PARSE_DECLTYPES,
16
+ Row,
17
+ complete_statement,
18
+ register_adapter,
19
+ register_converter,
20
+ )
21
+ from .async_database import AsyncDatabase, AsyncTable
22
+ from .exc import (
23
+ ConfigurationError,
24
+ ConnectionClosedError,
25
+ DataError,
26
+ DatabaseError,
27
+ Error,
28
+ IntegrityError,
29
+ InterfaceError,
30
+ InternalError,
31
+ InvalidIdentifierError,
32
+ NotSupportedError,
33
+ OperationalError,
34
+ ProgrammingError,
35
+ SQLite7Error,
36
+ ValidationError,
37
+ Warning,
38
+ )
39
+ from .result import RowDict, StatementResult
40
+ from .database import Database, Table
41
+
42
+ __all__ = [
43
+ "AsyncDatabase",
44
+ "AsyncTable",
45
+ "ConfigurationError",
46
+ "ConnectionClosedError",
47
+ "DataError",
48
+ "Database",
49
+ "DatabaseError",
50
+ "Error",
51
+ "IntegrityError",
52
+ "InterfaceError",
53
+ "InternalError",
54
+ "InvalidIdentifierError",
55
+ "NotSupportedError",
56
+ "OperationalError",
57
+ "ProgrammingError",
58
+ "RowDict",
59
+ "SQLite7Error",
60
+ "StatementResult",
61
+ "Table",
62
+ "ValidationError",
63
+ "Warning",
64
+ "connect",
65
+ "open_db",
66
+ "connect_async",
67
+ "open_async",
68
+ "Binary",
69
+ "Row",
70
+ "PARSE_COLNAMES",
71
+ "PARSE_DECLTYPES",
72
+ "register_adapter",
73
+ "register_converter",
74
+ "complete_statement",
75
+ ]
76
+
77
+
78
+ def connect(path: str | Path, **kwargs: Any) -> Database:
79
+ return Database(path, **kwargs)
80
+
81
+
82
+ def connect_async(path: str | Path, **kwargs: Any) -> AsyncDatabase:
83
+ return AsyncDatabase(path, **kwargs)
84
+
85
+
86
+ open_db = connect
87
+ open_async = connect_async
sqlite7/_native.py ADDED
@@ -0,0 +1,446 @@
1
+ from __future__ import annotations
2
+
3
+ import ctypes
4
+ import ctypes.util
5
+ from collections import OrderedDict
6
+ from collections.abc import Sequence
7
+ from pathlib import Path
8
+ from typing import Any, Callable
9
+
10
+ from . import exc
11
+ from .result import RowDict, StatementResult
12
+
13
+ SQLITE_OK = 0
14
+ SQLITE_ERROR = 1
15
+ SQLITE_INTERNAL = 2
16
+ SQLITE_PERM = 3
17
+ SQLITE_ABORT = 4
18
+ SQLITE_BUSY = 5
19
+ SQLITE_LOCKED = 6
20
+ SQLITE_NOMEM = 7
21
+ SQLITE_READONLY = 8
22
+ SQLITE_INTERRUPT = 9
23
+ SQLITE_IOERR = 10
24
+ SQLITE_CORRUPT = 11
25
+ SQLITE_NOTFOUND = 12
26
+ SQLITE_FULL = 13
27
+ SQLITE_CANTOPEN = 14
28
+ SQLITE_PROTOCOL = 15
29
+ SQLITE_EMPTY = 16
30
+ SQLITE_SCHEMA = 17
31
+ SQLITE_TOOBIG = 18
32
+ SQLITE_CONSTRAINT = 19
33
+ SQLITE_MISMATCH = 20
34
+ SQLITE_MISUSE = 21
35
+ SQLITE_NOLFS = 22
36
+ SQLITE_AUTH = 23
37
+ SQLITE_FORMAT = 24
38
+ SQLITE_RANGE = 25
39
+ SQLITE_NOTADB = 26
40
+ SQLITE_NOTICE = 27
41
+ SQLITE_WARNING = 28
42
+ SQLITE_ROW = 100
43
+ SQLITE_DONE = 101
44
+
45
+ SQLITE_INTEGER = 1
46
+ SQLITE_FLOAT = 2
47
+ SQLITE_TEXT = 3
48
+ SQLITE_BLOB = 4
49
+ SQLITE_NULL = 5
50
+
51
+ SQLITE_OPEN_READWRITE = 0x00000002
52
+ SQLITE_OPEN_CREATE = 0x00000004
53
+ SQLITE_OPEN_URI = 0x00000040
54
+ SQLITE_OPEN_NOMUTEX = 0x00008000
55
+ SQLITE_OPEN_FULLMUTEX = 0x00010000
56
+
57
+ SQLITE_TRANSIENT = ctypes.c_void_p(-1)
58
+
59
+ ERR_NAMES = {
60
+ SQLITE_OK: "SQLITE_OK",
61
+ SQLITE_ERROR: "SQLITE_ERROR",
62
+ SQLITE_INTERNAL: "SQLITE_INTERNAL",
63
+ SQLITE_PERM: "SQLITE_PERM",
64
+ SQLITE_ABORT: "SQLITE_ABORT",
65
+ SQLITE_BUSY: "SQLITE_BUSY",
66
+ SQLITE_LOCKED: "SQLITE_LOCKED",
67
+ SQLITE_NOMEM: "SQLITE_NOMEM",
68
+ SQLITE_READONLY: "SQLITE_READONLY",
69
+ SQLITE_INTERRUPT: "SQLITE_INTERRUPT",
70
+ SQLITE_IOERR: "SQLITE_IOERR",
71
+ SQLITE_CORRUPT: "SQLITE_CORRUPT",
72
+ SQLITE_NOTFOUND: "SQLITE_NOTFOUND",
73
+ SQLITE_FULL: "SQLITE_FULL",
74
+ SQLITE_CANTOPEN: "SQLITE_CANTOPEN",
75
+ SQLITE_PROTOCOL: "SQLITE_PROTOCOL",
76
+ SQLITE_EMPTY: "SQLITE_EMPTY",
77
+ SQLITE_SCHEMA: "SQLITE_SCHEMA",
78
+ SQLITE_TOOBIG: "SQLITE_TOOBIG",
79
+ SQLITE_CONSTRAINT: "SQLITE_CONSTRAINT",
80
+ SQLITE_MISMATCH: "SQLITE_MISMATCH",
81
+ SQLITE_MISUSE: "SQLITE_MISUSE",
82
+ SQLITE_NOLFS: "SQLITE_NOLFS",
83
+ SQLITE_AUTH: "SQLITE_AUTH",
84
+ SQLITE_FORMAT: "SQLITE_FORMAT",
85
+ SQLITE_RANGE: "SQLITE_RANGE",
86
+ SQLITE_NOTADB: "SQLITE_NOTADB",
87
+ SQLITE_NOTICE: "SQLITE_NOTICE",
88
+ SQLITE_WARNING: "SQLITE_WARNING",
89
+ }
90
+
91
+
92
+ def _load_sqlite() -> ctypes.CDLL:
93
+ library = ctypes.util.find_library("sqlite3")
94
+ if not library:
95
+ raise RuntimeError("Could not locate libsqlite3")
96
+ return ctypes.CDLL(library)
97
+
98
+
99
+ _lib = _load_sqlite()
100
+
101
+ sqlite3_p = ctypes.c_void_p
102
+ sqlite3_stmt_p = ctypes.c_void_p
103
+
104
+ _lib.sqlite3_open_v2.argtypes = [ctypes.c_char_p, ctypes.POINTER(sqlite3_p), ctypes.c_int, ctypes.c_char_p]
105
+ _lib.sqlite3_open_v2.restype = ctypes.c_int
106
+ _lib.sqlite3_close_v2.argtypes = [sqlite3_p]
107
+ _lib.sqlite3_close_v2.restype = ctypes.c_int
108
+ _lib.sqlite3_errmsg.argtypes = [sqlite3_p]
109
+ _lib.sqlite3_errmsg.restype = ctypes.c_char_p
110
+ _lib.sqlite3_errcode.argtypes = [sqlite3_p]
111
+ _lib.sqlite3_errcode.restype = ctypes.c_int
112
+ _lib.sqlite3_extended_errcode.argtypes = [sqlite3_p]
113
+ _lib.sqlite3_extended_errcode.restype = ctypes.c_int
114
+ _lib.sqlite3_exec.argtypes = [sqlite3_p, ctypes.c_char_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.POINTER(ctypes.c_char_p)]
115
+ _lib.sqlite3_exec.restype = ctypes.c_int
116
+ _lib.sqlite3_prepare_v2.argtypes = [sqlite3_p, ctypes.c_char_p, ctypes.c_int, ctypes.POINTER(sqlite3_stmt_p), ctypes.POINTER(ctypes.c_char_p)]
117
+ _lib.sqlite3_prepare_v2.restype = ctypes.c_int
118
+ _lib.sqlite3_step.argtypes = [sqlite3_stmt_p]
119
+ _lib.sqlite3_step.restype = ctypes.c_int
120
+ _lib.sqlite3_finalize.argtypes = [sqlite3_stmt_p]
121
+ _lib.sqlite3_finalize.restype = ctypes.c_int
122
+ _lib.sqlite3_reset.argtypes = [sqlite3_stmt_p]
123
+ _lib.sqlite3_reset.restype = ctypes.c_int
124
+ _lib.sqlite3_clear_bindings.argtypes = [sqlite3_stmt_p]
125
+ _lib.sqlite3_clear_bindings.restype = ctypes.c_int
126
+ _lib.sqlite3_bind_parameter_count.argtypes = [sqlite3_stmt_p]
127
+ _lib.sqlite3_bind_parameter_count.restype = ctypes.c_int
128
+ _lib.sqlite3_bind_null.argtypes = [sqlite3_stmt_p, ctypes.c_int]
129
+ _lib.sqlite3_bind_null.restype = ctypes.c_int
130
+ _lib.sqlite3_bind_int64.argtypes = [sqlite3_stmt_p, ctypes.c_int, ctypes.c_longlong]
131
+ _lib.sqlite3_bind_int64.restype = ctypes.c_int
132
+ _lib.sqlite3_bind_double.argtypes = [sqlite3_stmt_p, ctypes.c_int, ctypes.c_double]
133
+ _lib.sqlite3_bind_double.restype = ctypes.c_int
134
+ _lib.sqlite3_bind_text.argtypes = [sqlite3_stmt_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p]
135
+ _lib.sqlite3_bind_text.restype = ctypes.c_int
136
+ _lib.sqlite3_bind_blob.argtypes = [sqlite3_stmt_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p]
137
+ _lib.sqlite3_bind_blob.restype = ctypes.c_int
138
+ _lib.sqlite3_column_count.argtypes = [sqlite3_stmt_p]
139
+ _lib.sqlite3_column_count.restype = ctypes.c_int
140
+ _lib.sqlite3_column_name.argtypes = [sqlite3_stmt_p, ctypes.c_int]
141
+ _lib.sqlite3_column_name.restype = ctypes.c_char_p
142
+ _lib.sqlite3_column_type.argtypes = [sqlite3_stmt_p, ctypes.c_int]
143
+ _lib.sqlite3_column_type.restype = ctypes.c_int
144
+ _lib.sqlite3_column_int64.argtypes = [sqlite3_stmt_p, ctypes.c_int]
145
+ _lib.sqlite3_column_int64.restype = ctypes.c_longlong
146
+ _lib.sqlite3_column_double.argtypes = [sqlite3_stmt_p, ctypes.c_int]
147
+ _lib.sqlite3_column_double.restype = ctypes.c_double
148
+ _lib.sqlite3_column_text.argtypes = [sqlite3_stmt_p, ctypes.c_int]
149
+ _lib.sqlite3_column_text.restype = ctypes.POINTER(ctypes.c_ubyte)
150
+ _lib.sqlite3_column_blob.argtypes = [sqlite3_stmt_p, ctypes.c_int]
151
+ _lib.sqlite3_column_blob.restype = ctypes.c_void_p
152
+ _lib.sqlite3_column_bytes.argtypes = [sqlite3_stmt_p, ctypes.c_int]
153
+ _lib.sqlite3_column_bytes.restype = ctypes.c_int
154
+ _lib.sqlite3_changes.argtypes = [sqlite3_p]
155
+ _lib.sqlite3_changes.restype = ctypes.c_int
156
+ _lib.sqlite3_total_changes.argtypes = [sqlite3_p]
157
+ _lib.sqlite3_total_changes.restype = ctypes.c_int
158
+ _lib.sqlite3_last_insert_rowid.argtypes = [sqlite3_p]
159
+ _lib.sqlite3_last_insert_rowid.restype = ctypes.c_longlong
160
+ _lib.sqlite3_get_autocommit.argtypes = [sqlite3_p]
161
+ _lib.sqlite3_get_autocommit.restype = ctypes.c_int
162
+ _lib.sqlite3_libversion.restype = ctypes.c_char_p
163
+ _lib.sqlite3_complete.argtypes = [ctypes.c_char_p]
164
+ _lib.sqlite3_complete.restype = ctypes.c_int
165
+
166
+
167
+ class Row(tuple):
168
+ pass
169
+
170
+
171
+ class Binary(bytes):
172
+ pass
173
+
174
+
175
+ PARSE_DECLTYPES = 0
176
+ PARSE_COLNAMES = 0
177
+
178
+
179
+ def register_adapter(*args: Any, **kwargs: Any) -> None:
180
+ return None
181
+
182
+
183
+ def register_converter(*args: Any, **kwargs: Any) -> None:
184
+ return None
185
+
186
+
187
+ def complete_statement(statement: str) -> bool:
188
+ return bool(_lib.sqlite3_complete(statement.encode("utf-8")))
189
+
190
+
191
+ class NativeCursor:
192
+ def __init__(self, description: list[tuple[str, None, None, None, None, None, None]]) -> None:
193
+ self.description = description
194
+
195
+
196
+ class NativeConnection:
197
+ def __init__(
198
+ self,
199
+ path: str | Path,
200
+ *,
201
+ uri: bool = False,
202
+ check_same_thread: bool = False,
203
+ row_factory: Callable[[NativeCursor, tuple[Any, ...]], Any] | None = None,
204
+ statement_cache_size: int = 128,
205
+ ) -> None:
206
+ self.path = str(path)
207
+ self.row_factory = row_factory
208
+ self._statement_cache_size = max(int(statement_cache_size), 0)
209
+ self._statement_cache: OrderedDict[str, NativeStatement] = OrderedDict()
210
+ self._db = sqlite3_p()
211
+ flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE
212
+ flags |= SQLITE_OPEN_FULLMUTEX if check_same_thread else SQLITE_OPEN_NOMUTEX
213
+ if uri:
214
+ flags |= SQLITE_OPEN_URI
215
+ rc = _lib.sqlite3_open_v2(self.path.encode("utf-8"), ctypes.byref(self._db), flags, None)
216
+ if rc != SQLITE_OK:
217
+ try:
218
+ raise sqlite_error_from_db(self._db, rc)
219
+ finally:
220
+ if self._db:
221
+ _lib.sqlite3_close_v2(self._db)
222
+ self._db = sqlite3_p()
223
+
224
+ @property
225
+ def handle(self) -> sqlite3_p:
226
+ if not self._db:
227
+ raise exc.ConnectionClosedError("The database connection is closed")
228
+ return self._db
229
+
230
+ def close(self) -> None:
231
+ if not self._db:
232
+ return
233
+ for statement in self._statement_cache.values():
234
+ statement.close()
235
+ self._statement_cache.clear()
236
+ rc = _lib.sqlite3_close_v2(self._db)
237
+ if rc != SQLITE_OK:
238
+ raise sqlite_error_from_db(self._db, rc)
239
+ self._db = sqlite3_p()
240
+
241
+ def execute(self, sql: str, params: Sequence[Any] | None = None) -> tuple[StatementResult, list[Any], list[tuple[str, None, None, None, None, None, None]]]:
242
+ statement = self._acquire_statement(sql)
243
+ try:
244
+ statement.bind(params or ())
245
+ rows = statement.fetch_all()
246
+ return statement.result(), rows, statement.description
247
+ finally:
248
+ self._release_statement(sql, statement)
249
+
250
+ def executemany(self, sql: str, seq_of_params: Sequence[Sequence[Any]]) -> StatementResult:
251
+ statement = self._acquire_statement(sql)
252
+ try:
253
+ total_changes_before = _lib.sqlite3_total_changes(self.handle)
254
+ lastrowid: int | None = None
255
+ for params in seq_of_params:
256
+ statement.bind(params)
257
+ statement.run_to_completion()
258
+ lastrowid = int(_lib.sqlite3_last_insert_rowid(self.handle))
259
+ statement.reset()
260
+ total_delta = _lib.sqlite3_total_changes(self.handle) - total_changes_before
261
+ return StatementResult(rowcount=total_delta, lastrowid=lastrowid)
262
+ finally:
263
+ self._release_statement(sql, statement)
264
+
265
+ def _acquire_statement(self, sql: str) -> "NativeStatement":
266
+ statement = self._statement_cache.pop(sql, None)
267
+ if statement is not None:
268
+ statement.reset()
269
+ return statement
270
+ return NativeStatement(self, sql)
271
+
272
+ def _release_statement(self, sql: str, statement: "NativeStatement") -> None:
273
+ statement.reset()
274
+ if self._statement_cache_size <= 0:
275
+ statement.close()
276
+ return
277
+ self._statement_cache[sql] = statement
278
+ self._statement_cache.move_to_end(sql)
279
+ while len(self._statement_cache) > self._statement_cache_size:
280
+ _, evicted = self._statement_cache.popitem(last=False)
281
+ evicted.close()
282
+
283
+ def exec_script(self, sql_script: str) -> None:
284
+ errmsg = ctypes.c_char_p()
285
+ rc = _lib.sqlite3_exec(self.handle, sql_script.encode("utf-8"), None, None, ctypes.byref(errmsg))
286
+ if rc != SQLITE_OK:
287
+ message = errmsg.value.decode("utf-8", errors="replace") if errmsg.value else self.errmsg()
288
+ raise sqlite_error_from_result(rc, message)
289
+
290
+ def errmsg(self) -> str:
291
+ return _lib.sqlite3_errmsg(self.handle).decode("utf-8", errors="replace")
292
+
293
+ @property
294
+ def total_changes(self) -> int:
295
+ return int(_lib.sqlite3_total_changes(self.handle))
296
+
297
+ @property
298
+ def in_transaction(self) -> bool:
299
+ return not bool(_lib.sqlite3_get_autocommit(self.handle))
300
+
301
+
302
+ class NativeStatement:
303
+ def __init__(self, connection: NativeConnection, sql: str) -> None:
304
+ self.connection = connection
305
+ self.sql = sql
306
+ self._stmt = sqlite3_stmt_p()
307
+ tail = ctypes.c_char_p()
308
+ rc = _lib.sqlite3_prepare_v2(connection.handle, sql.encode("utf-8"), -1, ctypes.byref(self._stmt), ctypes.byref(tail))
309
+ if rc != SQLITE_OK:
310
+ raise sqlite_error_from_db(connection.handle, rc)
311
+ self._keepalive: list[Any] = []
312
+ self.description = self._build_description()
313
+
314
+ def _build_description(self) -> list[tuple[str, None, None, None, None, None, None]]:
315
+ count = _lib.sqlite3_column_count(self._stmt)
316
+ return [(_lib.sqlite3_column_name(self._stmt, i).decode("utf-8"), None, None, None, None, None, None) for i in range(count)]
317
+
318
+ def bind(self, params: Sequence[Any]) -> None:
319
+ self.reset()
320
+ count = _lib.sqlite3_bind_parameter_count(self._stmt)
321
+ if len(params) != count:
322
+ raise exc.ProgrammingError(f"Expected {count} SQL parameters but received {len(params)}")
323
+ self._keepalive.clear()
324
+ for index, value in enumerate(params, start=1):
325
+ self._bind_one(index, value)
326
+
327
+ def _bind_one(self, index: int, value: Any) -> None:
328
+ if value is None:
329
+ rc = _lib.sqlite3_bind_null(self._stmt, index)
330
+ elif isinstance(value, bool):
331
+ rc = _lib.sqlite3_bind_int64(self._stmt, index, int(value))
332
+ elif isinstance(value, int):
333
+ rc = _lib.sqlite3_bind_int64(self._stmt, index, value)
334
+ elif isinstance(value, float):
335
+ rc = _lib.sqlite3_bind_double(self._stmt, index, value)
336
+ elif isinstance(value, (bytes, bytearray, memoryview, Binary)):
337
+ data = bytes(value)
338
+ buffer = ctypes.create_string_buffer(data)
339
+ self._keepalive.append(buffer)
340
+ rc = _lib.sqlite3_bind_blob(self._stmt, index, ctypes.cast(buffer, ctypes.c_void_p), len(data), SQLITE_TRANSIENT)
341
+ else:
342
+ encoded = str(value).encode("utf-8")
343
+ buffer = ctypes.create_string_buffer(encoded)
344
+ self._keepalive.append(buffer)
345
+ rc = _lib.sqlite3_bind_text(self._stmt, index, ctypes.cast(buffer, ctypes.c_char_p), len(encoded), SQLITE_TRANSIENT)
346
+ if rc != SQLITE_OK:
347
+ raise sqlite_error_from_db(self.connection.handle, rc)
348
+
349
+ def run_to_completion(self) -> None:
350
+ while True:
351
+ rc = _lib.sqlite3_step(self._stmt)
352
+ if rc == SQLITE_DONE:
353
+ return
354
+ if rc == SQLITE_ROW:
355
+ continue
356
+ raise sqlite_error_from_db(self.connection.handle, rc)
357
+
358
+ def fetch_all(self) -> list[Any]:
359
+ rows: list[Any] = []
360
+ while True:
361
+ rc = _lib.sqlite3_step(self._stmt)
362
+ if rc == SQLITE_ROW:
363
+ row_tuple = self._read_row()
364
+ if self.connection.row_factory is None:
365
+ rows.append({desc[0]: row_tuple[i] for i, desc in enumerate(self.description)})
366
+ else:
367
+ rows.append(self.connection.row_factory(NativeCursor(self.description), row_tuple))
368
+ elif rc == SQLITE_DONE:
369
+ break
370
+ else:
371
+ raise sqlite_error_from_db(self.connection.handle, rc)
372
+ return rows
373
+
374
+ def _read_row(self) -> tuple[Any, ...]:
375
+ values: list[Any] = []
376
+ for i in range(len(self.description)):
377
+ col_type = _lib.sqlite3_column_type(self._stmt, i)
378
+ if col_type == SQLITE_NULL:
379
+ values.append(None)
380
+ elif col_type == SQLITE_INTEGER:
381
+ values.append(int(_lib.sqlite3_column_int64(self._stmt, i)))
382
+ elif col_type == SQLITE_FLOAT:
383
+ values.append(float(_lib.sqlite3_column_double(self._stmt, i)))
384
+ elif col_type == SQLITE_TEXT:
385
+ size = _lib.sqlite3_column_bytes(self._stmt, i)
386
+ ptr = _lib.sqlite3_column_text(self._stmt, i)
387
+ values.append(ctypes.string_at(ptr, size).decode("utf-8", errors="replace"))
388
+ elif col_type == SQLITE_BLOB:
389
+ size = _lib.sqlite3_column_bytes(self._stmt, i)
390
+ ptr = _lib.sqlite3_column_blob(self._stmt, i)
391
+ values.append(ctypes.string_at(ptr, size))
392
+ else:
393
+ values.append(None)
394
+ return tuple(values)
395
+
396
+ def result(self) -> StatementResult:
397
+ return StatementResult(
398
+ rowcount=int(_lib.sqlite3_changes(self.connection.handle)),
399
+ lastrowid=int(_lib.sqlite3_last_insert_rowid(self.connection.handle)),
400
+ )
401
+
402
+ def reset(self) -> None:
403
+ if not self._stmt:
404
+ return
405
+ _lib.sqlite3_reset(self._stmt)
406
+ _lib.sqlite3_clear_bindings(self._stmt)
407
+ self._keepalive.clear()
408
+
409
+ def close(self) -> None:
410
+ if self._stmt:
411
+ _lib.sqlite3_finalize(self._stmt)
412
+ self._stmt = sqlite3_stmt_p()
413
+
414
+
415
+ def sqlite_error_from_result(result_code: int, message: str) -> exc.Error:
416
+ details = exc.SQLiteErrorDetails(errorcode=result_code, errorname=ERR_NAMES.get(result_code))
417
+ base = result_code & 0xFF
418
+ if base in {SQLITE_CONSTRAINT}:
419
+ target = exc.IntegrityError
420
+ elif base in {SQLITE_INTERNAL}:
421
+ target = exc.InternalError
422
+ elif base in {SQLITE_MISUSE, SQLITE_RANGE, SQLITE_SCHEMA}:
423
+ target = exc.ProgrammingError
424
+ elif base in {SQLITE_BUSY, SQLITE_LOCKED, SQLITE_CANTOPEN, SQLITE_IOERR, SQLITE_READONLY, SQLITE_INTERRUPT}:
425
+ target = exc.OperationalError
426
+ elif base in {SQLITE_NOTADB, SQLITE_CORRUPT, SQLITE_FULL, SQLITE_ERROR}:
427
+ target = exc.DatabaseError
428
+ elif base in {SQLITE_MISMATCH, SQLITE_TOOBIG}:
429
+ target = exc.DataError
430
+ elif base in {SQLITE_NOTFOUND, SQLITE_AUTH, SQLITE_FORMAT, SQLITE_NOLFS}:
431
+ target = exc.NotSupportedError
432
+ else:
433
+ target = exc.Error
434
+ return target(message, details=details)
435
+
436
+
437
+ def sqlite_error_from_db(db: sqlite3_p, fallback_code: int | None = None) -> exc.Error:
438
+ if db:
439
+ code = int(_lib.sqlite3_extended_errcode(db))
440
+ message = _lib.sqlite3_errmsg(db).decode("utf-8", errors="replace")
441
+ return sqlite_error_from_result(code or fallback_code or SQLITE_ERROR, message)
442
+ return sqlite_error_from_result(fallback_code or SQLITE_ERROR, "SQLite error")
443
+
444
+
445
+ def sqlite_version() -> str:
446
+ return _lib.sqlite3_libversion().decode("ascii")