sqlspec 0.13.1__py3-none-any.whl → 0.16.2__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.

Files changed (185) hide show
  1. sqlspec/__init__.py +71 -8
  2. sqlspec/__main__.py +12 -0
  3. sqlspec/__metadata__.py +1 -3
  4. sqlspec/_serialization.py +1 -2
  5. sqlspec/_sql.py +930 -136
  6. sqlspec/_typing.py +278 -142
  7. sqlspec/adapters/adbc/__init__.py +4 -3
  8. sqlspec/adapters/adbc/_types.py +12 -0
  9. sqlspec/adapters/adbc/config.py +116 -285
  10. sqlspec/adapters/adbc/driver.py +462 -340
  11. sqlspec/adapters/aiosqlite/__init__.py +18 -3
  12. sqlspec/adapters/aiosqlite/_types.py +13 -0
  13. sqlspec/adapters/aiosqlite/config.py +202 -150
  14. sqlspec/adapters/aiosqlite/driver.py +226 -247
  15. sqlspec/adapters/asyncmy/__init__.py +18 -3
  16. sqlspec/adapters/asyncmy/_types.py +12 -0
  17. sqlspec/adapters/asyncmy/config.py +80 -199
  18. sqlspec/adapters/asyncmy/driver.py +257 -215
  19. sqlspec/adapters/asyncpg/__init__.py +19 -4
  20. sqlspec/adapters/asyncpg/_types.py +17 -0
  21. sqlspec/adapters/asyncpg/config.py +81 -214
  22. sqlspec/adapters/asyncpg/driver.py +284 -359
  23. sqlspec/adapters/bigquery/__init__.py +17 -3
  24. sqlspec/adapters/bigquery/_types.py +12 -0
  25. sqlspec/adapters/bigquery/config.py +191 -299
  26. sqlspec/adapters/bigquery/driver.py +474 -634
  27. sqlspec/adapters/duckdb/__init__.py +14 -3
  28. sqlspec/adapters/duckdb/_types.py +12 -0
  29. sqlspec/adapters/duckdb/config.py +414 -397
  30. sqlspec/adapters/duckdb/driver.py +342 -393
  31. sqlspec/adapters/oracledb/__init__.py +19 -5
  32. sqlspec/adapters/oracledb/_types.py +14 -0
  33. sqlspec/adapters/oracledb/config.py +123 -458
  34. sqlspec/adapters/oracledb/driver.py +505 -531
  35. sqlspec/adapters/psqlpy/__init__.py +13 -3
  36. sqlspec/adapters/psqlpy/_types.py +11 -0
  37. sqlspec/adapters/psqlpy/config.py +93 -307
  38. sqlspec/adapters/psqlpy/driver.py +504 -213
  39. sqlspec/adapters/psycopg/__init__.py +19 -5
  40. sqlspec/adapters/psycopg/_types.py +17 -0
  41. sqlspec/adapters/psycopg/config.py +143 -472
  42. sqlspec/adapters/psycopg/driver.py +704 -825
  43. sqlspec/adapters/sqlite/__init__.py +14 -3
  44. sqlspec/adapters/sqlite/_types.py +11 -0
  45. sqlspec/adapters/sqlite/config.py +208 -142
  46. sqlspec/adapters/sqlite/driver.py +263 -278
  47. sqlspec/base.py +105 -9
  48. sqlspec/{statement/builder → builder}/__init__.py +12 -14
  49. sqlspec/{statement/builder/base.py → builder/_base.py} +184 -86
  50. sqlspec/{statement/builder/column.py → builder/_column.py} +97 -60
  51. sqlspec/{statement/builder/ddl.py → builder/_ddl.py} +61 -131
  52. sqlspec/{statement/builder → builder}/_ddl_utils.py +4 -10
  53. sqlspec/{statement/builder/delete.py → builder/_delete.py} +10 -30
  54. sqlspec/builder/_insert.py +421 -0
  55. sqlspec/builder/_merge.py +71 -0
  56. sqlspec/{statement/builder → builder}/_parsing_utils.py +49 -26
  57. sqlspec/builder/_select.py +170 -0
  58. sqlspec/{statement/builder/update.py → builder/_update.py} +16 -20
  59. sqlspec/builder/mixins/__init__.py +55 -0
  60. sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
  61. sqlspec/{statement/builder/mixins/_delete_from.py → builder/mixins/_delete_operations.py} +8 -1
  62. sqlspec/builder/mixins/_insert_operations.py +244 -0
  63. sqlspec/{statement/builder/mixins/_join.py → builder/mixins/_join_operations.py} +45 -13
  64. sqlspec/{statement/builder/mixins/_merge_clauses.py → builder/mixins/_merge_operations.py} +188 -30
  65. sqlspec/builder/mixins/_order_limit_operations.py +135 -0
  66. sqlspec/builder/mixins/_pivot_operations.py +153 -0
  67. sqlspec/builder/mixins/_select_operations.py +604 -0
  68. sqlspec/builder/mixins/_update_operations.py +202 -0
  69. sqlspec/builder/mixins/_where_clause.py +644 -0
  70. sqlspec/cli.py +247 -0
  71. sqlspec/config.py +183 -138
  72. sqlspec/core/__init__.py +63 -0
  73. sqlspec/core/cache.py +871 -0
  74. sqlspec/core/compiler.py +417 -0
  75. sqlspec/core/filters.py +830 -0
  76. sqlspec/core/hashing.py +310 -0
  77. sqlspec/core/parameters.py +1237 -0
  78. sqlspec/core/result.py +677 -0
  79. sqlspec/{statement → core}/splitter.py +321 -191
  80. sqlspec/core/statement.py +676 -0
  81. sqlspec/driver/__init__.py +7 -10
  82. sqlspec/driver/_async.py +422 -163
  83. sqlspec/driver/_common.py +545 -287
  84. sqlspec/driver/_sync.py +426 -160
  85. sqlspec/driver/mixins/__init__.py +2 -13
  86. sqlspec/driver/mixins/_result_tools.py +193 -0
  87. sqlspec/driver/mixins/_sql_translator.py +65 -14
  88. sqlspec/exceptions.py +5 -252
  89. sqlspec/extensions/aiosql/adapter.py +93 -96
  90. sqlspec/extensions/litestar/__init__.py +2 -1
  91. sqlspec/extensions/litestar/cli.py +48 -0
  92. sqlspec/extensions/litestar/config.py +0 -1
  93. sqlspec/extensions/litestar/handlers.py +15 -26
  94. sqlspec/extensions/litestar/plugin.py +21 -16
  95. sqlspec/extensions/litestar/providers.py +17 -52
  96. sqlspec/loader.py +423 -104
  97. sqlspec/migrations/__init__.py +35 -0
  98. sqlspec/migrations/base.py +414 -0
  99. sqlspec/migrations/commands.py +443 -0
  100. sqlspec/migrations/loaders.py +402 -0
  101. sqlspec/migrations/runner.py +213 -0
  102. sqlspec/migrations/tracker.py +140 -0
  103. sqlspec/migrations/utils.py +129 -0
  104. sqlspec/protocols.py +51 -186
  105. sqlspec/storage/__init__.py +1 -1
  106. sqlspec/storage/backends/base.py +37 -40
  107. sqlspec/storage/backends/fsspec.py +136 -112
  108. sqlspec/storage/backends/obstore.py +138 -160
  109. sqlspec/storage/capabilities.py +5 -4
  110. sqlspec/storage/registry.py +57 -106
  111. sqlspec/typing.py +136 -115
  112. sqlspec/utils/__init__.py +2 -2
  113. sqlspec/utils/correlation.py +0 -3
  114. sqlspec/utils/deprecation.py +6 -6
  115. sqlspec/utils/fixtures.py +6 -6
  116. sqlspec/utils/logging.py +0 -2
  117. sqlspec/utils/module_loader.py +7 -12
  118. sqlspec/utils/singleton.py +0 -1
  119. sqlspec/utils/sync_tools.py +17 -38
  120. sqlspec/utils/text.py +12 -51
  121. sqlspec/utils/type_guards.py +482 -235
  122. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/METADATA +7 -2
  123. sqlspec-0.16.2.dist-info/RECORD +134 -0
  124. sqlspec-0.16.2.dist-info/entry_points.txt +2 -0
  125. sqlspec/driver/connection.py +0 -207
  126. sqlspec/driver/mixins/_csv_writer.py +0 -91
  127. sqlspec/driver/mixins/_pipeline.py +0 -512
  128. sqlspec/driver/mixins/_result_utils.py +0 -140
  129. sqlspec/driver/mixins/_storage.py +0 -926
  130. sqlspec/driver/mixins/_type_coercion.py +0 -130
  131. sqlspec/driver/parameters.py +0 -138
  132. sqlspec/service/__init__.py +0 -4
  133. sqlspec/service/_util.py +0 -147
  134. sqlspec/service/base.py +0 -1131
  135. sqlspec/service/pagination.py +0 -26
  136. sqlspec/statement/__init__.py +0 -21
  137. sqlspec/statement/builder/insert.py +0 -288
  138. sqlspec/statement/builder/merge.py +0 -95
  139. sqlspec/statement/builder/mixins/__init__.py +0 -65
  140. sqlspec/statement/builder/mixins/_aggregate_functions.py +0 -250
  141. sqlspec/statement/builder/mixins/_case_builder.py +0 -91
  142. sqlspec/statement/builder/mixins/_common_table_expr.py +0 -90
  143. sqlspec/statement/builder/mixins/_from.py +0 -63
  144. sqlspec/statement/builder/mixins/_group_by.py +0 -118
  145. sqlspec/statement/builder/mixins/_having.py +0 -35
  146. sqlspec/statement/builder/mixins/_insert_from_select.py +0 -47
  147. sqlspec/statement/builder/mixins/_insert_into.py +0 -36
  148. sqlspec/statement/builder/mixins/_insert_values.py +0 -67
  149. sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
  150. sqlspec/statement/builder/mixins/_order_by.py +0 -46
  151. sqlspec/statement/builder/mixins/_pivot.py +0 -79
  152. sqlspec/statement/builder/mixins/_returning.py +0 -37
  153. sqlspec/statement/builder/mixins/_select_columns.py +0 -61
  154. sqlspec/statement/builder/mixins/_set_ops.py +0 -122
  155. sqlspec/statement/builder/mixins/_unpivot.py +0 -77
  156. sqlspec/statement/builder/mixins/_update_from.py +0 -55
  157. sqlspec/statement/builder/mixins/_update_set.py +0 -94
  158. sqlspec/statement/builder/mixins/_update_table.py +0 -29
  159. sqlspec/statement/builder/mixins/_where.py +0 -401
  160. sqlspec/statement/builder/mixins/_window_functions.py +0 -86
  161. sqlspec/statement/builder/select.py +0 -221
  162. sqlspec/statement/filters.py +0 -596
  163. sqlspec/statement/parameter_manager.py +0 -220
  164. sqlspec/statement/parameters.py +0 -867
  165. sqlspec/statement/pipelines/__init__.py +0 -210
  166. sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
  167. sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
  168. sqlspec/statement/pipelines/context.py +0 -115
  169. sqlspec/statement/pipelines/transformers/__init__.py +0 -7
  170. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
  171. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
  172. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
  173. sqlspec/statement/pipelines/validators/__init__.py +0 -23
  174. sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
  175. sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
  176. sqlspec/statement/pipelines/validators/_performance.py +0 -718
  177. sqlspec/statement/pipelines/validators/_security.py +0 -967
  178. sqlspec/statement/result.py +0 -435
  179. sqlspec/statement/sql.py +0 -1704
  180. sqlspec/statement/sql_compiler.py +0 -140
  181. sqlspec/utils/cached_property.py +0 -25
  182. sqlspec-0.13.1.dist-info/RECORD +0 -150
  183. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/WHEEL +0 -0
  184. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/LICENSE +0 -0
  185. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/NOTICE +0 -0
@@ -1,232 +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
- from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union
5
-
6
- from asyncmy import Connection
7
- from typing_extensions import TypeAlias
8
-
9
- from sqlspec.driver import AsyncDriverAdapterProtocol
10
- from sqlspec.driver.connection import managed_transaction_async
11
- from sqlspec.driver.mixins import (
12
- AsyncPipelinedExecutionMixin,
13
- AsyncStorageMixin,
14
- SQLTranslatorMixin,
15
- ToSchemaMixin,
16
- TypeCoercionMixin,
17
- )
18
- from sqlspec.driver.parameters import normalize_parameter_sequence
19
- from sqlspec.statement.parameters import ParameterStyle, ParameterValidator
20
- from sqlspec.statement.result import SQLResult
21
- from sqlspec.statement.sql import SQL, SQLConfig
22
- from sqlspec.typing import DictRow, RowT
11
+ from typing import TYPE_CHECKING, Any, Optional, Union
12
+
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
23
23
 
24
24
  if TYPE_CHECKING:
25
- from asyncmy.cursors import Cursor, DictCursor
26
- from sqlglot.dialects.dialect import DialectType
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
+ )
27
60
 
28
- __all__ = ("AsyncmyConnection", "AsyncmyDriver")
29
61
 
30
- logger = logging.getLogger("sqlspec")
62
+ class AsyncmyCursor:
63
+ """Async context manager for AsyncMy cursor management."""
31
64
 
32
- AsyncmyConnection: TypeAlias = Connection
65
+ __slots__ = ("connection", "cursor")
33
66
 
67
+ def __init__(self, connection: "AsyncmyConnection") -> None:
68
+ self.connection = connection
69
+ self.cursor: Optional[Union[Cursor, DictCursor]] = None
34
70
 
35
- class AsyncmyDriver(
36
- AsyncDriverAdapterProtocol[AsyncmyConnection, RowT],
37
- SQLTranslatorMixin,
38
- TypeCoercionMixin,
39
- AsyncStorageMixin,
40
- AsyncPipelinedExecutionMixin,
41
- ToSchemaMixin,
42
- ):
43
- """Asyncmy MySQL/MariaDB Driver Adapter. Modern protocol implementation."""
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."""
44
83
 
45
- dialect: "DialectType" = "mysql"
46
- supported_parameter_styles: "tuple[ParameterStyle, ...]" = (ParameterStyle.POSITIONAL_PYFORMAT,)
47
- default_parameter_style: ParameterStyle = ParameterStyle.POSITIONAL_PYFORMAT
48
- __supports_arrow__: ClassVar[bool] = True
49
- __supports_parquet__: ClassVar[bool] = False
50
84
  __slots__ = ()
51
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"
139
+
52
140
  def __init__(
53
141
  self,
54
- connection: AsyncmyConnection,
55
- config: Optional[SQLConfig] = None,
56
- default_row_type: type[DictRow] = DictRow,
142
+ connection: "AsyncmyConnection",
143
+ statement_config: "Optional[StatementConfig]" = None,
144
+ driver_features: "Optional[dict[str, Any]]" = None,
57
145
  ) -> None:
58
- super().__init__(connection=connection, config=config, default_row_type=default_row_type)
59
-
60
- @asynccontextmanager
61
- async def _get_cursor(
62
- self, connection: "Optional[AsyncmyConnection]" = None
63
- ) -> "AsyncGenerator[Union[Cursor, DictCursor], None]":
64
- conn = self._connection(connection)
65
- cursor = conn.cursor()
66
- try:
67
- yield cursor
68
- finally:
69
- await cursor.close()
70
-
71
- async def _execute_statement(
72
- self, statement: SQL, connection: "Optional[AsyncmyConnection]" = None, **kwargs: Any
73
- ) -> SQLResult[RowT]:
74
- if statement.is_script:
75
- sql, _ = statement.compile(placeholder_style=ParameterStyle.STATIC)
76
- return await self._execute_script(sql, connection=connection, **kwargs)
77
-
78
- # Detect parameter styles in the SQL
79
- detected_styles = set()
80
- sql_str = statement.to_sql(placeholder_style=None) # Get raw SQL
81
- validator = self.config.parameter_validator if self.config else ParameterValidator()
82
- param_infos = validator.extract_parameters(sql_str)
83
- if param_infos:
84
- detected_styles = {p.style for p in param_infos}
85
-
86
- # Determine target style based on what's in the SQL
87
- target_style = self.default_parameter_style
88
-
89
- # Check if there are unsupported styles
90
- unsupported_styles = detected_styles - set(self.supported_parameter_styles)
91
- if unsupported_styles:
92
- # Force conversion to default style
93
- target_style = self.default_parameter_style
94
- elif detected_styles:
95
- # Prefer the first supported style found
96
- for style in detected_styles:
97
- if style in self.supported_parameter_styles:
98
- target_style = style
99
- break
100
-
101
- # Compile with the determined style
102
- sql, params = statement.compile(placeholder_style=target_style)
103
-
104
- if statement.is_many:
105
- params = self._process_parameters(params)
106
- return await self._execute_many(sql, params, connection=connection, **kwargs)
107
-
108
- params = self._process_parameters(params)
109
- return await self._execute(sql, params, statement, connection=connection, **kwargs)
110
-
111
- async def _execute(
112
- self, sql: str, parameters: Any, statement: SQL, connection: "Optional[AsyncmyConnection]" = None, **kwargs: Any
113
- ) -> SQLResult[RowT]:
114
- # Use provided connection or driver's default connection
115
- conn = connection if connection is not None else self._connection(None)
116
-
117
- async with managed_transaction_async(conn, auto_commit=True) as txn_conn:
118
- # Normalize parameters using consolidated utility
119
- normalized_params = normalize_parameter_sequence(parameters)
120
- # AsyncMy doesn't like empty lists/tuples, convert to None
121
- final_params = (
122
- normalized_params[0] if normalized_params and len(normalized_params) == 1 else normalized_params
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",
123
153
  )
124
- if not final_params:
125
- final_params = None
126
-
127
- async with self._get_cursor(txn_conn) as cursor:
128
- # AsyncMy expects list/tuple parameters or dict for named params
129
- await cursor.execute(sql, final_params)
130
-
131
- if self.returns_rows(statement.expression):
132
- # For SELECT queries, fetch data and return SQLResult
133
- data = await cursor.fetchall()
134
- column_names = [desc[0] for desc in cursor.description or []]
135
- return SQLResult(
136
- statement=statement,
137
- data=data,
138
- column_names=column_names,
139
- rows_affected=len(data),
140
- operation_type="SELECT",
141
- )
142
-
143
- # For DML/DDL queries
144
- return SQLResult(
145
- statement=statement,
146
- data=[],
147
- rows_affected=cursor.rowcount if cursor.rowcount is not None else -1,
148
- operation_type=self._determine_operation_type(statement),
149
- metadata={"status_message": "OK"},
150
- )
151
-
152
- async def _execute_many(
153
- self, sql: str, param_list: Any, connection: "Optional[AsyncmyConnection]" = None, **kwargs: Any
154
- ) -> SQLResult[RowT]:
155
- # Use provided connection or driver's default connection
156
- conn = connection if connection is not None else self._connection(None)
157
-
158
- async with managed_transaction_async(conn, auto_commit=True) as txn_conn:
159
- # Normalize parameter list using consolidated utility
160
- normalized_param_list = normalize_parameter_sequence(param_list)
161
-
162
- params_list: list[Union[list[Any], tuple[Any, ...]]] = []
163
- if normalized_param_list and isinstance(normalized_param_list, Sequence):
164
- for param_set in normalized_param_list:
165
- if isinstance(param_set, (list, tuple)):
166
- params_list.append(param_set)
167
- elif param_set is None:
168
- params_list.append([])
169
- else:
170
- params_list.append([param_set])
171
-
172
- async with self._get_cursor(txn_conn) as cursor:
173
- await cursor.executemany(sql, params_list)
174
- return SQLResult(
175
- statement=SQL(sql, _dialect=self.dialect),
176
- data=[],
177
- rows_affected=cursor.rowcount if cursor.rowcount != -1 else len(params_list),
178
- operation_type="EXECUTE",
179
- metadata={"status_message": "OK"},
180
- )
181
-
182
- async def _execute_script(
183
- self, script: str, connection: "Optional[AsyncmyConnection]" = None, **kwargs: Any
184
- ) -> SQLResult[RowT]:
185
- # Use provided connection or driver's default connection
186
- conn = connection if connection is not None else self._connection(None)
187
-
188
- async with managed_transaction_async(conn, auto_commit=True) as txn_conn:
189
- # AsyncMy may not support multi-statement scripts without CLIENT_MULTI_STATEMENTS flag
190
- statements = self._split_script_statements(script)
191
- statements_executed = 0
192
-
193
- async with self._get_cursor(txn_conn) as cursor:
194
- for statement_str in statements:
195
- if statement_str:
196
- await cursor.execute(statement_str)
197
- statements_executed += 1
198
-
199
- return SQLResult(
200
- statement=SQL(script, _dialect=self.dialect).as_script(),
201
- data=[],
202
- rows_affected=0,
203
- operation_type="SCRIPT",
204
- metadata={"status_message": "SCRIPT EXECUTED"},
205
- total_statements=statements_executed,
206
- successful_statements=statements_executed,
154
+ statement_config = enhanced_config
155
+
156
+ super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
157
+
158
+ def with_cursor(self, connection: "AsyncmyConnection") -> "AsyncmyCursor":
159
+ """Create async context manager for AsyncMy cursor."""
160
+ return AsyncmyCursor(connection)
161
+
162
+ def handle_database_exceptions(self) -> "AbstractAsyncContextManager[None]":
163
+ """Handle database-specific exceptions and wrap them appropriately."""
164
+ return AsyncmyExceptionHandler()
165
+
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
207
239
  )
208
240
 
209
- async def _ingest_arrow_table(self, table: "Any", table_name: str, mode: str = "append", **options: Any) -> int:
210
- self._ensure_pyarrow_installed()
211
- conn = self._connection(None)
212
- async with managed_transaction_async(conn, auto_commit=True) as txn_conn, self._get_cursor(txn_conn) as cursor:
213
- if mode == "replace":
214
- await cursor.execute(f"TRUNCATE TABLE {table_name}")
215
- elif mode == "create":
216
- msg = "'create' mode is not supported for asyncmy ingestion."
217
- raise NotImplementedError(msg)
218
-
219
- data_for_ingest = table.to_pylist()
220
- if not data_for_ingest:
221
- return 0
222
-
223
- # Generate column placeholders: %s, %s, etc.
224
- num_columns = len(data_for_ingest[0])
225
- placeholders = ", ".join("%s" for _ in range(num_columns))
226
- sql = f"INSERT INTO {table_name} VALUES ({placeholders})"
227
- await cursor.executemany(sql, data_for_ingest)
228
- return cursor.rowcount if cursor.rowcount is not None else -1
229
-
230
- def _connection(self, connection: Optional["AsyncmyConnection"] = None) -> "AsyncmyConnection":
231
- """Get the connection to use for the operation."""
232
- return connection or self.connection
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
- from sqlspec.adapters.asyncpg.config import CONNECTION_FIELDS, POOL_FIELDS, AsyncpgConfig
2
- from sqlspec.adapters.asyncpg.driver import AsyncpgConnection, AsyncpgDriver
1
+ """AsyncPG adapter for SQLSpec."""
3
2
 
4
- # AsyncpgDriver already imported above
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__ = ("CONNECTION_FIELDS", "POOL_FIELDS", "AsyncpgConfig", "AsyncpgConnection", "AsyncpgDriver")
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",)