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 +87 -0
- sqlite7/_native.py +446 -0
- sqlite7/async_database.py +312 -0
- sqlite7/database.py +579 -0
- sqlite7/dialect.py +38 -0
- sqlite7/exc.py +101 -0
- sqlite7/helpers.py +77 -0
- sqlite7/result.py +22 -0
- sqlite7/transaction.py +23 -0
- sqlite7-1.0.0.dist-info/METADATA +72 -0
- sqlite7-1.0.0.dist-info/RECORD +14 -0
- sqlite7-1.0.0.dist-info/WHEEL +5 -0
- sqlite7-1.0.0.dist-info/licenses/LICENSE +21 -0
- sqlite7-1.0.0.dist-info/top_level.txt +1 -0
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")
|