sqlspec 0.17.1__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 (75) hide show
  1. sqlspec/__init__.py +1 -1
  2. sqlspec/_sql.py +54 -159
  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/_ddl.py +407 -183
  32. sqlspec/builder/_insert.py +1 -1
  33. sqlspec/builder/mixins/_insert_operations.py +26 -6
  34. sqlspec/builder/mixins/_merge_operations.py +1 -1
  35. sqlspec/builder/mixins/_select_operations.py +1 -5
  36. sqlspec/config.py +32 -13
  37. sqlspec/core/__init__.py +89 -14
  38. sqlspec/core/cache.py +57 -104
  39. sqlspec/core/compiler.py +57 -112
  40. sqlspec/core/filters.py +1 -21
  41. sqlspec/core/hashing.py +13 -47
  42. sqlspec/core/parameters.py +272 -261
  43. sqlspec/core/result.py +12 -27
  44. sqlspec/core/splitter.py +17 -21
  45. sqlspec/core/statement.py +150 -159
  46. sqlspec/driver/_async.py +2 -15
  47. sqlspec/driver/_common.py +16 -95
  48. sqlspec/driver/_sync.py +2 -15
  49. sqlspec/driver/mixins/_result_tools.py +8 -29
  50. sqlspec/driver/mixins/_sql_translator.py +6 -8
  51. sqlspec/exceptions.py +1 -2
  52. sqlspec/loader.py +43 -115
  53. sqlspec/migrations/__init__.py +1 -1
  54. sqlspec/migrations/base.py +34 -45
  55. sqlspec/migrations/commands.py +34 -15
  56. sqlspec/migrations/loaders.py +1 -1
  57. sqlspec/migrations/runner.py +104 -19
  58. sqlspec/migrations/tracker.py +49 -2
  59. sqlspec/protocols.py +3 -6
  60. sqlspec/storage/__init__.py +4 -4
  61. sqlspec/storage/backends/fsspec.py +5 -6
  62. sqlspec/storage/backends/obstore.py +7 -8
  63. sqlspec/storage/registry.py +3 -3
  64. sqlspec/utils/__init__.py +2 -2
  65. sqlspec/utils/logging.py +6 -10
  66. sqlspec/utils/sync_tools.py +27 -4
  67. sqlspec/utils/text.py +6 -1
  68. {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/METADATA +1 -1
  69. sqlspec-0.18.0.dist-info/RECORD +138 -0
  70. sqlspec/builder/_ddl_utils.py +0 -103
  71. sqlspec-0.17.1.dist-info/RECORD +0 -138
  72. {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/WHEEL +0 -0
  73. {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/entry_points.txt +0 -0
  74. {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/licenses/LICENSE +0 -0
  75. {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/licenses/NOTICE +0 -0
@@ -1,25 +1,17 @@
1
- """Enhanced PostgreSQL psycopg driver with CORE_ROUND_3 architecture integration.
2
-
3
- This driver implements the complete CORE_ROUND_3 architecture for PostgreSQL connections using psycopg3:
4
- - 5-10x faster SQL compilation through single-pass processing
5
- - 40-60% memory reduction through __slots__ optimization
6
- - Enhanced caching for repeated statement execution
7
- - Complete backward compatibility with existing PostgreSQL functionality
8
-
9
- Architecture Features:
10
- - Direct integration with sqlspec.core modules
11
- - Enhanced PostgreSQL parameter processing with advanced type coercion
12
- - PostgreSQL-specific features (COPY, arrays, JSON, advanced types)
13
- - Thread-safe unified caching system
14
- - MyPyC-optimized performance patterns
15
- - Zero-copy data access where possible
1
+ """PostgreSQL psycopg driver implementation.
2
+
3
+ This driver provides PostgreSQL database connectivity using psycopg3:
4
+ - SQL statement execution with parameter binding
5
+ - Connection and transaction management
6
+ - Row result processing with dictionary-based access
7
+ - PostgreSQL-specific features (COPY, arrays, JSON types)
16
8
 
17
9
  PostgreSQL Features:
18
- - Advanced parameter styles ($1, %s, %(name)s)
19
- - PostgreSQL array support with optimized conversion
20
- - COPY operations with enhanced performance
10
+ - Parameter styles ($1, %s, %(name)s)
11
+ - PostgreSQL array support
12
+ - COPY operations for bulk data transfer
21
13
  - JSON/JSONB type handling
22
- - PostgreSQL-specific error categorization
14
+ - PostgreSQL-specific error handling
23
15
  """
24
16
 
25
17
  import io
@@ -44,7 +36,7 @@ if TYPE_CHECKING:
44
36
 
45
37
  logger = get_logger("adapters.psycopg")
46
38
 
47
- # PostgreSQL transaction status constants
39
+
48
40
  TRANSACTION_STATUS_IDLE = 0
49
41
  TRANSACTION_STATUS_ACTIVE = 1
50
42
  TRANSACTION_STATUS_INTRANS = 2
@@ -53,7 +45,7 @@ TRANSACTION_STATUS_UNKNOWN = 4
53
45
 
54
46
 
55
47
  def _convert_list_to_postgres_array(value: Any) -> str:
56
- """Convert Python list to PostgreSQL array literal format with enhanced type handling.
48
+ """Convert Python list to PostgreSQL array literal format.
57
49
 
58
50
  Args:
59
51
  value: Python list to convert
@@ -64,13 +56,11 @@ def _convert_list_to_postgres_array(value: Any) -> str:
64
56
  if not isinstance(value, list):
65
57
  return str(value)
66
58
 
67
- # Handle nested arrays and complex types
68
59
  elements = []
69
60
  for item in value:
70
61
  if isinstance(item, list):
71
62
  elements.append(_convert_list_to_postgres_array(item))
72
63
  elif isinstance(item, str):
73
- # Escape quotes and handle special characters
74
64
  escaped = item.replace("'", "''")
75
65
  elements.append(f"'{escaped}'")
76
66
  elif item is None:
@@ -81,7 +71,6 @@ def _convert_list_to_postgres_array(value: Any) -> str:
81
71
  return f"{{{','.join(elements)}}}"
82
72
 
83
73
 
84
- # Enhanced PostgreSQL statement configuration using core modules with performance optimizations
85
74
  psycopg_statement_config = StatementConfig(
86
75
  dialect="postgres",
87
76
  pre_process_steps=None,
@@ -105,12 +94,7 @@ psycopg_statement_config = StatementConfig(
105
94
  ParameterStyle.NAMED_PYFORMAT,
106
95
  ParameterStyle.NUMERIC,
107
96
  },
108
- type_coercion_map={
109
- dict: to_json
110
- # Note: Psycopg3 handles Python lists natively, so no conversion needed
111
- # list: _convert_list_to_postgres_array,
112
- # tuple: lambda v: _convert_list_to_postgres_array(list(v)),
113
- },
97
+ type_coercion_map={dict: to_json},
114
98
  has_native_list_expansion=True,
115
99
  needs_static_script_compilation=False,
116
100
  preserve_parameter_format=True,
@@ -129,7 +113,7 @@ __all__ = (
129
113
 
130
114
 
131
115
  class PsycopgSyncCursor:
132
- """Context manager for PostgreSQL psycopg cursor management with enhanced error handling."""
116
+ """Context manager for PostgreSQL psycopg cursor management."""
133
117
 
134
118
  __slots__ = ("connection", "cursor")
135
119
 
@@ -142,13 +126,13 @@ class PsycopgSyncCursor:
142
126
  return self.cursor
143
127
 
144
128
  def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
145
- _ = (exc_type, exc_val, exc_tb) # Mark as intentionally unused
129
+ _ = (exc_type, exc_val, exc_tb)
146
130
  if self.cursor is not None:
147
131
  self.cursor.close()
148
132
 
149
133
 
150
134
  class PsycopgSyncExceptionHandler:
151
- """Custom sync context manager for handling PostgreSQL psycopg database exceptions."""
135
+ """Context manager for handling PostgreSQL psycopg database exceptions."""
152
136
 
153
137
  __slots__ = ()
154
138
 
@@ -194,35 +178,19 @@ class PsycopgSyncExceptionHandler:
194
178
 
195
179
 
196
180
  class PsycopgSyncDriver(SyncDriverAdapterBase):
197
- """Enhanced PostgreSQL psycopg synchronous driver with CORE_ROUND_3 architecture integration.
198
-
199
- This driver leverages the complete core module system for maximum PostgreSQL performance:
181
+ """PostgreSQL psycopg synchronous driver.
200
182
 
201
- Performance Improvements:
202
- - 5-10x faster SQL compilation through single-pass processing
203
- - 40-60% memory reduction through __slots__ optimization
204
- - Enhanced caching for repeated statement execution
205
- - Optimized PostgreSQL array and JSON handling
206
- - Zero-copy parameter processing where possible
183
+ Provides synchronous database operations for PostgreSQL using psycopg3:
184
+ - SQL statement execution with parameter binding
185
+ - Transaction management (begin, commit, rollback)
186
+ - Result processing with column metadata
187
+ - PostgreSQL-specific features support
207
188
 
208
189
  PostgreSQL Features:
209
- - Advanced parameter styles ($1, %s, %(name)s)
210
- - PostgreSQL array support with optimized conversion
211
- - COPY operations with enhanced performance
212
- - JSON/JSONB type handling
213
- - PostgreSQL-specific error categorization
214
-
215
- Core Integration Features:
216
- - sqlspec.core.statement for enhanced SQL processing
217
- - sqlspec.core.parameters for optimized parameter handling
218
- - sqlspec.core.cache for unified statement caching
219
- - sqlspec.core.config for centralized configuration management
220
-
221
- Compatibility:
222
- - 100% backward compatibility with existing psycopg driver interface
223
- - All existing PostgreSQL tests pass without modification
224
- - Complete StatementConfig API compatibility
225
- - Preserved cursor management and exception handling patterns
190
+ - Parameter styles ($1, %s, %(name)s)
191
+ - PostgreSQL arrays and JSON handling
192
+ - COPY operations for bulk data transfer
193
+ - PostgreSQL-specific error handling
226
194
  """
227
195
 
228
196
  __slots__ = ()
@@ -234,33 +202,28 @@ class PsycopgSyncDriver(SyncDriverAdapterBase):
234
202
  statement_config: "Optional[StatementConfig]" = None,
235
203
  driver_features: "Optional[dict[str, Any]]" = None,
236
204
  ) -> None:
237
- # Enhanced configuration with global settings integration
238
205
  if statement_config is None:
239
206
  cache_config = get_cache_config()
240
- enhanced_config = psycopg_statement_config.replace(
207
+ default_config = psycopg_statement_config.replace(
241
208
  enable_caching=cache_config.compiled_cache_enabled,
242
- enable_parsing=True, # Default to enabled
243
- enable_validation=True, # Default to enabled
244
- dialect="postgres", # Use adapter-specific dialect
209
+ enable_parsing=True,
210
+ enable_validation=True,
211
+ dialect="postgres",
245
212
  )
246
- statement_config = enhanced_config
213
+ statement_config = default_config
247
214
 
248
215
  super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
249
216
 
250
217
  def with_cursor(self, connection: PsycopgSyncConnection) -> PsycopgSyncCursor:
251
- """Create context manager for PostgreSQL cursor with enhanced resource management."""
218
+ """Create context manager for PostgreSQL cursor."""
252
219
  return PsycopgSyncCursor(connection)
253
220
 
254
221
  def begin(self) -> None:
255
222
  """Begin a database transaction on the current connection."""
256
223
  try:
257
- # psycopg3 has explicit transaction support
258
- # If already in a transaction, this is a no-op
259
224
  if hasattr(self.connection, "autocommit") and not self.connection.autocommit:
260
- # Already in manual commit mode, just ensure we're in a clean state
261
225
  pass
262
226
  else:
263
- # Start manual transaction mode
264
227
  self.connection.autocommit = False
265
228
  except Exception as e:
266
229
  msg = f"Failed to begin transaction: {e}"
@@ -287,17 +250,15 @@ class PsycopgSyncDriver(SyncDriverAdapterBase):
287
250
  return PsycopgSyncExceptionHandler()
288
251
 
289
252
  def _handle_transaction_error_cleanup(self) -> None:
290
- """Handle transaction cleanup after database errors to prevent aborted transaction states."""
253
+ """Handle transaction cleanup after database errors."""
291
254
  try:
292
- # Check if connection is in a failed transaction state
293
255
  if hasattr(self.connection, "info") and hasattr(self.connection.info, "transaction_status"):
294
256
  status = self.connection.info.transaction_status
295
- # PostgreSQL transaction statuses: IDLE=0, ACTIVE=1, INTRANS=2, INERROR=3, UNKNOWN=4
257
+
296
258
  if status == TRANSACTION_STATUS_INERROR:
297
259
  logger.debug("Connection in aborted transaction state, performing rollback")
298
260
  self.connection.rollback()
299
261
  except Exception as cleanup_error:
300
- # If cleanup fails, log but don't raise - the original error is more important
301
262
  logger.warning("Failed to cleanup transaction state: %s", cleanup_error)
302
263
 
303
264
  def _try_special_handling(self, cursor: Any, statement: "SQL") -> "Optional[SQLResult]":
@@ -310,14 +271,12 @@ class PsycopgSyncDriver(SyncDriverAdapterBase):
310
271
  Returns:
311
272
  SQLResult if special handling was applied, None otherwise
312
273
  """
313
- # Compile the statement to get the operation type
274
+
314
275
  statement.compile()
315
276
 
316
- # Use the operation_type from the statement object
317
277
  if statement.operation_type in {"COPY_FROM", "COPY_TO"}:
318
278
  return self._handle_copy_operation(cursor, statement)
319
279
 
320
- # No special handling needed - proceed with standard execution
321
280
  return None
322
281
 
323
282
  def _handle_copy_operation(self, cursor: Any, statement: "SQL") -> "SQLResult":
@@ -330,27 +289,21 @@ class PsycopgSyncDriver(SyncDriverAdapterBase):
330
289
  Returns:
331
290
  SQLResult with COPY operation results
332
291
  """
333
- # Use the properly rendered SQL from the statement
292
+
334
293
  sql = statement.sql
335
294
 
336
- # Get COPY data from parameters - handle both direct value and list format
337
295
  copy_data = statement.parameters
338
296
  if isinstance(copy_data, list) and len(copy_data) == 1:
339
297
  copy_data = copy_data[0]
340
298
 
341
- # Use the operation_type from the statement
342
299
  if statement.operation_type == "COPY_FROM":
343
- # COPY FROM STDIN - import data
344
300
  if isinstance(copy_data, (str, bytes)):
345
301
  data_file = io.StringIO(copy_data) if isinstance(copy_data, str) else io.BytesIO(copy_data)
346
302
  elif hasattr(copy_data, "read"):
347
- # Already a file-like object
348
303
  data_file = copy_data
349
304
  else:
350
- # Convert to string representation
351
305
  data_file = io.StringIO(str(copy_data))
352
306
 
353
- # Use context manager for COPY FROM (sync version)
354
307
  with cursor.copy(sql) as copy_ctx:
355
308
  data_to_write = data_file.read() if hasattr(data_file, "read") else str(copy_data) # pyright: ignore
356
309
  if isinstance(data_to_write, str):
@@ -364,7 +317,6 @@ class PsycopgSyncDriver(SyncDriverAdapterBase):
364
317
  )
365
318
 
366
319
  if statement.operation_type == "COPY_TO":
367
- # COPY TO STDOUT - export data
368
320
  output_data: list[str] = []
369
321
  with cursor.copy(sql) as copy_ctx:
370
322
  output_data.extend(row.decode() if isinstance(row, bytes) else str(row) for row in copy_ctx)
@@ -372,13 +324,12 @@ class PsycopgSyncDriver(SyncDriverAdapterBase):
372
324
  exported_data = "".join(output_data)
373
325
 
374
326
  return SQLResult(
375
- data=[{"copy_output": exported_data}], # Wrap in list format for consistency
327
+ data=[{"copy_output": exported_data}],
376
328
  rows_affected=0,
377
329
  statement=statement,
378
330
  metadata={"copy_operation": "TO_STDOUT"},
379
331
  )
380
332
 
381
- # Regular COPY with file - execute normally
382
333
  cursor.execute(sql)
383
334
  rows_affected = max(cursor.rowcount, 0)
384
335
 
@@ -387,10 +338,14 @@ class PsycopgSyncDriver(SyncDriverAdapterBase):
387
338
  )
388
339
 
389
340
  def _execute_script(self, cursor: Any, statement: "SQL") -> "ExecutionResult":
390
- """Execute SQL script using enhanced statement splitting and parameter handling.
341
+ """Execute SQL script with multiple statements.
342
+
343
+ Args:
344
+ cursor: Database cursor
345
+ statement: SQL statement containing multiple commands
391
346
 
392
- Uses core module optimization for statement parsing and parameter processing.
393
- PostgreSQL supports complex scripts with multiple statements.
347
+ Returns:
348
+ ExecutionResult with script execution details
394
349
  """
395
350
  sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
396
351
  statements = self.split_script_statements(sql, statement.statement_config, strip_trailing_semicolon=True)
@@ -399,7 +354,6 @@ class PsycopgSyncDriver(SyncDriverAdapterBase):
399
354
  last_cursor = cursor
400
355
 
401
356
  for stmt in statements:
402
- # Only pass parameters if they exist - psycopg treats empty containers as parameterized mode
403
357
  if prepared_parameters:
404
358
  cursor.execute(stmt, prepared_parameters)
405
359
  else:
@@ -411,42 +365,47 @@ class PsycopgSyncDriver(SyncDriverAdapterBase):
411
365
  )
412
366
 
413
367
  def _execute_many(self, cursor: Any, statement: "SQL") -> "ExecutionResult":
414
- """Execute SQL with multiple parameter sets using optimized PostgreSQL batch processing.
368
+ """Execute SQL with multiple parameter sets.
369
+
370
+ Args:
371
+ cursor: Database cursor
372
+ statement: SQL statement with parameter list
415
373
 
416
- Leverages core parameter processing for enhanced PostgreSQL type handling.
374
+ Returns:
375
+ ExecutionResult with batch execution details
417
376
  """
418
377
  sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
419
378
 
420
- # Handle empty parameter list case
421
379
  if not prepared_parameters:
422
- # For empty parameter list, return a result with no rows affected
423
380
  return self.create_execution_result(cursor, rowcount_override=0, is_many_result=True)
424
381
 
425
382
  cursor.executemany(sql, prepared_parameters)
426
383
 
427
- # PostgreSQL cursor.rowcount gives total affected rows
428
384
  affected_rows = cursor.rowcount if cursor.rowcount and cursor.rowcount > 0 else 0
429
385
 
430
386
  return self.create_execution_result(cursor, rowcount_override=affected_rows, is_many_result=True)
431
387
 
432
388
  def _execute_statement(self, cursor: Any, statement: "SQL") -> "ExecutionResult":
433
- """Execute single SQL statement with enhanced PostgreSQL data handling and performance optimization.
389
+ """Execute single SQL statement.
390
+
391
+ Args:
392
+ cursor: Database cursor
393
+ statement: SQL statement to execute
434
394
 
435
- Uses core processing for optimal parameter handling and PostgreSQL result processing.
395
+ Returns:
396
+ ExecutionResult with statement execution details
436
397
  """
437
398
  sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
438
- # Only pass parameters if they exist - psycopg treats empty containers as parameterized mode
399
+
439
400
  if prepared_parameters:
440
401
  cursor.execute(sql, prepared_parameters)
441
402
  else:
442
403
  cursor.execute(sql)
443
404
 
444
- # Enhanced SELECT result processing for PostgreSQL
445
405
  if statement.returns_rows():
446
406
  fetched_data = cursor.fetchall()
447
407
  column_names = [col.name for col in cursor.description or []]
448
408
 
449
- # PostgreSQL returns raw data - pass it directly like the old driver
450
409
  return self.create_execution_result(
451
410
  cursor,
452
411
  selected_data=fetched_data,
@@ -455,13 +414,12 @@ class PsycopgSyncDriver(SyncDriverAdapterBase):
455
414
  is_select_result=True,
456
415
  )
457
416
 
458
- # Enhanced non-SELECT result processing for PostgreSQL
459
417
  affected_rows = cursor.rowcount if cursor.rowcount and cursor.rowcount > 0 else 0
460
418
  return self.create_execution_result(cursor, rowcount_override=affected_rows)
461
419
 
462
420
 
463
421
  class PsycopgAsyncCursor:
464
- """Async context manager for PostgreSQL psycopg cursor management with enhanced error handling."""
422
+ """Async context manager for PostgreSQL psycopg cursor management."""
465
423
 
466
424
  __slots__ = ("connection", "cursor")
467
425
 
@@ -474,13 +432,13 @@ class PsycopgAsyncCursor:
474
432
  return self.cursor
475
433
 
476
434
  async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
477
- _ = (exc_type, exc_val, exc_tb) # Mark as intentionally unused
435
+ _ = (exc_type, exc_val, exc_tb)
478
436
  if self.cursor is not None:
479
437
  await self.cursor.close()
480
438
 
481
439
 
482
440
  class PsycopgAsyncExceptionHandler:
483
- """Custom async context manager for handling PostgreSQL psycopg database exceptions."""
441
+ """Async context manager for handling PostgreSQL psycopg database exceptions."""
484
442
 
485
443
  __slots__ = ()
486
444
 
@@ -526,37 +484,20 @@ class PsycopgAsyncExceptionHandler:
526
484
 
527
485
 
528
486
  class PsycopgAsyncDriver(AsyncDriverAdapterBase):
529
- """Enhanced PostgreSQL psycopg asynchronous driver with CORE_ROUND_3 architecture integration.
530
-
531
- This async driver leverages the complete core module system for maximum PostgreSQL performance:
487
+ """PostgreSQL psycopg asynchronous driver.
532
488
 
533
- Performance Improvements:
534
- - 5-10x faster SQL compilation through single-pass processing
535
- - 40-60% memory reduction through __slots__ optimization
536
- - Enhanced caching for repeated statement execution
537
- - Optimized PostgreSQL array and JSON handling
538
- - Zero-copy parameter processing where possible
539
- - Async-optimized resource management
489
+ Provides asynchronous database operations for PostgreSQL using psycopg3:
490
+ - Async SQL statement execution with parameter binding
491
+ - Async transaction management (begin, commit, rollback)
492
+ - Async result processing with column metadata
493
+ - PostgreSQL-specific features support
540
494
 
541
495
  PostgreSQL Features:
542
- - Advanced parameter styles ($1, %s, %(name)s)
543
- - PostgreSQL array support with optimized conversion
544
- - COPY operations with enhanced performance
545
- - JSON/JSONB type handling
546
- - PostgreSQL-specific error categorization
496
+ - Parameter styles ($1, %s, %(name)s)
497
+ - PostgreSQL arrays and JSON handling
498
+ - COPY operations for bulk data transfer
499
+ - PostgreSQL-specific error handling
547
500
  - Async pub/sub support (LISTEN/NOTIFY)
548
-
549
- Core Integration Features:
550
- - sqlspec.core.statement for enhanced SQL processing
551
- - sqlspec.core.parameters for optimized parameter handling
552
- - sqlspec.core.cache for unified statement caching
553
- - sqlspec.core.config for centralized configuration management
554
-
555
- Compatibility:
556
- - 100% backward compatibility with existing async psycopg driver interface
557
- - All existing async PostgreSQL tests pass without modification
558
- - Complete StatementConfig API compatibility
559
- - Preserved async cursor management and exception handling patterns
560
501
  """
561
502
 
562
503
  __slots__ = ()
@@ -568,33 +509,28 @@ class PsycopgAsyncDriver(AsyncDriverAdapterBase):
568
509
  statement_config: "Optional[StatementConfig]" = None,
569
510
  driver_features: "Optional[dict[str, Any]]" = None,
570
511
  ) -> None:
571
- # Enhanced configuration with global settings integration
572
512
  if statement_config is None:
573
513
  cache_config = get_cache_config()
574
- enhanced_config = psycopg_statement_config.replace(
514
+ default_config = psycopg_statement_config.replace(
575
515
  enable_caching=cache_config.compiled_cache_enabled,
576
- enable_parsing=True, # Default to enabled
577
- enable_validation=True, # Default to enabled
578
- dialect="postgres", # Use adapter-specific dialect
516
+ enable_parsing=True,
517
+ enable_validation=True,
518
+ dialect="postgres",
579
519
  )
580
- statement_config = enhanced_config
520
+ statement_config = default_config
581
521
 
582
522
  super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
583
523
 
584
524
  def with_cursor(self, connection: "PsycopgAsyncConnection") -> "PsycopgAsyncCursor":
585
- """Create async context manager for PostgreSQL cursor with enhanced resource management."""
525
+ """Create async context manager for PostgreSQL cursor."""
586
526
  return PsycopgAsyncCursor(connection)
587
527
 
588
528
  async def begin(self) -> None:
589
529
  """Begin a database transaction on the current connection."""
590
530
  try:
591
- # psycopg3 has explicit transaction support
592
- # If already in a transaction, this is a no-op
593
531
  if hasattr(self.connection, "autocommit") and not self.connection.autocommit:
594
- # Already in manual commit mode, just ensure we're in a clean state
595
532
  pass
596
533
  else:
597
- # Start manual transaction mode
598
534
  self.connection.autocommit = False
599
535
  except Exception as e:
600
536
  msg = f"Failed to begin transaction: {e}"
@@ -621,17 +557,15 @@ class PsycopgAsyncDriver(AsyncDriverAdapterBase):
621
557
  return PsycopgAsyncExceptionHandler()
622
558
 
623
559
  async def _handle_transaction_error_cleanup_async(self) -> None:
624
- """Handle transaction cleanup after database errors to prevent aborted transaction states (async version)."""
560
+ """Handle async transaction cleanup after database errors."""
625
561
  try:
626
- # Check if connection is in a failed transaction state
627
562
  if hasattr(self.connection, "info") and hasattr(self.connection.info, "transaction_status"):
628
563
  status = self.connection.info.transaction_status
629
- # PostgreSQL transaction statuses: IDLE=0, ACTIVE=1, INTRANS=2, INERROR=3, UNKNOWN=4
564
+
630
565
  if status == TRANSACTION_STATUS_INERROR:
631
566
  logger.debug("Connection in aborted transaction state, performing async rollback")
632
567
  await self.connection.rollback()
633
568
  except Exception as cleanup_error:
634
- # If cleanup fails, log but don't raise - the original error is more important
635
569
  logger.warning("Failed to cleanup transaction state: %s", cleanup_error)
636
570
 
637
571
  async def _try_special_handling(self, cursor: Any, statement: "SQL") -> "Optional[SQLResult]":
@@ -644,16 +578,15 @@ class PsycopgAsyncDriver(AsyncDriverAdapterBase):
644
578
  Returns:
645
579
  SQLResult if special handling was applied, None otherwise
646
580
  """
647
- # Simple COPY detection - if the SQL starts with COPY and has FROM/TO STDIN/STDOUT
581
+
648
582
  sql_upper = statement.sql.strip().upper()
649
583
  if sql_upper.startswith("COPY ") and ("FROM STDIN" in sql_upper or "TO STDOUT" in sql_upper):
650
584
  return await self._handle_copy_operation_async(cursor, statement)
651
585
 
652
- # No special handling needed - proceed with standard execution
653
586
  return None
654
587
 
655
588
  async def _handle_copy_operation_async(self, cursor: Any, statement: "SQL") -> "SQLResult":
656
- """Handle PostgreSQL COPY operations using copy_expert (async version).
589
+ """Handle PostgreSQL COPY operations (async).
657
590
 
658
591
  Args:
659
592
  cursor: Psycopg async cursor object
@@ -662,31 +595,25 @@ class PsycopgAsyncDriver(AsyncDriverAdapterBase):
662
595
  Returns:
663
596
  SQLResult with COPY operation results
664
597
  """
665
- # Use the properly rendered SQL from the statement
598
+
666
599
  sql = statement.sql
667
600
 
668
- # Get COPY data from parameters - handle both direct value and list format
669
601
  copy_data = statement.parameters
670
602
  if isinstance(copy_data, list) and len(copy_data) == 1:
671
603
  copy_data = copy_data[0]
672
604
 
673
- # Simple string-based direction detection
674
605
  sql_upper = sql.upper()
675
606
  is_stdin = "FROM STDIN" in sql_upper
676
607
  is_stdout = "TO STDOUT" in sql_upper
677
608
 
678
609
  if is_stdin:
679
- # COPY FROM STDIN - import data
680
610
  if isinstance(copy_data, (str, bytes)):
681
611
  data_file = io.StringIO(copy_data) if isinstance(copy_data, str) else io.BytesIO(copy_data)
682
612
  elif hasattr(copy_data, "read"):
683
- # Already a file-like object
684
613
  data_file = copy_data
685
614
  else:
686
- # Convert to string representation
687
615
  data_file = io.StringIO(str(copy_data))
688
616
 
689
- # Use async context manager for COPY FROM
690
617
  async with cursor.copy(sql) as copy_ctx:
691
618
  data_to_write = data_file.read() if hasattr(data_file, "read") else str(copy_data) # pyright: ignore
692
619
  if isinstance(data_to_write, str):
@@ -700,7 +627,6 @@ class PsycopgAsyncDriver(AsyncDriverAdapterBase):
700
627
  )
701
628
 
702
629
  if is_stdout:
703
- # COPY TO STDOUT - export data
704
630
  output_data: list[str] = []
705
631
  async with cursor.copy(sql) as copy_ctx:
706
632
  output_data.extend([row.decode() if isinstance(row, bytes) else str(row) async for row in copy_ctx])
@@ -708,13 +634,12 @@ class PsycopgAsyncDriver(AsyncDriverAdapterBase):
708
634
  exported_data = "".join(output_data)
709
635
 
710
636
  return SQLResult(
711
- data=[{"copy_output": exported_data}], # Wrap in list format for consistency
637
+ data=[{"copy_output": exported_data}],
712
638
  rows_affected=0,
713
639
  statement=statement,
714
640
  metadata={"copy_operation": "TO_STDOUT"},
715
641
  )
716
642
 
717
- # Regular COPY with file - execute normally
718
643
  await cursor.execute(sql)
719
644
  rows_affected = max(cursor.rowcount, 0)
720
645
 
@@ -723,10 +648,14 @@ class PsycopgAsyncDriver(AsyncDriverAdapterBase):
723
648
  )
724
649
 
725
650
  async def _execute_script(self, cursor: Any, statement: "SQL") -> "ExecutionResult":
726
- """Execute SQL script using enhanced statement splitting and parameter handling.
651
+ """Execute SQL script with multiple statements (async).
652
+
653
+ Args:
654
+ cursor: Database cursor
655
+ statement: SQL statement containing multiple commands
727
656
 
728
- Uses core module optimization for statement parsing and parameter processing.
729
- PostgreSQL supports complex scripts with multiple statements.
657
+ Returns:
658
+ ExecutionResult with script execution details
730
659
  """
731
660
  sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
732
661
  statements = self.split_script_statements(sql, statement.statement_config, strip_trailing_semicolon=True)
@@ -735,7 +664,6 @@ class PsycopgAsyncDriver(AsyncDriverAdapterBase):
735
664
  last_cursor = cursor
736
665
 
737
666
  for stmt in statements:
738
- # Only pass parameters if they exist - psycopg treats empty containers as parameterized mode
739
667
  if prepared_parameters:
740
668
  await cursor.execute(stmt, prepared_parameters)
741
669
  else:
@@ -747,42 +675,47 @@ class PsycopgAsyncDriver(AsyncDriverAdapterBase):
747
675
  )
748
676
 
749
677
  async def _execute_many(self, cursor: Any, statement: "SQL") -> "ExecutionResult":
750
- """Execute SQL with multiple parameter sets using optimized PostgreSQL async batch processing.
678
+ """Execute SQL with multiple parameter sets (async).
679
+
680
+ Args:
681
+ cursor: Database cursor
682
+ statement: SQL statement with parameter list
751
683
 
752
- Leverages core parameter processing for enhanced PostgreSQL type handling.
684
+ Returns:
685
+ ExecutionResult with batch execution details
753
686
  """
754
687
  sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
755
688
 
756
- # Handle empty parameter list case
757
689
  if not prepared_parameters:
758
- # For empty parameter list, return a result with no rows affected
759
690
  return self.create_execution_result(cursor, rowcount_override=0, is_many_result=True)
760
691
 
761
692
  await cursor.executemany(sql, prepared_parameters)
762
693
 
763
- # PostgreSQL cursor.rowcount gives total affected rows
764
694
  affected_rows = cursor.rowcount if cursor.rowcount and cursor.rowcount > 0 else 0
765
695
 
766
696
  return self.create_execution_result(cursor, rowcount_override=affected_rows, is_many_result=True)
767
697
 
768
698
  async def _execute_statement(self, cursor: Any, statement: "SQL") -> "ExecutionResult":
769
- """Execute single SQL statement with enhanced PostgreSQL async data handling and performance optimization.
699
+ """Execute single SQL statement (async).
770
700
 
771
- Uses core processing for optimal parameter handling and PostgreSQL result processing.
701
+ Args:
702
+ cursor: Database cursor
703
+ statement: SQL statement to execute
704
+
705
+ Returns:
706
+ ExecutionResult with statement execution details
772
707
  """
773
708
  sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
774
- # Only pass parameters if they exist - psycopg treats empty containers as parameterized mode
709
+
775
710
  if prepared_parameters:
776
711
  await cursor.execute(sql, prepared_parameters)
777
712
  else:
778
713
  await cursor.execute(sql)
779
714
 
780
- # Enhanced SELECT result processing for PostgreSQL
781
715
  if statement.returns_rows():
782
716
  fetched_data = await cursor.fetchall()
783
717
  column_names = [col.name for col in cursor.description or []]
784
718
 
785
- # PostgreSQL returns raw data - pass it directly like the old driver
786
719
  return self.create_execution_result(
787
720
  cursor,
788
721
  selected_data=fetched_data,
@@ -791,6 +724,5 @@ class PsycopgAsyncDriver(AsyncDriverAdapterBase):
791
724
  is_select_result=True,
792
725
  )
793
726
 
794
- # Enhanced non-SELECT result processing for PostgreSQL
795
727
  affected_rows = cursor.rowcount if cursor.rowcount and cursor.rowcount > 0 else 0
796
728
  return self.create_execution_result(cursor, rowcount_override=affected_rows)