sqlspec 0.11.0__py3-none-any.whl → 0.12.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/__init__.py +16 -3
- sqlspec/_serialization.py +3 -10
- sqlspec/_sql.py +1147 -0
- sqlspec/_typing.py +343 -41
- sqlspec/adapters/adbc/__init__.py +2 -6
- sqlspec/adapters/adbc/config.py +474 -149
- sqlspec/adapters/adbc/driver.py +330 -644
- sqlspec/adapters/aiosqlite/__init__.py +2 -6
- sqlspec/adapters/aiosqlite/config.py +143 -57
- sqlspec/adapters/aiosqlite/driver.py +269 -462
- sqlspec/adapters/asyncmy/__init__.py +3 -8
- sqlspec/adapters/asyncmy/config.py +247 -202
- sqlspec/adapters/asyncmy/driver.py +217 -451
- sqlspec/adapters/asyncpg/__init__.py +4 -7
- sqlspec/adapters/asyncpg/config.py +329 -176
- sqlspec/adapters/asyncpg/driver.py +418 -498
- sqlspec/adapters/bigquery/__init__.py +2 -2
- sqlspec/adapters/bigquery/config.py +407 -0
- sqlspec/adapters/bigquery/driver.py +592 -634
- sqlspec/adapters/duckdb/__init__.py +4 -1
- sqlspec/adapters/duckdb/config.py +432 -321
- sqlspec/adapters/duckdb/driver.py +393 -436
- sqlspec/adapters/oracledb/__init__.py +3 -8
- sqlspec/adapters/oracledb/config.py +625 -0
- sqlspec/adapters/oracledb/driver.py +549 -942
- sqlspec/adapters/psqlpy/__init__.py +4 -7
- sqlspec/adapters/psqlpy/config.py +372 -203
- sqlspec/adapters/psqlpy/driver.py +197 -550
- sqlspec/adapters/psycopg/__init__.py +3 -8
- sqlspec/adapters/psycopg/config.py +741 -0
- sqlspec/adapters/psycopg/driver.py +732 -733
- sqlspec/adapters/sqlite/__init__.py +2 -6
- sqlspec/adapters/sqlite/config.py +146 -81
- sqlspec/adapters/sqlite/driver.py +243 -426
- sqlspec/base.py +220 -825
- sqlspec/config.py +354 -0
- sqlspec/driver/__init__.py +22 -0
- sqlspec/driver/_async.py +252 -0
- sqlspec/driver/_common.py +338 -0
- sqlspec/driver/_sync.py +261 -0
- sqlspec/driver/mixins/__init__.py +17 -0
- sqlspec/driver/mixins/_pipeline.py +523 -0
- sqlspec/driver/mixins/_result_utils.py +122 -0
- sqlspec/driver/mixins/_sql_translator.py +35 -0
- sqlspec/driver/mixins/_storage.py +993 -0
- sqlspec/driver/mixins/_type_coercion.py +131 -0
- sqlspec/exceptions.py +299 -7
- sqlspec/extensions/aiosql/__init__.py +10 -0
- sqlspec/extensions/aiosql/adapter.py +474 -0
- sqlspec/extensions/litestar/__init__.py +1 -6
- sqlspec/extensions/litestar/_utils.py +1 -5
- sqlspec/extensions/litestar/config.py +5 -6
- sqlspec/extensions/litestar/handlers.py +13 -12
- sqlspec/extensions/litestar/plugin.py +22 -24
- sqlspec/extensions/litestar/providers.py +37 -55
- sqlspec/loader.py +528 -0
- sqlspec/service/__init__.py +3 -0
- sqlspec/service/base.py +24 -0
- sqlspec/service/pagination.py +26 -0
- sqlspec/statement/__init__.py +21 -0
- sqlspec/statement/builder/__init__.py +54 -0
- sqlspec/statement/builder/_ddl_utils.py +119 -0
- sqlspec/statement/builder/_parsing_utils.py +135 -0
- sqlspec/statement/builder/base.py +328 -0
- sqlspec/statement/builder/ddl.py +1379 -0
- sqlspec/statement/builder/delete.py +80 -0
- sqlspec/statement/builder/insert.py +274 -0
- sqlspec/statement/builder/merge.py +95 -0
- sqlspec/statement/builder/mixins/__init__.py +65 -0
- sqlspec/statement/builder/mixins/_aggregate_functions.py +151 -0
- sqlspec/statement/builder/mixins/_case_builder.py +91 -0
- sqlspec/statement/builder/mixins/_common_table_expr.py +91 -0
- sqlspec/statement/builder/mixins/_delete_from.py +34 -0
- sqlspec/statement/builder/mixins/_from.py +61 -0
- sqlspec/statement/builder/mixins/_group_by.py +119 -0
- sqlspec/statement/builder/mixins/_having.py +35 -0
- sqlspec/statement/builder/mixins/_insert_from_select.py +48 -0
- sqlspec/statement/builder/mixins/_insert_into.py +36 -0
- sqlspec/statement/builder/mixins/_insert_values.py +69 -0
- sqlspec/statement/builder/mixins/_join.py +110 -0
- sqlspec/statement/builder/mixins/_limit_offset.py +53 -0
- sqlspec/statement/builder/mixins/_merge_clauses.py +405 -0
- sqlspec/statement/builder/mixins/_order_by.py +46 -0
- sqlspec/statement/builder/mixins/_pivot.py +82 -0
- sqlspec/statement/builder/mixins/_returning.py +37 -0
- sqlspec/statement/builder/mixins/_select_columns.py +60 -0
- sqlspec/statement/builder/mixins/_set_ops.py +122 -0
- sqlspec/statement/builder/mixins/_unpivot.py +80 -0
- sqlspec/statement/builder/mixins/_update_from.py +54 -0
- sqlspec/statement/builder/mixins/_update_set.py +91 -0
- sqlspec/statement/builder/mixins/_update_table.py +29 -0
- sqlspec/statement/builder/mixins/_where.py +374 -0
- sqlspec/statement/builder/mixins/_window_functions.py +86 -0
- sqlspec/statement/builder/protocols.py +20 -0
- sqlspec/statement/builder/select.py +206 -0
- sqlspec/statement/builder/update.py +178 -0
- sqlspec/statement/filters.py +571 -0
- sqlspec/statement/parameters.py +736 -0
- sqlspec/statement/pipelines/__init__.py +67 -0
- sqlspec/statement/pipelines/analyzers/__init__.py +9 -0
- sqlspec/statement/pipelines/analyzers/_analyzer.py +649 -0
- sqlspec/statement/pipelines/base.py +315 -0
- sqlspec/statement/pipelines/context.py +119 -0
- sqlspec/statement/pipelines/result_types.py +41 -0
- sqlspec/statement/pipelines/transformers/__init__.py +8 -0
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +256 -0
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +623 -0
- sqlspec/statement/pipelines/transformers/_remove_comments.py +66 -0
- sqlspec/statement/pipelines/transformers/_remove_hints.py +81 -0
- sqlspec/statement/pipelines/validators/__init__.py +23 -0
- sqlspec/statement/pipelines/validators/_dml_safety.py +275 -0
- sqlspec/statement/pipelines/validators/_parameter_style.py +297 -0
- sqlspec/statement/pipelines/validators/_performance.py +703 -0
- sqlspec/statement/pipelines/validators/_security.py +990 -0
- sqlspec/statement/pipelines/validators/base.py +67 -0
- sqlspec/statement/result.py +527 -0
- sqlspec/statement/splitter.py +701 -0
- sqlspec/statement/sql.py +1198 -0
- sqlspec/storage/__init__.py +15 -0
- sqlspec/storage/backends/__init__.py +0 -0
- sqlspec/storage/backends/base.py +166 -0
- sqlspec/storage/backends/fsspec.py +315 -0
- sqlspec/storage/backends/obstore.py +464 -0
- sqlspec/storage/protocol.py +170 -0
- sqlspec/storage/registry.py +315 -0
- sqlspec/typing.py +157 -36
- sqlspec/utils/correlation.py +155 -0
- sqlspec/utils/deprecation.py +3 -6
- sqlspec/utils/fixtures.py +6 -11
- sqlspec/utils/logging.py +135 -0
- sqlspec/utils/module_loader.py +45 -43
- sqlspec/utils/serializers.py +4 -0
- sqlspec/utils/singleton.py +6 -8
- sqlspec/utils/sync_tools.py +15 -27
- sqlspec/utils/text.py +58 -26
- {sqlspec-0.11.0.dist-info → sqlspec-0.12.0.dist-info}/METADATA +100 -26
- sqlspec-0.12.0.dist-info/RECORD +145 -0
- sqlspec/adapters/bigquery/config/__init__.py +0 -3
- sqlspec/adapters/bigquery/config/_common.py +0 -40
- sqlspec/adapters/bigquery/config/_sync.py +0 -87
- sqlspec/adapters/oracledb/config/__init__.py +0 -9
- sqlspec/adapters/oracledb/config/_asyncio.py +0 -186
- sqlspec/adapters/oracledb/config/_common.py +0 -131
- sqlspec/adapters/oracledb/config/_sync.py +0 -186
- sqlspec/adapters/psycopg/config/__init__.py +0 -19
- sqlspec/adapters/psycopg/config/_async.py +0 -169
- sqlspec/adapters/psycopg/config/_common.py +0 -56
- sqlspec/adapters/psycopg/config/_sync.py +0 -168
- sqlspec/filters.py +0 -330
- sqlspec/mixins.py +0 -306
- sqlspec/statement.py +0 -378
- sqlspec-0.11.0.dist-info/RECORD +0 -69
- {sqlspec-0.11.0.dist-info → sqlspec-0.12.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.11.0.dist-info → sqlspec-0.12.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.11.0.dist-info → sqlspec-0.12.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,567 +1,214 @@
|
|
|
1
1
|
"""Psqlpy Driver Implementation."""
|
|
2
2
|
|
|
3
|
+
import io
|
|
3
4
|
import logging
|
|
4
|
-
import
|
|
5
|
-
from re import Match
|
|
6
|
-
from typing import TYPE_CHECKING, Any, Optional, Union, overload
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
7
6
|
|
|
8
|
-
from psqlpy import Connection
|
|
9
|
-
from psqlpy.exceptions import RustPSQLDriverPyBaseError
|
|
10
|
-
from sqlglot import exp
|
|
7
|
+
from psqlpy import Connection
|
|
11
8
|
|
|
12
|
-
from sqlspec.
|
|
13
|
-
from sqlspec.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
from sqlspec.driver import AsyncDriverAdapterProtocol
|
|
10
|
+
from sqlspec.driver.mixins import (
|
|
11
|
+
AsyncPipelinedExecutionMixin,
|
|
12
|
+
AsyncStorageMixin,
|
|
13
|
+
SQLTranslatorMixin,
|
|
14
|
+
ToSchemaMixin,
|
|
15
|
+
TypeCoercionMixin,
|
|
16
|
+
)
|
|
17
|
+
from sqlspec.statement.parameters import ParameterStyle
|
|
18
|
+
from sqlspec.statement.result import DMLResultDict, ScriptResultDict, SelectResultDict, SQLResult
|
|
19
|
+
from sqlspec.statement.sql import SQL, SQLConfig
|
|
20
|
+
from sqlspec.typing import DictRow, ModelDTOT, RowT
|
|
18
21
|
|
|
19
22
|
if TYPE_CHECKING:
|
|
20
|
-
from
|
|
21
|
-
|
|
22
|
-
from psqlpy import QueryResult
|
|
23
|
-
|
|
24
|
-
from sqlspec.typing import ModelDTOT, StatementParameterType, T
|
|
23
|
+
from sqlglot.dialects.dialect import DialectType
|
|
25
24
|
|
|
26
25
|
__all__ = ("PsqlpyConnection", "PsqlpyDriver")
|
|
27
26
|
|
|
28
|
-
# Improved regex to match question mark placeholders only when they are outside string literals and comments
|
|
29
|
-
# This pattern handles:
|
|
30
|
-
# 1. Single quoted strings with escaped quotes
|
|
31
|
-
# 2. Double quoted strings with escaped quotes
|
|
32
|
-
# 3. Single-line comments (-- to end of line)
|
|
33
|
-
# 4. Multi-line comments (/* to */)
|
|
34
|
-
# 5. Only question marks outside of these contexts are considered parameters
|
|
35
|
-
QUESTION_MARK_PATTERN = re.compile(
|
|
36
|
-
r"""
|
|
37
|
-
(?:'[^']*(?:''[^']*)*') | # Skip single-quoted strings (with '' escapes)
|
|
38
|
-
(?:"[^"]*(?:""[^"]*)*") | # Skip double-quoted strings (with "" escapes)
|
|
39
|
-
(?:--.*?(?:\n|$)) | # Skip single-line comments
|
|
40
|
-
(?:/\*(?:[^*]|\*(?!/))*\*/) | # Skip multi-line comments
|
|
41
|
-
(\?) # Capture only question marks outside of these contexts
|
|
42
|
-
""",
|
|
43
|
-
re.VERBOSE | re.DOTALL,
|
|
44
|
-
)
|
|
45
|
-
|
|
46
27
|
PsqlpyConnection = Connection
|
|
47
28
|
logger = logging.getLogger("sqlspec")
|
|
48
29
|
|
|
49
30
|
|
|
50
31
|
class PsqlpyDriver(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
32
|
+
AsyncDriverAdapterProtocol[PsqlpyConnection, RowT],
|
|
33
|
+
SQLTranslatorMixin,
|
|
34
|
+
TypeCoercionMixin,
|
|
35
|
+
AsyncStorageMixin,
|
|
36
|
+
AsyncPipelinedExecutionMixin,
|
|
37
|
+
ToSchemaMixin,
|
|
54
38
|
):
|
|
55
|
-
"""Psqlpy
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
) ->
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
#
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
204
|
-
""
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
231
|
-
/,
|
|
232
|
-
*filters: StatementFilter,
|
|
233
|
-
connection: "Optional[PsqlpyConnection]" = None,
|
|
234
|
-
schema_type: "type[ModelDTOT]",
|
|
235
|
-
**kwargs: Any,
|
|
236
|
-
) -> "ModelDTOT": ...
|
|
237
|
-
async def select_one(
|
|
238
|
-
self,
|
|
239
|
-
sql: str,
|
|
240
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
241
|
-
/,
|
|
242
|
-
*filters: StatementFilter,
|
|
243
|
-
connection: "Optional[PsqlpyConnection]" = None,
|
|
244
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
245
|
-
**kwargs: Any,
|
|
246
|
-
) -> "Union[ModelDTOT, dict[str, Any]]":
|
|
247
|
-
"""Fetch one row from the database.
|
|
248
|
-
|
|
249
|
-
Args:
|
|
250
|
-
sql: The SQL query string.
|
|
251
|
-
parameters: The parameters for the query (dict, tuple, list, or None).
|
|
252
|
-
*filters: Statement filters to apply.
|
|
253
|
-
connection: Optional connection override.
|
|
254
|
-
schema_type: Optional schema class for the result.
|
|
255
|
-
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
256
|
-
|
|
257
|
-
Returns:
|
|
258
|
-
The first row of the query results.
|
|
259
|
-
"""
|
|
260
|
-
connection = self._connection(connection)
|
|
261
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
262
|
-
parameters = parameters or []
|
|
263
|
-
|
|
264
|
-
result = await connection.fetch(sql, parameters=parameters)
|
|
265
|
-
|
|
266
|
-
# Convert to dict and use ResultConverter
|
|
267
|
-
dict_results = result.result()
|
|
268
|
-
if not dict_results:
|
|
269
|
-
self.check_not_found(None)
|
|
270
|
-
|
|
271
|
-
return self.to_schema(dict_results[0], schema_type=schema_type)
|
|
272
|
-
|
|
273
|
-
@overload
|
|
274
|
-
async def select_one_or_none(
|
|
275
|
-
self,
|
|
276
|
-
sql: str,
|
|
277
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
278
|
-
/,
|
|
279
|
-
*filters: StatementFilter,
|
|
280
|
-
connection: "Optional[PsqlpyConnection]" = None,
|
|
281
|
-
schema_type: None = None,
|
|
282
|
-
**kwargs: Any,
|
|
283
|
-
) -> "Optional[dict[str, Any]]": ...
|
|
284
|
-
@overload
|
|
285
|
-
async def select_one_or_none(
|
|
286
|
-
self,
|
|
287
|
-
sql: str,
|
|
288
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
289
|
-
/,
|
|
290
|
-
*filters: StatementFilter,
|
|
291
|
-
connection: "Optional[PsqlpyConnection]" = None,
|
|
292
|
-
schema_type: "type[ModelDTOT]",
|
|
293
|
-
**kwargs: Any,
|
|
294
|
-
) -> "Optional[ModelDTOT]": ...
|
|
295
|
-
async def select_one_or_none(
|
|
296
|
-
self,
|
|
297
|
-
sql: str,
|
|
298
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
299
|
-
/,
|
|
300
|
-
*filters: StatementFilter,
|
|
301
|
-
connection: "Optional[PsqlpyConnection]" = None,
|
|
302
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
303
|
-
**kwargs: Any,
|
|
304
|
-
) -> "Optional[Union[ModelDTOT, dict[str, Any]]]":
|
|
305
|
-
"""Fetch one row from the database or return None if no rows found.
|
|
306
|
-
|
|
307
|
-
Args:
|
|
308
|
-
sql: The SQL query string.
|
|
309
|
-
parameters: The parameters for the query (dict, tuple, list, or None).
|
|
310
|
-
*filters: Statement filters to apply.
|
|
311
|
-
connection: Optional connection override.
|
|
312
|
-
schema_type: Optional schema class for the result.
|
|
313
|
-
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
314
|
-
|
|
315
|
-
Returns:
|
|
316
|
-
The first row of the query results, or None if no results found.
|
|
317
|
-
"""
|
|
318
|
-
connection = self._connection(connection)
|
|
319
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
320
|
-
parameters = parameters or []
|
|
321
|
-
|
|
322
|
-
result = await connection.fetch(sql, parameters=parameters)
|
|
323
|
-
dict_results = result.result()
|
|
324
|
-
|
|
325
|
-
if not dict_results:
|
|
326
|
-
return None
|
|
327
|
-
|
|
328
|
-
return self.to_schema(dict_results[0], schema_type=schema_type)
|
|
329
|
-
|
|
330
|
-
@overload
|
|
331
|
-
async def select_value(
|
|
332
|
-
self,
|
|
333
|
-
sql: str,
|
|
334
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
335
|
-
/,
|
|
336
|
-
*filters: StatementFilter,
|
|
337
|
-
connection: "Optional[PsqlpyConnection]" = None,
|
|
338
|
-
schema_type: None = None,
|
|
339
|
-
**kwargs: Any,
|
|
340
|
-
) -> "Any": ...
|
|
341
|
-
@overload
|
|
342
|
-
async def select_value(
|
|
343
|
-
self,
|
|
344
|
-
sql: str,
|
|
345
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
346
|
-
/,
|
|
347
|
-
*filters: StatementFilter,
|
|
348
|
-
connection: "Optional[PsqlpyConnection]" = None,
|
|
349
|
-
schema_type: "type[T]",
|
|
350
|
-
**kwargs: Any,
|
|
351
|
-
) -> "T": ...
|
|
352
|
-
async def select_value(
|
|
353
|
-
self,
|
|
354
|
-
sql: str,
|
|
355
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
356
|
-
/,
|
|
357
|
-
*filters: StatementFilter,
|
|
358
|
-
connection: "Optional[PsqlpyConnection]" = None,
|
|
359
|
-
schema_type: "Optional[type[T]]" = None,
|
|
360
|
-
**kwargs: Any,
|
|
361
|
-
) -> "Union[T, Any]":
|
|
362
|
-
"""Fetch a single value from the database.
|
|
363
|
-
|
|
364
|
-
Args:
|
|
365
|
-
sql: The SQL query string.
|
|
366
|
-
parameters: The parameters for the query (dict, tuple, list, or None).
|
|
367
|
-
*filters: Statement filters to apply.
|
|
368
|
-
connection: Optional connection override.
|
|
369
|
-
schema_type: Optional type to convert the result to.
|
|
370
|
-
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
371
|
-
|
|
372
|
-
Returns:
|
|
373
|
-
The first value of the first row of the query results.
|
|
374
|
-
"""
|
|
375
|
-
connection = self._connection(connection)
|
|
376
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
377
|
-
parameters = parameters or []
|
|
378
|
-
|
|
379
|
-
value = await connection.fetch_val(sql, parameters=parameters)
|
|
380
|
-
value = self.check_not_found(value)
|
|
381
|
-
|
|
382
|
-
if schema_type is None:
|
|
383
|
-
return value
|
|
384
|
-
return schema_type(value) # type: ignore[call-arg]
|
|
385
|
-
|
|
386
|
-
@overload
|
|
387
|
-
async def select_value_or_none(
|
|
388
|
-
self,
|
|
389
|
-
sql: str,
|
|
390
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
391
|
-
/,
|
|
392
|
-
*filters: StatementFilter,
|
|
393
|
-
connection: "Optional[PsqlpyConnection]" = None,
|
|
394
|
-
schema_type: None = None,
|
|
395
|
-
**kwargs: Any,
|
|
396
|
-
) -> "Optional[Any]": ...
|
|
397
|
-
@overload
|
|
398
|
-
async def select_value_or_none(
|
|
399
|
-
self,
|
|
400
|
-
sql: str,
|
|
401
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
402
|
-
/,
|
|
403
|
-
*filters: StatementFilter,
|
|
404
|
-
connection: "Optional[PsqlpyConnection]" = None,
|
|
405
|
-
schema_type: "type[T]",
|
|
406
|
-
**kwargs: Any,
|
|
407
|
-
) -> "Optional[T]": ...
|
|
408
|
-
async def select_value_or_none(
|
|
409
|
-
self,
|
|
410
|
-
sql: str,
|
|
411
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
412
|
-
/,
|
|
413
|
-
*filters: StatementFilter,
|
|
414
|
-
connection: "Optional[PsqlpyConnection]" = None,
|
|
415
|
-
schema_type: "Optional[type[T]]" = None,
|
|
416
|
-
**kwargs: Any,
|
|
417
|
-
) -> "Optional[Union[T, Any]]":
|
|
418
|
-
"""Fetch a single value or None if not found.
|
|
419
|
-
|
|
420
|
-
Args:
|
|
421
|
-
sql: The SQL query string.
|
|
422
|
-
parameters: The parameters for the query (dict, tuple, list, or None).
|
|
423
|
-
*filters: Statement filters to apply.
|
|
424
|
-
connection: Optional connection override.
|
|
425
|
-
schema_type: Optional type to convert the result to.
|
|
426
|
-
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
427
|
-
|
|
428
|
-
Returns:
|
|
429
|
-
The first value of the first row of the query results, or None if no results found.
|
|
430
|
-
"""
|
|
431
|
-
connection = self._connection(connection)
|
|
432
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
433
|
-
parameters = parameters or []
|
|
434
|
-
try:
|
|
435
|
-
value = await connection.fetch_val(sql, parameters=parameters)
|
|
436
|
-
except RustPSQLDriverPyBaseError:
|
|
437
|
-
return None
|
|
438
|
-
|
|
439
|
-
if value is None:
|
|
440
|
-
return None
|
|
441
|
-
if schema_type is None:
|
|
442
|
-
return value
|
|
443
|
-
return schema_type(value) # type: ignore[call-arg]
|
|
444
|
-
|
|
445
|
-
async def insert_update_delete(
|
|
446
|
-
self,
|
|
447
|
-
sql: str,
|
|
448
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
449
|
-
/,
|
|
450
|
-
*filters: StatementFilter,
|
|
451
|
-
connection: "Optional[PsqlpyConnection]" = None,
|
|
452
|
-
**kwargs: Any,
|
|
453
|
-
) -> int:
|
|
454
|
-
"""Execute an insert, update, or delete statement.
|
|
455
|
-
|
|
456
|
-
Args:
|
|
457
|
-
sql: The SQL statement to execute.
|
|
458
|
-
parameters: The parameters for the statement (dict, tuple, list, or None).
|
|
459
|
-
*filters: Statement filters to apply.
|
|
460
|
-
connection: Optional connection override.
|
|
461
|
-
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
462
|
-
|
|
463
|
-
Returns:
|
|
464
|
-
The number of rows affected by the statement.
|
|
465
|
-
"""
|
|
466
|
-
connection = self._connection(connection)
|
|
467
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
468
|
-
parameters = parameters or []
|
|
469
|
-
|
|
470
|
-
await connection.execute(sql, parameters=parameters)
|
|
471
|
-
# For INSERT/UPDATE/DELETE, psqlpy returns an empty list but the operation succeeded
|
|
472
|
-
# if no error was raised
|
|
473
|
-
return 1
|
|
474
|
-
|
|
475
|
-
@overload
|
|
476
|
-
async def insert_update_delete_returning(
|
|
477
|
-
self,
|
|
478
|
-
sql: str,
|
|
479
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
480
|
-
/,
|
|
481
|
-
*filters: StatementFilter,
|
|
482
|
-
connection: "Optional[PsqlpyConnection]" = None,
|
|
483
|
-
schema_type: None = None,
|
|
484
|
-
**kwargs: Any,
|
|
485
|
-
) -> "dict[str, Any]": ...
|
|
486
|
-
@overload
|
|
487
|
-
async def insert_update_delete_returning(
|
|
488
|
-
self,
|
|
489
|
-
sql: str,
|
|
490
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
491
|
-
/,
|
|
492
|
-
*filters: StatementFilter,
|
|
493
|
-
connection: "Optional[PsqlpyConnection]" = None,
|
|
494
|
-
schema_type: "type[ModelDTOT]",
|
|
495
|
-
**kwargs: Any,
|
|
496
|
-
) -> "ModelDTOT": ...
|
|
497
|
-
async def insert_update_delete_returning(
|
|
498
|
-
self,
|
|
499
|
-
sql: str,
|
|
500
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
501
|
-
/,
|
|
502
|
-
*filters: StatementFilter,
|
|
503
|
-
connection: "Optional[PsqlpyConnection]" = None,
|
|
504
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
505
|
-
**kwargs: Any,
|
|
506
|
-
) -> "Union[ModelDTOT, dict[str, Any]]":
|
|
507
|
-
"""Insert, update, or delete data from the database and return result.
|
|
508
|
-
|
|
509
|
-
Args:
|
|
510
|
-
sql: The SQL statement to execute.
|
|
511
|
-
parameters: The parameters for the statement (dict, tuple, list, or None).
|
|
512
|
-
*filters: Statement filters to apply.
|
|
513
|
-
connection: Optional connection override.
|
|
514
|
-
schema_type: Optional schema class for the result.
|
|
515
|
-
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
516
|
-
|
|
517
|
-
Returns:
|
|
518
|
-
The first row of results.
|
|
519
|
-
"""
|
|
520
|
-
connection = self._connection(connection)
|
|
521
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
522
|
-
parameters = parameters or []
|
|
523
|
-
|
|
524
|
-
result = await connection.execute(sql, parameters=parameters)
|
|
525
|
-
dict_results = result.result()
|
|
526
|
-
|
|
527
|
-
if not dict_results:
|
|
528
|
-
self.check_not_found(None)
|
|
529
|
-
|
|
530
|
-
return self.to_schema(dict_results[0], schema_type=schema_type)
|
|
531
|
-
|
|
532
|
-
async def execute_script(
|
|
533
|
-
self,
|
|
534
|
-
sql: str,
|
|
535
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
536
|
-
/,
|
|
537
|
-
connection: "Optional[PsqlpyConnection]" = None,
|
|
538
|
-
**kwargs: Any,
|
|
539
|
-
) -> str:
|
|
540
|
-
"""Execute a SQL script.
|
|
541
|
-
|
|
542
|
-
Args:
|
|
543
|
-
sql: The SQL script to execute.
|
|
544
|
-
parameters: The parameters for the script (dict, tuple, list, or None).
|
|
545
|
-
connection: Optional connection override.
|
|
546
|
-
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
547
|
-
|
|
548
|
-
Returns:
|
|
549
|
-
A success message.
|
|
550
|
-
"""
|
|
551
|
-
connection = self._connection(connection)
|
|
552
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
553
|
-
parameters = parameters or []
|
|
554
|
-
|
|
555
|
-
await connection.execute(sql, parameters=parameters)
|
|
556
|
-
return "Script executed successfully"
|
|
557
|
-
|
|
558
|
-
def _connection(self, connection: "Optional[PsqlpyConnection]" = None) -> "PsqlpyConnection":
|
|
559
|
-
"""Get the connection to use.
|
|
560
|
-
|
|
561
|
-
Args:
|
|
562
|
-
connection: Optional connection to use. If not provided, use the default connection.
|
|
563
|
-
|
|
564
|
-
Returns:
|
|
565
|
-
The connection to use.
|
|
566
|
-
"""
|
|
39
|
+
"""Psqlpy Driver Adapter.
|
|
40
|
+
|
|
41
|
+
Modern, high-performance driver for PostgreSQL.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
dialect: "DialectType" = "postgres"
|
|
45
|
+
supported_parameter_styles: "tuple[ParameterStyle, ...]" = (ParameterStyle.NUMERIC,)
|
|
46
|
+
default_parameter_style: ParameterStyle = ParameterStyle.NUMERIC
|
|
47
|
+
__slots__ = ()
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
connection: PsqlpyConnection,
|
|
52
|
+
config: "Optional[SQLConfig]" = None,
|
|
53
|
+
default_row_type: "type[DictRow]" = DictRow,
|
|
54
|
+
) -> None:
|
|
55
|
+
super().__init__(connection=connection, config=config, default_row_type=default_row_type)
|
|
56
|
+
|
|
57
|
+
def _coerce_boolean(self, value: Any) -> Any:
|
|
58
|
+
"""PostgreSQL has native boolean support, return as-is."""
|
|
59
|
+
return value
|
|
60
|
+
|
|
61
|
+
def _coerce_decimal(self, value: Any) -> Any:
|
|
62
|
+
"""PostgreSQL has native decimal support."""
|
|
63
|
+
if isinstance(value, str):
|
|
64
|
+
from decimal import Decimal
|
|
65
|
+
|
|
66
|
+
return Decimal(value)
|
|
67
|
+
return value
|
|
68
|
+
|
|
69
|
+
def _coerce_json(self, value: Any) -> Any:
|
|
70
|
+
"""PostgreSQL has native JSON/JSONB support, return as-is."""
|
|
71
|
+
return value
|
|
72
|
+
|
|
73
|
+
def _coerce_array(self, value: Any) -> Any:
|
|
74
|
+
"""PostgreSQL has native array support, return as-is."""
|
|
75
|
+
return value
|
|
76
|
+
|
|
77
|
+
async def _execute_statement(
|
|
78
|
+
self, statement: SQL, connection: Optional[PsqlpyConnection] = None, **kwargs: Any
|
|
79
|
+
) -> Union[SelectResultDict, DMLResultDict, ScriptResultDict]:
|
|
80
|
+
if statement.is_script:
|
|
81
|
+
sql, _ = statement.compile(placeholder_style=ParameterStyle.STATIC)
|
|
82
|
+
return await self._execute_script(sql, connection=connection, **kwargs)
|
|
83
|
+
|
|
84
|
+
# Let the SQL object handle parameter style conversion based on dialect support
|
|
85
|
+
sql, params = statement.compile(placeholder_style=self.default_parameter_style)
|
|
86
|
+
params = self._process_parameters(params)
|
|
87
|
+
|
|
88
|
+
if statement.is_many:
|
|
89
|
+
return await self._execute_many(sql, params, connection=connection, **kwargs)
|
|
90
|
+
|
|
91
|
+
return await self._execute(sql, params, statement, connection=connection, **kwargs)
|
|
92
|
+
|
|
93
|
+
async def _execute(
|
|
94
|
+
self, sql: str, parameters: Any, statement: SQL, connection: Optional[PsqlpyConnection] = None, **kwargs: Any
|
|
95
|
+
) -> Union[SelectResultDict, DMLResultDict]:
|
|
96
|
+
conn = self._connection(connection)
|
|
97
|
+
if self.returns_rows(statement.expression):
|
|
98
|
+
query_result = await conn.fetch(sql, parameters=parameters)
|
|
99
|
+
# Convert query_result to list of dicts
|
|
100
|
+
dict_rows: list[dict[str, Any]] = []
|
|
101
|
+
if query_result:
|
|
102
|
+
# psqlpy QueryResult has a result() method that returns list of dicts
|
|
103
|
+
dict_rows = query_result.result()
|
|
104
|
+
column_names = list(dict_rows[0].keys()) if dict_rows else []
|
|
105
|
+
return {"data": dict_rows, "column_names": column_names, "rows_affected": len(dict_rows)}
|
|
106
|
+
query_result = await conn.execute(sql, parameters=parameters)
|
|
107
|
+
# Note: psqlpy doesn't provide rows_affected for DML operations
|
|
108
|
+
# The QueryResult object only has result(), as_class(), and row_factory() methods
|
|
109
|
+
# For accurate row counts, use RETURNING clause
|
|
110
|
+
affected_count = -1 # Unknown, as psqlpy doesn't provide this info
|
|
111
|
+
return {"rows_affected": affected_count, "status_message": "OK"}
|
|
112
|
+
|
|
113
|
+
async def _execute_many(
|
|
114
|
+
self, sql: str, param_list: Any, connection: Optional[PsqlpyConnection] = None, **kwargs: Any
|
|
115
|
+
) -> DMLResultDict:
|
|
116
|
+
conn = self._connection(connection)
|
|
117
|
+
await conn.execute_many(sql, param_list or [])
|
|
118
|
+
# execute_many doesn't return a value with rows_affected
|
|
119
|
+
affected_count = -1
|
|
120
|
+
return {"rows_affected": affected_count, "status_message": "OK"}
|
|
121
|
+
|
|
122
|
+
async def _execute_script(
|
|
123
|
+
self, script: str, connection: Optional[PsqlpyConnection] = None, **kwargs: Any
|
|
124
|
+
) -> ScriptResultDict:
|
|
125
|
+
conn = self._connection(connection)
|
|
126
|
+
# psqlpy can execute multi-statement scripts directly
|
|
127
|
+
await conn.execute(script)
|
|
128
|
+
return {
|
|
129
|
+
"statements_executed": -1, # Not directly supported, but script is executed
|
|
130
|
+
"status_message": "SCRIPT EXECUTED",
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async def _ingest_arrow_table(self, table: "Any", table_name: str, mode: str = "append", **options: Any) -> int:
|
|
134
|
+
self._ensure_pyarrow_installed()
|
|
135
|
+
import pyarrow.csv as pacsv
|
|
136
|
+
|
|
137
|
+
conn = self._connection(None)
|
|
138
|
+
if mode == "replace":
|
|
139
|
+
await conn.execute(f"TRUNCATE TABLE {table_name}")
|
|
140
|
+
elif mode == "create":
|
|
141
|
+
msg = "'create' mode is not supported for psqlpy ingestion."
|
|
142
|
+
raise NotImplementedError(msg)
|
|
143
|
+
|
|
144
|
+
buffer = io.BytesIO()
|
|
145
|
+
pacsv.write_csv(table, buffer)
|
|
146
|
+
buffer.seek(0)
|
|
147
|
+
|
|
148
|
+
# Use copy_from_raw or copy_from depending on what's available
|
|
149
|
+
# The method name might have changed in newer versions
|
|
150
|
+
copy_method = getattr(conn, "copy_from_raw", getattr(conn, "copy_from_query", None))
|
|
151
|
+
if copy_method:
|
|
152
|
+
await copy_method(f"COPY {table_name} FROM STDIN WITH (FORMAT CSV, HEADER)", data=buffer.read())
|
|
153
|
+
return table.num_rows # type: ignore[no-any-return]
|
|
154
|
+
msg = "Connection does not support COPY operations"
|
|
155
|
+
raise NotImplementedError(msg)
|
|
156
|
+
|
|
157
|
+
async def _wrap_select_result(
|
|
158
|
+
self, statement: SQL, result: SelectResultDict, schema_type: Optional[type[ModelDTOT]] = None, **kwargs: Any
|
|
159
|
+
) -> Union[SQLResult[ModelDTOT], SQLResult[RowT]]:
|
|
160
|
+
dict_rows = result["data"]
|
|
161
|
+
column_names = result["column_names"]
|
|
162
|
+
rows_affected = result["rows_affected"]
|
|
163
|
+
|
|
164
|
+
if schema_type:
|
|
165
|
+
converted_data = self.to_schema(data=dict_rows, schema_type=schema_type)
|
|
166
|
+
return SQLResult[ModelDTOT](
|
|
167
|
+
statement=statement,
|
|
168
|
+
data=list(converted_data),
|
|
169
|
+
column_names=column_names,
|
|
170
|
+
rows_affected=rows_affected,
|
|
171
|
+
operation_type="SELECT",
|
|
172
|
+
)
|
|
173
|
+
return SQLResult[RowT](
|
|
174
|
+
statement=statement,
|
|
175
|
+
data=cast("list[RowT]", dict_rows),
|
|
176
|
+
column_names=column_names,
|
|
177
|
+
rows_affected=rows_affected,
|
|
178
|
+
operation_type="SELECT",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
async def _wrap_execute_result(
|
|
182
|
+
self, statement: SQL, result: Union[DMLResultDict, ScriptResultDict], **kwargs: Any
|
|
183
|
+
) -> SQLResult[RowT]:
|
|
184
|
+
operation_type = "UNKNOWN"
|
|
185
|
+
if statement.expression:
|
|
186
|
+
operation_type = str(statement.expression.key).upper()
|
|
187
|
+
|
|
188
|
+
if "statements_executed" in result:
|
|
189
|
+
script_result = cast("ScriptResultDict", result)
|
|
190
|
+
return SQLResult[RowT](
|
|
191
|
+
statement=statement,
|
|
192
|
+
data=[],
|
|
193
|
+
rows_affected=0,
|
|
194
|
+
operation_type="SCRIPT",
|
|
195
|
+
metadata={
|
|
196
|
+
"status_message": script_result.get("status_message", ""),
|
|
197
|
+
"statements_executed": script_result.get("statements_executed", -1),
|
|
198
|
+
},
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
dml_result = cast("DMLResultDict", result)
|
|
202
|
+
rows_affected = dml_result.get("rows_affected", -1)
|
|
203
|
+
status_message = dml_result.get("status_message", "")
|
|
204
|
+
return SQLResult[RowT](
|
|
205
|
+
statement=statement,
|
|
206
|
+
data=[],
|
|
207
|
+
rows_affected=rows_affected,
|
|
208
|
+
operation_type=operation_type,
|
|
209
|
+
metadata={"status_message": status_message},
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def _connection(self, connection: Optional[PsqlpyConnection] = None) -> PsqlpyConnection:
|
|
213
|
+
"""Get the connection to use for the operation."""
|
|
567
214
|
return connection or self.connection
|