sqlspec 0.17.0__py3-none-any.whl → 0.18.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.

Files changed (80) hide show
  1. sqlspec/__init__.py +1 -1
  2. sqlspec/_sql.py +188 -234
  3. sqlspec/adapters/adbc/config.py +24 -30
  4. sqlspec/adapters/adbc/driver.py +42 -61
  5. sqlspec/adapters/aiosqlite/config.py +5 -10
  6. sqlspec/adapters/aiosqlite/driver.py +9 -25
  7. sqlspec/adapters/aiosqlite/pool.py +43 -35
  8. sqlspec/adapters/asyncmy/config.py +10 -7
  9. sqlspec/adapters/asyncmy/driver.py +18 -39
  10. sqlspec/adapters/asyncpg/config.py +4 -0
  11. sqlspec/adapters/asyncpg/driver.py +32 -79
  12. sqlspec/adapters/bigquery/config.py +12 -65
  13. sqlspec/adapters/bigquery/driver.py +39 -133
  14. sqlspec/adapters/duckdb/config.py +11 -15
  15. sqlspec/adapters/duckdb/driver.py +61 -85
  16. sqlspec/adapters/duckdb/pool.py +2 -5
  17. sqlspec/adapters/oracledb/_types.py +8 -1
  18. sqlspec/adapters/oracledb/config.py +55 -38
  19. sqlspec/adapters/oracledb/driver.py +35 -92
  20. sqlspec/adapters/oracledb/migrations.py +257 -0
  21. sqlspec/adapters/psqlpy/config.py +13 -9
  22. sqlspec/adapters/psqlpy/driver.py +28 -103
  23. sqlspec/adapters/psycopg/config.py +9 -5
  24. sqlspec/adapters/psycopg/driver.py +107 -175
  25. sqlspec/adapters/sqlite/config.py +7 -5
  26. sqlspec/adapters/sqlite/driver.py +37 -73
  27. sqlspec/adapters/sqlite/pool.py +3 -12
  28. sqlspec/base.py +1 -8
  29. sqlspec/builder/__init__.py +1 -1
  30. sqlspec/builder/_base.py +34 -20
  31. sqlspec/builder/_column.py +5 -1
  32. sqlspec/builder/_ddl.py +407 -183
  33. sqlspec/builder/_expression_wrappers.py +46 -0
  34. sqlspec/builder/_insert.py +2 -4
  35. sqlspec/builder/_update.py +5 -5
  36. sqlspec/builder/mixins/_insert_operations.py +26 -6
  37. sqlspec/builder/mixins/_merge_operations.py +1 -1
  38. sqlspec/builder/mixins/_order_limit_operations.py +16 -4
  39. sqlspec/builder/mixins/_select_operations.py +3 -7
  40. sqlspec/builder/mixins/_update_operations.py +4 -4
  41. sqlspec/config.py +32 -13
  42. sqlspec/core/__init__.py +89 -14
  43. sqlspec/core/cache.py +57 -104
  44. sqlspec/core/compiler.py +57 -112
  45. sqlspec/core/filters.py +1 -21
  46. sqlspec/core/hashing.py +13 -47
  47. sqlspec/core/parameters.py +272 -261
  48. sqlspec/core/result.py +12 -27
  49. sqlspec/core/splitter.py +17 -21
  50. sqlspec/core/statement.py +150 -159
  51. sqlspec/driver/_async.py +2 -15
  52. sqlspec/driver/_common.py +16 -95
  53. sqlspec/driver/_sync.py +2 -15
  54. sqlspec/driver/mixins/_result_tools.py +8 -29
  55. sqlspec/driver/mixins/_sql_translator.py +6 -8
  56. sqlspec/exceptions.py +1 -2
  57. sqlspec/loader.py +43 -115
  58. sqlspec/migrations/__init__.py +1 -1
  59. sqlspec/migrations/base.py +34 -45
  60. sqlspec/migrations/commands.py +34 -15
  61. sqlspec/migrations/loaders.py +1 -1
  62. sqlspec/migrations/runner.py +104 -19
  63. sqlspec/migrations/tracker.py +49 -2
  64. sqlspec/protocols.py +13 -6
  65. sqlspec/storage/__init__.py +4 -4
  66. sqlspec/storage/backends/fsspec.py +5 -6
  67. sqlspec/storage/backends/obstore.py +7 -8
  68. sqlspec/storage/registry.py +3 -3
  69. sqlspec/utils/__init__.py +2 -2
  70. sqlspec/utils/logging.py +6 -10
  71. sqlspec/utils/sync_tools.py +27 -4
  72. sqlspec/utils/text.py +6 -1
  73. {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/METADATA +1 -1
  74. sqlspec-0.18.0.dist-info/RECORD +138 -0
  75. sqlspec/builder/_ddl_utils.py +0 -103
  76. sqlspec-0.17.0.dist-info/RECORD +0 -137
  77. {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/WHEEL +0 -0
  78. {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/entry_points.txt +0 -0
  79. {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/licenses/LICENSE +0 -0
  80. {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/licenses/NOTICE +0 -0
@@ -1,4 +1,4 @@
1
- """Efficient multi-connection pool for aiosqlite with proper shutdown handling."""
1
+ """Multi-connection pool for aiosqlite."""
2
2
 
3
3
  import asyncio
4
4
  import logging
@@ -36,7 +36,7 @@ class AiosqliteConnectTimeoutError(SQLSpecError):
36
36
 
37
37
 
38
38
  class AiosqlitePoolConnection:
39
- """Wrapper for database connections with pool lifecycle management."""
39
+ """Wrapper for database connections in the pool."""
40
40
 
41
41
  __slots__ = ("_closed", "connection", "id", "idle_since")
42
42
 
@@ -120,18 +120,18 @@ class AiosqlitePoolConnection:
120
120
 
121
121
 
122
122
  class AiosqliteConnectionPool:
123
- """Multi-connection pool for aiosqlite with proper shutdown handling."""
123
+ """Multi-connection pool for aiosqlite."""
124
124
 
125
125
  __slots__ = (
126
- "_closed_event",
126
+ "_closed_event_instance",
127
127
  "_connect_timeout",
128
128
  "_connection_parameters",
129
129
  "_connection_registry",
130
130
  "_idle_timeout",
131
- "_lock",
131
+ "_lock_instance",
132
132
  "_operation_timeout",
133
133
  "_pool_size",
134
- "_queue",
134
+ "_queue_instance",
135
135
  "_tracked_threads",
136
136
  "_wal_initialized",
137
137
  )
@@ -141,7 +141,7 @@ class AiosqliteConnectionPool:
141
141
  connection_parameters: "dict[str, Any]",
142
142
  pool_size: int = 5,
143
143
  connect_timeout: float = 30.0,
144
- idle_timeout: float = 24 * 60 * 60, # 24 hours
144
+ idle_timeout: float = 24 * 60 * 60,
145
145
  operation_timeout: float = 10.0,
146
146
  ) -> None:
147
147
  """Initialize connection pool.
@@ -159,13 +159,35 @@ class AiosqliteConnectionPool:
159
159
  self._idle_timeout = idle_timeout
160
160
  self._operation_timeout = operation_timeout
161
161
 
162
- self._queue: asyncio.Queue[AiosqlitePoolConnection] = asyncio.Queue(maxsize=pool_size)
163
162
  self._connection_registry: dict[str, AiosqlitePoolConnection] = {}
164
- self._lock = asyncio.Lock()
165
- self._closed_event = asyncio.Event()
166
163
  self._tracked_threads: set[Union[threading.Thread, AiosqliteConnection]] = set()
167
164
  self._wal_initialized = False
168
165
 
166
+ self._queue_instance: Optional[asyncio.Queue[AiosqlitePoolConnection]] = None
167
+ self._lock_instance: Optional[asyncio.Lock] = None
168
+ self._closed_event_instance: Optional[asyncio.Event] = None
169
+
170
+ @property
171
+ def _queue(self) -> "asyncio.Queue[AiosqlitePoolConnection]":
172
+ """Lazy initialization of asyncio.Queue for Python 3.9 compatibility."""
173
+ if self._queue_instance is None:
174
+ self._queue_instance = asyncio.Queue(maxsize=self._pool_size)
175
+ return self._queue_instance
176
+
177
+ @property
178
+ def _lock(self) -> asyncio.Lock:
179
+ """Lazy initialization of asyncio.Lock for Python 3.9 compatibility."""
180
+ if self._lock_instance is None:
181
+ self._lock_instance = asyncio.Lock()
182
+ return self._lock_instance
183
+
184
+ @property
185
+ def _closed_event(self) -> asyncio.Event:
186
+ """Lazy initialization of asyncio.Event for Python 3.9 compatibility."""
187
+ if self._closed_event_instance is None:
188
+ self._closed_event_instance = asyncio.Event()
189
+ return self._closed_event_instance
190
+
169
191
  @property
170
192
  def is_closed(self) -> bool:
171
193
  """Check if pool is closed.
@@ -173,7 +195,7 @@ class AiosqliteConnectionPool:
173
195
  Returns:
174
196
  True if pool is closed
175
197
  """
176
- return self._closed_event.is_set()
198
+ return self._closed_event_instance is not None and self._closed_event.is_set()
177
199
 
178
200
  def size(self) -> int:
179
201
  """Get total number of connections in pool.
@@ -189,6 +211,8 @@ class AiosqliteConnectionPool:
189
211
  Returns:
190
212
  Number of connections currently in use
191
213
  """
214
+ if self._queue_instance is None:
215
+ return len(self._connection_registry)
192
216
  return len(self._connection_registry) - self._queue.qsize()
193
217
 
194
218
  def _track_aiosqlite_thread(self, connection: "AiosqliteConnection") -> None:
@@ -200,7 +224,7 @@ class AiosqliteConnectionPool:
200
224
  self._tracked_threads.add(connection)
201
225
 
202
226
  async def _create_connection(self) -> AiosqlitePoolConnection:
203
- """Create a new connection with SQLite optimizations.
227
+ """Create a new connection.
204
228
 
205
229
  Returns:
206
230
  New pool connection instance
@@ -209,7 +233,6 @@ class AiosqliteConnectionPool:
209
233
  connection.daemon = True
210
234
  connection = await connection
211
235
 
212
- # Detect database type for appropriate optimization
213
236
  database_path = str(self._connection_parameters.get("database", ""))
214
237
  is_shared_cache = "cache=shared" in database_path
215
238
  is_memory_db = ":memory:" in database_path or "mode=memory" in database_path
@@ -234,10 +257,9 @@ class AiosqliteConnectionPool:
234
257
 
235
258
  if is_shared_cache:
236
259
  self._wal_initialized = True
237
- logger.debug("Database optimized for shared cache (memory: %s)", is_memory_db)
238
260
 
239
261
  except Exception as e:
240
- logger.warning("Failed to optimize connection: %s", e)
262
+ logger.warning("Failed to configure connection: %s", e)
241
263
  await connection.execute("PRAGMA foreign_keys = ON")
242
264
  await connection.execute("PRAGMA busy_timeout = 30000")
243
265
  await connection.commit()
@@ -249,7 +271,7 @@ class AiosqliteConnectionPool:
249
271
  async with self._lock:
250
272
  self._connection_registry[pool_connection.id] = pool_connection
251
273
 
252
- logger.debug("Created new aiosqlite connection: %s", pool_connection.id)
274
+ logger.debug("Created aiosqlite connection: %s", pool_connection.id)
253
275
  return pool_connection
254
276
 
255
277
  async def _claim_if_healthy(self, connection: AiosqlitePoolConnection) -> bool:
@@ -259,7 +281,7 @@ class AiosqliteConnectionPool:
259
281
  connection: Connection to check and claim
260
282
 
261
283
  Returns:
262
- True if connection was successfully claimed
284
+ True if connection was claimed
263
285
  """
264
286
  if connection.idle_time > self._idle_timeout:
265
287
  logger.debug("Connection %s exceeded idle timeout, retiring", connection.id)
@@ -368,9 +390,6 @@ class AiosqliteConnectionPool:
368
390
  async def _wait_for_threads_to_terminate(self, timeout: float = 1.0) -> None:
369
391
  """Wait for all tracked aiosqlite connection threads to terminate.
370
392
 
371
- Since we use daemon threads, this is just a best-effort cleanup.
372
- The threads will terminate when the process exits regardless.
373
-
374
393
  Args:
375
394
  timeout: Maximum time to wait for thread termination in seconds
376
395
  """
@@ -396,13 +415,9 @@ class AiosqliteConnectionPool:
396
415
  elapsed = time.time() - start_time
397
416
 
398
417
  if remaining_threads > 0:
399
- logger.debug(
400
- "%d aiosqlite threads still running after %.2fs (daemon threads will terminate on exit)",
401
- remaining_threads,
402
- elapsed,
403
- )
418
+ logger.debug("%d aiosqlite threads still running after %.2fs", remaining_threads, elapsed)
404
419
  else:
405
- logger.debug("All aiosqlite connection threads terminated successfully in %.2fs", elapsed)
420
+ logger.debug("All aiosqlite connection threads terminated in %.2fs", elapsed)
406
421
 
407
422
  async def acquire(self) -> AiosqlitePoolConnection:
408
423
  """Acquire a connection from the pool.
@@ -461,32 +476,25 @@ class AiosqliteConnectionPool:
461
476
  await self.release(connection)
462
477
 
463
478
  async def close(self) -> None:
464
- """Close the connection pool gracefully.
465
-
466
- Ensures all connections are properly closed and background threads are terminated.
467
- """
479
+ """Close the connection pool."""
468
480
  if self.is_closed:
469
481
  return
470
482
  self._closed_event.set()
471
483
 
472
- # Clear the queue
473
484
  while not self._queue.empty():
474
485
  self._queue.get_nowait()
475
486
 
476
- # Get all connections and clear registry
477
487
  async with self._lock:
478
488
  connections = list(self._connection_registry.values())
479
489
  self._connection_registry.clear()
480
490
 
481
- # Close all connections
482
491
  if connections:
483
492
  close_tasks = [asyncio.wait_for(conn.close(), timeout=self._operation_timeout) for conn in connections]
484
493
  results = await asyncio.gather(*close_tasks, return_exceptions=True)
485
494
 
486
- # Log any close errors
487
495
  for i, result in enumerate(results):
488
496
  if isinstance(result, Exception):
489
497
  logger.warning("Error closing connection %s: %s", connections[i].id, result)
490
498
 
491
499
  await self._wait_for_threads_to_terminate(timeout=1.0)
492
- logger.debug("Aiosqlite connection pool closed successfully")
500
+ logger.debug("Aiosqlite connection pool closed")
@@ -1,4 +1,4 @@
1
- """Asyncmy database configuration with direct field-based configuration."""
1
+ """Asyncmy database configuration."""
2
2
 
3
3
  import logging
4
4
  from collections.abc import AsyncGenerator
@@ -58,7 +58,7 @@ class AsyncmyPoolParams(AsyncmyConnectionParams, total=False):
58
58
 
59
59
 
60
60
  class AsyncmyConfig(AsyncDatabaseConfig[AsyncmyConnection, "Pool", AsyncmyDriver]): # pyright: ignore
61
- """Configuration for Asyncmy database connections with direct field-based configuration."""
61
+ """Configuration for Asyncmy database connections."""
62
62
 
63
63
  driver_type: ClassVar[type[AsyncmyDriver]] = AsyncmyDriver
64
64
  connection_type: "ClassVar[type[AsyncmyConnection]]" = AsyncmyConnection # pyright: ignore
@@ -70,6 +70,7 @@ class AsyncmyConfig(AsyncDatabaseConfig[AsyncmyConnection, "Pool", AsyncmyDriver
70
70
  pool_instance: "Optional[Pool]" = None,
71
71
  migration_config: Optional[dict[str, Any]] = None,
72
72
  statement_config: "Optional[StatementConfig]" = None,
73
+ driver_features: "Optional[dict[str, Any]]" = None,
73
74
  ) -> None:
74
75
  """Initialize Asyncmy configuration.
75
76
 
@@ -78,6 +79,7 @@ class AsyncmyConfig(AsyncDatabaseConfig[AsyncmyConnection, "Pool", AsyncmyDriver
78
79
  pool_instance: Existing pool instance to use
79
80
  migration_config: Migration configuration
80
81
  statement_config: Statement configuration override
82
+ driver_features: Driver feature configuration
81
83
  """
82
84
  processed_pool_config: dict[str, Any] = dict(pool_config) if pool_config else {}
83
85
  if "extra" in processed_pool_config:
@@ -97,7 +99,7 @@ class AsyncmyConfig(AsyncDatabaseConfig[AsyncmyConnection, "Pool", AsyncmyDriver
97
99
  pool_instance=pool_instance,
98
100
  migration_config=migration_config,
99
101
  statement_config=statement_config,
100
- driver_features={},
102
+ driver_features=driver_features or {},
101
103
  )
102
104
 
103
105
  async def _create_pool(self) -> "Pool": # pyright: ignore
@@ -107,7 +109,11 @@ class AsyncmyConfig(AsyncDatabaseConfig[AsyncmyConnection, "Pool", AsyncmyDriver
107
109
  async def _close_pool(self) -> None:
108
110
  """Close the actual async connection pool."""
109
111
  if self.pool_instance:
110
- await self.pool_instance.close()
112
+ self.pool_instance.close()
113
+
114
+ async def close_pool(self) -> None:
115
+ """Close the connection pool."""
116
+ await self._close_pool()
111
117
 
112
118
  async def create_connection(self) -> AsyncmyConnection: # pyright: ignore
113
119
  """Create a single async connection (not from pool).
@@ -166,9 +172,6 @@ class AsyncmyConfig(AsyncDatabaseConfig[AsyncmyConnection, "Pool", AsyncmyDriver
166
172
  def get_signature_namespace(self) -> "dict[str, type[Any]]":
167
173
  """Get the signature namespace for Asyncmy types.
168
174
 
169
- This provides all Asyncmy-specific types that Litestar needs to recognize
170
- to avoid serialization attempts.
171
-
172
175
  Returns:
173
176
  Dictionary mapping type names to types.
174
177
  """
@@ -1,10 +1,7 @@
1
- """AsyncMy MySQL driver implementation for async MySQL operations.
1
+ """AsyncMy MySQL driver implementation.
2
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
3
+ Provides MySQL/MariaDB connectivity with parameter style conversion,
4
+ type coercion, error handling, and transaction management.
8
5
  """
9
6
 
10
7
  import logging
@@ -34,7 +31,6 @@ logger = logging.getLogger(__name__)
34
31
  __all__ = ("AsyncmyCursor", "AsyncmyDriver", "AsyncmyExceptionHandler", "asyncmy_statement_config")
35
32
 
36
33
 
37
- # Enhanced AsyncMy statement configuration using core modules with performance optimizations
38
34
  asyncmy_statement_config = StatementConfig(
39
35
  dialect="mysql",
40
36
  parameter_config=ParameterStyleConfig(
@@ -42,12 +38,7 @@ asyncmy_statement_config = StatementConfig(
42
38
  supported_parameter_styles={ParameterStyle.QMARK, ParameterStyle.POSITIONAL_PYFORMAT},
43
39
  default_execution_parameter_style=ParameterStyle.POSITIONAL_PYFORMAT,
44
40
  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
- },
41
+ type_coercion_map={dict: to_json, list: to_json, tuple: lambda v: to_json(list(v)), bool: int},
51
42
  has_native_list_expansion=False,
52
43
  needs_static_script_compilation=True,
53
44
  preserve_parameter_format=True,
@@ -125,13 +116,10 @@ class AsyncmyExceptionHandler:
125
116
 
126
117
 
127
118
  class AsyncmyDriver(AsyncDriverAdapterBase):
128
- """AsyncMy MySQL/MariaDB driver for async database operations.
119
+ """AsyncMy MySQL/MariaDB driver.
129
120
 
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
121
+ Provides MySQL/MariaDB connectivity with parameter style conversion,
122
+ type coercion, error handling, and transaction management.
135
123
  """
136
124
 
137
125
  __slots__ = ()
@@ -145,18 +133,17 @@ class AsyncmyDriver(AsyncDriverAdapterBase):
145
133
  ) -> None:
146
134
  if statement_config is None:
147
135
  cache_config = get_cache_config()
148
- enhanced_config = asyncmy_statement_config.replace(
136
+ statement_config = asyncmy_statement_config.replace(
149
137
  enable_caching=cache_config.compiled_cache_enabled,
150
138
  enable_parsing=True,
151
139
  enable_validation=True,
152
140
  dialect="mysql",
153
141
  )
154
- statement_config = enhanced_config
155
142
 
156
143
  super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
157
144
 
158
145
  def with_cursor(self, connection: "AsyncmyConnection") -> "AsyncmyCursor":
159
- """Create async context manager for AsyncMy cursor."""
146
+ """Create context manager for AsyncMy cursor."""
160
147
  return AsyncmyCursor(connection)
161
148
 
162
149
  def handle_database_exceptions(self) -> "AbstractAsyncContextManager[None]":
@@ -173,13 +160,12 @@ class AsyncmyDriver(AsyncDriverAdapterBase):
173
160
  Returns:
174
161
  None - always proceeds with standard execution for AsyncMy
175
162
  """
176
- _ = (cursor, statement) # Mark as intentionally unused
163
+ _ = (cursor, statement)
177
164
  return None
178
165
 
179
166
  async def _execute_script(self, cursor: Any, statement: "SQL") -> "ExecutionResult":
180
- """Execute SQL script using enhanced statement splitting and parameter handling.
167
+ """Execute SQL script with statement splitting and parameter handling.
181
168
 
182
- Uses core module optimization for statement parsing and parameter processing.
183
169
  Parameters are embedded as static values for script execution compatibility.
184
170
  """
185
171
  sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
@@ -197,38 +183,34 @@ class AsyncmyDriver(AsyncDriverAdapterBase):
197
183
  )
198
184
 
199
185
  async def _execute_many(self, cursor: Any, statement: "SQL") -> "ExecutionResult":
200
- """Execute SQL with multiple parameter sets using optimized AsyncMy batch processing.
186
+ """Execute SQL with multiple parameter sets using AsyncMy batch processing.
201
187
 
202
- Leverages core parameter processing for enhanced MySQL type handling and parameter conversion.
188
+ Handles MySQL type conversion and parameter processing.
203
189
  """
204
190
  sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
205
191
 
206
- # Enhanced parameter validation for executemany
207
192
  if not prepared_parameters:
208
193
  msg = "execute_many requires parameters"
209
194
  raise ValueError(msg)
210
195
 
211
196
  await cursor.executemany(sql, prepared_parameters)
212
197
 
213
- # Calculate affected rows based on parameter count for AsyncMy
214
198
  affected_rows = len(prepared_parameters) if prepared_parameters else 0
215
199
 
216
200
  return self.create_execution_result(cursor, rowcount_override=affected_rows, is_many_result=True)
217
201
 
218
202
  async def _execute_statement(self, cursor: Any, statement: "SQL") -> "ExecutionResult":
219
- """Execute single SQL statement with enhanced AsyncMy MySQL data handling and performance optimization.
203
+ """Execute single SQL statement with AsyncMy MySQL data handling.
220
204
 
221
- Uses core processing for optimal parameter handling and MySQL result processing.
205
+ Handles parameter processing and MySQL result processing.
222
206
  """
223
207
  sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
224
208
  await cursor.execute(sql, prepared_parameters or None)
225
209
 
226
- # Enhanced SELECT result processing for MySQL
227
210
  if statement.returns_rows():
228
211
  fetched_data = await cursor.fetchall()
229
212
  column_names = [desc[0] for desc in cursor.description or []]
230
213
 
231
- # AsyncMy may return tuples or dicts - ensure consistent dict format
232
214
  if fetched_data and not isinstance(fetched_data[0], dict):
233
215
  data = [dict(zip(column_names, row)) for row in fetched_data]
234
216
  else:
@@ -238,19 +220,16 @@ class AsyncmyDriver(AsyncDriverAdapterBase):
238
220
  cursor, selected_data=data, column_names=column_names, data_row_count=len(data), is_select_result=True
239
221
  )
240
222
 
241
- # Enhanced non-SELECT result processing for MySQL
242
223
  affected_rows = cursor.rowcount if cursor.rowcount is not None else -1
243
224
  last_id = getattr(cursor, "lastrowid", None) if cursor.rowcount and cursor.rowcount > 0 else None
244
225
  return self.create_execution_result(cursor, rowcount_override=affected_rows, last_inserted_id=last_id)
245
226
 
246
- # MySQL transaction management with enhanced async error handling
247
227
  async def begin(self) -> None:
248
- """Begin a database transaction with enhanced async error handling.
228
+ """Begin a database transaction.
249
229
 
250
230
  Explicitly starts a MySQL transaction to ensure proper transaction boundaries.
251
231
  """
252
232
  try:
253
- # Execute explicit BEGIN to start transaction
254
233
  async with AsyncmyCursor(self.connection) as cursor:
255
234
  await cursor.execute("BEGIN")
256
235
  except asyncmy.errors.MySQLError as e:
@@ -258,7 +237,7 @@ class AsyncmyDriver(AsyncDriverAdapterBase):
258
237
  raise SQLSpecError(msg) from e
259
238
 
260
239
  async def rollback(self) -> None:
261
- """Rollback the current transaction with enhanced async error handling."""
240
+ """Rollback the current transaction."""
262
241
  try:
263
242
  await self.connection.rollback()
264
243
  except asyncmy.errors.MySQLError as e:
@@ -266,7 +245,7 @@ class AsyncmyDriver(AsyncDriverAdapterBase):
266
245
  raise SQLSpecError(msg) from e
267
246
 
268
247
  async def commit(self) -> None:
269
- """Commit the current transaction with enhanced async error handling."""
248
+ """Commit the current transaction."""
270
249
  try:
271
250
  await self.connection.commit()
272
251
  except asyncmy.errors.MySQLError as e:
@@ -144,6 +144,10 @@ class AsyncpgConfig(AsyncDatabaseConfig[AsyncpgConnection, "Pool[Record]", Async
144
144
  if self.pool_instance:
145
145
  await self.pool_instance.close()
146
146
 
147
+ async def close_pool(self) -> None:
148
+ """Close the connection pool."""
149
+ await self._close_pool()
150
+
147
151
  async def create_connection(self) -> "AsyncpgConnection":
148
152
  """Create a single async connection from the pool.
149
153