sqlspec 0.11.1__py3-none-any.whl → 0.12.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/__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 -621
- sqlspec/adapters/aiosqlite/__init__.py +2 -6
- sqlspec/adapters/aiosqlite/config.py +143 -57
- sqlspec/adapters/aiosqlite/driver.py +269 -431
- sqlspec/adapters/asyncmy/__init__.py +3 -8
- sqlspec/adapters/asyncmy/config.py +247 -202
- sqlspec/adapters/asyncmy/driver.py +218 -436
- sqlspec/adapters/asyncpg/__init__.py +4 -7
- sqlspec/adapters/asyncpg/config.py +329 -176
- sqlspec/adapters/asyncpg/driver.py +417 -487
- sqlspec/adapters/bigquery/__init__.py +2 -2
- sqlspec/adapters/bigquery/config.py +407 -0
- sqlspec/adapters/bigquery/driver.py +600 -553
- sqlspec/adapters/duckdb/__init__.py +4 -1
- sqlspec/adapters/duckdb/config.py +432 -321
- sqlspec/adapters/duckdb/driver.py +392 -406
- sqlspec/adapters/oracledb/__init__.py +3 -8
- sqlspec/adapters/oracledb/config.py +625 -0
- sqlspec/adapters/oracledb/driver.py +548 -921
- sqlspec/adapters/psqlpy/__init__.py +4 -7
- sqlspec/adapters/psqlpy/config.py +372 -203
- sqlspec/adapters/psqlpy/driver.py +197 -533
- sqlspec/adapters/psycopg/__init__.py +3 -8
- sqlspec/adapters/psycopg/config.py +725 -0
- sqlspec/adapters/psycopg/driver.py +734 -694
- sqlspec/adapters/sqlite/__init__.py +2 -6
- sqlspec/adapters/sqlite/config.py +146 -81
- sqlspec/adapters/sqlite/driver.py +242 -405
- sqlspec/base.py +220 -784
- 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.1.dist-info → sqlspec-0.12.1.dist-info}/METADATA +97 -26
- sqlspec-0.12.1.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 -331
- sqlspec/mixins.py +0 -305
- sqlspec/statement.py +0 -378
- sqlspec-0.11.1.dist-info/RECORD +0 -69
- {sqlspec-0.11.1.dist-info → sqlspec-0.12.1.dist-info}/WHEEL +0 -0
- {sqlspec-0.11.1.dist-info → sqlspec-0.12.1.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.11.1.dist-info → sqlspec-0.12.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,462 +1,244 @@
|
|
|
1
|
-
# type: ignore
|
|
2
1
|
import logging
|
|
3
|
-
import
|
|
4
|
-
from collections.abc import AsyncGenerator
|
|
2
|
+
from collections.abc import AsyncGenerator, Sequence
|
|
5
3
|
from contextlib import asynccontextmanager
|
|
6
|
-
from typing import TYPE_CHECKING, Any, Optional, Union,
|
|
4
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union, cast
|
|
7
5
|
|
|
8
6
|
from asyncmy import Connection
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from sqlspec.
|
|
12
|
-
from sqlspec.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
from typing_extensions import TypeAlias
|
|
8
|
+
|
|
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
|
|
16
21
|
|
|
17
22
|
if TYPE_CHECKING:
|
|
18
|
-
from
|
|
19
|
-
|
|
20
|
-
from asyncmy.cursors import Cursor
|
|
21
|
-
|
|
22
|
-
from sqlspec.typing import ModelDTOT, StatementParameterType, T
|
|
23
|
+
from asyncmy.cursors import Cursor, DictCursor
|
|
24
|
+
from sqlglot.dialects.dialect import DialectType
|
|
23
25
|
|
|
24
|
-
__all__ = ("AsyncmyDriver"
|
|
25
|
-
|
|
26
|
-
AsyncmyConnection = Connection
|
|
26
|
+
__all__ = ("AsyncmyConnection", "AsyncmyDriver")
|
|
27
27
|
|
|
28
28
|
logger = logging.getLogger("sqlspec")
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
MYSQL_PLACEHOLDER_PATTERN = re.compile(r"(?<!%)%s")
|
|
30
|
+
AsyncmyConnection: TypeAlias = Connection
|
|
32
31
|
|
|
33
32
|
|
|
34
33
|
class AsyncmyDriver(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
AsyncDriverAdapterProtocol[AsyncmyConnection, RowT],
|
|
35
|
+
SQLTranslatorMixin,
|
|
36
|
+
TypeCoercionMixin,
|
|
37
|
+
AsyncStorageMixin,
|
|
38
|
+
AsyncPipelinedExecutionMixin,
|
|
39
|
+
ToSchemaMixin,
|
|
38
40
|
):
|
|
39
|
-
"""Asyncmy MySQL/MariaDB Driver Adapter."""
|
|
41
|
+
"""Asyncmy MySQL/MariaDB Driver Adapter. Modern protocol implementation."""
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
dialect: "DialectType" = "mysql"
|
|
44
|
+
supported_parameter_styles: "tuple[ParameterStyle, ...]" = (ParameterStyle.POSITIONAL_PYFORMAT,)
|
|
45
|
+
default_parameter_style: ParameterStyle = ParameterStyle.POSITIONAL_PYFORMAT
|
|
46
|
+
__supports_arrow__: ClassVar[bool] = True
|
|
47
|
+
__supports_parquet__: ClassVar[bool] = False
|
|
48
|
+
__slots__ = ()
|
|
43
49
|
|
|
44
|
-
def __init__(
|
|
45
|
-
self
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
connection: AsyncmyConnection,
|
|
53
|
+
config: Optional[SQLConfig] = None,
|
|
54
|
+
default_row_type: type[DictRow] = DictRow,
|
|
55
|
+
) -> None:
|
|
56
|
+
super().__init__(connection=connection, config=config, default_row_type=default_row_type)
|
|
46
57
|
|
|
47
|
-
@staticmethod
|
|
48
58
|
@asynccontextmanager
|
|
49
|
-
async def
|
|
50
|
-
|
|
59
|
+
async def _get_cursor(
|
|
60
|
+
self, connection: "Optional[AsyncmyConnection]" = None
|
|
61
|
+
) -> "AsyncGenerator[Union[Cursor, DictCursor], None]":
|
|
62
|
+
conn = self._connection(connection)
|
|
63
|
+
cursor = await conn.cursor()
|
|
51
64
|
try:
|
|
52
65
|
yield cursor
|
|
53
66
|
finally:
|
|
54
67
|
await cursor.close()
|
|
55
68
|
|
|
56
|
-
def
|
|
57
|
-
self,
|
|
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
|
-
# actual_parameters remains None
|
|
85
|
-
else:
|
|
86
|
-
actual_parameters = parameters # type: ignore[assignment]
|
|
87
|
-
|
|
88
|
-
# Handle MySQL-specific placeholders (%s) which SQLGlot doesn't parse well
|
|
89
|
-
# If %s placeholders are present, handle them directly
|
|
90
|
-
mysql_placeholders_count = len(MYSQL_PLACEHOLDER_PATTERN.findall(sql))
|
|
91
|
-
|
|
92
|
-
if mysql_placeholders_count > 0:
|
|
93
|
-
# For MySQL format placeholders, minimal processing is needed
|
|
94
|
-
if actual_parameters is None:
|
|
95
|
-
if mysql_placeholders_count > 0:
|
|
96
|
-
msg = f"asyncmy: SQL statement contains {mysql_placeholders_count} format placeholders ('%s'), but no parameters were provided. SQL: {sql}"
|
|
97
|
-
raise ParameterStyleMismatchError(msg)
|
|
98
|
-
return sql, None
|
|
99
|
-
|
|
100
|
-
# Convert dict to tuple if needed
|
|
101
|
-
if is_dict(actual_parameters):
|
|
102
|
-
# MySQL's %s placeholders require positional params
|
|
103
|
-
msg = "asyncmy: Dictionary parameters provided with '%s' placeholders. MySQL format placeholders require tuple/list parameters."
|
|
104
|
-
raise ParameterStyleMismatchError(msg)
|
|
105
|
-
|
|
106
|
-
# Convert to tuple (handles both scalar and sequence cases)
|
|
107
|
-
if not isinstance(actual_parameters, (list, tuple)):
|
|
108
|
-
# Scalar parameter case
|
|
109
|
-
return sql, (actual_parameters,)
|
|
110
|
-
|
|
111
|
-
# Sequence parameter case - ensure appropriate length
|
|
112
|
-
if len(actual_parameters) != mysql_placeholders_count: # type: ignore[arg-type]
|
|
113
|
-
msg = f"asyncmy: Parameter count mismatch. SQL expects {mysql_placeholders_count} '%s' placeholders, but {len(actual_parameters)} parameters were provided. SQL: {sql}" # type: ignore[arg-type]
|
|
114
|
-
raise ParameterStyleMismatchError(msg)
|
|
115
|
-
|
|
116
|
-
return sql, tuple(actual_parameters) # type: ignore[arg-type]
|
|
117
|
-
|
|
118
|
-
# Create a SQLStatement with MySQL dialect
|
|
119
|
-
statement = SQLStatement(sql, actual_parameters, kwargs=kwargs, dialect=self.dialect)
|
|
120
|
-
|
|
121
|
-
# Apply any filters
|
|
122
|
-
for filter_obj in current_filters: # Use the modified list of filters
|
|
123
|
-
statement = statement.apply_filter(filter_obj)
|
|
124
|
-
|
|
125
|
-
# Process the statement for execution
|
|
126
|
-
processed_sql, processed_params, _ = statement.process()
|
|
127
|
-
|
|
128
|
-
# Convert parameters to the format expected by MySQL
|
|
129
|
-
if processed_params is None:
|
|
130
|
-
return processed_sql, None
|
|
131
|
-
|
|
132
|
-
# For MySQL, ensure parameters are in the right format
|
|
133
|
-
if is_dict(processed_params):
|
|
134
|
-
# Dictionary parameters are not well supported by asyncmy
|
|
135
|
-
msg = "asyncmy: Dictionary parameters are not supported for MySQL placeholders. Use sequence parameters."
|
|
136
|
-
raise ParameterStyleMismatchError(msg)
|
|
137
|
-
|
|
138
|
-
# For sequence parameters, ensure they're a tuple
|
|
139
|
-
if isinstance(processed_params, (list, tuple)):
|
|
140
|
-
return processed_sql, tuple(processed_params)
|
|
141
|
-
|
|
142
|
-
# For scalar parameter, wrap in a tuple
|
|
143
|
-
return processed_sql, (processed_params,)
|
|
144
|
-
|
|
145
|
-
# --- Public API Methods --- #
|
|
146
|
-
@overload
|
|
147
|
-
async def select(
|
|
148
|
-
self,
|
|
149
|
-
sql: str,
|
|
150
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
151
|
-
*filters: "StatementFilter",
|
|
152
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
153
|
-
schema_type: None = None,
|
|
154
|
-
**kwargs: Any,
|
|
155
|
-
) -> "Sequence[dict[str, Any]]": ...
|
|
156
|
-
@overload
|
|
157
|
-
async def select(
|
|
158
|
-
self,
|
|
159
|
-
sql: str,
|
|
160
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
161
|
-
*filters: "StatementFilter",
|
|
162
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
163
|
-
schema_type: "type[ModelDTOT]",
|
|
164
|
-
**kwargs: Any,
|
|
165
|
-
) -> "Sequence[ModelDTOT]": ...
|
|
166
|
-
async def select(
|
|
167
|
-
self,
|
|
168
|
-
sql: str,
|
|
169
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
170
|
-
*filters: "StatementFilter",
|
|
171
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
172
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
173
|
-
**kwargs: Any,
|
|
174
|
-
) -> "Sequence[Union[dict[str, Any], ModelDTOT]]":
|
|
175
|
-
"""Fetch data from the database.
|
|
176
|
-
|
|
177
|
-
Returns:
|
|
178
|
-
List of row data as either model instances or dictionaries.
|
|
179
|
-
"""
|
|
180
|
-
connection = self._connection(connection)
|
|
181
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
182
|
-
async with self._with_cursor(connection) as cursor:
|
|
183
|
-
await cursor.execute(sql, parameters)
|
|
184
|
-
results = await cursor.fetchall()
|
|
185
|
-
if not results:
|
|
186
|
-
return []
|
|
187
|
-
column_names = [c[0] for c in cursor.description or []]
|
|
188
|
-
return self.to_schema([dict(zip(column_names, row)) for row in results], schema_type=schema_type)
|
|
189
|
-
|
|
190
|
-
@overload
|
|
191
|
-
async def select_one(
|
|
192
|
-
self,
|
|
193
|
-
sql: str,
|
|
194
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
195
|
-
*filters: "StatementFilter",
|
|
196
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
197
|
-
schema_type: None = None,
|
|
198
|
-
**kwargs: Any,
|
|
199
|
-
) -> "dict[str, Any]": ...
|
|
200
|
-
@overload
|
|
201
|
-
async def select_one(
|
|
202
|
-
self,
|
|
203
|
-
sql: str,
|
|
204
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
205
|
-
*filters: "StatementFilter",
|
|
206
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
207
|
-
schema_type: "type[ModelDTOT]",
|
|
208
|
-
**kwargs: Any,
|
|
209
|
-
) -> "ModelDTOT": ...
|
|
210
|
-
async def select_one(
|
|
211
|
-
self,
|
|
212
|
-
sql: str,
|
|
213
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
214
|
-
*filters: "StatementFilter",
|
|
215
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
216
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
217
|
-
**kwargs: Any,
|
|
218
|
-
) -> "Union[dict[str, Any], ModelDTOT]":
|
|
219
|
-
"""Fetch one row from the database.
|
|
220
|
-
|
|
221
|
-
Returns:
|
|
222
|
-
The first row of the query results.
|
|
223
|
-
"""
|
|
224
|
-
connection = self._connection(connection)
|
|
225
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
226
|
-
async with self._with_cursor(connection) as cursor:
|
|
227
|
-
await cursor.execute(sql, parameters)
|
|
228
|
-
result = await cursor.fetchone()
|
|
229
|
-
result = self.check_not_found(result)
|
|
230
|
-
column_names = [c[0] for c in cursor.description or []]
|
|
231
|
-
return self.to_schema(dict(zip(column_names, result)), schema_type=schema_type)
|
|
232
|
-
|
|
233
|
-
@overload
|
|
234
|
-
async def select_one_or_none(
|
|
235
|
-
self,
|
|
236
|
-
sql: str,
|
|
237
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
238
|
-
*filters: "StatementFilter",
|
|
239
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
240
|
-
schema_type: None = None,
|
|
241
|
-
**kwargs: Any,
|
|
242
|
-
) -> "Optional[dict[str, Any]]": ...
|
|
243
|
-
@overload
|
|
244
|
-
async def select_one_or_none(
|
|
245
|
-
self,
|
|
246
|
-
sql: str,
|
|
247
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
248
|
-
*filters: "StatementFilter",
|
|
249
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
250
|
-
schema_type: "type[ModelDTOT]",
|
|
251
|
-
**kwargs: Any,
|
|
252
|
-
) -> "Optional[ModelDTOT]": ...
|
|
253
|
-
async def select_one_or_none(
|
|
254
|
-
self,
|
|
255
|
-
sql: str,
|
|
256
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
257
|
-
*filters: "StatementFilter",
|
|
258
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
259
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
260
|
-
**kwargs: Any,
|
|
261
|
-
) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
|
|
262
|
-
"""Fetch one row from the database.
|
|
263
|
-
|
|
264
|
-
Returns:
|
|
265
|
-
The first row of the query results.
|
|
266
|
-
"""
|
|
267
|
-
connection = self._connection(connection)
|
|
268
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
269
|
-
async with self._with_cursor(connection) as cursor:
|
|
69
|
+
async def _execute_statement(
|
|
70
|
+
self, statement: SQL, connection: "Optional[AsyncmyConnection]" = None, **kwargs: Any
|
|
71
|
+
) -> Union[SelectResultDict, DMLResultDict, ScriptResultDict]:
|
|
72
|
+
if statement.is_script:
|
|
73
|
+
sql, _ = statement.compile(placeholder_style=ParameterStyle.STATIC)
|
|
74
|
+
return await self._execute_script(sql, connection=connection, **kwargs)
|
|
75
|
+
|
|
76
|
+
# Let the SQL object handle parameter style conversion based on dialect support
|
|
77
|
+
sql, params = statement.compile(placeholder_style=self.default_parameter_style)
|
|
78
|
+
|
|
79
|
+
if statement.is_many:
|
|
80
|
+
# Process parameter list through type coercion
|
|
81
|
+
params = self._process_parameters(params)
|
|
82
|
+
return await self._execute_many(sql, params, connection=connection, **kwargs)
|
|
83
|
+
|
|
84
|
+
# Process parameters through type coercion
|
|
85
|
+
params = self._process_parameters(params)
|
|
86
|
+
return await self._execute(sql, params, statement, connection=connection, **kwargs)
|
|
87
|
+
|
|
88
|
+
async def _execute(
|
|
89
|
+
self, sql: str, parameters: Any, statement: SQL, connection: "Optional[AsyncmyConnection]" = None, **kwargs: Any
|
|
90
|
+
) -> Union[SelectResultDict, DMLResultDict]:
|
|
91
|
+
conn = self._connection(connection)
|
|
92
|
+
# AsyncMy doesn't like empty lists/tuples, convert to None
|
|
93
|
+
if not parameters:
|
|
94
|
+
parameters = None
|
|
95
|
+
async with self._get_cursor(conn) as cursor:
|
|
96
|
+
# AsyncMy expects list/tuple parameters or dict for named params
|
|
270
97
|
await cursor.execute(sql, parameters)
|
|
271
|
-
result = await cursor.fetchone()
|
|
272
|
-
if result is None:
|
|
273
|
-
return None
|
|
274
|
-
column_names = [c[0] for c in cursor.description or []]
|
|
275
|
-
return self.to_schema(dict(zip(column_names, result)), schema_type=schema_type)
|
|
276
|
-
|
|
277
|
-
@overload
|
|
278
|
-
async def select_value(
|
|
279
|
-
self,
|
|
280
|
-
sql: str,
|
|
281
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
282
|
-
*filters: "StatementFilter",
|
|
283
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
284
|
-
schema_type: None = None,
|
|
285
|
-
**kwargs: Any,
|
|
286
|
-
) -> "Any": ...
|
|
287
|
-
@overload
|
|
288
|
-
async def select_value(
|
|
289
|
-
self,
|
|
290
|
-
sql: str,
|
|
291
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
292
|
-
*filters: "StatementFilter",
|
|
293
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
294
|
-
schema_type: "type[T]",
|
|
295
|
-
**kwargs: Any,
|
|
296
|
-
) -> "T": ...
|
|
297
|
-
async def select_value(
|
|
298
|
-
self,
|
|
299
|
-
sql: str,
|
|
300
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
301
|
-
*filters: "StatementFilter",
|
|
302
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
303
|
-
schema_type: "Optional[type[T]]" = None,
|
|
304
|
-
**kwargs: Any,
|
|
305
|
-
) -> "Union[T, Any]":
|
|
306
|
-
"""Fetch a single value from the database.
|
|
307
|
-
|
|
308
|
-
Returns:
|
|
309
|
-
The first value from the first row of results.
|
|
310
|
-
"""
|
|
311
|
-
connection = self._connection(connection)
|
|
312
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
313
|
-
async with self._with_cursor(connection) as cursor:
|
|
314
|
-
await cursor.execute(sql, parameters)
|
|
315
|
-
result = await cursor.fetchone()
|
|
316
|
-
result = self.check_not_found(result)
|
|
317
|
-
value = result[0]
|
|
318
|
-
if schema_type is not None:
|
|
319
|
-
return schema_type(value) # type: ignore[call-arg]
|
|
320
|
-
return value
|
|
321
|
-
|
|
322
|
-
@overload
|
|
323
|
-
async def select_value_or_none(
|
|
324
|
-
self,
|
|
325
|
-
sql: str,
|
|
326
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
327
|
-
*filters: "StatementFilter",
|
|
328
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
329
|
-
schema_type: None = None,
|
|
330
|
-
**kwargs: Any,
|
|
331
|
-
) -> "Optional[Any]": ...
|
|
332
|
-
@overload
|
|
333
|
-
async def select_value_or_none(
|
|
334
|
-
self,
|
|
335
|
-
sql: str,
|
|
336
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
337
|
-
*filters: "StatementFilter",
|
|
338
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
339
|
-
schema_type: "type[T]",
|
|
340
|
-
**kwargs: Any,
|
|
341
|
-
) -> "Optional[T]": ...
|
|
342
|
-
async def select_value_or_none(
|
|
343
|
-
self,
|
|
344
|
-
sql: str,
|
|
345
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
346
|
-
*filters: "StatementFilter",
|
|
347
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
348
|
-
schema_type: "Optional[type[T]]" = None,
|
|
349
|
-
**kwargs: Any,
|
|
350
|
-
) -> "Optional[Union[T, Any]]":
|
|
351
|
-
"""Fetch a single value from the database.
|
|
352
|
-
|
|
353
|
-
Returns:
|
|
354
|
-
The first value from the first row of results, or None if no results.
|
|
355
|
-
"""
|
|
356
|
-
connection = self._connection(connection)
|
|
357
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
358
|
-
async with self._with_cursor(connection) as cursor:
|
|
359
|
-
await cursor.execute(sql, parameters)
|
|
360
|
-
result = await cursor.fetchone()
|
|
361
|
-
if result is None:
|
|
362
|
-
return None
|
|
363
|
-
value = result[0]
|
|
364
|
-
if schema_type is not None:
|
|
365
|
-
return schema_type(value) # type: ignore[call-arg]
|
|
366
|
-
return value
|
|
367
|
-
|
|
368
|
-
async def insert_update_delete(
|
|
369
|
-
self,
|
|
370
|
-
sql: str,
|
|
371
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
372
|
-
*filters: "StatementFilter",
|
|
373
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
374
|
-
**kwargs: Any,
|
|
375
|
-
) -> int:
|
|
376
|
-
"""Insert, update, or delete data from the database.
|
|
377
|
-
|
|
378
|
-
Returns:
|
|
379
|
-
Row count affected by the operation.
|
|
380
|
-
"""
|
|
381
|
-
connection = self._connection(connection)
|
|
382
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
383
|
-
async with self._with_cursor(connection) as cursor:
|
|
384
|
-
await cursor.execute(sql, parameters)
|
|
385
|
-
return cursor.rowcount
|
|
386
|
-
|
|
387
|
-
@overload
|
|
388
|
-
async def insert_update_delete_returning(
|
|
389
|
-
self,
|
|
390
|
-
sql: str,
|
|
391
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
392
|
-
*filters: "StatementFilter",
|
|
393
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
394
|
-
schema_type: None = None,
|
|
395
|
-
**kwargs: Any,
|
|
396
|
-
) -> "dict[str, Any]": ...
|
|
397
|
-
@overload
|
|
398
|
-
async def insert_update_delete_returning(
|
|
399
|
-
self,
|
|
400
|
-
sql: str,
|
|
401
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
402
|
-
*filters: "StatementFilter",
|
|
403
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
404
|
-
schema_type: "type[ModelDTOT]",
|
|
405
|
-
**kwargs: Any,
|
|
406
|
-
) -> "ModelDTOT": ...
|
|
407
|
-
async def insert_update_delete_returning(
|
|
408
|
-
self,
|
|
409
|
-
sql: str,
|
|
410
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
411
|
-
*filters: "StatementFilter",
|
|
412
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
413
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
414
|
-
**kwargs: Any,
|
|
415
|
-
) -> "Union[dict[str, Any], ModelDTOT]":
|
|
416
|
-
"""Insert, update, or delete data from the database and return result.
|
|
417
|
-
|
|
418
|
-
Returns:
|
|
419
|
-
The first row of results.
|
|
420
|
-
"""
|
|
421
|
-
connection = self._connection(connection)
|
|
422
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
423
|
-
|
|
424
|
-
async with self._with_cursor(connection) as cursor:
|
|
425
|
-
await cursor.execute(sql, parameters)
|
|
426
|
-
result = await cursor.fetchone()
|
|
427
|
-
if result is None:
|
|
428
|
-
return None
|
|
429
|
-
column_names = [c[0] for c in cursor.description or []]
|
|
430
|
-
|
|
431
|
-
# Convert to dict and use ResultConverter
|
|
432
|
-
dict_result = dict(zip(column_names, result))
|
|
433
|
-
return self.to_schema(dict_result, schema_type=schema_type)
|
|
434
|
-
|
|
435
|
-
async def execute_script(
|
|
436
|
-
self,
|
|
437
|
-
sql: str,
|
|
438
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
439
|
-
connection: "Optional[AsyncmyConnection]" = None,
|
|
440
|
-
**kwargs: Any,
|
|
441
|
-
) -> str:
|
|
442
|
-
"""Execute a script.
|
|
443
|
-
|
|
444
|
-
Returns:
|
|
445
|
-
Status message for the operation.
|
|
446
|
-
"""
|
|
447
|
-
connection = self._connection(connection)
|
|
448
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
449
|
-
async with self._with_cursor(connection) as cursor:
|
|
450
|
-
await cursor.execute(sql, parameters)
|
|
451
|
-
return f"Script executed successfully. Rows affected: {cursor.rowcount}"
|
|
452
|
-
|
|
453
|
-
def _connection(self, connection: "Optional[AsyncmyConnection]" = None) -> "AsyncmyConnection":
|
|
454
|
-
"""Get the connection to use for the operation.
|
|
455
|
-
|
|
456
|
-
Args:
|
|
457
|
-
connection: Optional connection to use.
|
|
458
98
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
99
|
+
if self.returns_rows(statement.expression):
|
|
100
|
+
# For SELECT queries, fetch data and return SelectResultDict
|
|
101
|
+
data = await cursor.fetchall()
|
|
102
|
+
column_names = [desc[0] for desc in cursor.description or []]
|
|
103
|
+
result: SelectResultDict = {"data": data, "column_names": column_names, "rows_affected": len(data)}
|
|
104
|
+
return result
|
|
105
|
+
|
|
106
|
+
# For DML/DDL queries, return DMLResultDict
|
|
107
|
+
dml_result: DMLResultDict = {
|
|
108
|
+
"rows_affected": cursor.rowcount if cursor.rowcount is not None else -1,
|
|
109
|
+
"status_message": "OK",
|
|
110
|
+
}
|
|
111
|
+
return dml_result
|
|
112
|
+
|
|
113
|
+
async def _execute_many(
|
|
114
|
+
self, sql: str, param_list: Any, connection: "Optional[AsyncmyConnection]" = None, **kwargs: Any
|
|
115
|
+
) -> DMLResultDict:
|
|
116
|
+
conn = self._connection(connection)
|
|
117
|
+
|
|
118
|
+
# Convert parameter list to proper format for executemany
|
|
119
|
+
params_list: list[Union[list[Any], tuple[Any, ...]]] = []
|
|
120
|
+
if param_list and isinstance(param_list, Sequence):
|
|
121
|
+
for param_set in param_list:
|
|
122
|
+
if isinstance(param_set, (list, tuple)):
|
|
123
|
+
params_list.append(param_set)
|
|
124
|
+
elif param_set is None:
|
|
125
|
+
params_list.append([])
|
|
126
|
+
else:
|
|
127
|
+
params_list.append([param_set])
|
|
128
|
+
|
|
129
|
+
async with self._get_cursor(conn) as cursor:
|
|
130
|
+
await cursor.executemany(sql, params_list)
|
|
131
|
+
result: DMLResultDict = {
|
|
132
|
+
"rows_affected": cursor.rowcount if cursor.rowcount != -1 else len(params_list),
|
|
133
|
+
"status_message": "OK",
|
|
134
|
+
}
|
|
135
|
+
return result
|
|
136
|
+
|
|
137
|
+
async def _execute_script(
|
|
138
|
+
self, script: str, connection: "Optional[AsyncmyConnection]" = None, **kwargs: Any
|
|
139
|
+
) -> ScriptResultDict:
|
|
140
|
+
conn = self._connection(connection)
|
|
141
|
+
# AsyncMy may not support multi-statement scripts without CLIENT_MULTI_STATEMENTS flag
|
|
142
|
+
# Use the shared implementation to split and execute statements individually
|
|
143
|
+
statements = self._split_script_statements(script)
|
|
144
|
+
statements_executed = 0
|
|
145
|
+
|
|
146
|
+
async with self._get_cursor(conn) as cursor:
|
|
147
|
+
for statement_str in statements:
|
|
148
|
+
if statement_str:
|
|
149
|
+
await cursor.execute(statement_str)
|
|
150
|
+
statements_executed += 1
|
|
151
|
+
|
|
152
|
+
result: ScriptResultDict = {"statements_executed": statements_executed, "status_message": "SCRIPT EXECUTED"}
|
|
153
|
+
return result
|
|
154
|
+
|
|
155
|
+
async def _ingest_arrow_table(self, table: "Any", table_name: str, mode: str = "append", **options: Any) -> int:
|
|
156
|
+
self._ensure_pyarrow_installed()
|
|
157
|
+
conn = self._connection(None)
|
|
158
|
+
|
|
159
|
+
async with self._get_cursor(conn) as cursor:
|
|
160
|
+
if mode == "replace":
|
|
161
|
+
await cursor.execute(f"TRUNCATE TABLE {table_name}")
|
|
162
|
+
elif mode == "create":
|
|
163
|
+
msg = "'create' mode is not supported for asyncmy ingestion."
|
|
164
|
+
raise NotImplementedError(msg)
|
|
165
|
+
|
|
166
|
+
data_for_ingest = table.to_pylist()
|
|
167
|
+
if not data_for_ingest:
|
|
168
|
+
return 0
|
|
169
|
+
|
|
170
|
+
# Generate column placeholders: %s, %s, etc.
|
|
171
|
+
num_columns = len(data_for_ingest[0])
|
|
172
|
+
placeholders = ", ".join("%s" for _ in range(num_columns))
|
|
173
|
+
sql = f"INSERT INTO {table_name} VALUES ({placeholders})"
|
|
174
|
+
await cursor.executemany(sql, data_for_ingest)
|
|
175
|
+
return cursor.rowcount if cursor.rowcount is not None else -1
|
|
176
|
+
|
|
177
|
+
async def _wrap_select_result(
|
|
178
|
+
self, statement: SQL, result: SelectResultDict, schema_type: "Optional[type[ModelDTOT]]" = None, **kwargs: Any
|
|
179
|
+
) -> "Union[SQLResult[ModelDTOT], SQLResult[RowT]]":
|
|
180
|
+
data = result["data"]
|
|
181
|
+
column_names = result["column_names"]
|
|
182
|
+
rows_affected = result["rows_affected"]
|
|
183
|
+
|
|
184
|
+
if not data:
|
|
185
|
+
return SQLResult[RowT](
|
|
186
|
+
statement=statement, data=[], column_names=column_names, rows_affected=0, operation_type="SELECT"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
rows_as_dicts = [dict(zip(column_names, row)) for row in data]
|
|
190
|
+
|
|
191
|
+
if schema_type:
|
|
192
|
+
converted_data = self.to_schema(data=rows_as_dicts, schema_type=schema_type)
|
|
193
|
+
return SQLResult[ModelDTOT](
|
|
194
|
+
statement=statement,
|
|
195
|
+
data=list(converted_data),
|
|
196
|
+
column_names=column_names,
|
|
197
|
+
rows_affected=rows_affected,
|
|
198
|
+
operation_type="SELECT",
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
return SQLResult[RowT](
|
|
202
|
+
statement=statement,
|
|
203
|
+
data=rows_as_dicts,
|
|
204
|
+
column_names=column_names,
|
|
205
|
+
rows_affected=rows_affected,
|
|
206
|
+
operation_type="SELECT",
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
async def _wrap_execute_result(
|
|
210
|
+
self, statement: SQL, result: Union[DMLResultDict, ScriptResultDict], **kwargs: Any
|
|
211
|
+
) -> SQLResult[RowT]:
|
|
212
|
+
operation_type = "UNKNOWN"
|
|
213
|
+
if statement.expression:
|
|
214
|
+
operation_type = str(statement.expression.key).upper()
|
|
215
|
+
|
|
216
|
+
# Handle script results
|
|
217
|
+
if "statements_executed" in result:
|
|
218
|
+
script_result = cast("ScriptResultDict", result)
|
|
219
|
+
return SQLResult[RowT](
|
|
220
|
+
statement=statement,
|
|
221
|
+
data=[],
|
|
222
|
+
rows_affected=0,
|
|
223
|
+
operation_type="SCRIPT",
|
|
224
|
+
metadata={
|
|
225
|
+
"status_message": script_result.get("status_message", ""),
|
|
226
|
+
"statements_executed": script_result.get("statements_executed", -1),
|
|
227
|
+
},
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Handle DML results
|
|
231
|
+
dml_result = cast("DMLResultDict", result)
|
|
232
|
+
rows_affected = dml_result.get("rows_affected", -1)
|
|
233
|
+
status_message = dml_result.get("status_message", "")
|
|
234
|
+
return SQLResult[RowT](
|
|
235
|
+
statement=statement,
|
|
236
|
+
data=[],
|
|
237
|
+
rows_affected=rows_affected,
|
|
238
|
+
operation_type=operation_type,
|
|
239
|
+
metadata={"status_message": status_message},
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def _connection(self, connection: Optional["AsyncmyConnection"] = None) -> "AsyncmyConnection":
|
|
243
|
+
"""Get the connection to use for the operation."""
|
|
462
244
|
return connection or self.connection
|