sqlspec 0.10.1__py3-none-any.whl → 0.11.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/adapters/adbc/config.py +1 -1
- sqlspec/adapters/adbc/driver.py +340 -192
- sqlspec/adapters/aiosqlite/driver.py +183 -129
- sqlspec/adapters/asyncmy/driver.py +168 -88
- sqlspec/adapters/asyncpg/config.py +3 -1
- sqlspec/adapters/asyncpg/driver.py +208 -259
- sqlspec/adapters/bigquery/driver.py +184 -264
- sqlspec/adapters/duckdb/driver.py +172 -110
- sqlspec/adapters/oracledb/driver.py +274 -160
- sqlspec/adapters/psqlpy/driver.py +274 -211
- sqlspec/adapters/psycopg/driver.py +196 -283
- sqlspec/adapters/sqlite/driver.py +154 -142
- sqlspec/base.py +56 -85
- 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 +215 -11
- sqlspec/mixins.py +161 -12
- 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.1.dist-info → sqlspec-0.11.1.dist-info}/METADATA +8 -1
- {sqlspec-0.10.1.dist-info → sqlspec-0.11.1.dist-info}/RECORD +31 -29
- {sqlspec-0.10.1.dist-info → sqlspec-0.11.1.dist-info}/WHEEL +0 -0
- {sqlspec-0.10.1.dist-info → sqlspec-0.11.1.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.10.1.dist-info → sqlspec-0.11.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,18 +1,21 @@
|
|
|
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.mixins import SQLTranslatorMixin
|
|
11
|
-
from sqlspec.statement import
|
|
11
|
+
from sqlspec.filters import StatementFilter
|
|
12
|
+
from sqlspec.mixins import ResultConverter, SQLTranslatorMixin
|
|
13
|
+
from sqlspec.statement import SQLStatement
|
|
12
14
|
|
|
13
15
|
if TYPE_CHECKING:
|
|
14
|
-
from collections.abc import Sequence
|
|
16
|
+
from collections.abc import Mapping, Sequence
|
|
15
17
|
|
|
18
|
+
from asyncpg import Record
|
|
16
19
|
from asyncpg.connection import Connection
|
|
17
20
|
from asyncpg.pool import PoolConnectionProxy
|
|
18
21
|
|
|
@@ -22,23 +25,37 @@ __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,167 +65,116 @@ 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
|
+
*filters: "StatementFilter",
|
|
56
73
|
**kwargs: Any,
|
|
57
74
|
) -> "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
|
|
75
|
+
"""Process SQL and parameters for AsyncPG using SQLStatement.
|
|
97
76
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
"Dict params provided (%s), but no :name placeholders found. SQL: %s",
|
|
107
|
-
list(parameters.keys()),
|
|
108
|
-
sql,
|
|
109
|
-
)
|
|
110
|
-
# If no placeholders, return original SQL from SQLStatement and empty tuple for asyncpg
|
|
111
|
-
return sql, ()
|
|
112
|
-
|
|
113
|
-
# Additional checks (potentially redundant if SQLStatement covers them):
|
|
114
|
-
# 1. Ensure all found placeholders have corresponding params (covered by check inside loop)
|
|
115
|
-
# 2. Ensure all provided params correspond to a placeholder
|
|
116
|
-
provided_keys = set(parameters.keys())
|
|
117
|
-
found_keys = set(found_params_regex)
|
|
118
|
-
unused_keys = provided_keys - found_keys
|
|
119
|
-
if unused_keys:
|
|
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
|
|
77
|
+
This method applies filters (if provided), processes the SQL through SQLStatement
|
|
78
|
+
with dialect support, and converts parameters to the format required by AsyncPG.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
sql: SQL statement.
|
|
82
|
+
parameters: Query parameters. Can be data or a StatementFilter.
|
|
83
|
+
*filters: Statement filters to apply.
|
|
84
|
+
**kwargs: Additional keyword arguments.
|
|
149
85
|
|
|
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
|
-
if
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
86
|
+
Returns:
|
|
87
|
+
Tuple of processed SQL and parameters.
|
|
88
|
+
"""
|
|
89
|
+
data_params_for_statement: Optional[Union[Mapping[str, Any], Sequence[Any]]] = None
|
|
90
|
+
combined_filters_list: list[StatementFilter] = list(filters)
|
|
91
|
+
|
|
92
|
+
if parameters is not None:
|
|
93
|
+
if isinstance(parameters, StatementFilter):
|
|
94
|
+
combined_filters_list.insert(0, parameters)
|
|
95
|
+
# data_params_for_statement remains None
|
|
96
|
+
else:
|
|
97
|
+
# If parameters is not a StatementFilter, it's actual data parameters.
|
|
98
|
+
data_params_for_statement = parameters
|
|
99
|
+
|
|
100
|
+
# Handle scalar parameter by converting to a single-item tuple if it's data
|
|
101
|
+
if data_params_for_statement is not None and not isinstance(data_params_for_statement, (list, tuple, dict)):
|
|
102
|
+
data_params_for_statement = (data_params_for_statement,)
|
|
103
|
+
|
|
104
|
+
# Create a SQLStatement with PostgreSQL dialect
|
|
105
|
+
statement = SQLStatement(sql, data_params_for_statement, kwargs=kwargs, dialect=self.dialect)
|
|
106
|
+
|
|
107
|
+
# Apply any filters from the combined list
|
|
108
|
+
for filter_obj in combined_filters_list:
|
|
109
|
+
statement = statement.apply_filter(filter_obj)
|
|
110
|
+
|
|
111
|
+
# Process the statement
|
|
112
|
+
processed_sql, processed_params, parsed_expr = statement.process()
|
|
113
|
+
|
|
114
|
+
if processed_params is None:
|
|
115
|
+
return processed_sql, ()
|
|
116
|
+
|
|
117
|
+
# Convert question marks to PostgreSQL style $N parameters
|
|
118
|
+
if isinstance(processed_params, (list, tuple)) and "?" in processed_sql:
|
|
119
|
+
# Use a counter to generate $1, $2, etc. for each ? in the SQL that's outside strings/comments
|
|
120
|
+
param_index = 0
|
|
121
|
+
|
|
122
|
+
def replace_question_mark(match: Match[str]) -> str:
|
|
123
|
+
# Only process the match if it's not in a skipped context (string/comment)
|
|
124
|
+
if match.group(1): # This is a question mark outside string/comment
|
|
125
|
+
nonlocal param_index
|
|
126
|
+
param_index += 1
|
|
127
|
+
return f"${param_index}"
|
|
128
|
+
# Return the entire matched text unchanged for strings/comments
|
|
129
|
+
return match.group(0)
|
|
130
|
+
|
|
131
|
+
processed_sql = QUESTION_MARK_PATTERN.sub(replace_question_mark, processed_sql)
|
|
132
|
+
|
|
133
|
+
# Now handle the asyncpg-specific parameter conversion - asyncpg requires positional parameters
|
|
134
|
+
if isinstance(processed_params, dict):
|
|
135
|
+
if parsed_expr is not None:
|
|
136
|
+
# Find named parameters
|
|
137
|
+
named_params = []
|
|
138
|
+
for node in parsed_expr.find_all(exp.Parameter, exp.Placeholder):
|
|
139
|
+
if isinstance(node, exp.Parameter) and node.name and node.name in processed_params:
|
|
140
|
+
named_params.append(node.name)
|
|
141
|
+
elif (
|
|
142
|
+
isinstance(node, exp.Placeholder)
|
|
143
|
+
and isinstance(node.this, str)
|
|
144
|
+
and node.this in processed_params
|
|
145
|
+
):
|
|
146
|
+
named_params.append(node.this)
|
|
147
|
+
|
|
148
|
+
# Convert named parameters to positional
|
|
149
|
+
if named_params:
|
|
150
|
+
# Transform the SQL to use $1, $2, etc.
|
|
151
|
+
def replace_named_with_positional(node: exp.Expression) -> exp.Expression:
|
|
152
|
+
if isinstance(node, exp.Parameter) and node.name and node.name in processed_params:
|
|
153
|
+
idx = named_params.index(node.name) + 1
|
|
154
|
+
return exp.Parameter(this=str(idx))
|
|
155
|
+
if (
|
|
156
|
+
isinstance(node, exp.Placeholder)
|
|
157
|
+
and isinstance(node.this, str)
|
|
158
|
+
and node.this in processed_params
|
|
159
|
+
):
|
|
160
|
+
idx = named_params.index(node.this) + 1
|
|
161
|
+
return exp.Parameter(this=str(idx))
|
|
162
|
+
return node
|
|
163
|
+
|
|
164
|
+
return parsed_expr.transform(replace_named_with_positional, copy=True).sql(
|
|
165
|
+
dialect=self.dialect
|
|
166
|
+
), tuple(processed_params[name] for name in named_params)
|
|
167
|
+
return processed_sql, tuple(processed_params.values())
|
|
168
|
+
if isinstance(processed_params, (list, tuple)):
|
|
169
|
+
return processed_sql, tuple(processed_params)
|
|
170
|
+
return processed_sql, (processed_params,) # type: ignore[unreachable]
|
|
204
171
|
|
|
205
172
|
@overload
|
|
206
173
|
async def select(
|
|
207
174
|
self,
|
|
208
175
|
sql: str,
|
|
209
176
|
parameters: "Optional[StatementParameterType]" = None,
|
|
210
|
-
|
|
211
|
-
*,
|
|
177
|
+
*filters: "StatementFilter",
|
|
212
178
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
213
179
|
schema_type: None = None,
|
|
214
180
|
**kwargs: Any,
|
|
@@ -218,8 +184,7 @@ class AsyncpgDriver(
|
|
|
218
184
|
self,
|
|
219
185
|
sql: str,
|
|
220
186
|
parameters: "Optional[StatementParameterType]" = None,
|
|
221
|
-
|
|
222
|
-
*,
|
|
187
|
+
*filters: "StatementFilter",
|
|
223
188
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
224
189
|
schema_type: "type[ModelDTOT]",
|
|
225
190
|
**kwargs: Any,
|
|
@@ -227,18 +192,18 @@ class AsyncpgDriver(
|
|
|
227
192
|
async def select(
|
|
228
193
|
self,
|
|
229
194
|
sql: str,
|
|
230
|
-
parameters: Optional[
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
connection: Optional["AsyncpgConnection"] = None,
|
|
195
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
196
|
+
*filters: "StatementFilter",
|
|
197
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
234
198
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
235
199
|
**kwargs: Any,
|
|
236
|
-
) -> "Sequence[Union[
|
|
200
|
+
) -> "Sequence[Union[dict[str, Any], ModelDTOT]]":
|
|
237
201
|
"""Fetch data from the database.
|
|
238
202
|
|
|
239
203
|
Args:
|
|
240
204
|
sql: SQL statement.
|
|
241
|
-
parameters: Query parameters.
|
|
205
|
+
parameters: Query parameters. Can be data or a StatementFilter.
|
|
206
|
+
*filters: Statement filters to apply.
|
|
242
207
|
connection: Optional connection to use.
|
|
243
208
|
schema_type: Optional schema class for the result.
|
|
244
209
|
**kwargs: Additional keyword arguments.
|
|
@@ -247,23 +212,20 @@ class AsyncpgDriver(
|
|
|
247
212
|
List of row data as either model instances or dictionaries.
|
|
248
213
|
"""
|
|
249
214
|
connection = self._connection(connection)
|
|
250
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
251
|
-
parameters = parameters if parameters is not None else
|
|
215
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
216
|
+
parameters = parameters if parameters is not None else ()
|
|
252
217
|
|
|
253
218
|
results = await connection.fetch(sql, *parameters) # pyright: ignore
|
|
254
219
|
if not results:
|
|
255
220
|
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]
|
|
221
|
+
return self.to_schema([dict(row.items()) for row in results], schema_type=schema_type) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
259
222
|
|
|
260
223
|
@overload
|
|
261
224
|
async def select_one(
|
|
262
225
|
self,
|
|
263
226
|
sql: str,
|
|
264
227
|
parameters: "Optional[StatementParameterType]" = None,
|
|
265
|
-
|
|
266
|
-
*,
|
|
228
|
+
*filters: "StatementFilter",
|
|
267
229
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
268
230
|
schema_type: None = None,
|
|
269
231
|
**kwargs: Any,
|
|
@@ -273,8 +235,7 @@ class AsyncpgDriver(
|
|
|
273
235
|
self,
|
|
274
236
|
sql: str,
|
|
275
237
|
parameters: "Optional[StatementParameterType]" = None,
|
|
276
|
-
|
|
277
|
-
*,
|
|
238
|
+
*filters: "StatementFilter",
|
|
278
239
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
279
240
|
schema_type: "type[ModelDTOT]",
|
|
280
241
|
**kwargs: Any,
|
|
@@ -282,18 +243,18 @@ class AsyncpgDriver(
|
|
|
282
243
|
async def select_one(
|
|
283
244
|
self,
|
|
284
245
|
sql: str,
|
|
285
|
-
parameters: Optional[
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
connection: Optional["AsyncpgConnection"] = None,
|
|
246
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
247
|
+
*filters: "StatementFilter",
|
|
248
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
289
249
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
290
250
|
**kwargs: Any,
|
|
291
|
-
) -> "Union[
|
|
251
|
+
) -> "Union[dict[str, Any], ModelDTOT]":
|
|
292
252
|
"""Fetch one row from the database.
|
|
293
253
|
|
|
294
254
|
Args:
|
|
295
255
|
sql: SQL statement.
|
|
296
|
-
parameters: Query parameters.
|
|
256
|
+
parameters: Query parameters. Can be data or a StatementFilter.
|
|
257
|
+
*filters: Statement filters to apply.
|
|
297
258
|
connection: Optional connection to use.
|
|
298
259
|
schema_type: Optional schema class for the result.
|
|
299
260
|
**kwargs: Additional keyword arguments.
|
|
@@ -302,23 +263,18 @@ class AsyncpgDriver(
|
|
|
302
263
|
The first row of the query results.
|
|
303
264
|
"""
|
|
304
265
|
connection = self._connection(connection)
|
|
305
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
306
|
-
parameters = parameters if parameters is not None else
|
|
266
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
267
|
+
parameters = parameters if parameters is not None else ()
|
|
307
268
|
result = await connection.fetchrow(sql, *parameters) # pyright: ignore
|
|
308
269
|
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]
|
|
270
|
+
return self.to_schema(dict(result.items()), schema_type=schema_type)
|
|
314
271
|
|
|
315
272
|
@overload
|
|
316
273
|
async def select_one_or_none(
|
|
317
274
|
self,
|
|
318
275
|
sql: str,
|
|
319
276
|
parameters: "Optional[StatementParameterType]" = None,
|
|
320
|
-
|
|
321
|
-
*,
|
|
277
|
+
*filters: "StatementFilter",
|
|
322
278
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
323
279
|
schema_type: None = None,
|
|
324
280
|
**kwargs: Any,
|
|
@@ -328,8 +284,7 @@ class AsyncpgDriver(
|
|
|
328
284
|
self,
|
|
329
285
|
sql: str,
|
|
330
286
|
parameters: "Optional[StatementParameterType]" = None,
|
|
331
|
-
|
|
332
|
-
*,
|
|
287
|
+
*filters: "StatementFilter",
|
|
333
288
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
334
289
|
schema_type: "type[ModelDTOT]",
|
|
335
290
|
**kwargs: Any,
|
|
@@ -337,18 +292,18 @@ class AsyncpgDriver(
|
|
|
337
292
|
async def select_one_or_none(
|
|
338
293
|
self,
|
|
339
294
|
sql: str,
|
|
340
|
-
parameters: Optional[
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
connection: Optional["AsyncpgConnection"] = None,
|
|
295
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
296
|
+
*filters: "StatementFilter",
|
|
297
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
344
298
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
345
299
|
**kwargs: Any,
|
|
346
|
-
) -> "Optional[Union[
|
|
300
|
+
) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
|
|
347
301
|
"""Fetch one row from the database.
|
|
348
302
|
|
|
349
303
|
Args:
|
|
350
304
|
sql: SQL statement.
|
|
351
|
-
parameters: Query parameters.
|
|
305
|
+
parameters: Query parameters. Can be data or a StatementFilter.
|
|
306
|
+
*filters: Statement filters to apply.
|
|
352
307
|
connection: Optional connection to use.
|
|
353
308
|
schema_type: Optional schema class for the result.
|
|
354
309
|
**kwargs: Additional keyword arguments.
|
|
@@ -357,23 +312,19 @@ class AsyncpgDriver(
|
|
|
357
312
|
The first row of the query results.
|
|
358
313
|
"""
|
|
359
314
|
connection = self._connection(connection)
|
|
360
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
361
|
-
parameters = parameters if parameters is not None else
|
|
315
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
316
|
+
parameters = parameters if parameters is not None else ()
|
|
362
317
|
result = await connection.fetchrow(sql, *parameters) # pyright: ignore
|
|
363
318
|
if result is None:
|
|
364
319
|
return None
|
|
365
|
-
|
|
366
|
-
# Always return as dictionary
|
|
367
|
-
return dict(result.items())
|
|
368
|
-
return cast("ModelDTOT", schema_type(**dict(result.items())))
|
|
320
|
+
return self.to_schema(dict(result.items()), schema_type=schema_type)
|
|
369
321
|
|
|
370
322
|
@overload
|
|
371
323
|
async def select_value(
|
|
372
324
|
self,
|
|
373
325
|
sql: str,
|
|
374
326
|
parameters: "Optional[StatementParameterType]" = None,
|
|
375
|
-
|
|
376
|
-
*,
|
|
327
|
+
*filters: "StatementFilter",
|
|
377
328
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
378
329
|
schema_type: None = None,
|
|
379
330
|
**kwargs: Any,
|
|
@@ -383,8 +334,7 @@ class AsyncpgDriver(
|
|
|
383
334
|
self,
|
|
384
335
|
sql: str,
|
|
385
336
|
parameters: "Optional[StatementParameterType]" = None,
|
|
386
|
-
|
|
387
|
-
*,
|
|
337
|
+
*filters: "StatementFilter",
|
|
388
338
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
389
339
|
schema_type: "type[T]",
|
|
390
340
|
**kwargs: Any,
|
|
@@ -393,8 +343,7 @@ class AsyncpgDriver(
|
|
|
393
343
|
self,
|
|
394
344
|
sql: str,
|
|
395
345
|
parameters: "Optional[StatementParameterType]" = None,
|
|
396
|
-
|
|
397
|
-
*,
|
|
346
|
+
*filters: "StatementFilter",
|
|
398
347
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
399
348
|
schema_type: "Optional[type[T]]" = None,
|
|
400
349
|
**kwargs: Any,
|
|
@@ -403,7 +352,8 @@ class AsyncpgDriver(
|
|
|
403
352
|
|
|
404
353
|
Args:
|
|
405
354
|
sql: SQL statement.
|
|
406
|
-
parameters: Query parameters.
|
|
355
|
+
parameters: Query parameters. Can be data or a StatementFilter.
|
|
356
|
+
*filters: Statement filters to apply.
|
|
407
357
|
connection: Optional connection to use.
|
|
408
358
|
schema_type: Optional schema class for the result.
|
|
409
359
|
**kwargs: Additional keyword arguments.
|
|
@@ -412,8 +362,8 @@ class AsyncpgDriver(
|
|
|
412
362
|
The first value from the first row of results, or None if no results.
|
|
413
363
|
"""
|
|
414
364
|
connection = self._connection(connection)
|
|
415
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
416
|
-
parameters = parameters if parameters is not None else
|
|
365
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
366
|
+
parameters = parameters if parameters is not None else ()
|
|
417
367
|
result = await connection.fetchval(sql, *parameters) # pyright: ignore
|
|
418
368
|
result = self.check_not_found(result)
|
|
419
369
|
if schema_type is None:
|
|
@@ -425,8 +375,7 @@ class AsyncpgDriver(
|
|
|
425
375
|
self,
|
|
426
376
|
sql: str,
|
|
427
377
|
parameters: "Optional[StatementParameterType]" = None,
|
|
428
|
-
|
|
429
|
-
*,
|
|
378
|
+
*filters: "StatementFilter",
|
|
430
379
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
431
380
|
schema_type: None = None,
|
|
432
381
|
**kwargs: Any,
|
|
@@ -436,8 +385,7 @@ class AsyncpgDriver(
|
|
|
436
385
|
self,
|
|
437
386
|
sql: str,
|
|
438
387
|
parameters: "Optional[StatementParameterType]" = None,
|
|
439
|
-
|
|
440
|
-
*,
|
|
388
|
+
*filters: "StatementFilter",
|
|
441
389
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
442
390
|
schema_type: "type[T]",
|
|
443
391
|
**kwargs: Any,
|
|
@@ -446,20 +394,27 @@ class AsyncpgDriver(
|
|
|
446
394
|
self,
|
|
447
395
|
sql: str,
|
|
448
396
|
parameters: "Optional[StatementParameterType]" = None,
|
|
449
|
-
|
|
450
|
-
*,
|
|
397
|
+
*filters: "StatementFilter",
|
|
451
398
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
452
399
|
schema_type: "Optional[type[T]]" = None,
|
|
453
400
|
**kwargs: Any,
|
|
454
401
|
) -> "Optional[Union[T, Any]]":
|
|
455
402
|
"""Fetch a single value from the database.
|
|
456
403
|
|
|
404
|
+
Args:
|
|
405
|
+
sql: SQL statement.
|
|
406
|
+
parameters: Query parameters. Can be data or a StatementFilter.
|
|
407
|
+
*filters: Statement filters to apply.
|
|
408
|
+
connection: Optional connection to use.
|
|
409
|
+
schema_type: Optional schema class for the result.
|
|
410
|
+
**kwargs: Additional keyword arguments.
|
|
411
|
+
|
|
457
412
|
Returns:
|
|
458
413
|
The first value from the first row of results, or None if no results.
|
|
459
414
|
"""
|
|
460
415
|
connection = self._connection(connection)
|
|
461
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
462
|
-
parameters = parameters if parameters is not None else
|
|
416
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
417
|
+
parameters = parameters if parameters is not None else ()
|
|
463
418
|
result = await connection.fetchval(sql, *parameters) # pyright: ignore
|
|
464
419
|
if result is None:
|
|
465
420
|
return None
|
|
@@ -470,9 +425,8 @@ class AsyncpgDriver(
|
|
|
470
425
|
async def insert_update_delete(
|
|
471
426
|
self,
|
|
472
427
|
sql: str,
|
|
473
|
-
parameters: Optional[
|
|
474
|
-
|
|
475
|
-
*,
|
|
428
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
429
|
+
*filters: "StatementFilter",
|
|
476
430
|
connection: Optional["AsyncpgConnection"] = None,
|
|
477
431
|
**kwargs: Any,
|
|
478
432
|
) -> int:
|
|
@@ -480,7 +434,8 @@ class AsyncpgDriver(
|
|
|
480
434
|
|
|
481
435
|
Args:
|
|
482
436
|
sql: SQL statement.
|
|
483
|
-
parameters: Query parameters.
|
|
437
|
+
parameters: Query parameters. Can be data or a StatementFilter.
|
|
438
|
+
*filters: Statement filters to apply.
|
|
484
439
|
connection: Optional connection to use.
|
|
485
440
|
**kwargs: Additional keyword arguments.
|
|
486
441
|
|
|
@@ -488,22 +443,21 @@ class AsyncpgDriver(
|
|
|
488
443
|
Row count affected by the operation.
|
|
489
444
|
"""
|
|
490
445
|
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
|
-
|
|
446
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
447
|
+
parameters = parameters if parameters is not None else ()
|
|
448
|
+
result = await connection.execute(sql, *parameters) # pyright: ignore
|
|
449
|
+
# asyncpg returns e.g. 'INSERT 0 1', 'UPDATE 0 2', etc.
|
|
450
|
+
match = ROWCOUNT_REGEX.match(result)
|
|
451
|
+
if match:
|
|
452
|
+
return int(match.group(1))
|
|
453
|
+
return 0
|
|
499
454
|
|
|
500
455
|
@overload
|
|
501
456
|
async def insert_update_delete_returning(
|
|
502
457
|
self,
|
|
503
458
|
sql: str,
|
|
504
459
|
parameters: "Optional[StatementParameterType]" = None,
|
|
505
|
-
|
|
506
|
-
*,
|
|
460
|
+
*filters: "StatementFilter",
|
|
507
461
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
508
462
|
schema_type: None = None,
|
|
509
463
|
**kwargs: Any,
|
|
@@ -513,8 +467,7 @@ class AsyncpgDriver(
|
|
|
513
467
|
self,
|
|
514
468
|
sql: str,
|
|
515
469
|
parameters: "Optional[StatementParameterType]" = None,
|
|
516
|
-
|
|
517
|
-
*,
|
|
470
|
+
*filters: "StatementFilter",
|
|
518
471
|
connection: "Optional[AsyncpgConnection]" = None,
|
|
519
472
|
schema_type: "type[ModelDTOT]",
|
|
520
473
|
**kwargs: Any,
|
|
@@ -522,10 +475,9 @@ class AsyncpgDriver(
|
|
|
522
475
|
async def insert_update_delete_returning(
|
|
523
476
|
self,
|
|
524
477
|
sql: str,
|
|
525
|
-
parameters: Optional[
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
connection: Optional["AsyncpgConnection"] = None,
|
|
478
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
479
|
+
*filters: "StatementFilter",
|
|
480
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
529
481
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
530
482
|
**kwargs: Any,
|
|
531
483
|
) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
|
|
@@ -533,7 +485,8 @@ class AsyncpgDriver(
|
|
|
533
485
|
|
|
534
486
|
Args:
|
|
535
487
|
sql: SQL statement.
|
|
536
|
-
parameters: Query parameters.
|
|
488
|
+
parameters: Query parameters. Can be data or a StatementFilter.
|
|
489
|
+
*filters: Statement filters to apply.
|
|
537
490
|
connection: Optional connection to use.
|
|
538
491
|
schema_type: Optional schema class for the result.
|
|
539
492
|
**kwargs: Additional keyword arguments.
|
|
@@ -542,23 +495,19 @@ class AsyncpgDriver(
|
|
|
542
495
|
The affected row data as either a model instance or dictionary.
|
|
543
496
|
"""
|
|
544
497
|
connection = self._connection(connection)
|
|
545
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
546
|
-
parameters = parameters if parameters is not None else
|
|
498
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
499
|
+
parameters = parameters if parameters is not None else ()
|
|
547
500
|
result = await connection.fetchrow(sql, *parameters) # pyright: ignore
|
|
548
501
|
if result is None:
|
|
549
502
|
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]
|
|
503
|
+
|
|
504
|
+
return self.to_schema(dict(result.items()), schema_type=schema_type)
|
|
554
505
|
|
|
555
506
|
async def execute_script(
|
|
556
507
|
self,
|
|
557
508
|
sql: str,
|
|
558
|
-
parameters: Optional[
|
|
559
|
-
|
|
560
|
-
*,
|
|
561
|
-
connection: Optional["AsyncpgConnection"] = None,
|
|
509
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
510
|
+
connection: "Optional[AsyncpgConnection]" = None,
|
|
562
511
|
**kwargs: Any,
|
|
563
512
|
) -> str:
|
|
564
513
|
"""Execute a script.
|
|
@@ -574,9 +523,9 @@ class AsyncpgDriver(
|
|
|
574
523
|
"""
|
|
575
524
|
connection = self._connection(connection)
|
|
576
525
|
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
577
|
-
parameters = parameters if parameters is not None else
|
|
526
|
+
parameters = parameters if parameters is not None else ()
|
|
578
527
|
return await connection.execute(sql, *parameters) # pyright: ignore
|
|
579
528
|
|
|
580
|
-
def _connection(self, connection: Optional[
|
|
529
|
+
def _connection(self, connection: "Optional[AsyncpgConnection]" = None) -> "AsyncpgConnection":
|
|
581
530
|
"""Return the connection to use. If None, use the default connection."""
|
|
582
531
|
return connection if connection is not None else self.connection
|