sqlspec 0.8.0__py3-none-any.whl → 0.9.1__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.
Potentially problematic release.
This version of sqlspec might be problematic. Click here for more details.
- sqlspec/_typing.py +39 -6
- sqlspec/adapters/adbc/__init__.py +2 -2
- sqlspec/adapters/adbc/config.py +34 -11
- sqlspec/adapters/adbc/driver.py +302 -111
- sqlspec/adapters/aiosqlite/__init__.py +2 -2
- sqlspec/adapters/aiosqlite/config.py +2 -2
- sqlspec/adapters/aiosqlite/driver.py +164 -42
- sqlspec/adapters/asyncmy/__init__.py +3 -3
- sqlspec/adapters/asyncmy/config.py +11 -12
- sqlspec/adapters/asyncmy/driver.py +161 -37
- sqlspec/adapters/asyncpg/__init__.py +5 -5
- sqlspec/adapters/asyncpg/config.py +17 -19
- sqlspec/adapters/asyncpg/driver.py +386 -96
- sqlspec/adapters/duckdb/__init__.py +2 -2
- sqlspec/adapters/duckdb/config.py +2 -2
- sqlspec/adapters/duckdb/driver.py +190 -60
- sqlspec/adapters/oracledb/__init__.py +8 -8
- sqlspec/adapters/oracledb/config/__init__.py +6 -6
- sqlspec/adapters/oracledb/config/_asyncio.py +9 -10
- sqlspec/adapters/oracledb/config/_sync.py +8 -9
- sqlspec/adapters/oracledb/driver.py +384 -45
- sqlspec/adapters/psqlpy/__init__.py +0 -0
- sqlspec/adapters/psqlpy/config.py +250 -0
- sqlspec/adapters/psqlpy/driver.py +481 -0
- sqlspec/adapters/psycopg/__init__.py +10 -5
- sqlspec/adapters/psycopg/config/__init__.py +6 -6
- sqlspec/adapters/psycopg/config/_async.py +12 -12
- sqlspec/adapters/psycopg/config/_sync.py +13 -13
- sqlspec/adapters/psycopg/driver.py +432 -222
- sqlspec/adapters/sqlite/__init__.py +2 -2
- sqlspec/adapters/sqlite/config.py +2 -2
- sqlspec/adapters/sqlite/driver.py +176 -72
- sqlspec/base.py +687 -161
- sqlspec/exceptions.py +30 -0
- sqlspec/extensions/litestar/config.py +6 -0
- sqlspec/extensions/litestar/handlers.py +25 -0
- sqlspec/extensions/litestar/plugin.py +8 -1
- sqlspec/statement.py +373 -0
- sqlspec/typing.py +10 -1
- {sqlspec-0.8.0.dist-info → sqlspec-0.9.1.dist-info}/METADATA +144 -2
- sqlspec-0.9.1.dist-info/RECORD +61 -0
- sqlspec-0.8.0.dist-info/RECORD +0 -57
- {sqlspec-0.8.0.dist-info → sqlspec-0.9.1.dist-info}/WHEEL +0 -0
- {sqlspec-0.8.0.dist-info → sqlspec-0.9.1.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.8.0.dist-info → sqlspec-0.9.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,44 +1,235 @@
|
|
|
1
|
-
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Optional, Union, cast, overload
|
|
2
4
|
|
|
3
5
|
from asyncpg import Connection
|
|
4
6
|
from typing_extensions import TypeAlias
|
|
5
7
|
|
|
6
8
|
from sqlspec.base import AsyncDriverAdapterProtocol, T
|
|
9
|
+
from sqlspec.exceptions import SQLParsingError
|
|
10
|
+
from sqlspec.statement import PARAM_REGEX, SQLStatement
|
|
7
11
|
|
|
8
12
|
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Sequence
|
|
14
|
+
|
|
9
15
|
from asyncpg.connection import Connection
|
|
10
16
|
from asyncpg.pool import PoolConnectionProxy
|
|
11
17
|
|
|
12
18
|
from sqlspec.typing import ModelDTOT, StatementParameterType
|
|
13
19
|
|
|
14
|
-
__all__ = ("AsyncpgDriver"
|
|
20
|
+
__all__ = ("AsyncpgConnection", "AsyncpgDriver")
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("sqlspec")
|
|
15
23
|
|
|
24
|
+
# Regex to find '?' placeholders, skipping those inside quotes or SQL comments
|
|
25
|
+
# Simplified version, assumes standard SQL quoting/comments
|
|
26
|
+
QMARK_REGEX = re.compile(
|
|
27
|
+
r"""(?P<dquote>"[^"]*") | # Double-quoted strings
|
|
28
|
+
(?P<squote>\'[^\']*\') | # Single-quoted strings
|
|
29
|
+
(?P<comment>--[^\n]*|/\*.*?\*/) | # SQL comments (single/multi-line)
|
|
30
|
+
(?P<qmark>\?) # The question mark placeholder
|
|
31
|
+
""",
|
|
32
|
+
re.VERBOSE | re.DOTALL,
|
|
33
|
+
)
|
|
16
34
|
|
|
17
|
-
|
|
35
|
+
AsyncpgConnection: TypeAlias = "Union[Connection[Any], PoolConnectionProxy[Any]]" # pyright: ignore[reportMissingTypeArgument]
|
|
18
36
|
|
|
19
37
|
|
|
20
|
-
class AsyncpgDriver(AsyncDriverAdapterProtocol["
|
|
38
|
+
class AsyncpgDriver(AsyncDriverAdapterProtocol["AsyncpgConnection"]):
|
|
21
39
|
"""AsyncPG Postgres Driver Adapter."""
|
|
22
40
|
|
|
23
|
-
connection: "
|
|
41
|
+
connection: "AsyncpgConnection"
|
|
42
|
+
dialect: str = "postgres"
|
|
24
43
|
|
|
25
|
-
def __init__(self, connection: "
|
|
44
|
+
def __init__(self, connection: "AsyncpgConnection") -> None:
|
|
26
45
|
self.connection = connection
|
|
27
46
|
|
|
28
|
-
def _process_sql_params(
|
|
29
|
-
self,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
def _process_sql_params( # noqa: C901, PLR0912, PLR0915
|
|
48
|
+
self,
|
|
49
|
+
sql: str,
|
|
50
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
51
|
+
/,
|
|
52
|
+
**kwargs: Any,
|
|
53
|
+
) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
|
|
54
|
+
# Use SQLStatement for parameter validation and merging first
|
|
55
|
+
# It also handles potential dialect-specific logic if implemented there.
|
|
56
|
+
stmt = SQLStatement(sql=sql, parameters=parameters, dialect=self.dialect, kwargs=kwargs or None)
|
|
57
|
+
sql, parameters = stmt.process()
|
|
58
|
+
|
|
59
|
+
# Case 1: Parameters are effectively a dictionary (either passed as dict or via kwargs merged by SQLStatement)
|
|
60
|
+
if isinstance(parameters, dict):
|
|
61
|
+
processed_sql_parts: list[str] = []
|
|
62
|
+
ordered_params = []
|
|
63
|
+
last_end = 0
|
|
64
|
+
param_index = 1
|
|
65
|
+
found_params_regex: list[str] = []
|
|
66
|
+
|
|
67
|
+
# Manually parse the PROCESSED SQL for :name -> $n conversion
|
|
68
|
+
for match in PARAM_REGEX.finditer(sql):
|
|
69
|
+
# Skip matches inside quotes or comments
|
|
70
|
+
if match.group("dquote") or match.group("squote") or match.group("comment"):
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
if match.group("var_name"): # Finds :var_name
|
|
74
|
+
var_name = match.group("var_name")
|
|
75
|
+
found_params_regex.append(var_name)
|
|
76
|
+
start = match.start("var_name") - 1 # Include the ':'
|
|
77
|
+
end = match.end("var_name")
|
|
78
|
+
|
|
79
|
+
# SQLStatement should have already validated parameter existence,
|
|
80
|
+
# but we double-check here during ordering.
|
|
81
|
+
if var_name not in parameters:
|
|
82
|
+
# This should ideally not happen if SQLStatement validation is robust.
|
|
83
|
+
msg = (
|
|
84
|
+
f"Named parameter ':{var_name}' found in SQL but missing from processed parameters. "
|
|
85
|
+
f"Processed SQL: {sql}"
|
|
86
|
+
)
|
|
87
|
+
raise SQLParsingError(msg)
|
|
88
|
+
|
|
89
|
+
processed_sql_parts.extend((sql[last_end:start], f"${param_index}"))
|
|
90
|
+
ordered_params.append(parameters[var_name])
|
|
91
|
+
last_end = end
|
|
92
|
+
param_index += 1
|
|
93
|
+
|
|
94
|
+
processed_sql_parts.append(sql[last_end:])
|
|
95
|
+
final_sql = "".join(processed_sql_parts)
|
|
96
|
+
|
|
97
|
+
# --- Validation ---
|
|
98
|
+
# Check if named placeholders were found if dict params were provided
|
|
99
|
+
# SQLStatement might handle this validation, but a warning here can be useful.
|
|
100
|
+
if not found_params_regex and parameters:
|
|
101
|
+
logger.warning(
|
|
102
|
+
"Dict params provided (%s), but no :name placeholders found. SQL: %s",
|
|
103
|
+
list(parameters.keys()),
|
|
104
|
+
sql,
|
|
105
|
+
)
|
|
106
|
+
# If no placeholders, return original SQL from SQLStatement and empty tuple for asyncpg
|
|
107
|
+
return sql, ()
|
|
108
|
+
|
|
109
|
+
# Additional checks (potentially redundant if SQLStatement covers them):
|
|
110
|
+
# 1. Ensure all found placeholders have corresponding params (covered by check inside loop)
|
|
111
|
+
# 2. Ensure all provided params correspond to a placeholder
|
|
112
|
+
provided_keys = set(parameters.keys())
|
|
113
|
+
found_keys = set(found_params_regex)
|
|
114
|
+
unused_keys = provided_keys - found_keys
|
|
115
|
+
if unused_keys:
|
|
116
|
+
# SQLStatement might handle this, but log a warning just in case.
|
|
117
|
+
logger.warning(
|
|
118
|
+
"Parameters provided but not used in SQL: %s. SQL: %s",
|
|
119
|
+
unused_keys,
|
|
120
|
+
sql,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return final_sql, tuple(ordered_params) # asyncpg expects a sequence
|
|
124
|
+
|
|
125
|
+
# Case 2: Parameters are effectively a sequence/scalar (merged by SQLStatement)
|
|
126
|
+
if isinstance(parameters, (list, tuple)):
|
|
127
|
+
# Parameters are a sequence, need to convert ? -> $n
|
|
128
|
+
sequence_processed_parts: list[str] = []
|
|
129
|
+
param_index = 1
|
|
130
|
+
last_end = 0
|
|
131
|
+
qmark_found = False
|
|
132
|
+
|
|
133
|
+
# Manually parse the PROCESSED SQL to find '?' outside comments/quotes and convert to $n
|
|
134
|
+
for match in QMARK_REGEX.finditer(sql):
|
|
135
|
+
if match.group("dquote") or match.group("squote") or match.group("comment"):
|
|
136
|
+
continue # Skip quotes and comments
|
|
137
|
+
|
|
138
|
+
if match.group("qmark"):
|
|
139
|
+
qmark_found = True
|
|
140
|
+
start = match.start("qmark")
|
|
141
|
+
end = match.end("qmark")
|
|
142
|
+
sequence_processed_parts.extend((sql[last_end:start], f"${param_index}"))
|
|
143
|
+
last_end = end
|
|
144
|
+
param_index += 1
|
|
145
|
+
|
|
146
|
+
sequence_processed_parts.append(sql[last_end:])
|
|
147
|
+
final_sql = "".join(sequence_processed_parts)
|
|
148
|
+
|
|
149
|
+
# --- Validation ---
|
|
150
|
+
# Check if '?' was found if parameters were provided
|
|
151
|
+
if parameters and not qmark_found:
|
|
152
|
+
# SQLStatement might allow this, log a warning.
|
|
153
|
+
logger.warning(
|
|
154
|
+
"Sequence/scalar parameters provided, but no '?' placeholders found. SQL: %s",
|
|
155
|
+
sql,
|
|
156
|
+
)
|
|
157
|
+
# Return PROCESSED SQL from SQLStatement as no conversion happened here
|
|
158
|
+
return sql, parameters
|
|
159
|
+
|
|
160
|
+
# Check parameter count match (using count from manual parsing vs count from stmt)
|
|
161
|
+
expected_params = param_index - 1
|
|
162
|
+
actual_params = len(parameters)
|
|
163
|
+
if expected_params != actual_params:
|
|
164
|
+
msg = (
|
|
165
|
+
f"Parameter count mismatch: Processed SQL expected {expected_params} parameters ('$n'), "
|
|
166
|
+
f"but {actual_params} were provided by SQLStatement. "
|
|
167
|
+
f"Final Processed SQL: {final_sql}"
|
|
168
|
+
)
|
|
169
|
+
raise SQLParsingError(msg)
|
|
170
|
+
|
|
171
|
+
return final_sql, parameters
|
|
172
|
+
|
|
173
|
+
# Case 3: Parameters are None (as determined by SQLStatement)
|
|
174
|
+
# processed_params is None
|
|
175
|
+
# Check if the SQL contains any placeholders unexpectedly
|
|
176
|
+
# Check for :name style
|
|
177
|
+
named_placeholders_found = False
|
|
178
|
+
for match in PARAM_REGEX.finditer(sql):
|
|
179
|
+
if not (match.group("dquote") or match.group("squote") or match.group("comment")) and match.group(
|
|
180
|
+
"var_name"
|
|
181
|
+
):
|
|
182
|
+
named_placeholders_found = True
|
|
183
|
+
break
|
|
184
|
+
if named_placeholders_found:
|
|
185
|
+
msg = f"Processed SQL contains named parameters (:name) but no parameters were provided. SQL: {sql}"
|
|
186
|
+
raise SQLParsingError(msg)
|
|
187
|
+
|
|
188
|
+
# Check for ? style
|
|
189
|
+
qmark_placeholders_found = False
|
|
190
|
+
for match in QMARK_REGEX.finditer(sql):
|
|
191
|
+
if not (match.group("dquote") or match.group("squote") or match.group("comment")) and match.group("qmark"):
|
|
192
|
+
qmark_placeholders_found = True
|
|
193
|
+
break
|
|
194
|
+
if qmark_placeholders_found:
|
|
195
|
+
msg = f"Processed SQL contains positional parameters (?) but no parameters were provided. SQL: {sql}"
|
|
196
|
+
raise SQLParsingError(msg)
|
|
197
|
+
|
|
198
|
+
# No parameters provided and none found in SQL, return original SQL from SQLStatement and empty tuple
|
|
199
|
+
return sql, () # asyncpg expects a sequence, even if empty
|
|
200
|
+
|
|
201
|
+
@overload
|
|
202
|
+
async def select(
|
|
203
|
+
self,
|
|
204
|
+
sql: str,
|
|
205
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
206
|
+
/,
|
|
207
|
+
*,
|
|
208
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
209
|
+
schema_type: None = None,
|
|
210
|
+
**kwargs: Any,
|
|
211
|
+
) -> "Sequence[dict[str, Any]]": ...
|
|
212
|
+
@overload
|
|
213
|
+
async def select(
|
|
214
|
+
self,
|
|
215
|
+
sql: str,
|
|
216
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
217
|
+
/,
|
|
218
|
+
*,
|
|
219
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
220
|
+
schema_type: "type[ModelDTOT]",
|
|
221
|
+
**kwargs: Any,
|
|
222
|
+
) -> "Sequence[ModelDTOT]": ...
|
|
34
223
|
async def select(
|
|
35
224
|
self,
|
|
36
225
|
sql: str,
|
|
37
226
|
parameters: Optional["StatementParameterType"] = None,
|
|
38
227
|
/,
|
|
39
|
-
|
|
228
|
+
*,
|
|
229
|
+
connection: Optional["AsyncpgConnection"] = None,
|
|
40
230
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
41
|
-
|
|
231
|
+
**kwargs: Any,
|
|
232
|
+
) -> "Sequence[Union[ModelDTOT, dict[str, Any]]]":
|
|
42
233
|
"""Fetch data from the database.
|
|
43
234
|
|
|
44
235
|
Args:
|
|
@@ -46,27 +237,53 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
46
237
|
parameters: Query parameters.
|
|
47
238
|
connection: Optional connection to use.
|
|
48
239
|
schema_type: Optional schema class for the result.
|
|
240
|
+
**kwargs: Additional keyword arguments.
|
|
49
241
|
|
|
50
242
|
Returns:
|
|
51
243
|
List of row data as either model instances or dictionaries.
|
|
52
244
|
"""
|
|
53
245
|
connection = self._connection(connection)
|
|
54
|
-
sql, parameters = self._process_sql_params(sql, parameters)
|
|
246
|
+
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
247
|
+
parameters = parameters if parameters is not None else {}
|
|
55
248
|
|
|
56
|
-
results = await connection.fetch(sql, *parameters) # pyright: ignore
|
|
249
|
+
results = await connection.fetch(sql, *parameters) # pyright: ignore
|
|
57
250
|
if not results:
|
|
58
251
|
return []
|
|
59
252
|
if schema_type is None:
|
|
60
253
|
return [dict(row.items()) for row in results] # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
61
254
|
return [cast("ModelDTOT", schema_type(**dict(row.items()))) for row in results] # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
62
255
|
|
|
256
|
+
@overload
|
|
257
|
+
async def select_one(
|
|
258
|
+
self,
|
|
259
|
+
sql: str,
|
|
260
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
261
|
+
/,
|
|
262
|
+
*,
|
|
263
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
264
|
+
schema_type: None = None,
|
|
265
|
+
**kwargs: Any,
|
|
266
|
+
) -> "dict[str, Any]": ...
|
|
267
|
+
@overload
|
|
268
|
+
async def select_one(
|
|
269
|
+
self,
|
|
270
|
+
sql: str,
|
|
271
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
272
|
+
/,
|
|
273
|
+
*,
|
|
274
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
275
|
+
schema_type: "type[ModelDTOT]",
|
|
276
|
+
**kwargs: Any,
|
|
277
|
+
) -> "ModelDTOT": ...
|
|
63
278
|
async def select_one(
|
|
64
279
|
self,
|
|
65
280
|
sql: str,
|
|
66
281
|
parameters: Optional["StatementParameterType"] = None,
|
|
67
282
|
/,
|
|
68
|
-
|
|
283
|
+
*,
|
|
284
|
+
connection: Optional["AsyncpgConnection"] = None,
|
|
69
285
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
286
|
+
**kwargs: Any,
|
|
70
287
|
) -> "Union[ModelDTOT, dict[str, Any]]":
|
|
71
288
|
"""Fetch one row from the database.
|
|
72
289
|
|
|
@@ -75,16 +292,15 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
75
292
|
parameters: Query parameters.
|
|
76
293
|
connection: Optional connection to use.
|
|
77
294
|
schema_type: Optional schema class for the result.
|
|
295
|
+
**kwargs: Additional keyword arguments.
|
|
78
296
|
|
|
79
297
|
Returns:
|
|
80
298
|
The first row of the query results.
|
|
81
299
|
"""
|
|
82
300
|
connection = self._connection(connection)
|
|
83
|
-
sql,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
result = await connection.fetchrow(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
301
|
+
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
302
|
+
parameters = parameters if parameters is not None else {}
|
|
303
|
+
result = await connection.fetchrow(sql, *parameters) # pyright: ignore
|
|
88
304
|
result = self.check_not_found(result)
|
|
89
305
|
|
|
90
306
|
if schema_type is None:
|
|
@@ -92,13 +308,37 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
92
308
|
return dict(result.items()) # type: ignore[attr-defined]
|
|
93
309
|
return cast("ModelDTOT", schema_type(**dict(result.items()))) # type: ignore[attr-defined]
|
|
94
310
|
|
|
311
|
+
@overload
|
|
312
|
+
async def select_one_or_none(
|
|
313
|
+
self,
|
|
314
|
+
sql: str,
|
|
315
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
316
|
+
/,
|
|
317
|
+
*,
|
|
318
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
319
|
+
schema_type: None = None,
|
|
320
|
+
**kwargs: Any,
|
|
321
|
+
) -> "Optional[dict[str, Any]]": ...
|
|
322
|
+
@overload
|
|
323
|
+
async def select_one_or_none(
|
|
324
|
+
self,
|
|
325
|
+
sql: str,
|
|
326
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
327
|
+
/,
|
|
328
|
+
*,
|
|
329
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
330
|
+
schema_type: "type[ModelDTOT]",
|
|
331
|
+
**kwargs: Any,
|
|
332
|
+
) -> "Optional[ModelDTOT]": ...
|
|
95
333
|
async def select_one_or_none(
|
|
96
334
|
self,
|
|
97
335
|
sql: str,
|
|
98
336
|
parameters: Optional["StatementParameterType"] = None,
|
|
99
337
|
/,
|
|
100
|
-
|
|
338
|
+
*,
|
|
339
|
+
connection: Optional["AsyncpgConnection"] = None,
|
|
101
340
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
341
|
+
**kwargs: Any,
|
|
102
342
|
) -> "Optional[Union[ModelDTOT, dict[str, Any]]]":
|
|
103
343
|
"""Fetch one row from the database.
|
|
104
344
|
|
|
@@ -107,51 +347,106 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
107
347
|
parameters: Query parameters.
|
|
108
348
|
connection: Optional connection to use.
|
|
109
349
|
schema_type: Optional schema class for the result.
|
|
350
|
+
**kwargs: Additional keyword arguments.
|
|
110
351
|
|
|
111
352
|
Returns:
|
|
112
353
|
The first row of the query results.
|
|
113
354
|
"""
|
|
114
355
|
connection = self._connection(connection)
|
|
115
|
-
sql, parameters = self._process_sql_params(sql, parameters)
|
|
116
|
-
|
|
117
|
-
result = await connection.fetchrow(sql, *parameters) # pyright: ignore
|
|
118
|
-
result
|
|
356
|
+
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
357
|
+
parameters = parameters if parameters is not None else {}
|
|
358
|
+
result = await connection.fetchrow(sql, *parameters) # pyright: ignore
|
|
359
|
+
if result is None:
|
|
360
|
+
return None
|
|
119
361
|
if schema_type is None:
|
|
120
362
|
# Always return as dictionary
|
|
121
|
-
return dict(result.items())
|
|
122
|
-
return cast("ModelDTOT", schema_type(**dict(result.items())))
|
|
363
|
+
return dict(result.items())
|
|
364
|
+
return cast("ModelDTOT", schema_type(**dict(result.items())))
|
|
123
365
|
|
|
366
|
+
@overload
|
|
367
|
+
async def select_value(
|
|
368
|
+
self,
|
|
369
|
+
sql: str,
|
|
370
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
371
|
+
/,
|
|
372
|
+
*,
|
|
373
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
374
|
+
schema_type: None = None,
|
|
375
|
+
**kwargs: Any,
|
|
376
|
+
) -> "Any": ...
|
|
377
|
+
@overload
|
|
378
|
+
async def select_value(
|
|
379
|
+
self,
|
|
380
|
+
sql: str,
|
|
381
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
382
|
+
/,
|
|
383
|
+
*,
|
|
384
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
385
|
+
schema_type: "type[T]",
|
|
386
|
+
**kwargs: Any,
|
|
387
|
+
) -> "T": ...
|
|
124
388
|
async def select_value(
|
|
125
389
|
self,
|
|
126
390
|
sql: str,
|
|
127
391
|
parameters: "Optional[StatementParameterType]" = None,
|
|
128
392
|
/,
|
|
129
|
-
|
|
393
|
+
*,
|
|
394
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
130
395
|
schema_type: "Optional[type[T]]" = None,
|
|
396
|
+
**kwargs: Any,
|
|
131
397
|
) -> "Union[T, Any]":
|
|
132
398
|
"""Fetch a single value from the database.
|
|
133
399
|
|
|
400
|
+
Args:
|
|
401
|
+
sql: SQL statement.
|
|
402
|
+
parameters: Query parameters.
|
|
403
|
+
connection: Optional connection to use.
|
|
404
|
+
schema_type: Optional schema class for the result.
|
|
405
|
+
**kwargs: Additional keyword arguments.
|
|
406
|
+
|
|
134
407
|
Returns:
|
|
135
408
|
The first value from the first row of results, or None if no results.
|
|
136
409
|
"""
|
|
137
410
|
connection = self._connection(connection)
|
|
138
|
-
sql,
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
result = await connection.fetchval(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
411
|
+
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
412
|
+
parameters = parameters if parameters is not None else {}
|
|
413
|
+
result = await connection.fetchval(sql, *parameters) # pyright: ignore
|
|
143
414
|
result = self.check_not_found(result)
|
|
144
415
|
if schema_type is None:
|
|
145
|
-
return result
|
|
146
|
-
return schema_type(result
|
|
416
|
+
return result
|
|
417
|
+
return schema_type(result) # type: ignore[call-arg]
|
|
147
418
|
|
|
419
|
+
@overload
|
|
148
420
|
async def select_value_or_none(
|
|
149
421
|
self,
|
|
150
422
|
sql: str,
|
|
151
423
|
parameters: "Optional[StatementParameterType]" = None,
|
|
152
424
|
/,
|
|
153
|
-
|
|
425
|
+
*,
|
|
426
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
427
|
+
schema_type: None = None,
|
|
428
|
+
**kwargs: Any,
|
|
429
|
+
) -> "Optional[Any]": ...
|
|
430
|
+
@overload
|
|
431
|
+
async def select_value_or_none(
|
|
432
|
+
self,
|
|
433
|
+
sql: str,
|
|
434
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
435
|
+
/,
|
|
436
|
+
*,
|
|
437
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
438
|
+
schema_type: "type[T]",
|
|
439
|
+
**kwargs: Any,
|
|
440
|
+
) -> "Optional[T]": ...
|
|
441
|
+
async def select_value_or_none(
|
|
442
|
+
self,
|
|
443
|
+
sql: str,
|
|
444
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
445
|
+
/,
|
|
446
|
+
*,
|
|
447
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
154
448
|
schema_type: "Optional[type[T]]" = None,
|
|
449
|
+
**kwargs: Any,
|
|
155
450
|
) -> "Optional[Union[T, Any]]":
|
|
156
451
|
"""Fetch a single value from the database.
|
|
157
452
|
|
|
@@ -159,23 +454,23 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
159
454
|
The first value from the first row of results, or None if no results.
|
|
160
455
|
"""
|
|
161
456
|
connection = self._connection(connection)
|
|
162
|
-
sql,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
result = await connection.fetchval(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
457
|
+
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
458
|
+
parameters = parameters if parameters is not None else {}
|
|
459
|
+
result = await connection.fetchval(sql, *parameters) # pyright: ignore
|
|
167
460
|
if result is None:
|
|
168
461
|
return None
|
|
169
462
|
if schema_type is None:
|
|
170
|
-
return result
|
|
171
|
-
return schema_type(result
|
|
463
|
+
return result
|
|
464
|
+
return schema_type(result) # type: ignore[call-arg]
|
|
172
465
|
|
|
173
466
|
async def insert_update_delete(
|
|
174
467
|
self,
|
|
175
468
|
sql: str,
|
|
176
469
|
parameters: Optional["StatementParameterType"] = None,
|
|
177
470
|
/,
|
|
178
|
-
|
|
471
|
+
*,
|
|
472
|
+
connection: Optional["AsyncpgConnection"] = None,
|
|
473
|
+
**kwargs: Any,
|
|
179
474
|
) -> int:
|
|
180
475
|
"""Insert, update, or delete data from the database.
|
|
181
476
|
|
|
@@ -183,47 +478,69 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
183
478
|
sql: SQL statement.
|
|
184
479
|
parameters: Query parameters.
|
|
185
480
|
connection: Optional connection to use.
|
|
481
|
+
**kwargs: Additional keyword arguments.
|
|
186
482
|
|
|
187
483
|
Returns:
|
|
188
484
|
Row count affected by the operation.
|
|
189
485
|
"""
|
|
190
486
|
connection = self._connection(connection)
|
|
191
|
-
sql,
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
status = await connection.execute(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
487
|
+
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
488
|
+
parameters = parameters if parameters is not None else {}
|
|
489
|
+
status = await connection.execute(sql, *parameters) # pyright: ignore
|
|
196
490
|
# AsyncPG returns a string like "INSERT 0 1" where the last number is the affected rows
|
|
197
491
|
try:
|
|
198
492
|
return int(status.split()[-1]) # pyright: ignore[reportUnknownMemberType]
|
|
199
493
|
except (ValueError, IndexError, AttributeError):
|
|
200
494
|
return -1 # Fallback if we can't parse the status
|
|
201
495
|
|
|
496
|
+
@overload
|
|
497
|
+
async def insert_update_delete_returning(
|
|
498
|
+
self,
|
|
499
|
+
sql: str,
|
|
500
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
501
|
+
/,
|
|
502
|
+
*,
|
|
503
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
504
|
+
schema_type: None = None,
|
|
505
|
+
**kwargs: Any,
|
|
506
|
+
) -> "dict[str, Any]": ...
|
|
507
|
+
@overload
|
|
508
|
+
async def insert_update_delete_returning(
|
|
509
|
+
self,
|
|
510
|
+
sql: str,
|
|
511
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
512
|
+
/,
|
|
513
|
+
*,
|
|
514
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
515
|
+
schema_type: "type[ModelDTOT]",
|
|
516
|
+
**kwargs: Any,
|
|
517
|
+
) -> "ModelDTOT": ...
|
|
202
518
|
async def insert_update_delete_returning(
|
|
203
519
|
self,
|
|
204
520
|
sql: str,
|
|
205
521
|
parameters: Optional["StatementParameterType"] = None,
|
|
206
522
|
/,
|
|
207
|
-
|
|
523
|
+
*,
|
|
524
|
+
connection: Optional["AsyncpgConnection"] = None,
|
|
208
525
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
526
|
+
**kwargs: Any,
|
|
209
527
|
) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
|
|
210
|
-
"""Insert, update, or delete data from the database and return
|
|
528
|
+
"""Insert, update, or delete data from the database and return the affected row.
|
|
211
529
|
|
|
212
530
|
Args:
|
|
213
531
|
sql: SQL statement.
|
|
214
532
|
parameters: Query parameters.
|
|
215
533
|
connection: Optional connection to use.
|
|
216
534
|
schema_type: Optional schema class for the result.
|
|
535
|
+
**kwargs: Additional keyword arguments.
|
|
217
536
|
|
|
218
537
|
Returns:
|
|
219
|
-
The
|
|
538
|
+
The affected row data as either a model instance or dictionary.
|
|
220
539
|
"""
|
|
221
540
|
connection = self._connection(connection)
|
|
222
|
-
sql,
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
result = await connection.fetchrow(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
541
|
+
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
542
|
+
parameters = parameters if parameters is not None else {}
|
|
543
|
+
result = await connection.fetchrow(sql, *parameters) # pyright: ignore
|
|
227
544
|
if result is None:
|
|
228
545
|
return None
|
|
229
546
|
if schema_type is None:
|
|
@@ -236,7 +553,9 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
236
553
|
sql: str,
|
|
237
554
|
parameters: Optional["StatementParameterType"] = None,
|
|
238
555
|
/,
|
|
239
|
-
|
|
556
|
+
*,
|
|
557
|
+
connection: Optional["AsyncpgConnection"] = None,
|
|
558
|
+
**kwargs: Any,
|
|
240
559
|
) -> str:
|
|
241
560
|
"""Execute a script.
|
|
242
561
|
|
|
@@ -244,45 +563,16 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
244
563
|
sql: SQL statement.
|
|
245
564
|
parameters: Query parameters.
|
|
246
565
|
connection: Optional connection to use.
|
|
566
|
+
**kwargs: Additional keyword arguments.
|
|
247
567
|
|
|
248
568
|
Returns:
|
|
249
569
|
Status message for the operation.
|
|
250
570
|
"""
|
|
251
571
|
connection = self._connection(connection)
|
|
252
|
-
sql,
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return await connection.execute(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
257
|
-
|
|
258
|
-
async def execute_script_returning(
|
|
259
|
-
self,
|
|
260
|
-
sql: str,
|
|
261
|
-
parameters: Optional["StatementParameterType"] = None,
|
|
262
|
-
/,
|
|
263
|
-
connection: Optional["PgConnection"] = None,
|
|
264
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
265
|
-
) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
|
|
266
|
-
"""Execute a script and return result.
|
|
572
|
+
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
573
|
+
parameters = parameters if parameters is not None else {}
|
|
574
|
+
return await connection.execute(sql, *parameters) # pyright: ignore
|
|
267
575
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
connection: Optional connection to use.
|
|
272
|
-
schema_type: Optional schema class for the result.
|
|
273
|
-
|
|
274
|
-
Returns:
|
|
275
|
-
The first row of results.
|
|
276
|
-
"""
|
|
277
|
-
connection = self._connection(connection)
|
|
278
|
-
sql, params = self._process_sql_params(sql, parameters)
|
|
279
|
-
# Use empty tuple if params is None
|
|
280
|
-
params = params if params is not None else ()
|
|
281
|
-
|
|
282
|
-
result = await connection.fetchrow(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
283
|
-
if result is None:
|
|
284
|
-
return None
|
|
285
|
-
if schema_type is None:
|
|
286
|
-
# Always return as dictionary
|
|
287
|
-
return dict(result.items()) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
288
|
-
return cast("ModelDTOT", schema_type(**dict(result.items()))) # pyright: ignore[reportUnknownArgumentType, reportUnknownMemberType, reportUnknownVariableType]
|
|
576
|
+
def _connection(self, connection: Optional["AsyncpgConnection"] = None) -> "AsyncpgConnection":
|
|
577
|
+
"""Return the connection to use. If None, use the default connection."""
|
|
578
|
+
return connection if connection is not None else self.connection
|