arrowsql 0.1.0__cp313-cp313-win_amd64.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.
- arrowsql/__init__.py +653 -0
- arrowsql/_rust_auth.py +170 -0
- arrowsql/_server_sqlstate.py +11 -0
- arrowsql/_utf8_strings.py +78 -0
- arrowsql/agent.py +620 -0
- arrowsql/aio.py +533 -0
- arrowsql/arrow_parallel.py +464 -0
- arrowsql/async_query/__init__.py +40 -0
- arrowsql/async_query/_connection_context.py +39 -0
- arrowsql/async_query/_native.py +24 -0
- arrowsql/async_query/async_connection.py +160 -0
- arrowsql/async_query/async_cursor.py +225 -0
- arrowsql/async_query/async_execute.py +113 -0
- arrowsql/async_query/async_fetch.py +153 -0
- arrowsql/async_query/exception_translator.py +132 -0
- arrowsql/auth.py +913 -0
- arrowsql/bulk_parallel.py +385 -0
- arrowsql/connection.py +2377 -0
- arrowsql/connection_string_builder.py +114 -0
- arrowsql/connection_string_parser.py +417 -0
- arrowsql/constants.py +701 -0
- arrowsql/cursor.py +4516 -0
- arrowsql/db_connection.py +98 -0
- arrowsql/ddbc_bindings.cp313-win_amd64.pyd +0 -0
- arrowsql/ddbc_bindings.pyi +283 -0
- arrowsql/decimal_config.py +98 -0
- arrowsql/exceptions.py +378 -0
- arrowsql/helpers.py +436 -0
- arrowsql/logging.py +700 -0
- arrowsql/mssql_python.pyi +579 -0
- arrowsql/odbc_provider.py +269 -0
- arrowsql/parameter_helper.py +442 -0
- arrowsql/perf_timer.py +286 -0
- arrowsql/pooling.py +147 -0
- arrowsql/py.typed +1 -0
- arrowsql/row.py +462 -0
- arrowsql/type.py +160 -0
- arrowsql-0.1.0.dist-info/METADATA +146 -0
- arrowsql-0.1.0.dist-info/RECORD +45 -0
- arrowsql-0.1.0.dist-info/WHEEL +4 -0
- arrowsql-0.1.0.dist-info/entry_points.txt +2 -0
- arrowsql-0.1.0.dist-info/licenses/LICENSE +21 -0
- arrowsql-0.1.0.dist-info/licenses/LICENSE.upstream-mssql-python +47 -0
- arrowsql-0.1.0.dist-info/sboms/ddbc_bindings.cyclonedx.json +5195 -0
- mssql_python/__init__.py +39 -0
arrowsql/aio.py
ADDED
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
"""Native asyncio API on the Rust core.
|
|
2
|
+
|
|
3
|
+
from arrowsql import aio
|
|
4
|
+
|
|
5
|
+
async with await aio.connect(conn_str, autocommit=True) as conn:
|
|
6
|
+
cur = conn.cursor()
|
|
7
|
+
await cur.execute("SELECT id, name FROM t WHERE id > ?", 10)
|
|
8
|
+
rows = await cur.fetchall()
|
|
9
|
+
|
|
10
|
+
pool = await aio.create_pool(conn_str, min_size=2, max_size=16)
|
|
11
|
+
rows = await pool.fetch("SELECT * FROM t WHERE id = ?", 42)
|
|
12
|
+
|
|
13
|
+
Every operation runs on the Rust core's tokio runtime with the GIL released;
|
|
14
|
+
results come back to the event loop through one completion hub per loop.
|
|
15
|
+
Cancelling the awaiting task (``task.cancel()``, ``asyncio.wait_for``
|
|
16
|
+
timeouts) cancels the query on the server too. See docs/ASYNC.md.
|
|
17
|
+
|
|
18
|
+
Unix only (the hub needs ``loop.add_reader``): on other platforms the module
|
|
19
|
+
imports, but ``connect`` and ``create_pool`` raise ``NotSupportedError``.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import asyncio
|
|
23
|
+
import weakref
|
|
24
|
+
from typing import Any, Optional, Sequence
|
|
25
|
+
|
|
26
|
+
from arrowsql import ddbc_bindings as _core
|
|
27
|
+
from arrowsql.constants import ConstantsDDBC as _const
|
|
28
|
+
from arrowsql.cursor import Cursor as _SyncCursor
|
|
29
|
+
from arrowsql.exceptions import InterfaceError, NotSupportedError, ProgrammingError
|
|
30
|
+
from arrowsql.helpers import get_settings
|
|
31
|
+
|
|
32
|
+
__all__ = ["connect", "create_pool", "AsyncConnection", "AsyncCursor", "Pool"]
|
|
33
|
+
|
|
34
|
+
_hubs: "weakref.WeakKeyDictionary[asyncio.AbstractEventLoop, Any]" = weakref.WeakKeyDictionary()
|
|
35
|
+
_SQL_NULLABLE = _const.SQL_NULLABLE.value
|
|
36
|
+
_map_type = _SyncCursor._map_data_type
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class _Shape:
|
|
40
|
+
"""Description and column maps of one result shape (cached by the hub)."""
|
|
41
|
+
|
|
42
|
+
__slots__ = ("description", "column_map", "column_map_lower", "column_names")
|
|
43
|
+
|
|
44
|
+
def __init__(self, columns):
|
|
45
|
+
lowercase = get_settings().lowercase
|
|
46
|
+
description = []
|
|
47
|
+
names = []
|
|
48
|
+
for name, sql_type, size, digits, nullable in columns:
|
|
49
|
+
if lowercase:
|
|
50
|
+
name = name.lower()
|
|
51
|
+
names.append(name)
|
|
52
|
+
description.append(
|
|
53
|
+
(name, _map_type(None, sql_type), None, size, size, digits, nullable == _SQL_NULLABLE)
|
|
54
|
+
)
|
|
55
|
+
self.description = description
|
|
56
|
+
self.column_names = tuple(names)
|
|
57
|
+
self.column_map = {name: i for i, name in enumerate(names)}
|
|
58
|
+
self.column_map_lower = (
|
|
59
|
+
None if lowercase else {name.lower(): i for i, name in enumerate(names)}
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _require_native() -> None:
|
|
64
|
+
# RUST-PORT: the native hub is compiled for Unix only (src/lib.rs).
|
|
65
|
+
if not hasattr(_core, "AioHub"):
|
|
66
|
+
message = "arrowsql.aio is only available on Unix platforms (Linux, macOS)"
|
|
67
|
+
raise NotSupportedError(driver_error=message, ddbc_error=message)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _hub():
|
|
71
|
+
_require_native()
|
|
72
|
+
loop = asyncio.get_running_loop()
|
|
73
|
+
hub = _hubs.get(loop)
|
|
74
|
+
if hub is None:
|
|
75
|
+
# The hub holds the loop weakly: the loop owns the hub through add_reader.
|
|
76
|
+
hub = _core.AioHub(weakref.ref(loop), _Shape)
|
|
77
|
+
loop.add_reader(hub.fileno(), hub.drain)
|
|
78
|
+
_hubs[loop] = hub
|
|
79
|
+
return hub
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def _wait(conn, op):
|
|
83
|
+
"""Await `op` = (future, request id); cancel the request on cancellation."""
|
|
84
|
+
future, request = op
|
|
85
|
+
try:
|
|
86
|
+
return await future
|
|
87
|
+
except asyncio.CancelledError:
|
|
88
|
+
conn.cancel(request)
|
|
89
|
+
raise
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _params(params: Sequence[Any]):
|
|
93
|
+
"""execute(sql, a, b) and execute(sql, (a, b)) both work."""
|
|
94
|
+
if len(params) == 1 and isinstance(params[0], (list, tuple)):
|
|
95
|
+
return params[0]
|
|
96
|
+
return params
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class AsyncCursor:
|
|
100
|
+
"""DB-API style cursor; every method that talks to the server is a coroutine."""
|
|
101
|
+
|
|
102
|
+
__slots__ = (
|
|
103
|
+
"_conn", "_c", "_id", "_batch", "_rows", "_pos", "_done",
|
|
104
|
+
"description", "rowcount", "messages", "arraysize", "_closed",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def __init__(self, connection: "AsyncConnection"):
|
|
108
|
+
self._conn = connection
|
|
109
|
+
self._c = connection._c
|
|
110
|
+
self._id = self._c.cursor()
|
|
111
|
+
self._batch = None # rows read ahead by execute (converted lazily)
|
|
112
|
+
self._rows = [] # rows fetched by fetchone in chunks
|
|
113
|
+
self._pos = 0
|
|
114
|
+
self._done = True
|
|
115
|
+
self.description = None
|
|
116
|
+
self.rowcount = -1
|
|
117
|
+
self.messages = []
|
|
118
|
+
self.arraysize = 1
|
|
119
|
+
self._closed = False
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def connection(self) -> "AsyncConnection":
|
|
123
|
+
return self._conn
|
|
124
|
+
|
|
125
|
+
def _install(self, result) -> None:
|
|
126
|
+
shape, batch, done, rowcount, messages = result
|
|
127
|
+
self._batch = batch
|
|
128
|
+
self._rows = []
|
|
129
|
+
self._pos = 0
|
|
130
|
+
self._done = done
|
|
131
|
+
self.description = shape.description if shape is not None else None
|
|
132
|
+
self.rowcount = rowcount
|
|
133
|
+
self.messages = messages or []
|
|
134
|
+
|
|
135
|
+
def _check(self) -> None:
|
|
136
|
+
if self._closed:
|
|
137
|
+
raise _interface_error("Cursor is closed")
|
|
138
|
+
|
|
139
|
+
async def execute(self, operation: str, *parameters: Any) -> "AsyncCursor":
|
|
140
|
+
self._check()
|
|
141
|
+
params = _params(parameters) if parameters else None
|
|
142
|
+
result = await _wait(self._c, self._c.execute(_hub(), self._id, operation, params))
|
|
143
|
+
self._install(result)
|
|
144
|
+
return self
|
|
145
|
+
|
|
146
|
+
async def executemany(self, operation: str, seq_of_parameters: Sequence[Sequence[Any]]) -> None:
|
|
147
|
+
self._check()
|
|
148
|
+
rows = await _wait(self._c, self._c.executemany(_hub(), self._id, operation, seq_of_parameters))
|
|
149
|
+
self._batch = None
|
|
150
|
+
self._done = True
|
|
151
|
+
self.description = None
|
|
152
|
+
self.rowcount = rows
|
|
153
|
+
|
|
154
|
+
async def _more(self, n: int) -> list:
|
|
155
|
+
rows, done = await _wait(self._c, self._c.fetch(_hub(), self._id, n))
|
|
156
|
+
self._done = done
|
|
157
|
+
return rows
|
|
158
|
+
|
|
159
|
+
def _local(self, n: int) -> list:
|
|
160
|
+
"""Up to `n` rows already on the Python side (no await)."""
|
|
161
|
+
out = []
|
|
162
|
+
batch = self._batch
|
|
163
|
+
if batch is not None and batch.remaining():
|
|
164
|
+
out = batch.take(n)
|
|
165
|
+
if len(out) < n and self._pos < len(self._rows):
|
|
166
|
+
end = self._pos + (n - len(out))
|
|
167
|
+
if out:
|
|
168
|
+
out.extend(self._rows[self._pos:end])
|
|
169
|
+
else:
|
|
170
|
+
out = self._rows[self._pos:end]
|
|
171
|
+
self._pos = min(end, len(self._rows))
|
|
172
|
+
return out
|
|
173
|
+
|
|
174
|
+
async def fetchone(self) -> Optional[Any]:
|
|
175
|
+
batch = self._batch
|
|
176
|
+
if batch is not None:
|
|
177
|
+
row = batch.next()
|
|
178
|
+
if row is not None:
|
|
179
|
+
return row
|
|
180
|
+
self._batch = None
|
|
181
|
+
if self._pos < len(self._rows):
|
|
182
|
+
row = self._rows[self._pos]
|
|
183
|
+
self._pos += 1
|
|
184
|
+
return row
|
|
185
|
+
if self._done:
|
|
186
|
+
return None
|
|
187
|
+
self._rows = await self._more(max(self.arraysize, 512))
|
|
188
|
+
self._pos = 0
|
|
189
|
+
return await self.fetchone()
|
|
190
|
+
|
|
191
|
+
async def fetchmany(self, size: Optional[int] = None) -> list:
|
|
192
|
+
size = self.arraysize if size is None else size
|
|
193
|
+
out = self._local(size)
|
|
194
|
+
while len(out) < size and not self._done:
|
|
195
|
+
more = await self._more(size - len(out))
|
|
196
|
+
if out:
|
|
197
|
+
out.extend(more)
|
|
198
|
+
else:
|
|
199
|
+
out = more
|
|
200
|
+
return out
|
|
201
|
+
|
|
202
|
+
async def fetchall(self) -> list:
|
|
203
|
+
out = self._local(2**62)
|
|
204
|
+
while not self._done: # a result that fails after some rows ends with the error
|
|
205
|
+
more = await self._more(2**62)
|
|
206
|
+
if out:
|
|
207
|
+
out.extend(more)
|
|
208
|
+
else:
|
|
209
|
+
out = more
|
|
210
|
+
return out
|
|
211
|
+
|
|
212
|
+
async def fetchall_arrow(self, batch_size: int = 65_536):
|
|
213
|
+
"""All remaining rows as a native ArrowTable (DuckDB/polars/pyarrow read it directly)."""
|
|
214
|
+
self._check()
|
|
215
|
+
if self.description is None:
|
|
216
|
+
raise _programming_error("No active result set. Cannot fetch Arrow batch.")
|
|
217
|
+
if self._pos < len(self._rows):
|
|
218
|
+
raise _programming_error("fetchall_arrow() after a partial fetchone() is not supported")
|
|
219
|
+
head, self._batch = self._batch, None
|
|
220
|
+
table = await _wait(
|
|
221
|
+
self._c, self._c.fetch_arrow(_hub(), self._id, head, batch_size)
|
|
222
|
+
)
|
|
223
|
+
self._done = True
|
|
224
|
+
return table
|
|
225
|
+
|
|
226
|
+
async def nextset(self) -> Optional[bool]:
|
|
227
|
+
self._check()
|
|
228
|
+
result = await _wait(self._c, self._c.nextset(_hub(), self._id))
|
|
229
|
+
if result is None:
|
|
230
|
+
self._batch = None
|
|
231
|
+
self._done = True
|
|
232
|
+
self.description = None
|
|
233
|
+
return None
|
|
234
|
+
self._install(result)
|
|
235
|
+
return True
|
|
236
|
+
|
|
237
|
+
async def close(self) -> None:
|
|
238
|
+
if not self._closed:
|
|
239
|
+
self._closed = True
|
|
240
|
+
self._batch = None
|
|
241
|
+
self._c.close_cursor(self._id)
|
|
242
|
+
|
|
243
|
+
def __del__(self):
|
|
244
|
+
# Rust keeps a cursor's results until it is closed: retire it with the object.
|
|
245
|
+
try:
|
|
246
|
+
if not self._closed:
|
|
247
|
+
self._closed = True
|
|
248
|
+
self._c.close_cursor(self._id)
|
|
249
|
+
except Exception:
|
|
250
|
+
pass
|
|
251
|
+
|
|
252
|
+
def __aiter__(self):
|
|
253
|
+
return self
|
|
254
|
+
|
|
255
|
+
async def __anext__(self):
|
|
256
|
+
batch = self._batch
|
|
257
|
+
if batch is not None:
|
|
258
|
+
row = batch.next()
|
|
259
|
+
if row is not None:
|
|
260
|
+
return row
|
|
261
|
+
elif self._pos < len(self._rows):
|
|
262
|
+
row = self._rows[self._pos]
|
|
263
|
+
self._pos += 1
|
|
264
|
+
return row
|
|
265
|
+
row = await self.fetchone()
|
|
266
|
+
if row is None:
|
|
267
|
+
raise StopAsyncIteration
|
|
268
|
+
return row
|
|
269
|
+
|
|
270
|
+
async def __aenter__(self) -> "AsyncCursor":
|
|
271
|
+
return self
|
|
272
|
+
|
|
273
|
+
async def __aexit__(self, *exc) -> None:
|
|
274
|
+
await self.close()
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class _FetchMixin:
|
|
278
|
+
"""fetch/fetchrow/fetchval shared by connections and pools; needs ``_query``."""
|
|
279
|
+
|
|
280
|
+
__slots__ = ()
|
|
281
|
+
|
|
282
|
+
async def fetch(self, operation: str, *parameters: Any) -> list:
|
|
283
|
+
"""All rows of the first result, in one round trip."""
|
|
284
|
+
return await self._query(operation, parameters, 0)
|
|
285
|
+
|
|
286
|
+
async def fetchrow(self, operation: str, *parameters: Any) -> Optional[Any]:
|
|
287
|
+
return await self._query(operation, parameters, 1)
|
|
288
|
+
|
|
289
|
+
async def fetchval(self, operation: str, *parameters: Any) -> Any:
|
|
290
|
+
return await self._query(operation, parameters, 2)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class AsyncConnection(_FetchMixin):
|
|
294
|
+
"""One TDS session. Operations on one connection run one at a time."""
|
|
295
|
+
|
|
296
|
+
def __init__(self, native):
|
|
297
|
+
self._c = native
|
|
298
|
+
self._closed = False
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def autocommit(self) -> bool:
|
|
302
|
+
return self._c.autocommit
|
|
303
|
+
|
|
304
|
+
async def set_autocommit(self, value: bool) -> None:
|
|
305
|
+
await _wait(self._c, self._c.set_autocommit(_hub(), bool(value)))
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def timeout(self) -> int:
|
|
309
|
+
return self._c.timeout
|
|
310
|
+
|
|
311
|
+
@timeout.setter
|
|
312
|
+
def timeout(self, value: int) -> None:
|
|
313
|
+
self._c.timeout = value
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def closed(self) -> bool:
|
|
317
|
+
return self._closed
|
|
318
|
+
|
|
319
|
+
def cursor(self) -> AsyncCursor:
|
|
320
|
+
if self._closed:
|
|
321
|
+
raise _interface_error("Connection is closed")
|
|
322
|
+
return AsyncCursor(self)
|
|
323
|
+
|
|
324
|
+
async def execute(self, operation: str, *parameters: Any) -> AsyncCursor:
|
|
325
|
+
return await self.cursor().execute(operation, *parameters)
|
|
326
|
+
|
|
327
|
+
async def _query(self, operation, parameters, mode):
|
|
328
|
+
if self._closed:
|
|
329
|
+
raise _interface_error("Connection is closed")
|
|
330
|
+
params = _params(parameters) if parameters else None
|
|
331
|
+
return await _wait(self._c, self._c.query(_hub(), operation, params, mode))
|
|
332
|
+
|
|
333
|
+
async def commit(self) -> None:
|
|
334
|
+
await _wait(self._c, self._c.commit(_hub()))
|
|
335
|
+
|
|
336
|
+
async def rollback(self) -> None:
|
|
337
|
+
await _wait(self._c, self._c.rollback(_hub()))
|
|
338
|
+
|
|
339
|
+
async def close(self) -> None:
|
|
340
|
+
if not self._closed:
|
|
341
|
+
self._closed = True
|
|
342
|
+
await _wait(self._c, self._c.close(_hub()))
|
|
343
|
+
|
|
344
|
+
async def __aenter__(self) -> "AsyncConnection":
|
|
345
|
+
return self
|
|
346
|
+
|
|
347
|
+
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
348
|
+
try:
|
|
349
|
+
if not self._closed and not self.autocommit:
|
|
350
|
+
if exc_type is None:
|
|
351
|
+
await self.commit()
|
|
352
|
+
else:
|
|
353
|
+
await self.rollback()
|
|
354
|
+
finally:
|
|
355
|
+
await self.close()
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
_POOL_TIMEOUT_TEXT = "Timed out waiting for a connection from the pool"
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
async def _pool_wait(pool, op):
|
|
362
|
+
"""`_wait` that raises asyncio.TimeoutError when the pool's acquire timeout expires."""
|
|
363
|
+
try:
|
|
364
|
+
return await _wait(pool, op)
|
|
365
|
+
except Exception as exc:
|
|
366
|
+
if _POOL_TIMEOUT_TEXT in str(exc):
|
|
367
|
+
raise asyncio.TimeoutError(_POOL_TIMEOUT_TEXT) from exc
|
|
368
|
+
raise
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
class _Acquire:
|
|
372
|
+
def __init__(self, pool: "Pool", timeout: Optional[float] = None):
|
|
373
|
+
self._pool = pool
|
|
374
|
+
self._timeout = timeout
|
|
375
|
+
self._conn: Optional[AsyncConnection] = None
|
|
376
|
+
|
|
377
|
+
def __await__(self):
|
|
378
|
+
return self._acquire().__await__()
|
|
379
|
+
|
|
380
|
+
async def _acquire(self) -> AsyncConnection:
|
|
381
|
+
native = await _pool_wait(
|
|
382
|
+
self._pool._p, self._pool._p.acquire(_hub(), self._timeout)
|
|
383
|
+
)
|
|
384
|
+
return AsyncConnection(native)
|
|
385
|
+
|
|
386
|
+
async def __aenter__(self) -> AsyncConnection:
|
|
387
|
+
self._conn = await self._acquire()
|
|
388
|
+
return self._conn
|
|
389
|
+
|
|
390
|
+
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
391
|
+
# Same as leaving ``async with connect(...)``: a pool created with
|
|
392
|
+
# autocommit=False commits on a clean exit and rolls back on an
|
|
393
|
+
# exception, then the session goes back to the pool.
|
|
394
|
+
conn, self._conn = self._conn, None
|
|
395
|
+
if conn is not None:
|
|
396
|
+
await conn.__aexit__(exc_type, exc, tb)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
class Pool(_FetchMixin):
|
|
400
|
+
"""A pool of sessions; the fetch/execute shortcuts use one session per call.
|
|
401
|
+
|
|
402
|
+
With ``autocommit=False``, leaving ``async with pool.acquire()`` commits on
|
|
403
|
+
a clean exit and rolls back on an exception (like ``AsyncConnection``);
|
|
404
|
+
closing a connection obtained with ``await pool.acquire()`` rolls back.
|
|
405
|
+
"""
|
|
406
|
+
|
|
407
|
+
def __init__(self, native):
|
|
408
|
+
self._p = native
|
|
409
|
+
|
|
410
|
+
def acquire(self, *, timeout: Optional[float] = None) -> _Acquire:
|
|
411
|
+
"""``async with pool.acquire() as conn`` (or ``conn = await pool.acquire()``).
|
|
412
|
+
|
|
413
|
+
``timeout`` (seconds, default: the pool's ``acquire_timeout``) bounds the
|
|
414
|
+
wait for a free session, including opening a new one; on expiry
|
|
415
|
+
``asyncio.TimeoutError`` is raised."""
|
|
416
|
+
return _Acquire(self, timeout)
|
|
417
|
+
|
|
418
|
+
@property
|
|
419
|
+
def acquire_timeout(self) -> float:
|
|
420
|
+
"""Default seconds ``acquire`` and the fetch/execute shortcuts wait (0 = unbounded)."""
|
|
421
|
+
return self._p.acquire_timeout
|
|
422
|
+
|
|
423
|
+
@acquire_timeout.setter
|
|
424
|
+
def acquire_timeout(self, value: float) -> None:
|
|
425
|
+
self._p.acquire_timeout = value
|
|
426
|
+
|
|
427
|
+
async def execute(self, operation: str, *parameters: Any) -> int:
|
|
428
|
+
"""Run a statement; returns its rowcount (or rows, if it returns any)."""
|
|
429
|
+
return await self._query(operation, parameters, 0)
|
|
430
|
+
|
|
431
|
+
async def _query(self, operation, parameters, mode):
|
|
432
|
+
params = _params(parameters) if parameters else None
|
|
433
|
+
return await _pool_wait(self._p, self._p.query(_hub(), operation, params, mode))
|
|
434
|
+
|
|
435
|
+
async def close(self) -> None:
|
|
436
|
+
await _wait(self._p, self._p.close(_hub()))
|
|
437
|
+
|
|
438
|
+
@property
|
|
439
|
+
def max_size(self) -> int:
|
|
440
|
+
return self._p.max_size
|
|
441
|
+
|
|
442
|
+
@property
|
|
443
|
+
def idle(self) -> int:
|
|
444
|
+
return self._p.idle
|
|
445
|
+
|
|
446
|
+
@property
|
|
447
|
+
def in_use(self) -> int:
|
|
448
|
+
return self._p.in_use
|
|
449
|
+
|
|
450
|
+
async def __aenter__(self) -> "Pool":
|
|
451
|
+
return self
|
|
452
|
+
|
|
453
|
+
async def __aexit__(self, *exc) -> None:
|
|
454
|
+
await self.close()
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
async def connect(
|
|
458
|
+
connection_str: str,
|
|
459
|
+
autocommit: bool = False,
|
|
460
|
+
timeout: int = 0,
|
|
461
|
+
*,
|
|
462
|
+
tuple_rows: bool = False,
|
|
463
|
+
) -> AsyncConnection:
|
|
464
|
+
"""Open a connection. ``timeout`` is the per-query timeout in seconds
|
|
465
|
+
(0 = none); ``tuple_rows=True`` returns plain tuples instead of Row objects.
|
|
466
|
+
|
|
467
|
+
Cancelling the awaiting task (``asyncio.wait_for``) abandons the login."""
|
|
468
|
+
hub = _hub() # first: it reports an unsupported platform before the native symbol is looked up
|
|
469
|
+
future, request, opening = _core.aio_connect(
|
|
470
|
+
hub, connection_str, autocommit, timeout, tuple_rows
|
|
471
|
+
)
|
|
472
|
+
native = await _wait(opening, (future, request))
|
|
473
|
+
return AsyncConnection(native)
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
async def create_pool(
|
|
477
|
+
connection_str: str,
|
|
478
|
+
min_size: int = 1,
|
|
479
|
+
max_size: int = 10,
|
|
480
|
+
autocommit: bool = True,
|
|
481
|
+
timeout: int = 0,
|
|
482
|
+
*,
|
|
483
|
+
tuple_rows: bool = False,
|
|
484
|
+
reset: bool = True,
|
|
485
|
+
max_inactive_connection_lifetime: float = 300.0,
|
|
486
|
+
max_lifetime: float = 0.0,
|
|
487
|
+
acquire_timeout: float = 0.0,
|
|
488
|
+
validate_after: float = 30.0,
|
|
489
|
+
) -> Pool:
|
|
490
|
+
"""Open ``min_size`` sessions now; up to ``max_size`` on demand.
|
|
491
|
+
|
|
492
|
+
``reset=True`` resets each session's state (temp tables, SET options,
|
|
493
|
+
open transactions are rolled back regardless) before it is reused, via the
|
|
494
|
+
TDS reset flag on the next request; ``reset=False`` skips the server-side
|
|
495
|
+
reset for pools whose queries leave no session state behind.
|
|
496
|
+
|
|
497
|
+
Lifetimes are in seconds, 0 turns a limit off (asyncpg-style):
|
|
498
|
+
|
|
499
|
+
* ``max_inactive_connection_lifetime``: idle sessions older than this are
|
|
500
|
+
closed (checked when sessions are returned or handed out).
|
|
501
|
+
* ``max_lifetime``: sessions older than this are closed when returned and
|
|
502
|
+
never handed out again.
|
|
503
|
+
* ``acquire_timeout``: default wait for a free session in ``pool.acquire()``
|
|
504
|
+
and the shortcuts; ``asyncio.TimeoutError`` on expiry.
|
|
505
|
+
* ``validate_after``: a session idle at least this long is checked with a
|
|
506
|
+
``SELECT 1`` before it is handed out; a dead one is replaced.
|
|
507
|
+
|
|
508
|
+
Cancelling the awaiting task abandons the sessions still being opened."""
|
|
509
|
+
hub = _hub() # first: it reports an unsupported platform before the native symbol is looked up
|
|
510
|
+
future, request, opening = _core.aio_create_pool(
|
|
511
|
+
hub,
|
|
512
|
+
connection_str,
|
|
513
|
+
min_size,
|
|
514
|
+
max_size,
|
|
515
|
+
autocommit,
|
|
516
|
+
timeout,
|
|
517
|
+
tuple_rows,
|
|
518
|
+
reset,
|
|
519
|
+
max_inactive_connection_lifetime,
|
|
520
|
+
max_lifetime,
|
|
521
|
+
acquire_timeout,
|
|
522
|
+
validate_after,
|
|
523
|
+
)
|
|
524
|
+
native = await _wait(opening, (future, request))
|
|
525
|
+
return Pool(native)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def _interface_error(message: str):
|
|
529
|
+
return InterfaceError(driver_error=message, ddbc_error=message)
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def _programming_error(message: str):
|
|
533
|
+
return ProgrammingError(driver_error=message, ddbc_error=message)
|