sqlspec 0.14.1__py3-none-any.whl → 0.15.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 +50 -25
- sqlspec/__main__.py +1 -1
- sqlspec/__metadata__.py +1 -3
- sqlspec/_serialization.py +1 -2
- sqlspec/_sql.py +256 -120
- sqlspec/_typing.py +278 -142
- sqlspec/adapters/adbc/__init__.py +4 -3
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +115 -260
- sqlspec/adapters/adbc/driver.py +462 -367
- sqlspec/adapters/aiosqlite/__init__.py +18 -3
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +199 -129
- sqlspec/adapters/aiosqlite/driver.py +230 -269
- sqlspec/adapters/asyncmy/__init__.py +18 -3
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +80 -168
- sqlspec/adapters/asyncmy/driver.py +260 -225
- sqlspec/adapters/asyncpg/__init__.py +19 -4
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +82 -181
- sqlspec/adapters/asyncpg/driver.py +285 -383
- sqlspec/adapters/bigquery/__init__.py +17 -3
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +191 -258
- sqlspec/adapters/bigquery/driver.py +474 -646
- sqlspec/adapters/duckdb/__init__.py +14 -3
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +415 -351
- sqlspec/adapters/duckdb/driver.py +343 -413
- sqlspec/adapters/oracledb/__init__.py +19 -5
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +123 -379
- sqlspec/adapters/oracledb/driver.py +507 -560
- sqlspec/adapters/psqlpy/__init__.py +13 -3
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +93 -254
- sqlspec/adapters/psqlpy/driver.py +505 -234
- sqlspec/adapters/psycopg/__init__.py +19 -5
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +143 -403
- sqlspec/adapters/psycopg/driver.py +706 -872
- sqlspec/adapters/sqlite/__init__.py +14 -3
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +202 -118
- sqlspec/adapters/sqlite/driver.py +264 -303
- sqlspec/base.py +105 -9
- sqlspec/{statement/builder → builder}/__init__.py +12 -14
- sqlspec/{statement/builder → builder}/_base.py +120 -55
- sqlspec/{statement/builder → builder}/_column.py +17 -6
- sqlspec/{statement/builder → builder}/_ddl.py +46 -79
- sqlspec/{statement/builder → builder}/_ddl_utils.py +5 -10
- sqlspec/{statement/builder → builder}/_delete.py +6 -25
- sqlspec/{statement/builder → builder}/_insert.py +6 -64
- sqlspec/builder/_merge.py +56 -0
- sqlspec/{statement/builder → builder}/_parsing_utils.py +3 -10
- sqlspec/{statement/builder → builder}/_select.py +11 -56
- sqlspec/{statement/builder → builder}/_update.py +12 -18
- sqlspec/{statement/builder → builder}/mixins/__init__.py +10 -14
- sqlspec/{statement/builder → builder}/mixins/_cte_and_set_ops.py +48 -59
- sqlspec/{statement/builder → builder}/mixins/_insert_operations.py +22 -16
- sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
- sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +3 -5
- sqlspec/{statement/builder → builder}/mixins/_order_limit_operations.py +3 -3
- sqlspec/{statement/builder → builder}/mixins/_pivot_operations.py +4 -8
- sqlspec/{statement/builder → builder}/mixins/_select_operations.py +21 -36
- sqlspec/{statement/builder → builder}/mixins/_update_operations.py +3 -14
- sqlspec/{statement/builder → builder}/mixins/_where_clause.py +52 -79
- sqlspec/cli.py +4 -5
- sqlspec/config.py +180 -133
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.py +873 -0
- sqlspec/core/compiler.py +396 -0
- sqlspec/core/filters.py +828 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.py +1209 -0
- sqlspec/core/result.py +664 -0
- sqlspec/{statement → core}/splitter.py +321 -191
- sqlspec/core/statement.py +651 -0
- sqlspec/driver/__init__.py +7 -10
- sqlspec/driver/_async.py +387 -176
- sqlspec/driver/_common.py +527 -289
- sqlspec/driver/_sync.py +390 -172
- sqlspec/driver/mixins/__init__.py +2 -19
- sqlspec/driver/mixins/_result_tools.py +168 -0
- sqlspec/driver/mixins/_sql_translator.py +6 -3
- sqlspec/exceptions.py +5 -252
- sqlspec/extensions/aiosql/adapter.py +93 -96
- sqlspec/extensions/litestar/config.py +0 -1
- sqlspec/extensions/litestar/handlers.py +15 -26
- sqlspec/extensions/litestar/plugin.py +16 -14
- sqlspec/extensions/litestar/providers.py +17 -52
- sqlspec/loader.py +424 -105
- sqlspec/migrations/__init__.py +12 -0
- sqlspec/migrations/base.py +92 -68
- sqlspec/migrations/commands.py +24 -106
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +49 -51
- sqlspec/migrations/tracker.py +31 -44
- sqlspec/migrations/utils.py +64 -24
- sqlspec/protocols.py +7 -183
- sqlspec/storage/__init__.py +1 -1
- sqlspec/storage/backends/base.py +37 -40
- sqlspec/storage/backends/fsspec.py +136 -112
- sqlspec/storage/backends/obstore.py +138 -160
- sqlspec/storage/capabilities.py +5 -4
- sqlspec/storage/registry.py +57 -106
- sqlspec/typing.py +136 -115
- sqlspec/utils/__init__.py +2 -3
- sqlspec/utils/correlation.py +0 -3
- sqlspec/utils/deprecation.py +6 -6
- sqlspec/utils/fixtures.py +6 -6
- sqlspec/utils/logging.py +0 -2
- sqlspec/utils/module_loader.py +7 -12
- sqlspec/utils/singleton.py +0 -1
- sqlspec/utils/sync_tools.py +16 -37
- sqlspec/utils/text.py +12 -51
- sqlspec/utils/type_guards.py +443 -232
- {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/METADATA +7 -2
- sqlspec-0.15.0.dist-info/RECORD +134 -0
- sqlspec/adapters/adbc/transformers.py +0 -108
- sqlspec/driver/connection.py +0 -207
- sqlspec/driver/mixins/_cache.py +0 -114
- sqlspec/driver/mixins/_csv_writer.py +0 -91
- sqlspec/driver/mixins/_pipeline.py +0 -508
- sqlspec/driver/mixins/_query_tools.py +0 -796
- sqlspec/driver/mixins/_result_utils.py +0 -138
- sqlspec/driver/mixins/_storage.py +0 -912
- sqlspec/driver/mixins/_type_coercion.py +0 -128
- sqlspec/driver/parameters.py +0 -138
- sqlspec/statement/__init__.py +0 -21
- sqlspec/statement/builder/_merge.py +0 -95
- sqlspec/statement/cache.py +0 -50
- sqlspec/statement/filters.py +0 -625
- sqlspec/statement/parameters.py +0 -956
- sqlspec/statement/pipelines/__init__.py +0 -210
- sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
- sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
- sqlspec/statement/pipelines/context.py +0 -109
- sqlspec/statement/pipelines/transformers/__init__.py +0 -7
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
- sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
- sqlspec/statement/pipelines/validators/__init__.py +0 -23
- sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
- sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
- sqlspec/statement/pipelines/validators/_performance.py +0 -714
- sqlspec/statement/pipelines/validators/_security.py +0 -967
- sqlspec/statement/result.py +0 -435
- sqlspec/statement/sql.py +0 -1774
- sqlspec/utils/cached_property.py +0 -25
- sqlspec/utils/statement_hashing.py +0 -203
- sqlspec-0.14.1.dist-info/RECORD +0 -145
- /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,239 +1,274 @@
|
|
|
1
|
+
"""AsyncMy MySQL driver implementation for async MySQL operations.
|
|
2
|
+
|
|
3
|
+
Provides async MySQL/MariaDB connectivity with:
|
|
4
|
+
- Parameter style conversion (QMARK to POSITIONAL_PYFORMAT)
|
|
5
|
+
- MySQL-specific type coercion and data handling
|
|
6
|
+
- Error categorization for MySQL/MariaDB
|
|
7
|
+
- Transaction management
|
|
8
|
+
"""
|
|
9
|
+
|
|
1
10
|
import logging
|
|
2
|
-
from collections.abc import AsyncGenerator, Sequence
|
|
3
|
-
from contextlib import asynccontextmanager
|
|
4
11
|
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
5
12
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
from sqlspec.
|
|
11
|
-
from sqlspec.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
ToSchemaMixin,
|
|
17
|
-
TypeCoercionMixin,
|
|
18
|
-
)
|
|
19
|
-
from sqlspec.driver.parameters import convert_parameter_sequence
|
|
20
|
-
from sqlspec.statement.parameters import ParameterStyle, ParameterValidator
|
|
21
|
-
from sqlspec.statement.result import SQLResult
|
|
22
|
-
from sqlspec.statement.sql import SQL, SQLConfig
|
|
23
|
-
from sqlspec.typing import DictRow, RowT
|
|
13
|
+
import asyncmy
|
|
14
|
+
import asyncmy.errors
|
|
15
|
+
from asyncmy.cursors import Cursor, DictCursor
|
|
16
|
+
|
|
17
|
+
from sqlspec.core.cache import get_cache_config
|
|
18
|
+
from sqlspec.core.parameters import ParameterStyle, ParameterStyleConfig
|
|
19
|
+
from sqlspec.core.statement import StatementConfig
|
|
20
|
+
from sqlspec.driver import AsyncDriverAdapterBase
|
|
21
|
+
from sqlspec.exceptions import SQLParsingError, SQLSpecError
|
|
22
|
+
from sqlspec.utils.serializers import to_json
|
|
24
23
|
|
|
25
24
|
if TYPE_CHECKING:
|
|
26
|
-
from
|
|
27
|
-
|
|
25
|
+
from contextlib import AbstractAsyncContextManager
|
|
26
|
+
|
|
27
|
+
from sqlspec.adapters.asyncmy._types import AsyncmyConnection
|
|
28
|
+
from sqlspec.core.result import SQLResult
|
|
29
|
+
from sqlspec.core.statement import SQL
|
|
30
|
+
from sqlspec.driver import ExecutionResult
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
__all__ = ("AsyncmyCursor", "AsyncmyDriver", "AsyncmyExceptionHandler", "asyncmy_statement_config")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Enhanced AsyncMy statement configuration using core modules with performance optimizations
|
|
38
|
+
asyncmy_statement_config = StatementConfig(
|
|
39
|
+
dialect="mysql",
|
|
40
|
+
parameter_config=ParameterStyleConfig(
|
|
41
|
+
default_parameter_style=ParameterStyle.QMARK,
|
|
42
|
+
supported_parameter_styles={ParameterStyle.QMARK, ParameterStyle.POSITIONAL_PYFORMAT},
|
|
43
|
+
default_execution_parameter_style=ParameterStyle.POSITIONAL_PYFORMAT,
|
|
44
|
+
supported_execution_parameter_styles={ParameterStyle.POSITIONAL_PYFORMAT},
|
|
45
|
+
type_coercion_map={
|
|
46
|
+
dict: to_json,
|
|
47
|
+
list: to_json,
|
|
48
|
+
tuple: lambda v: to_json(list(v)),
|
|
49
|
+
bool: int, # MySQL represents booleans as integers
|
|
50
|
+
},
|
|
51
|
+
has_native_list_expansion=False,
|
|
52
|
+
needs_static_script_compilation=True,
|
|
53
|
+
preserve_parameter_format=True,
|
|
54
|
+
),
|
|
55
|
+
enable_parsing=True,
|
|
56
|
+
enable_validation=True,
|
|
57
|
+
enable_caching=True,
|
|
58
|
+
enable_parameter_type_wrapping=True,
|
|
59
|
+
)
|
|
28
60
|
|
|
29
|
-
__all__ = ("AsyncmyConnection", "AsyncmyDriver")
|
|
30
61
|
|
|
31
|
-
|
|
62
|
+
class AsyncmyCursor:
|
|
63
|
+
"""Async context manager for AsyncMy cursor management."""
|
|
64
|
+
|
|
65
|
+
__slots__ = ("connection", "cursor")
|
|
66
|
+
|
|
67
|
+
def __init__(self, connection: "AsyncmyConnection") -> None:
|
|
68
|
+
self.connection = connection
|
|
69
|
+
self.cursor: Optional[Union[Cursor, DictCursor]] = None
|
|
70
|
+
|
|
71
|
+
async def __aenter__(self) -> Union[Cursor, DictCursor]:
|
|
72
|
+
self.cursor = self.connection.cursor()
|
|
73
|
+
return self.cursor
|
|
74
|
+
|
|
75
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
76
|
+
_ = (exc_type, exc_val, exc_tb)
|
|
77
|
+
if self.cursor is not None:
|
|
78
|
+
await self.cursor.close()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class AsyncmyExceptionHandler:
|
|
82
|
+
"""Custom async context manager for handling AsyncMy database exceptions."""
|
|
83
|
+
|
|
84
|
+
__slots__ = ()
|
|
85
|
+
|
|
86
|
+
async def __aenter__(self) -> None:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
90
|
+
if exc_type is None:
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
if issubclass(exc_type, asyncmy.errors.IntegrityError):
|
|
94
|
+
e = exc_val
|
|
95
|
+
msg = f"AsyncMy MySQL integrity constraint violation: {e}"
|
|
96
|
+
raise SQLSpecError(msg) from e
|
|
97
|
+
if issubclass(exc_type, asyncmy.errors.ProgrammingError):
|
|
98
|
+
e = exc_val
|
|
99
|
+
error_msg = str(e).lower()
|
|
100
|
+
if "syntax" in error_msg or "parse" in error_msg:
|
|
101
|
+
msg = f"AsyncMy MySQL SQL syntax error: {e}"
|
|
102
|
+
raise SQLParsingError(msg) from e
|
|
103
|
+
msg = f"AsyncMy MySQL programming error: {e}"
|
|
104
|
+
raise SQLSpecError(msg) from e
|
|
105
|
+
if issubclass(exc_type, asyncmy.errors.OperationalError):
|
|
106
|
+
e = exc_val
|
|
107
|
+
msg = f"AsyncMy MySQL operational error: {e}"
|
|
108
|
+
raise SQLSpecError(msg) from e
|
|
109
|
+
if issubclass(exc_type, asyncmy.errors.DatabaseError):
|
|
110
|
+
e = exc_val
|
|
111
|
+
msg = f"AsyncMy MySQL database error: {e}"
|
|
112
|
+
raise SQLSpecError(msg) from e
|
|
113
|
+
if issubclass(exc_type, asyncmy.errors.Error):
|
|
114
|
+
e = exc_val
|
|
115
|
+
msg = f"AsyncMy MySQL error: {e}"
|
|
116
|
+
raise SQLSpecError(msg) from e
|
|
117
|
+
if issubclass(exc_type, Exception):
|
|
118
|
+
e = exc_val
|
|
119
|
+
error_msg = str(e).lower()
|
|
120
|
+
if "parse" in error_msg or "syntax" in error_msg:
|
|
121
|
+
msg = f"SQL parsing failed: {e}"
|
|
122
|
+
raise SQLParsingError(msg) from e
|
|
123
|
+
msg = f"Unexpected async database operation error: {e}"
|
|
124
|
+
raise SQLSpecError(msg) from e
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class AsyncmyDriver(AsyncDriverAdapterBase):
|
|
128
|
+
"""AsyncMy MySQL/MariaDB driver for async database operations.
|
|
129
|
+
|
|
130
|
+
Provides MySQL/MariaDB connectivity with:
|
|
131
|
+
- Parameter style conversion (QMARK to POSITIONAL_PYFORMAT)
|
|
132
|
+
- MySQL-specific type coercion (bool -> int, dict/list -> JSON)
|
|
133
|
+
- Error categorization for MySQL/MariaDB
|
|
134
|
+
- Transaction management
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
__slots__ = ()
|
|
138
|
+
dialect = "mysql"
|
|
32
139
|
|
|
33
|
-
|
|
140
|
+
def __init__(
|
|
141
|
+
self,
|
|
142
|
+
connection: "AsyncmyConnection",
|
|
143
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
144
|
+
driver_features: "Optional[dict[str, Any]]" = None,
|
|
145
|
+
) -> None:
|
|
146
|
+
if statement_config is None:
|
|
147
|
+
cache_config = get_cache_config()
|
|
148
|
+
enhanced_config = asyncmy_statement_config.replace(
|
|
149
|
+
enable_caching=cache_config.compiled_cache_enabled,
|
|
150
|
+
enable_parsing=True,
|
|
151
|
+
enable_validation=True,
|
|
152
|
+
dialect="mysql",
|
|
153
|
+
)
|
|
154
|
+
statement_config = enhanced_config
|
|
34
155
|
|
|
156
|
+
super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
|
|
35
157
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
SQLTranslatorMixin,
|
|
40
|
-
TypeCoercionMixin,
|
|
41
|
-
AsyncStorageMixin,
|
|
42
|
-
AsyncPipelinedExecutionMixin,
|
|
43
|
-
ToSchemaMixin,
|
|
44
|
-
):
|
|
45
|
-
"""Asyncmy MySQL/MariaDB Driver Adapter. Modern protocol implementation."""
|
|
158
|
+
def with_cursor(self, connection: "AsyncmyConnection") -> "AsyncmyCursor":
|
|
159
|
+
"""Create async context manager for AsyncMy cursor."""
|
|
160
|
+
return AsyncmyCursor(connection)
|
|
46
161
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
162
|
+
def handle_database_exceptions(self) -> "AbstractAsyncContextManager[None]":
|
|
163
|
+
"""Handle database-specific exceptions and wrap them appropriately."""
|
|
164
|
+
return AsyncmyExceptionHandler()
|
|
50
165
|
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
self, statement
|
|
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
|
-
async with self._get_cursor(txn_conn) as cursor:
|
|
125
|
-
# AsyncMy expects list/tuple parameters or dict for named params
|
|
126
|
-
await cursor.execute(sql, final_params)
|
|
127
|
-
|
|
128
|
-
if self.returns_rows(statement.expression):
|
|
129
|
-
# For SELECT queries, fetch data and return SQLResult
|
|
130
|
-
data = await cursor.fetchall()
|
|
131
|
-
column_names = [desc[0] for desc in cursor.description or []]
|
|
132
|
-
return SQLResult(
|
|
133
|
-
statement=statement,
|
|
134
|
-
data=data,
|
|
135
|
-
column_names=column_names,
|
|
136
|
-
rows_affected=len(data),
|
|
137
|
-
operation_type="SELECT",
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
# For DML/DDL queries
|
|
141
|
-
return SQLResult(
|
|
142
|
-
statement=statement,
|
|
143
|
-
data=[],
|
|
144
|
-
rows_affected=cursor.rowcount if cursor.rowcount is not None else -1,
|
|
145
|
-
operation_type=self._determine_operation_type(statement),
|
|
146
|
-
metadata={"status_message": "OK"},
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
async def _execute_many(
|
|
150
|
-
self, sql: str, param_list: Any, connection: "Optional[AsyncmyConnection]" = None, **kwargs: Any
|
|
151
|
-
) -> SQLResult[RowT]:
|
|
152
|
-
# Use provided connection or driver's default connection
|
|
153
|
-
conn = connection if connection is not None else self._connection(None)
|
|
154
|
-
|
|
155
|
-
async with managed_transaction_async(conn, auto_commit=True) as txn_conn:
|
|
156
|
-
# Normalize parameter list using consolidated utility
|
|
157
|
-
converted_param_list = convert_parameter_sequence(param_list)
|
|
158
|
-
|
|
159
|
-
params_list: list[Union[list[Any], tuple[Any, ...]]] = []
|
|
160
|
-
if converted_param_list and isinstance(converted_param_list, Sequence):
|
|
161
|
-
for param_set in converted_param_list:
|
|
162
|
-
if isinstance(param_set, (list, tuple)):
|
|
163
|
-
params_list.append(param_set)
|
|
164
|
-
elif param_set is None:
|
|
165
|
-
params_list.append([])
|
|
166
|
-
else:
|
|
167
|
-
params_list.append([param_set])
|
|
168
|
-
|
|
169
|
-
async with self._get_cursor(txn_conn) as cursor:
|
|
170
|
-
await cursor.executemany(sql, params_list)
|
|
171
|
-
return SQLResult(
|
|
172
|
-
statement=SQL(sql, _dialect=self.dialect),
|
|
173
|
-
data=[],
|
|
174
|
-
rows_affected=cursor.rowcount if cursor.rowcount != -1 else len(params_list),
|
|
175
|
-
operation_type="EXECUTE",
|
|
176
|
-
metadata={"status_message": "OK"},
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
async def _execute_script(
|
|
180
|
-
self, script: str, connection: "Optional[AsyncmyConnection]" = None, **kwargs: Any
|
|
181
|
-
) -> SQLResult[RowT]:
|
|
182
|
-
# Use provided connection or driver's default connection
|
|
183
|
-
conn = connection if connection is not None else self._connection(None)
|
|
184
|
-
|
|
185
|
-
async with managed_transaction_async(conn, auto_commit=True) as txn_conn:
|
|
186
|
-
# AsyncMy may not support multi-statement scripts without CLIENT_MULTI_STATEMENTS flag
|
|
187
|
-
statements = self._split_script_statements(script)
|
|
188
|
-
suppress_warnings = kwargs.get("_suppress_warnings", False)
|
|
189
|
-
statements_executed = 0
|
|
190
|
-
total_rows = 0
|
|
191
|
-
|
|
192
|
-
async with self._get_cursor(txn_conn) as cursor:
|
|
193
|
-
for statement_str in statements:
|
|
194
|
-
if statement_str:
|
|
195
|
-
# Validate each statement unless warnings suppressed
|
|
196
|
-
if not suppress_warnings:
|
|
197
|
-
# Run validation through pipeline
|
|
198
|
-
temp_sql = SQL(statement_str, config=self.config)
|
|
199
|
-
temp_sql._ensure_processed()
|
|
200
|
-
# Validation errors are logged as warnings by default
|
|
201
|
-
|
|
202
|
-
await cursor.execute(statement_str)
|
|
203
|
-
statements_executed += 1
|
|
204
|
-
total_rows += cursor.rowcount if cursor.rowcount is not None else 0
|
|
205
|
-
|
|
206
|
-
return SQLResult(
|
|
207
|
-
statement=SQL(script, _dialect=self.dialect).as_script(),
|
|
208
|
-
data=[],
|
|
209
|
-
rows_affected=total_rows,
|
|
210
|
-
operation_type="SCRIPT",
|
|
211
|
-
metadata={"status_message": "SCRIPT EXECUTED"},
|
|
212
|
-
total_statements=statements_executed,
|
|
213
|
-
successful_statements=statements_executed,
|
|
166
|
+
async def _try_special_handling(self, cursor: Any, statement: "SQL") -> "Optional[SQLResult]":
|
|
167
|
+
"""Hook for AsyncMy-specific special operations.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
cursor: AsyncMy cursor object
|
|
171
|
+
statement: SQL statement to analyze
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
None - always proceeds with standard execution for AsyncMy
|
|
175
|
+
"""
|
|
176
|
+
_ = (cursor, statement) # Mark as intentionally unused
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
async def _execute_script(self, cursor: Any, statement: "SQL") -> "ExecutionResult":
|
|
180
|
+
"""Execute SQL script using enhanced statement splitting and parameter handling.
|
|
181
|
+
|
|
182
|
+
Uses core module optimization for statement parsing and parameter processing.
|
|
183
|
+
Parameters are embedded as static values for script execution compatibility.
|
|
184
|
+
"""
|
|
185
|
+
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
186
|
+
statements = self.split_script_statements(sql, statement.statement_config, strip_trailing_semicolon=True)
|
|
187
|
+
|
|
188
|
+
successful_count = 0
|
|
189
|
+
last_cursor = cursor
|
|
190
|
+
|
|
191
|
+
for stmt in statements:
|
|
192
|
+
await cursor.execute(stmt, prepared_parameters or None)
|
|
193
|
+
successful_count += 1
|
|
194
|
+
|
|
195
|
+
return self.create_execution_result(
|
|
196
|
+
last_cursor, statement_count=len(statements), successful_statements=successful_count, is_script_result=True
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
async def _execute_many(self, cursor: Any, statement: "SQL") -> "ExecutionResult":
|
|
200
|
+
"""Execute SQL with multiple parameter sets using optimized AsyncMy batch processing.
|
|
201
|
+
|
|
202
|
+
Leverages core parameter processing for enhanced MySQL type handling and parameter conversion.
|
|
203
|
+
"""
|
|
204
|
+
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
205
|
+
|
|
206
|
+
# Enhanced parameter validation for executemany
|
|
207
|
+
if not prepared_parameters:
|
|
208
|
+
msg = "execute_many requires parameters"
|
|
209
|
+
raise ValueError(msg)
|
|
210
|
+
|
|
211
|
+
await cursor.executemany(sql, prepared_parameters)
|
|
212
|
+
|
|
213
|
+
# Calculate affected rows based on parameter count for AsyncMy
|
|
214
|
+
affected_rows = len(prepared_parameters) if prepared_parameters else 0
|
|
215
|
+
|
|
216
|
+
return self.create_execution_result(cursor, rowcount_override=affected_rows, is_many_result=True)
|
|
217
|
+
|
|
218
|
+
async def _execute_statement(self, cursor: Any, statement: "SQL") -> "ExecutionResult":
|
|
219
|
+
"""Execute single SQL statement with enhanced AsyncMy MySQL data handling and performance optimization.
|
|
220
|
+
|
|
221
|
+
Uses core processing for optimal parameter handling and MySQL result processing.
|
|
222
|
+
"""
|
|
223
|
+
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
224
|
+
await cursor.execute(sql, prepared_parameters or None)
|
|
225
|
+
|
|
226
|
+
# Enhanced SELECT result processing for MySQL
|
|
227
|
+
if statement.returns_rows():
|
|
228
|
+
fetched_data = await cursor.fetchall()
|
|
229
|
+
column_names = [desc[0] for desc in cursor.description or []]
|
|
230
|
+
|
|
231
|
+
# AsyncMy may return tuples or dicts - ensure consistent dict format
|
|
232
|
+
if fetched_data and not isinstance(fetched_data[0], dict):
|
|
233
|
+
data = [dict(zip(column_names, row)) for row in fetched_data]
|
|
234
|
+
else:
|
|
235
|
+
data = fetched_data
|
|
236
|
+
|
|
237
|
+
return self.create_execution_result(
|
|
238
|
+
cursor, selected_data=data, column_names=column_names, data_row_count=len(data), is_select_result=True
|
|
214
239
|
)
|
|
215
240
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
241
|
+
# Enhanced non-SELECT result processing for MySQL
|
|
242
|
+
affected_rows = cursor.rowcount if cursor.rowcount is not None else -1
|
|
243
|
+
last_id = getattr(cursor, "lastrowid", None) if cursor.rowcount and cursor.rowcount > 0 else None
|
|
244
|
+
return self.create_execution_result(cursor, rowcount_override=affected_rows, last_inserted_id=last_id)
|
|
245
|
+
|
|
246
|
+
# MySQL transaction management with enhanced async error handling
|
|
247
|
+
async def begin(self) -> None:
|
|
248
|
+
"""Begin a database transaction with enhanced async error handling.
|
|
249
|
+
|
|
250
|
+
Explicitly starts a MySQL transaction to ensure proper transaction boundaries.
|
|
251
|
+
"""
|
|
252
|
+
try:
|
|
253
|
+
# Execute explicit BEGIN to start transaction
|
|
254
|
+
async with AsyncmyCursor(self.connection) as cursor:
|
|
255
|
+
await cursor.execute("BEGIN")
|
|
256
|
+
except asyncmy.errors.MySQLError as e:
|
|
257
|
+
msg = f"Failed to begin MySQL transaction: {e}"
|
|
258
|
+
raise SQLSpecError(msg) from e
|
|
259
|
+
|
|
260
|
+
async def rollback(self) -> None:
|
|
261
|
+
"""Rollback the current transaction with enhanced async error handling."""
|
|
262
|
+
try:
|
|
263
|
+
await self.connection.rollback()
|
|
264
|
+
except asyncmy.errors.MySQLError as e:
|
|
265
|
+
msg = f"Failed to rollback MySQL transaction: {e}"
|
|
266
|
+
raise SQLSpecError(msg) from e
|
|
267
|
+
|
|
268
|
+
async def commit(self) -> None:
|
|
269
|
+
"""Commit the current transaction with enhanced async error handling."""
|
|
270
|
+
try:
|
|
271
|
+
await self.connection.commit()
|
|
272
|
+
except asyncmy.errors.MySQLError as e:
|
|
273
|
+
msg = f"Failed to commit MySQL transaction: {e}"
|
|
274
|
+
raise SQLSpecError(msg) from e
|
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
from sqlspec.adapters.asyncpg.driver import AsyncpgConnection, AsyncpgDriver
|
|
1
|
+
"""AsyncPG adapter for SQLSpec."""
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
from sqlspec.adapters.asyncpg._types import AsyncpgConnection
|
|
4
|
+
from sqlspec.adapters.asyncpg.config import AsyncpgConfig, AsyncpgConnectionConfig, AsyncpgPoolConfig
|
|
5
|
+
from sqlspec.adapters.asyncpg.driver import (
|
|
6
|
+
AsyncpgCursor,
|
|
7
|
+
AsyncpgDriver,
|
|
8
|
+
AsyncpgExceptionHandler,
|
|
9
|
+
asyncpg_statement_config,
|
|
10
|
+
)
|
|
5
11
|
|
|
6
|
-
__all__ = (
|
|
12
|
+
__all__ = (
|
|
13
|
+
"AsyncpgConfig",
|
|
14
|
+
"AsyncpgConnection",
|
|
15
|
+
"AsyncpgConnectionConfig",
|
|
16
|
+
"AsyncpgCursor",
|
|
17
|
+
"AsyncpgDriver",
|
|
18
|
+
"AsyncpgExceptionHandler",
|
|
19
|
+
"AsyncpgPoolConfig",
|
|
20
|
+
"asyncpg_statement_config",
|
|
21
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Union
|
|
2
|
+
|
|
3
|
+
from asyncpg import Connection
|
|
4
|
+
from asyncpg.pool import PoolConnectionProxy
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from asyncpg import Record
|
|
8
|
+
from typing_extensions import TypeAlias
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
AsyncpgConnection: TypeAlias = Union[Connection[Record], PoolConnectionProxy[Record]]
|
|
13
|
+
else:
|
|
14
|
+
AsyncpgConnection = Union[Connection, PoolConnectionProxy]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = ("AsyncpgConnection",)
|