sqlspec 0.10.0__py3-none-any.whl → 0.11.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 +24 -32
- sqlspec/adapters/adbc/config.py +1 -1
- sqlspec/adapters/adbc/driver.py +336 -165
- sqlspec/adapters/aiosqlite/driver.py +211 -126
- sqlspec/adapters/asyncmy/driver.py +164 -68
- sqlspec/adapters/asyncpg/config.py +3 -1
- sqlspec/adapters/asyncpg/driver.py +190 -231
- sqlspec/adapters/bigquery/driver.py +178 -169
- sqlspec/adapters/duckdb/driver.py +175 -84
- sqlspec/adapters/oracledb/driver.py +224 -90
- sqlspec/adapters/psqlpy/driver.py +267 -187
- sqlspec/adapters/psycopg/driver.py +138 -184
- sqlspec/adapters/sqlite/driver.py +153 -121
- sqlspec/base.py +57 -45
- sqlspec/extensions/litestar/__init__.py +3 -12
- sqlspec/extensions/litestar/config.py +22 -7
- sqlspec/extensions/litestar/handlers.py +142 -85
- sqlspec/extensions/litestar/plugin.py +9 -8
- sqlspec/extensions/litestar/providers.py +521 -0
- sqlspec/filters.py +214 -11
- sqlspec/mixins.py +152 -2
- sqlspec/statement.py +276 -271
- sqlspec/typing.py +18 -1
- sqlspec/utils/__init__.py +2 -2
- sqlspec/utils/singleton.py +35 -0
- sqlspec/utils/sync_tools.py +90 -151
- sqlspec/utils/text.py +68 -5
- {sqlspec-0.10.0.dist-info → sqlspec-0.11.0.dist-info}/METADATA +5 -1
- {sqlspec-0.10.0.dist-info → sqlspec-0.11.0.dist-info}/RECORD +32 -30
- {sqlspec-0.10.0.dist-info → sqlspec-0.11.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.10.0.dist-info → sqlspec-0.11.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.10.0.dist-info → sqlspec-0.11.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,44 +1,61 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import re
|
|
3
|
-
from
|
|
3
|
+
from re import Match
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Optional, Union, overload
|
|
4
5
|
|
|
5
6
|
from asyncpg import Connection
|
|
7
|
+
from sqlglot import exp
|
|
6
8
|
from typing_extensions import TypeAlias
|
|
7
9
|
|
|
8
10
|
from sqlspec.base import AsyncDriverAdapterProtocol
|
|
9
|
-
from sqlspec.
|
|
10
|
-
from sqlspec.
|
|
11
|
-
from sqlspec.statement import PARAM_REGEX, SQLStatement
|
|
11
|
+
from sqlspec.mixins import ResultConverter, SQLTranslatorMixin
|
|
12
|
+
from sqlspec.statement import SQLStatement
|
|
12
13
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
14
15
|
from collections.abc import Sequence
|
|
15
16
|
|
|
17
|
+
from asyncpg import Record
|
|
16
18
|
from asyncpg.connection import Connection
|
|
17
19
|
from asyncpg.pool import PoolConnectionProxy
|
|
18
20
|
|
|
21
|
+
from sqlspec.filters import StatementFilter
|
|
19
22
|
from sqlspec.typing import ModelDTOT, StatementParameterType, T
|
|
20
23
|
|
|
21
24
|
__all__ = ("AsyncpgConnection", "AsyncpgDriver")
|
|
22
25
|
|
|
23
26
|
logger = logging.getLogger("sqlspec")
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
AsyncpgConnection: TypeAlias = Union[Connection[Record], PoolConnectionProxy[Record]]
|
|
30
|
+
else:
|
|
31
|
+
AsyncpgConnection: TypeAlias = "Union[Connection, PoolConnectionProxy]"
|
|
32
|
+
|
|
33
|
+
# Compile the row count regex once for efficiency
|
|
34
|
+
ROWCOUNT_REGEX = re.compile(r"^(?:INSERT|UPDATE|DELETE) \d+ (\d+)$")
|
|
35
|
+
|
|
36
|
+
# Improved regex to match question mark placeholders only when they are outside string literals and comments
|
|
37
|
+
# This pattern handles:
|
|
38
|
+
# 1. Single quoted strings with escaped quotes
|
|
39
|
+
# 2. Double quoted strings with escaped quotes
|
|
40
|
+
# 3. Single-line comments (-- to end of line)
|
|
41
|
+
# 4. Multi-line comments (/* to */)
|
|
42
|
+
# 5. Only question marks outside of these contexts are considered parameters
|
|
43
|
+
QUESTION_MARK_PATTERN = re.compile(
|
|
44
|
+
r"""
|
|
45
|
+
(?:'[^']*(?:''[^']*)*') | # Skip single-quoted strings (with '' escapes)
|
|
46
|
+
(?:"[^"]*(?:""[^"]*)*") | # Skip double-quoted strings (with "" escapes)
|
|
47
|
+
(?:--.*?(?:\n|$)) | # Skip single-line comments
|
|
48
|
+
(?:/\*(?:[^*]|\*(?!/))*\*/) | # Skip multi-line comments
|
|
49
|
+
(\?) # Capture only question marks outside of these contexts
|
|
50
|
+
""",
|
|
33
51
|
re.VERBOSE | re.DOTALL,
|
|
34
52
|
)
|
|
35
53
|
|
|
36
|
-
AsyncpgConnection: TypeAlias = "Union[Connection[Any], PoolConnectionProxy[Any]]" # pyright: ignore[reportMissingTypeArgument]
|
|
37
|
-
|
|
38
54
|
|
|
39
55
|
class AsyncpgDriver(
|
|
40
56
|
SQLTranslatorMixin["AsyncpgConnection"],
|
|
41
57
|
AsyncDriverAdapterProtocol["AsyncpgConnection"],
|
|
58
|
+
ResultConverter,
|
|
42
59
|
):
|
|
43
60
|
"""AsyncPG Postgres Driver Adapter."""
|
|
44
61
|
|
|
@@ -48,159 +65,99 @@ class AsyncpgDriver(
|
|
|
48
65
|
def __init__(self, connection: "AsyncpgConnection") -> None:
|
|
49
66
|
self.connection = connection
|
|
50
67
|
|
|
51
|
-
def _process_sql_params(
|
|
68
|
+
def _process_sql_params(
|
|
52
69
|
self,
|
|
53
70
|
sql: str,
|
|
54
71
|
parameters: "Optional[StatementParameterType]" = None,
|
|
55
72
|
/,
|
|
73
|
+
*filters: "StatementFilter",
|
|
56
74
|
**kwargs: Any,
|
|
57
75
|
) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
|
|
58
|
-
|
|
59
|
-
# It also handles potential dialect-specific logic if implemented there.
|
|
60
|
-
stmt = SQLStatement(sql=sql, parameters=parameters, dialect=self.dialect, kwargs=kwargs or None)
|
|
61
|
-
sql, parameters = stmt.process()
|
|
62
|
-
|
|
63
|
-
# Case 1: Parameters are effectively a dictionary (either passed as dict or via kwargs merged by SQLStatement)
|
|
64
|
-
if isinstance(parameters, dict):
|
|
65
|
-
processed_sql_parts: list[str] = []
|
|
66
|
-
ordered_params = []
|
|
67
|
-
last_end = 0
|
|
68
|
-
param_index = 1
|
|
69
|
-
found_params_regex: list[str] = []
|
|
70
|
-
|
|
71
|
-
# Manually parse the PROCESSED SQL for :name -> $n conversion
|
|
72
|
-
for match in PARAM_REGEX.finditer(sql):
|
|
73
|
-
# Skip matches inside quotes or comments
|
|
74
|
-
if match.group("dquote") or match.group("squote") or match.group("comment"):
|
|
75
|
-
continue
|
|
76
|
-
|
|
77
|
-
if match.group("var_name"): # Finds :var_name
|
|
78
|
-
var_name = match.group("var_name")
|
|
79
|
-
found_params_regex.append(var_name)
|
|
80
|
-
start = match.start("var_name") - 1 # Include the ':'
|
|
81
|
-
end = match.end("var_name")
|
|
82
|
-
|
|
83
|
-
# SQLStatement should have already validated parameter existence,
|
|
84
|
-
# but we double-check here during ordering.
|
|
85
|
-
if var_name not in parameters:
|
|
86
|
-
# This should ideally not happen if SQLStatement validation is robust.
|
|
87
|
-
msg = (
|
|
88
|
-
f"Named parameter ':{var_name}' found in SQL but missing from processed parameters. "
|
|
89
|
-
f"Processed SQL: {sql}"
|
|
90
|
-
)
|
|
91
|
-
raise SQLParsingError(msg)
|
|
92
|
-
|
|
93
|
-
processed_sql_parts.extend((sql[last_end:start], f"${param_index}"))
|
|
94
|
-
ordered_params.append(parameters[var_name])
|
|
95
|
-
last_end = end
|
|
96
|
-
param_index += 1
|
|
76
|
+
"""Process SQL and parameters for AsyncPG using SQLStatement.
|
|
97
77
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
# SQLStatement might handle this, but log a warning just in case.
|
|
121
|
-
logger.warning(
|
|
122
|
-
"Parameters provided but not used in SQL: %s. SQL: %s",
|
|
123
|
-
unused_keys,
|
|
124
|
-
sql,
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
return final_sql, tuple(ordered_params) # asyncpg expects a sequence
|
|
128
|
-
|
|
129
|
-
# Case 2: Parameters are effectively a sequence/scalar (merged by SQLStatement)
|
|
130
|
-
if isinstance(parameters, (list, tuple)):
|
|
131
|
-
# Parameters are a sequence, need to convert ? -> $n
|
|
132
|
-
sequence_processed_parts: list[str] = []
|
|
133
|
-
param_index = 1
|
|
134
|
-
last_end = 0
|
|
135
|
-
qmark_found = False
|
|
136
|
-
|
|
137
|
-
# Manually parse the PROCESSED SQL to find '?' outside comments/quotes and convert to $n
|
|
138
|
-
for match in QMARK_REGEX.finditer(sql):
|
|
139
|
-
if match.group("dquote") or match.group("squote") or match.group("comment"):
|
|
140
|
-
continue # Skip quotes and comments
|
|
141
|
-
|
|
142
|
-
if match.group("qmark"):
|
|
143
|
-
qmark_found = True
|
|
144
|
-
start = match.start("qmark")
|
|
145
|
-
end = match.end("qmark")
|
|
146
|
-
sequence_processed_parts.extend((sql[last_end:start], f"${param_index}"))
|
|
147
|
-
last_end = end
|
|
148
|
-
param_index += 1
|
|
78
|
+
This method applies filters (if provided), processes the SQL through SQLStatement
|
|
79
|
+
with dialect support, and converts parameters to the format required by AsyncPG.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
sql: SQL statement.
|
|
83
|
+
parameters: Query parameters.
|
|
84
|
+
*filters: Statement filters to apply.
|
|
85
|
+
**kwargs: Additional keyword arguments.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Tuple of processed SQL and parameters.
|
|
89
|
+
"""
|
|
90
|
+
# Handle scalar parameter by converting to a single-item tuple
|
|
91
|
+
if parameters is not None and not isinstance(parameters, (list, tuple, dict)):
|
|
92
|
+
parameters = (parameters,)
|
|
93
|
+
|
|
94
|
+
# Create a SQLStatement with PostgreSQL dialect
|
|
95
|
+
statement = SQLStatement(sql, parameters, kwargs=kwargs, dialect=self.dialect)
|
|
96
|
+
|
|
97
|
+
# Apply any filters
|
|
98
|
+
for filter_obj in filters:
|
|
99
|
+
statement = statement.apply_filter(filter_obj)
|
|
149
100
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
101
|
+
# Process the statement
|
|
102
|
+
processed_sql, processed_params, parsed_expr = statement.process()
|
|
103
|
+
|
|
104
|
+
if processed_params is None:
|
|
105
|
+
return processed_sql, ()
|
|
106
|
+
|
|
107
|
+
# Convert question marks to PostgreSQL style $N parameters
|
|
108
|
+
if isinstance(processed_params, (list, tuple)) and "?" in processed_sql:
|
|
109
|
+
# Use a counter to generate $1, $2, etc. for each ? in the SQL that's outside strings/comments
|
|
110
|
+
param_index = 0
|
|
111
|
+
|
|
112
|
+
def replace_question_mark(match: Match[str]) -> str:
|
|
113
|
+
# Only process the match if it's not in a skipped context (string/comment)
|
|
114
|
+
if match.group(1): # This is a question mark outside string/comment
|
|
115
|
+
nonlocal param_index
|
|
116
|
+
param_index += 1
|
|
117
|
+
return f"${param_index}"
|
|
118
|
+
# Return the entire matched text unchanged for strings/comments
|
|
119
|
+
return match.group(0)
|
|
120
|
+
|
|
121
|
+
processed_sql = QUESTION_MARK_PATTERN.sub(replace_question_mark, processed_sql)
|
|
122
|
+
|
|
123
|
+
# Now handle the asyncpg-specific parameter conversion - asyncpg requires positional parameters
|
|
124
|
+
if isinstance(processed_params, dict):
|
|
125
|
+
if parsed_expr is not None:
|
|
126
|
+
# Find named parameters
|
|
127
|
+
named_params = []
|
|
128
|
+
for node in parsed_expr.find_all(exp.Parameter, exp.Placeholder):
|
|
129
|
+
if isinstance(node, exp.Parameter) and node.name and node.name in processed_params:
|
|
130
|
+
named_params.append(node.name)
|
|
131
|
+
elif (
|
|
132
|
+
isinstance(node, exp.Placeholder)
|
|
133
|
+
and isinstance(node.this, str)
|
|
134
|
+
and node.this in processed_params
|
|
135
|
+
):
|
|
136
|
+
named_params.append(node.this)
|
|
137
|
+
|
|
138
|
+
# Convert named parameters to positional
|
|
139
|
+
if named_params:
|
|
140
|
+
# Transform the SQL to use $1, $2, etc.
|
|
141
|
+
def replace_named_with_positional(node: exp.Expression) -> exp.Expression:
|
|
142
|
+
if isinstance(node, exp.Parameter) and node.name and node.name in processed_params:
|
|
143
|
+
idx = named_params.index(node.name) + 1
|
|
144
|
+
return exp.Parameter(this=str(idx))
|
|
145
|
+
if (
|
|
146
|
+
isinstance(node, exp.Placeholder)
|
|
147
|
+
and isinstance(node.this, str)
|
|
148
|
+
and node.this in processed_params
|
|
149
|
+
):
|
|
150
|
+
idx = named_params.index(node.this) + 1
|
|
151
|
+
return exp.Parameter(this=str(idx))
|
|
152
|
+
return node
|
|
153
|
+
|
|
154
|
+
return parsed_expr.transform(replace_named_with_positional, copy=True).sql(
|
|
155
|
+
dialect=self.dialect
|
|
156
|
+
), tuple(processed_params[name] for name in named_params)
|
|
157
|
+
return processed_sql, tuple(processed_params.values())
|
|
158
|
+
if isinstance(processed_params, (list, tuple)):
|
|
159
|
+
return processed_sql, tuple(processed_params)
|
|
160
|
+
return processed_sql, (processed_params,) # type: ignore[unreachable]
|
|
204
161
|
|
|
205
162
|
@overload
|
|
206
163
|
async def select(
|
|
@@ -208,7 +165,7 @@ class AsyncpgDriver(
|
|
|
208
165
|
sql: str,
|
|
209
166
|
parameters: "Optional[StatementParameterType]" = None,
|
|
210
167
|
/,
|
|
211
|
-
|
|
168
|
+
*filters: "StatementFilter",
|
|
212
169
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
213
170
|
schema_type: None = None,
|
|
214
171
|
**kwargs: Any,
|
|
@@ -219,7 +176,7 @@ class AsyncpgDriver(
|
|
|
219
176
|
sql: str,
|
|
220
177
|
parameters: "Optional[StatementParameterType]" = None,
|
|
221
178
|
/,
|
|
222
|
-
|
|
179
|
+
*filters: "StatementFilter",
|
|
223
180
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
224
181
|
schema_type: "type[ModelDTOT]",
|
|
225
182
|
**kwargs: Any,
|
|
@@ -227,16 +184,17 @@ class AsyncpgDriver(
|
|
|
227
184
|
async def select(
|
|
228
185
|
self,
|
|
229
186
|
sql: str,
|
|
230
|
-
parameters: Optional[
|
|
187
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
231
188
|
/,
|
|
232
|
-
|
|
233
|
-
connection: Optional[
|
|
189
|
+
*filters: "StatementFilter",
|
|
190
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
234
191
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
235
192
|
**kwargs: Any,
|
|
236
|
-
) -> "Sequence[Union[
|
|
193
|
+
) -> "Sequence[Union[dict[str, Any], ModelDTOT]]":
|
|
237
194
|
"""Fetch data from the database.
|
|
238
195
|
|
|
239
196
|
Args:
|
|
197
|
+
*filters: Statement filters to apply.
|
|
240
198
|
sql: SQL statement.
|
|
241
199
|
parameters: Query parameters.
|
|
242
200
|
connection: Optional connection to use.
|
|
@@ -247,15 +205,13 @@ class AsyncpgDriver(
|
|
|
247
205
|
List of row data as either model instances or dictionaries.
|
|
248
206
|
"""
|
|
249
207
|
connection = self._connection(connection)
|
|
250
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
251
|
-
parameters = parameters if parameters is not None else
|
|
208
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
209
|
+
parameters = parameters if parameters is not None else ()
|
|
252
210
|
|
|
253
211
|
results = await connection.fetch(sql, *parameters) # pyright: ignore
|
|
254
212
|
if not results:
|
|
255
213
|
return []
|
|
256
|
-
|
|
257
|
-
return [dict(row.items()) for row in results] # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
258
|
-
return [cast("ModelDTOT", schema_type(**dict(row.items()))) for row in results] # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
214
|
+
return self.to_schema([dict(row.items()) for row in results], schema_type=schema_type) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
259
215
|
|
|
260
216
|
@overload
|
|
261
217
|
async def select_one(
|
|
@@ -263,7 +219,7 @@ class AsyncpgDriver(
|
|
|
263
219
|
sql: str,
|
|
264
220
|
parameters: "Optional[StatementParameterType]" = None,
|
|
265
221
|
/,
|
|
266
|
-
|
|
222
|
+
*filters: "StatementFilter",
|
|
267
223
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
268
224
|
schema_type: None = None,
|
|
269
225
|
**kwargs: Any,
|
|
@@ -274,7 +230,7 @@ class AsyncpgDriver(
|
|
|
274
230
|
sql: str,
|
|
275
231
|
parameters: "Optional[StatementParameterType]" = None,
|
|
276
232
|
/,
|
|
277
|
-
|
|
233
|
+
*filters: "StatementFilter",
|
|
278
234
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
279
235
|
schema_type: "type[ModelDTOT]",
|
|
280
236
|
**kwargs: Any,
|
|
@@ -282,16 +238,17 @@ class AsyncpgDriver(
|
|
|
282
238
|
async def select_one(
|
|
283
239
|
self,
|
|
284
240
|
sql: str,
|
|
285
|
-
parameters: Optional[
|
|
241
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
286
242
|
/,
|
|
287
|
-
|
|
288
|
-
connection: Optional[
|
|
243
|
+
*filters: "StatementFilter",
|
|
244
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
289
245
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
290
246
|
**kwargs: Any,
|
|
291
|
-
) -> "Union[
|
|
247
|
+
) -> "Union[dict[str, Any], ModelDTOT]":
|
|
292
248
|
"""Fetch one row from the database.
|
|
293
249
|
|
|
294
250
|
Args:
|
|
251
|
+
*filters: Statement filters to apply.
|
|
295
252
|
sql: SQL statement.
|
|
296
253
|
parameters: Query parameters.
|
|
297
254
|
connection: Optional connection to use.
|
|
@@ -302,15 +259,11 @@ class AsyncpgDriver(
|
|
|
302
259
|
The first row of the query results.
|
|
303
260
|
"""
|
|
304
261
|
connection = self._connection(connection)
|
|
305
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
306
|
-
parameters = parameters if parameters is not None else
|
|
262
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
263
|
+
parameters = parameters if parameters is not None else ()
|
|
307
264
|
result = await connection.fetchrow(sql, *parameters) # pyright: ignore
|
|
308
265
|
result = self.check_not_found(result)
|
|
309
|
-
|
|
310
|
-
if schema_type is None:
|
|
311
|
-
# Always return as dictionary
|
|
312
|
-
return dict(result.items()) # type: ignore[attr-defined]
|
|
313
|
-
return cast("ModelDTOT", schema_type(**dict(result.items()))) # type: ignore[attr-defined]
|
|
266
|
+
return self.to_schema(dict(result.items()), schema_type=schema_type)
|
|
314
267
|
|
|
315
268
|
@overload
|
|
316
269
|
async def select_one_or_none(
|
|
@@ -318,7 +271,7 @@ class AsyncpgDriver(
|
|
|
318
271
|
sql: str,
|
|
319
272
|
parameters: "Optional[StatementParameterType]" = None,
|
|
320
273
|
/,
|
|
321
|
-
|
|
274
|
+
*filters: "StatementFilter",
|
|
322
275
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
323
276
|
schema_type: None = None,
|
|
324
277
|
**kwargs: Any,
|
|
@@ -329,7 +282,7 @@ class AsyncpgDriver(
|
|
|
329
282
|
sql: str,
|
|
330
283
|
parameters: "Optional[StatementParameterType]" = None,
|
|
331
284
|
/,
|
|
332
|
-
|
|
285
|
+
*filters: "StatementFilter",
|
|
333
286
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
334
287
|
schema_type: "type[ModelDTOT]",
|
|
335
288
|
**kwargs: Any,
|
|
@@ -337,16 +290,17 @@ class AsyncpgDriver(
|
|
|
337
290
|
async def select_one_or_none(
|
|
338
291
|
self,
|
|
339
292
|
sql: str,
|
|
340
|
-
parameters: Optional[
|
|
293
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
341
294
|
/,
|
|
342
|
-
|
|
343
|
-
connection: Optional[
|
|
295
|
+
*filters: "StatementFilter",
|
|
296
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
344
297
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
345
298
|
**kwargs: Any,
|
|
346
|
-
) -> "Optional[Union[
|
|
299
|
+
) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
|
|
347
300
|
"""Fetch one row from the database.
|
|
348
301
|
|
|
349
302
|
Args:
|
|
303
|
+
*filters: Statement filters to apply.
|
|
350
304
|
sql: SQL statement.
|
|
351
305
|
parameters: Query parameters.
|
|
352
306
|
connection: Optional connection to use.
|
|
@@ -357,15 +311,12 @@ class AsyncpgDriver(
|
|
|
357
311
|
The first row of the query results.
|
|
358
312
|
"""
|
|
359
313
|
connection = self._connection(connection)
|
|
360
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
361
|
-
parameters = parameters if parameters is not None else
|
|
314
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
315
|
+
parameters = parameters if parameters is not None else ()
|
|
362
316
|
result = await connection.fetchrow(sql, *parameters) # pyright: ignore
|
|
363
317
|
if result is None:
|
|
364
318
|
return None
|
|
365
|
-
|
|
366
|
-
# Always return as dictionary
|
|
367
|
-
return dict(result.items())
|
|
368
|
-
return cast("ModelDTOT", schema_type(**dict(result.items())))
|
|
319
|
+
return self.to_schema(dict(result.items()), schema_type=schema_type)
|
|
369
320
|
|
|
370
321
|
@overload
|
|
371
322
|
async def select_value(
|
|
@@ -373,7 +324,7 @@ class AsyncpgDriver(
|
|
|
373
324
|
sql: str,
|
|
374
325
|
parameters: "Optional[StatementParameterType]" = None,
|
|
375
326
|
/,
|
|
376
|
-
|
|
327
|
+
*filters: "StatementFilter",
|
|
377
328
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
378
329
|
schema_type: None = None,
|
|
379
330
|
**kwargs: Any,
|
|
@@ -384,7 +335,7 @@ class AsyncpgDriver(
|
|
|
384
335
|
sql: str,
|
|
385
336
|
parameters: "Optional[StatementParameterType]" = None,
|
|
386
337
|
/,
|
|
387
|
-
|
|
338
|
+
*filters: "StatementFilter",
|
|
388
339
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
389
340
|
schema_type: "type[T]",
|
|
390
341
|
**kwargs: Any,
|
|
@@ -394,7 +345,7 @@ class AsyncpgDriver(
|
|
|
394
345
|
sql: str,
|
|
395
346
|
parameters: "Optional[StatementParameterType]" = None,
|
|
396
347
|
/,
|
|
397
|
-
|
|
348
|
+
*filters: "StatementFilter",
|
|
398
349
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
399
350
|
schema_type: "Optional[type[T]]" = None,
|
|
400
351
|
**kwargs: Any,
|
|
@@ -402,6 +353,7 @@ class AsyncpgDriver(
|
|
|
402
353
|
"""Fetch a single value from the database.
|
|
403
354
|
|
|
404
355
|
Args:
|
|
356
|
+
*filters: Statement filters to apply.
|
|
405
357
|
sql: SQL statement.
|
|
406
358
|
parameters: Query parameters.
|
|
407
359
|
connection: Optional connection to use.
|
|
@@ -412,8 +364,8 @@ class AsyncpgDriver(
|
|
|
412
364
|
The first value from the first row of results, or None if no results.
|
|
413
365
|
"""
|
|
414
366
|
connection = self._connection(connection)
|
|
415
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
416
|
-
parameters = parameters if parameters is not None else
|
|
367
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
368
|
+
parameters = parameters if parameters is not None else ()
|
|
417
369
|
result = await connection.fetchval(sql, *parameters) # pyright: ignore
|
|
418
370
|
result = self.check_not_found(result)
|
|
419
371
|
if schema_type is None:
|
|
@@ -426,7 +378,7 @@ class AsyncpgDriver(
|
|
|
426
378
|
sql: str,
|
|
427
379
|
parameters: "Optional[StatementParameterType]" = None,
|
|
428
380
|
/,
|
|
429
|
-
|
|
381
|
+
*filters: "StatementFilter",
|
|
430
382
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
431
383
|
schema_type: None = None,
|
|
432
384
|
**kwargs: Any,
|
|
@@ -437,7 +389,7 @@ class AsyncpgDriver(
|
|
|
437
389
|
sql: str,
|
|
438
390
|
parameters: "Optional[StatementParameterType]" = None,
|
|
439
391
|
/,
|
|
440
|
-
|
|
392
|
+
*filters: "StatementFilter",
|
|
441
393
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
442
394
|
schema_type: "type[T]",
|
|
443
395
|
**kwargs: Any,
|
|
@@ -447,19 +399,27 @@ class AsyncpgDriver(
|
|
|
447
399
|
sql: str,
|
|
448
400
|
parameters: "Optional[StatementParameterType]" = None,
|
|
449
401
|
/,
|
|
450
|
-
|
|
402
|
+
*filters: "StatementFilter",
|
|
451
403
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
452
404
|
schema_type: "Optional[type[T]]" = None,
|
|
453
405
|
**kwargs: Any,
|
|
454
406
|
) -> "Optional[Union[T, Any]]":
|
|
455
407
|
"""Fetch a single value from the database.
|
|
456
408
|
|
|
409
|
+
Args:
|
|
410
|
+
*filters: Statement filters to apply.
|
|
411
|
+
sql: SQL statement.
|
|
412
|
+
parameters: Query parameters.
|
|
413
|
+
connection: Optional connection to use.
|
|
414
|
+
schema_type: Optional schema class for the result.
|
|
415
|
+
**kwargs: Additional keyword arguments.
|
|
416
|
+
|
|
457
417
|
Returns:
|
|
458
418
|
The first value from the first row of results, or None if no results.
|
|
459
419
|
"""
|
|
460
420
|
connection = self._connection(connection)
|
|
461
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
462
|
-
parameters = parameters if parameters is not None else
|
|
421
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
422
|
+
parameters = parameters if parameters is not None else ()
|
|
463
423
|
result = await connection.fetchval(sql, *parameters) # pyright: ignore
|
|
464
424
|
if result is None:
|
|
465
425
|
return None
|
|
@@ -470,15 +430,16 @@ class AsyncpgDriver(
|
|
|
470
430
|
async def insert_update_delete(
|
|
471
431
|
self,
|
|
472
432
|
sql: str,
|
|
473
|
-
parameters: Optional[
|
|
433
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
474
434
|
/,
|
|
475
|
-
|
|
435
|
+
*filters: "StatementFilter",
|
|
476
436
|
connection: Optional["AsyncpgConnection"] = None,
|
|
477
437
|
**kwargs: Any,
|
|
478
438
|
) -> int:
|
|
479
439
|
"""Insert, update, or delete data from the database.
|
|
480
440
|
|
|
481
441
|
Args:
|
|
442
|
+
*filters: Statement filters to apply.
|
|
482
443
|
sql: SQL statement.
|
|
483
444
|
parameters: Query parameters.
|
|
484
445
|
connection: Optional connection to use.
|
|
@@ -488,14 +449,14 @@ class AsyncpgDriver(
|
|
|
488
449
|
Row count affected by the operation.
|
|
489
450
|
"""
|
|
490
451
|
connection = self._connection(connection)
|
|
491
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
492
|
-
parameters = parameters if parameters is not None else
|
|
493
|
-
|
|
494
|
-
#
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
452
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
453
|
+
parameters = parameters if parameters is not None else ()
|
|
454
|
+
result = await connection.execute(sql, *parameters) # pyright: ignore
|
|
455
|
+
# asyncpg returns e.g. 'INSERT 0 1', 'UPDATE 0 2', etc.
|
|
456
|
+
match = ROWCOUNT_REGEX.match(result)
|
|
457
|
+
if match:
|
|
458
|
+
return int(match.group(1))
|
|
459
|
+
return 0
|
|
499
460
|
|
|
500
461
|
@overload
|
|
501
462
|
async def insert_update_delete_returning(
|
|
@@ -503,7 +464,7 @@ class AsyncpgDriver(
|
|
|
503
464
|
sql: str,
|
|
504
465
|
parameters: "Optional[StatementParameterType]" = None,
|
|
505
466
|
/,
|
|
506
|
-
|
|
467
|
+
*filters: "StatementFilter",
|
|
507
468
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
508
469
|
schema_type: None = None,
|
|
509
470
|
**kwargs: Any,
|
|
@@ -514,7 +475,7 @@ class AsyncpgDriver(
|
|
|
514
475
|
sql: str,
|
|
515
476
|
parameters: "Optional[StatementParameterType]" = None,
|
|
516
477
|
/,
|
|
517
|
-
|
|
478
|
+
*filters: "StatementFilter",
|
|
518
479
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
519
480
|
schema_type: "type[ModelDTOT]",
|
|
520
481
|
**kwargs: Any,
|
|
@@ -522,16 +483,17 @@ class AsyncpgDriver(
|
|
|
522
483
|
async def insert_update_delete_returning(
|
|
523
484
|
self,
|
|
524
485
|
sql: str,
|
|
525
|
-
parameters: Optional[
|
|
486
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
526
487
|
/,
|
|
527
|
-
|
|
528
|
-
connection: Optional[
|
|
488
|
+
*filters: "StatementFilter",
|
|
489
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
529
490
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
530
491
|
**kwargs: Any,
|
|
531
492
|
) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
|
|
532
493
|
"""Insert, update, or delete data from the database and return the affected row.
|
|
533
494
|
|
|
534
495
|
Args:
|
|
496
|
+
*filters: Statement filters to apply.
|
|
535
497
|
sql: SQL statement.
|
|
536
498
|
parameters: Query parameters.
|
|
537
499
|
connection: Optional connection to use.
|
|
@@ -542,23 +504,20 @@ class AsyncpgDriver(
|
|
|
542
504
|
The affected row data as either a model instance or dictionary.
|
|
543
505
|
"""
|
|
544
506
|
connection = self._connection(connection)
|
|
545
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
546
|
-
parameters = parameters if parameters is not None else
|
|
507
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
508
|
+
parameters = parameters if parameters is not None else ()
|
|
547
509
|
result = await connection.fetchrow(sql, *parameters) # pyright: ignore
|
|
548
510
|
if result is None:
|
|
549
511
|
return None
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
return dict(result.items()) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
553
|
-
return cast("ModelDTOT", schema_type(**dict(result.items()))) # pyright: ignore[reportUnknownArgumentType, reportUnknownMemberType, reportUnknownVariableType]
|
|
512
|
+
|
|
513
|
+
return self.to_schema(dict(result.items()), schema_type=schema_type)
|
|
554
514
|
|
|
555
515
|
async def execute_script(
|
|
556
516
|
self,
|
|
557
517
|
sql: str,
|
|
558
|
-
parameters: Optional[
|
|
518
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
559
519
|
/,
|
|
560
|
-
|
|
561
|
-
connection: Optional["AsyncpgConnection"] = None,
|
|
520
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
562
521
|
**kwargs: Any,
|
|
563
522
|
) -> str:
|
|
564
523
|
"""Execute a script.
|
|
@@ -574,9 +533,9 @@ class AsyncpgDriver(
|
|
|
574
533
|
"""
|
|
575
534
|
connection = self._connection(connection)
|
|
576
535
|
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
577
|
-
parameters = parameters if parameters is not None else
|
|
536
|
+
parameters = parameters if parameters is not None else ()
|
|
578
537
|
return await connection.execute(sql, *parameters) # pyright: ignore
|
|
579
538
|
|
|
580
|
-
def _connection(self, connection: Optional[
|
|
539
|
+
def _connection(self, connection: "Optional[AsyncpgConnection]" = None) -> "AsyncpgConnection":
|
|
581
540
|
"""Return the connection to use. If None, use the default connection."""
|
|
582
541
|
return connection if connection is not None else self.connection
|