sqlspec 0.8.0__py3-none-any.whl → 0.9.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.
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 +167 -108
- sqlspec/adapters/aiosqlite/__init__.py +2 -2
- sqlspec/adapters/aiosqlite/config.py +2 -2
- sqlspec/adapters/aiosqlite/driver.py +28 -39
- sqlspec/adapters/asyncmy/__init__.py +3 -3
- sqlspec/adapters/asyncmy/config.py +11 -12
- sqlspec/adapters/asyncmy/driver.py +25 -34
- sqlspec/adapters/asyncpg/__init__.py +5 -5
- sqlspec/adapters/asyncpg/config.py +17 -19
- sqlspec/adapters/asyncpg/driver.py +249 -93
- sqlspec/adapters/duckdb/__init__.py +2 -2
- sqlspec/adapters/duckdb/config.py +2 -2
- sqlspec/adapters/duckdb/driver.py +49 -49
- 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 +114 -41
- sqlspec/adapters/psqlpy/__init__.py +0 -0
- sqlspec/adapters/psqlpy/config.py +258 -0
- sqlspec/adapters/psqlpy/driver.py +335 -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 +180 -218
- sqlspec/adapters/sqlite/__init__.py +2 -2
- sqlspec/adapters/sqlite/config.py +2 -2
- sqlspec/adapters/sqlite/driver.py +43 -41
- sqlspec/base.py +275 -153
- sqlspec/exceptions.py +30 -0
- sqlspec/extensions/litestar/config.py +6 -0
- sqlspec/extensions/litestar/handlers.py +25 -0
- sqlspec/extensions/litestar/plugin.py +6 -1
- sqlspec/statement.py +373 -0
- sqlspec/typing.py +10 -1
- {sqlspec-0.8.0.dist-info → sqlspec-0.9.0.dist-info}/METADATA +4 -1
- sqlspec-0.9.0.dist-info/RECORD +61 -0
- sqlspec-0.8.0.dist-info/RECORD +0 -57
- {sqlspec-0.8.0.dist-info → sqlspec-0.9.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.8.0.dist-info → sqlspec-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.8.0.dist-info → sqlspec-0.9.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
1
3
|
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
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:
|
|
9
13
|
from asyncpg.connection import Connection
|
|
@@ -11,33 +15,196 @@ if TYPE_CHECKING:
|
|
|
11
15
|
|
|
12
16
|
from sqlspec.typing import ModelDTOT, StatementParameterType
|
|
13
17
|
|
|
14
|
-
__all__ = ("AsyncpgDriver"
|
|
18
|
+
__all__ = ("AsyncpgConnection", "AsyncpgDriver")
|
|
15
19
|
|
|
20
|
+
logger = logging.getLogger("sqlspec")
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
# Regex to find '?' placeholders, skipping those inside quotes or SQL comments
|
|
23
|
+
# Simplified version, assumes standard SQL quoting/comments
|
|
24
|
+
QMARK_REGEX = re.compile(
|
|
25
|
+
r"""(?P<dquote>"[^"]*") | # Double-quoted strings
|
|
26
|
+
(?P<squote>\'[^\']*\') | # Single-quoted strings
|
|
27
|
+
(?P<comment>--[^\n]*|/\*.*?\*/) | # SQL comments (single/multi-line)
|
|
28
|
+
(?P<qmark>\?) # The question mark placeholder
|
|
29
|
+
""",
|
|
30
|
+
re.VERBOSE | re.DOTALL,
|
|
31
|
+
)
|
|
18
32
|
|
|
33
|
+
AsyncpgConnection: TypeAlias = "Union[Connection[Any], PoolConnectionProxy[Any]]" # pyright: ignore[reportMissingTypeArgument]
|
|
19
34
|
|
|
20
|
-
|
|
35
|
+
|
|
36
|
+
class AsyncpgDriver(AsyncDriverAdapterProtocol["AsyncpgConnection"]):
|
|
21
37
|
"""AsyncPG Postgres Driver Adapter."""
|
|
22
38
|
|
|
23
|
-
connection: "
|
|
39
|
+
connection: "AsyncpgConnection"
|
|
40
|
+
dialect: str = "postgres"
|
|
24
41
|
|
|
25
|
-
def __init__(self, connection: "
|
|
42
|
+
def __init__(self, connection: "AsyncpgConnection") -> None:
|
|
26
43
|
self.connection = connection
|
|
27
44
|
|
|
28
|
-
def _process_sql_params(
|
|
29
|
-
self,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
45
|
+
def _process_sql_params( # noqa: C901, PLR0912, PLR0915
|
|
46
|
+
self,
|
|
47
|
+
sql: str,
|
|
48
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
49
|
+
/,
|
|
50
|
+
**kwargs: Any,
|
|
51
|
+
) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
|
|
52
|
+
# Use SQLStatement for parameter validation and merging first
|
|
53
|
+
# It also handles potential dialect-specific logic if implemented there.
|
|
54
|
+
stmt = SQLStatement(sql=sql, parameters=parameters, dialect=self.dialect, kwargs=kwargs or None)
|
|
55
|
+
sql, parameters = stmt.process()
|
|
56
|
+
|
|
57
|
+
# Case 1: Parameters are effectively a dictionary (either passed as dict or via kwargs merged by SQLStatement)
|
|
58
|
+
if isinstance(parameters, dict):
|
|
59
|
+
processed_sql_parts: list[str] = []
|
|
60
|
+
ordered_params = []
|
|
61
|
+
last_end = 0
|
|
62
|
+
param_index = 1
|
|
63
|
+
found_params_regex: list[str] = []
|
|
64
|
+
|
|
65
|
+
# Manually parse the PROCESSED SQL for :name -> $n conversion
|
|
66
|
+
for match in PARAM_REGEX.finditer(sql):
|
|
67
|
+
# Skip matches inside quotes or comments
|
|
68
|
+
if match.group("dquote") or match.group("squote") or match.group("comment"):
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
if match.group("var_name"): # Finds :var_name
|
|
72
|
+
var_name = match.group("var_name")
|
|
73
|
+
found_params_regex.append(var_name)
|
|
74
|
+
start = match.start("var_name") - 1 # Include the ':'
|
|
75
|
+
end = match.end("var_name")
|
|
76
|
+
|
|
77
|
+
# SQLStatement should have already validated parameter existence,
|
|
78
|
+
# but we double-check here during ordering.
|
|
79
|
+
if var_name not in parameters:
|
|
80
|
+
# This should ideally not happen if SQLStatement validation is robust.
|
|
81
|
+
msg = (
|
|
82
|
+
f"Named parameter ':{var_name}' found in SQL but missing from processed parameters. "
|
|
83
|
+
f"Processed SQL: {sql}"
|
|
84
|
+
)
|
|
85
|
+
raise SQLParsingError(msg)
|
|
86
|
+
|
|
87
|
+
processed_sql_parts.extend((sql[last_end:start], f"${param_index}"))
|
|
88
|
+
ordered_params.append(parameters[var_name])
|
|
89
|
+
last_end = end
|
|
90
|
+
param_index += 1
|
|
91
|
+
|
|
92
|
+
processed_sql_parts.append(sql[last_end:])
|
|
93
|
+
final_sql = "".join(processed_sql_parts)
|
|
94
|
+
|
|
95
|
+
# --- Validation ---
|
|
96
|
+
# Check if named placeholders were found if dict params were provided
|
|
97
|
+
# SQLStatement might handle this validation, but a warning here can be useful.
|
|
98
|
+
if not found_params_regex and parameters:
|
|
99
|
+
logger.warning(
|
|
100
|
+
"Dict params provided (%s), but no :name placeholders found. SQL: %s",
|
|
101
|
+
list(parameters.keys()),
|
|
102
|
+
sql,
|
|
103
|
+
)
|
|
104
|
+
# If no placeholders, return original SQL from SQLStatement and empty tuple for asyncpg
|
|
105
|
+
return sql, ()
|
|
106
|
+
|
|
107
|
+
# Additional checks (potentially redundant if SQLStatement covers them):
|
|
108
|
+
# 1. Ensure all found placeholders have corresponding params (covered by check inside loop)
|
|
109
|
+
# 2. Ensure all provided params correspond to a placeholder
|
|
110
|
+
provided_keys = set(parameters.keys())
|
|
111
|
+
found_keys = set(found_params_regex)
|
|
112
|
+
unused_keys = provided_keys - found_keys
|
|
113
|
+
if unused_keys:
|
|
114
|
+
# SQLStatement might handle this, but log a warning just in case.
|
|
115
|
+
logger.warning(
|
|
116
|
+
"Parameters provided but not used in SQL: %s. SQL: %s",
|
|
117
|
+
unused_keys,
|
|
118
|
+
sql,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return final_sql, tuple(ordered_params) # asyncpg expects a sequence
|
|
122
|
+
|
|
123
|
+
# Case 2: Parameters are effectively a sequence/scalar (merged by SQLStatement)
|
|
124
|
+
if isinstance(parameters, (list, tuple)):
|
|
125
|
+
# Parameters are a sequence, need to convert ? -> $n
|
|
126
|
+
sequence_processed_parts: list[str] = []
|
|
127
|
+
param_index = 1
|
|
128
|
+
last_end = 0
|
|
129
|
+
qmark_found = False
|
|
130
|
+
|
|
131
|
+
# Manually parse the PROCESSED SQL to find '?' outside comments/quotes and convert to $n
|
|
132
|
+
for match in QMARK_REGEX.finditer(sql):
|
|
133
|
+
if match.group("dquote") or match.group("squote") or match.group("comment"):
|
|
134
|
+
continue # Skip quotes and comments
|
|
135
|
+
|
|
136
|
+
if match.group("qmark"):
|
|
137
|
+
qmark_found = True
|
|
138
|
+
start = match.start("qmark")
|
|
139
|
+
end = match.end("qmark")
|
|
140
|
+
sequence_processed_parts.extend((sql[last_end:start], f"${param_index}"))
|
|
141
|
+
last_end = end
|
|
142
|
+
param_index += 1
|
|
143
|
+
|
|
144
|
+
sequence_processed_parts.append(sql[last_end:])
|
|
145
|
+
final_sql = "".join(sequence_processed_parts)
|
|
146
|
+
|
|
147
|
+
# --- Validation ---
|
|
148
|
+
# Check if '?' was found if parameters were provided
|
|
149
|
+
if parameters and not qmark_found:
|
|
150
|
+
# SQLStatement might allow this, log a warning.
|
|
151
|
+
logger.warning(
|
|
152
|
+
"Sequence/scalar parameters provided, but no '?' placeholders found. SQL: %s",
|
|
153
|
+
sql,
|
|
154
|
+
)
|
|
155
|
+
# Return PROCESSED SQL from SQLStatement as no conversion happened here
|
|
156
|
+
return sql, parameters
|
|
157
|
+
|
|
158
|
+
# Check parameter count match (using count from manual parsing vs count from stmt)
|
|
159
|
+
expected_params = param_index - 1
|
|
160
|
+
actual_params = len(parameters)
|
|
161
|
+
if expected_params != actual_params:
|
|
162
|
+
msg = (
|
|
163
|
+
f"Parameter count mismatch: Processed SQL expected {expected_params} parameters ('$n'), "
|
|
164
|
+
f"but {actual_params} were provided by SQLStatement. "
|
|
165
|
+
f"Final Processed SQL: {final_sql}"
|
|
166
|
+
)
|
|
167
|
+
raise SQLParsingError(msg)
|
|
168
|
+
|
|
169
|
+
return final_sql, parameters
|
|
170
|
+
|
|
171
|
+
# Case 3: Parameters are None (as determined by SQLStatement)
|
|
172
|
+
# processed_params is None
|
|
173
|
+
# Check if the SQL contains any placeholders unexpectedly
|
|
174
|
+
# Check for :name style
|
|
175
|
+
named_placeholders_found = False
|
|
176
|
+
for match in PARAM_REGEX.finditer(sql):
|
|
177
|
+
if not (match.group("dquote") or match.group("squote") or match.group("comment")) and match.group(
|
|
178
|
+
"var_name"
|
|
179
|
+
):
|
|
180
|
+
named_placeholders_found = True
|
|
181
|
+
break
|
|
182
|
+
if named_placeholders_found:
|
|
183
|
+
msg = f"Processed SQL contains named parameters (:name) but no parameters were provided. SQL: {sql}"
|
|
184
|
+
raise SQLParsingError(msg)
|
|
185
|
+
|
|
186
|
+
# Check for ? style
|
|
187
|
+
qmark_placeholders_found = False
|
|
188
|
+
for match in QMARK_REGEX.finditer(sql):
|
|
189
|
+
if not (match.group("dquote") or match.group("squote") or match.group("comment")) and match.group("qmark"):
|
|
190
|
+
qmark_placeholders_found = True
|
|
191
|
+
break
|
|
192
|
+
if qmark_placeholders_found:
|
|
193
|
+
msg = f"Processed SQL contains positional parameters (?) but no parameters were provided. SQL: {sql}"
|
|
194
|
+
raise SQLParsingError(msg)
|
|
195
|
+
|
|
196
|
+
# No parameters provided and none found in SQL, return original SQL from SQLStatement and empty tuple
|
|
197
|
+
return sql, () # asyncpg expects a sequence, even if empty
|
|
33
198
|
|
|
34
199
|
async def select(
|
|
35
200
|
self,
|
|
36
201
|
sql: str,
|
|
37
202
|
parameters: Optional["StatementParameterType"] = None,
|
|
38
203
|
/,
|
|
39
|
-
|
|
204
|
+
*,
|
|
205
|
+
connection: Optional["AsyncpgConnection"] = None,
|
|
40
206
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
207
|
+
**kwargs: Any,
|
|
41
208
|
) -> "list[Union[ModelDTOT, dict[str, Any]]]":
|
|
42
209
|
"""Fetch data from the database.
|
|
43
210
|
|
|
@@ -46,14 +213,16 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
46
213
|
parameters: Query parameters.
|
|
47
214
|
connection: Optional connection to use.
|
|
48
215
|
schema_type: Optional schema class for the result.
|
|
216
|
+
**kwargs: Additional keyword arguments.
|
|
49
217
|
|
|
50
218
|
Returns:
|
|
51
219
|
List of row data as either model instances or dictionaries.
|
|
52
220
|
"""
|
|
53
221
|
connection = self._connection(connection)
|
|
54
|
-
sql, parameters = self._process_sql_params(sql, parameters)
|
|
222
|
+
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
223
|
+
parameters = parameters if parameters is not None else {}
|
|
55
224
|
|
|
56
|
-
results = await connection.fetch(sql, *parameters) # pyright: ignore
|
|
225
|
+
results = await connection.fetch(sql, *parameters) # pyright: ignore
|
|
57
226
|
if not results:
|
|
58
227
|
return []
|
|
59
228
|
if schema_type is None:
|
|
@@ -65,8 +234,10 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
65
234
|
sql: str,
|
|
66
235
|
parameters: Optional["StatementParameterType"] = None,
|
|
67
236
|
/,
|
|
68
|
-
|
|
237
|
+
*,
|
|
238
|
+
connection: Optional["AsyncpgConnection"] = None,
|
|
69
239
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
240
|
+
**kwargs: Any,
|
|
70
241
|
) -> "Union[ModelDTOT, dict[str, Any]]":
|
|
71
242
|
"""Fetch one row from the database.
|
|
72
243
|
|
|
@@ -75,16 +246,15 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
75
246
|
parameters: Query parameters.
|
|
76
247
|
connection: Optional connection to use.
|
|
77
248
|
schema_type: Optional schema class for the result.
|
|
249
|
+
**kwargs: Additional keyword arguments.
|
|
78
250
|
|
|
79
251
|
Returns:
|
|
80
252
|
The first row of the query results.
|
|
81
253
|
"""
|
|
82
254
|
connection = self._connection(connection)
|
|
83
|
-
sql,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
result = await connection.fetchrow(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
255
|
+
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
256
|
+
parameters = parameters if parameters is not None else {}
|
|
257
|
+
result = await connection.fetchrow(sql, *parameters) # pyright: ignore
|
|
88
258
|
result = self.check_not_found(result)
|
|
89
259
|
|
|
90
260
|
if schema_type is None:
|
|
@@ -97,8 +267,10 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
97
267
|
sql: str,
|
|
98
268
|
parameters: Optional["StatementParameterType"] = None,
|
|
99
269
|
/,
|
|
100
|
-
|
|
270
|
+
*,
|
|
271
|
+
connection: Optional["AsyncpgConnection"] = None,
|
|
101
272
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
273
|
+
**kwargs: Any,
|
|
102
274
|
) -> "Optional[Union[ModelDTOT, dict[str, Any]]]":
|
|
103
275
|
"""Fetch one row from the database.
|
|
104
276
|
|
|
@@ -107,51 +279,62 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
107
279
|
parameters: Query parameters.
|
|
108
280
|
connection: Optional connection to use.
|
|
109
281
|
schema_type: Optional schema class for the result.
|
|
282
|
+
**kwargs: Additional keyword arguments.
|
|
110
283
|
|
|
111
284
|
Returns:
|
|
112
285
|
The first row of the query results.
|
|
113
286
|
"""
|
|
114
287
|
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
|
|
288
|
+
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
289
|
+
parameters = parameters if parameters is not None else {}
|
|
290
|
+
result = await connection.fetchrow(sql, *parameters) # pyright: ignore
|
|
291
|
+
if result is None:
|
|
292
|
+
return None
|
|
119
293
|
if schema_type is None:
|
|
120
294
|
# Always return as dictionary
|
|
121
|
-
return dict(result.items())
|
|
122
|
-
return cast("ModelDTOT", schema_type(**dict(result.items())))
|
|
295
|
+
return dict(result.items())
|
|
296
|
+
return cast("ModelDTOT", schema_type(**dict(result.items())))
|
|
123
297
|
|
|
124
298
|
async def select_value(
|
|
125
299
|
self,
|
|
126
300
|
sql: str,
|
|
127
301
|
parameters: "Optional[StatementParameterType]" = None,
|
|
128
302
|
/,
|
|
129
|
-
|
|
303
|
+
*,
|
|
304
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
130
305
|
schema_type: "Optional[type[T]]" = None,
|
|
306
|
+
**kwargs: Any,
|
|
131
307
|
) -> "Union[T, Any]":
|
|
132
308
|
"""Fetch a single value from the database.
|
|
133
309
|
|
|
310
|
+
Args:
|
|
311
|
+
sql: SQL statement.
|
|
312
|
+
parameters: Query parameters.
|
|
313
|
+
connection: Optional connection to use.
|
|
314
|
+
schema_type: Optional schema class for the result.
|
|
315
|
+
**kwargs: Additional keyword arguments.
|
|
316
|
+
|
|
134
317
|
Returns:
|
|
135
318
|
The first value from the first row of results, or None if no results.
|
|
136
319
|
"""
|
|
137
320
|
connection = self._connection(connection)
|
|
138
|
-
sql,
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
result = await connection.fetchval(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
321
|
+
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
322
|
+
parameters = parameters if parameters is not None else {}
|
|
323
|
+
result = await connection.fetchval(sql, *parameters) # pyright: ignore
|
|
143
324
|
result = self.check_not_found(result)
|
|
144
325
|
if schema_type is None:
|
|
145
|
-
return result
|
|
146
|
-
return schema_type(result
|
|
326
|
+
return result
|
|
327
|
+
return schema_type(result) # type: ignore[call-arg]
|
|
147
328
|
|
|
148
329
|
async def select_value_or_none(
|
|
149
330
|
self,
|
|
150
331
|
sql: str,
|
|
151
332
|
parameters: "Optional[StatementParameterType]" = None,
|
|
152
333
|
/,
|
|
153
|
-
|
|
334
|
+
*,
|
|
335
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
154
336
|
schema_type: "Optional[type[T]]" = None,
|
|
337
|
+
**kwargs: Any,
|
|
155
338
|
) -> "Optional[Union[T, Any]]":
|
|
156
339
|
"""Fetch a single value from the database.
|
|
157
340
|
|
|
@@ -159,23 +342,23 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
159
342
|
The first value from the first row of results, or None if no results.
|
|
160
343
|
"""
|
|
161
344
|
connection = self._connection(connection)
|
|
162
|
-
sql,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
result = await connection.fetchval(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
345
|
+
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
346
|
+
parameters = parameters if parameters is not None else {}
|
|
347
|
+
result = await connection.fetchval(sql, *parameters) # pyright: ignore
|
|
167
348
|
if result is None:
|
|
168
349
|
return None
|
|
169
350
|
if schema_type is None:
|
|
170
|
-
return result
|
|
171
|
-
return schema_type(result
|
|
351
|
+
return result
|
|
352
|
+
return schema_type(result) # type: ignore[call-arg]
|
|
172
353
|
|
|
173
354
|
async def insert_update_delete(
|
|
174
355
|
self,
|
|
175
356
|
sql: str,
|
|
176
357
|
parameters: Optional["StatementParameterType"] = None,
|
|
177
358
|
/,
|
|
178
|
-
|
|
359
|
+
*,
|
|
360
|
+
connection: Optional["AsyncpgConnection"] = None,
|
|
361
|
+
**kwargs: Any,
|
|
179
362
|
) -> int:
|
|
180
363
|
"""Insert, update, or delete data from the database.
|
|
181
364
|
|
|
@@ -183,16 +366,15 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
183
366
|
sql: SQL statement.
|
|
184
367
|
parameters: Query parameters.
|
|
185
368
|
connection: Optional connection to use.
|
|
369
|
+
**kwargs: Additional keyword arguments.
|
|
186
370
|
|
|
187
371
|
Returns:
|
|
188
372
|
Row count affected by the operation.
|
|
189
373
|
"""
|
|
190
374
|
connection = self._connection(connection)
|
|
191
|
-
sql,
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
status = await connection.execute(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
375
|
+
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
376
|
+
parameters = parameters if parameters is not None else {}
|
|
377
|
+
status = await connection.execute(sql, *parameters) # pyright: ignore
|
|
196
378
|
# AsyncPG returns a string like "INSERT 0 1" where the last number is the affected rows
|
|
197
379
|
try:
|
|
198
380
|
return int(status.split()[-1]) # pyright: ignore[reportUnknownMemberType]
|
|
@@ -204,26 +386,27 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
204
386
|
sql: str,
|
|
205
387
|
parameters: Optional["StatementParameterType"] = None,
|
|
206
388
|
/,
|
|
207
|
-
|
|
389
|
+
*,
|
|
390
|
+
connection: Optional["AsyncpgConnection"] = None,
|
|
208
391
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
392
|
+
**kwargs: Any,
|
|
209
393
|
) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
|
|
210
|
-
"""Insert, update, or delete data from the database and return
|
|
394
|
+
"""Insert, update, or delete data from the database and return the affected row.
|
|
211
395
|
|
|
212
396
|
Args:
|
|
213
397
|
sql: SQL statement.
|
|
214
398
|
parameters: Query parameters.
|
|
215
399
|
connection: Optional connection to use.
|
|
216
400
|
schema_type: Optional schema class for the result.
|
|
401
|
+
**kwargs: Additional keyword arguments.
|
|
217
402
|
|
|
218
403
|
Returns:
|
|
219
|
-
The
|
|
404
|
+
The affected row data as either a model instance or dictionary.
|
|
220
405
|
"""
|
|
221
406
|
connection = self._connection(connection)
|
|
222
|
-
sql,
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
result = await connection.fetchrow(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
407
|
+
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
408
|
+
parameters = parameters if parameters is not None else {}
|
|
409
|
+
result = await connection.fetchrow(sql, *parameters) # pyright: ignore
|
|
227
410
|
if result is None:
|
|
228
411
|
return None
|
|
229
412
|
if schema_type is None:
|
|
@@ -236,7 +419,9 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
236
419
|
sql: str,
|
|
237
420
|
parameters: Optional["StatementParameterType"] = None,
|
|
238
421
|
/,
|
|
239
|
-
|
|
422
|
+
*,
|
|
423
|
+
connection: Optional["AsyncpgConnection"] = None,
|
|
424
|
+
**kwargs: Any,
|
|
240
425
|
) -> str:
|
|
241
426
|
"""Execute a script.
|
|
242
427
|
|
|
@@ -244,45 +429,16 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
|
|
|
244
429
|
sql: SQL statement.
|
|
245
430
|
parameters: Query parameters.
|
|
246
431
|
connection: Optional connection to use.
|
|
432
|
+
**kwargs: Additional keyword arguments.
|
|
247
433
|
|
|
248
434
|
Returns:
|
|
249
435
|
Status message for the operation.
|
|
250
436
|
"""
|
|
251
437
|
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.
|
|
267
|
-
|
|
268
|
-
Args:
|
|
269
|
-
sql: SQL statement.
|
|
270
|
-
parameters: Query parameters.
|
|
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 ()
|
|
438
|
+
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
439
|
+
parameters = parameters if parameters is not None else {}
|
|
440
|
+
return await connection.execute(sql, *parameters) # pyright: ignore
|
|
281
441
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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]
|
|
442
|
+
def _connection(self, connection: Optional["AsyncpgConnection"] = None) -> "AsyncpgConnection":
|
|
443
|
+
"""Return the connection to use. If None, use the default connection."""
|
|
444
|
+
return connection if connection is not None else self.connection
|
|
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
|
|
|
14
14
|
from collections.abc import Generator, Sequence
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
__all__ = ("
|
|
17
|
+
__all__ = ("DuckDBConfig", "ExtensionConfig")
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class ExtensionConfig(TypedDict):
|
|
@@ -69,7 +69,7 @@ class SecretConfig(TypedDict):
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
@dataclass
|
|
72
|
-
class
|
|
72
|
+
class DuckDBConfig(NoPoolSyncConfig["DuckDBPyConnection", "DuckDBDriver"]):
|
|
73
73
|
"""Configuration for DuckDB database connections.
|
|
74
74
|
|
|
75
75
|
This class provides configuration options for DuckDB database connections, wrapping all parameters
|