sqlspec 0.13.0__py3-none-any.whl → 0.14.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 (110) hide show
  1. sqlspec/__init__.py +39 -1
  2. sqlspec/adapters/adbc/config.py +4 -40
  3. sqlspec/adapters/adbc/driver.py +29 -16
  4. sqlspec/adapters/aiosqlite/config.py +15 -20
  5. sqlspec/adapters/aiosqlite/driver.py +36 -18
  6. sqlspec/adapters/asyncmy/config.py +16 -33
  7. sqlspec/adapters/asyncmy/driver.py +23 -16
  8. sqlspec/adapters/asyncpg/config.py +19 -61
  9. sqlspec/adapters/asyncpg/driver.py +41 -18
  10. sqlspec/adapters/bigquery/config.py +2 -43
  11. sqlspec/adapters/bigquery/driver.py +26 -14
  12. sqlspec/adapters/duckdb/config.py +2 -49
  13. sqlspec/adapters/duckdb/driver.py +35 -16
  14. sqlspec/adapters/oracledb/config.py +30 -83
  15. sqlspec/adapters/oracledb/driver.py +54 -27
  16. sqlspec/adapters/psqlpy/config.py +17 -57
  17. sqlspec/adapters/psqlpy/driver.py +28 -8
  18. sqlspec/adapters/psycopg/config.py +30 -73
  19. sqlspec/adapters/psycopg/driver.py +69 -24
  20. sqlspec/adapters/sqlite/config.py +3 -21
  21. sqlspec/adapters/sqlite/driver.py +50 -26
  22. sqlspec/cli.py +248 -0
  23. sqlspec/config.py +18 -20
  24. sqlspec/driver/_async.py +28 -10
  25. sqlspec/driver/_common.py +5 -4
  26. sqlspec/driver/_sync.py +28 -10
  27. sqlspec/driver/mixins/__init__.py +6 -0
  28. sqlspec/driver/mixins/_cache.py +114 -0
  29. sqlspec/driver/mixins/_pipeline.py +0 -4
  30. sqlspec/{service/base.py → driver/mixins/_query_tools.py} +86 -421
  31. sqlspec/driver/mixins/_result_utils.py +0 -2
  32. sqlspec/driver/mixins/_sql_translator.py +0 -2
  33. sqlspec/driver/mixins/_storage.py +4 -18
  34. sqlspec/driver/mixins/_type_coercion.py +0 -2
  35. sqlspec/driver/parameters.py +4 -4
  36. sqlspec/extensions/aiosql/adapter.py +4 -4
  37. sqlspec/extensions/litestar/__init__.py +2 -1
  38. sqlspec/extensions/litestar/cli.py +48 -0
  39. sqlspec/extensions/litestar/plugin.py +3 -0
  40. sqlspec/loader.py +1 -1
  41. sqlspec/migrations/__init__.py +23 -0
  42. sqlspec/migrations/base.py +390 -0
  43. sqlspec/migrations/commands.py +525 -0
  44. sqlspec/migrations/runner.py +215 -0
  45. sqlspec/migrations/tracker.py +153 -0
  46. sqlspec/migrations/utils.py +89 -0
  47. sqlspec/protocols.py +37 -3
  48. sqlspec/statement/builder/__init__.py +8 -8
  49. sqlspec/statement/builder/{column.py → _column.py} +82 -52
  50. sqlspec/statement/builder/{ddl.py → _ddl.py} +5 -5
  51. sqlspec/statement/builder/_ddl_utils.py +1 -1
  52. sqlspec/statement/builder/{delete.py → _delete.py} +1 -1
  53. sqlspec/statement/builder/{insert.py → _insert.py} +1 -1
  54. sqlspec/statement/builder/{merge.py → _merge.py} +1 -1
  55. sqlspec/statement/builder/_parsing_utils.py +5 -3
  56. sqlspec/statement/builder/{select.py → _select.py} +59 -61
  57. sqlspec/statement/builder/{update.py → _update.py} +2 -2
  58. sqlspec/statement/builder/mixins/__init__.py +24 -30
  59. sqlspec/statement/builder/mixins/{_set_ops.py → _cte_and_set_ops.py} +86 -2
  60. sqlspec/statement/builder/mixins/{_delete_from.py → _delete_operations.py} +2 -0
  61. sqlspec/statement/builder/mixins/{_insert_values.py → _insert_operations.py} +70 -1
  62. sqlspec/statement/builder/mixins/{_merge_clauses.py → _merge_operations.py} +2 -0
  63. sqlspec/statement/builder/mixins/_order_limit_operations.py +123 -0
  64. sqlspec/statement/builder/mixins/{_pivot.py → _pivot_operations.py} +71 -2
  65. sqlspec/statement/builder/mixins/_select_operations.py +612 -0
  66. sqlspec/statement/builder/mixins/{_update_set.py → _update_operations.py} +73 -2
  67. sqlspec/statement/builder/mixins/_where_clause.py +536 -0
  68. sqlspec/statement/cache.py +50 -0
  69. sqlspec/statement/filters.py +37 -8
  70. sqlspec/statement/parameters.py +154 -25
  71. sqlspec/statement/pipelines/__init__.py +1 -1
  72. sqlspec/statement/pipelines/context.py +4 -4
  73. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +3 -3
  74. sqlspec/statement/pipelines/validators/_parameter_style.py +22 -22
  75. sqlspec/statement/pipelines/validators/_performance.py +1 -5
  76. sqlspec/statement/sql.py +246 -176
  77. sqlspec/utils/__init__.py +2 -1
  78. sqlspec/utils/statement_hashing.py +203 -0
  79. sqlspec/utils/type_guards.py +32 -0
  80. {sqlspec-0.13.0.dist-info → sqlspec-0.14.0.dist-info}/METADATA +1 -1
  81. sqlspec-0.14.0.dist-info/RECORD +143 -0
  82. sqlspec-0.14.0.dist-info/entry_points.txt +2 -0
  83. sqlspec/service/__init__.py +0 -4
  84. sqlspec/service/_util.py +0 -147
  85. sqlspec/service/pagination.py +0 -26
  86. sqlspec/statement/builder/mixins/_aggregate_functions.py +0 -250
  87. sqlspec/statement/builder/mixins/_case_builder.py +0 -91
  88. sqlspec/statement/builder/mixins/_common_table_expr.py +0 -90
  89. sqlspec/statement/builder/mixins/_from.py +0 -63
  90. sqlspec/statement/builder/mixins/_group_by.py +0 -118
  91. sqlspec/statement/builder/mixins/_having.py +0 -35
  92. sqlspec/statement/builder/mixins/_insert_from_select.py +0 -47
  93. sqlspec/statement/builder/mixins/_insert_into.py +0 -36
  94. sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
  95. sqlspec/statement/builder/mixins/_order_by.py +0 -46
  96. sqlspec/statement/builder/mixins/_returning.py +0 -37
  97. sqlspec/statement/builder/mixins/_select_columns.py +0 -61
  98. sqlspec/statement/builder/mixins/_unpivot.py +0 -77
  99. sqlspec/statement/builder/mixins/_update_from.py +0 -55
  100. sqlspec/statement/builder/mixins/_update_table.py +0 -29
  101. sqlspec/statement/builder/mixins/_where.py +0 -401
  102. sqlspec/statement/builder/mixins/_window_functions.py +0 -86
  103. sqlspec/statement/parameter_manager.py +0 -220
  104. sqlspec/statement/sql_compiler.py +0 -140
  105. sqlspec-0.13.0.dist-info/RECORD +0 -150
  106. /sqlspec/statement/builder/{base.py → _base.py} +0 -0
  107. /sqlspec/statement/builder/mixins/{_join.py → _join_operations.py} +0 -0
  108. {sqlspec-0.13.0.dist-info → sqlspec-0.14.0.dist-info}/WHEEL +0 -0
  109. {sqlspec-0.13.0.dist-info → sqlspec-0.14.0.dist-info}/licenses/LICENSE +0 -0
  110. {sqlspec-0.13.0.dist-info → sqlspec-0.14.0.dist-info}/licenses/NOTICE +0 -0
@@ -9,6 +9,7 @@ from psqlpy import Connection
9
9
  from sqlspec.driver import AsyncDriverAdapterProtocol
10
10
  from sqlspec.driver.connection import managed_transaction_async
11
11
  from sqlspec.driver.mixins import (
12
+ AsyncAdapterCacheMixin,
12
13
  AsyncPipelinedExecutionMixin,
13
14
  AsyncStorageMixin,
14
15
  SQLTranslatorMixin,
@@ -31,6 +32,7 @@ logger = logging.getLogger("sqlspec")
31
32
 
32
33
  class PsqlpyDriver(
33
34
  AsyncDriverAdapterProtocol[PsqlpyConnection, RowT],
35
+ AsyncAdapterCacheMixin,
34
36
  SQLTranslatorMixin,
35
37
  TypeCoercionMixin,
36
38
  AsyncStorageMixin,
@@ -45,7 +47,6 @@ class PsqlpyDriver(
45
47
  dialect: "DialectType" = "postgres"
46
48
  supported_parameter_styles: "tuple[ParameterStyle, ...]" = (ParameterStyle.NUMERIC,)
47
49
  default_parameter_style: ParameterStyle = ParameterStyle.NUMERIC
48
- __slots__ = ()
49
50
 
50
51
  def __init__(
51
52
  self,
@@ -79,7 +80,7 @@ class PsqlpyDriver(
79
80
  self, statement: SQL, connection: Optional[PsqlpyConnection] = None, **kwargs: Any
80
81
  ) -> SQLResult[RowT]:
81
82
  if statement.is_script:
82
- sql, _ = statement.compile(placeholder_style=ParameterStyle.STATIC)
83
+ sql, _ = self._get_compiled_sql(statement, ParameterStyle.STATIC)
83
84
  return await self._execute_script(sql, connection=connection, **kwargs)
84
85
 
85
86
  # Detect parameter styles in the SQL
@@ -106,7 +107,7 @@ class PsqlpyDriver(
106
107
  break
107
108
 
108
109
  # Compile with the determined style
109
- sql, params = statement.compile(placeholder_style=target_style)
110
+ sql, params = self._get_compiled_sql(statement, target_style)
110
111
  params = self._process_parameters(params)
111
112
 
112
113
  if statement.is_many:
@@ -198,16 +199,35 @@ class PsqlpyDriver(
198
199
  conn = connection if connection is not None else self._connection(None)
199
200
 
200
201
  async with managed_transaction_async(conn, auto_commit=True) as txn_conn:
201
- # psqlpy can execute multi-statement scripts directly
202
- await txn_conn.execute(script)
202
+ # Split script into individual statements for validation
203
+ statements = self._split_script_statements(script)
204
+ suppress_warnings = kwargs.get("_suppress_warnings", False)
205
+
206
+ executed_count = 0
207
+ total_rows = 0
208
+
209
+ # Execute each statement individually for better control and validation
210
+ for statement in statements:
211
+ if statement.strip():
212
+ # Validate each statement unless warnings suppressed
213
+ if not suppress_warnings:
214
+ # Run validation through pipeline
215
+ temp_sql = SQL(statement, config=self.config)
216
+ temp_sql._ensure_processed()
217
+ # Validation errors are logged as warnings by default
218
+
219
+ await txn_conn.execute(statement)
220
+ executed_count += 1
221
+ # psqlpy doesn't provide row count from execute()
222
+
203
223
  return SQLResult(
204
224
  statement=SQL(script, _dialect=self.dialect).as_script(),
205
225
  data=[],
206
- rows_affected=0,
226
+ rows_affected=total_rows,
207
227
  operation_type="SCRIPT",
208
228
  metadata={"status_message": "SCRIPT EXECUTED"},
209
- total_statements=-1, # Not directly supported, but script is executed
210
- successful_statements=-1,
229
+ total_statements=executed_count,
230
+ successful_statements=executed_count,
211
231
  )
212
232
 
213
233
  async def _ingest_arrow_table(self, table: "Any", table_name: str, mode: str = "append", **options: Any) -> int:
@@ -22,7 +22,6 @@ if TYPE_CHECKING:
22
22
  from collections.abc import AsyncGenerator, Callable, Generator
23
23
 
24
24
  from psycopg import Connection
25
- from sqlglot.dialects.dialect import DialectType
26
25
 
27
26
  logger = logging.getLogger("sqlspec.adapters.psycopg")
28
27
 
@@ -67,39 +66,6 @@ __all__ = ("CONNECTION_FIELDS", "POOL_FIELDS", "PsycopgAsyncConfig", "PsycopgSyn
67
66
  class PsycopgSyncConfig(SyncDatabaseConfig[PsycopgSyncConnection, ConnectionPool, PsycopgSyncDriver]):
68
67
  """Configuration for Psycopg synchronous database connections with direct field-based configuration."""
69
68
 
70
- __slots__ = (
71
- "_dialect",
72
- "application_name",
73
- "autocommit",
74
- "configure",
75
- "connect_timeout",
76
- "conninfo",
77
- "dbname",
78
- "default_row_type",
79
- "extras",
80
- "host",
81
- "kwargs",
82
- "max_idle",
83
- "max_lifetime",
84
- "max_size",
85
- "max_waiting",
86
- "min_size",
87
- "name",
88
- "num_workers",
89
- "options",
90
- "password",
91
- "pool_instance",
92
- "port",
93
- "reconnect_timeout",
94
- "sslcert",
95
- "sslkey",
96
- "sslmode",
97
- "sslrootcert",
98
- "statement_config",
99
- "timeout",
100
- "user",
101
- )
102
-
103
69
  is_async: ClassVar[bool] = False
104
70
  supports_connection_pooling: ClassVar[bool] = True
105
71
 
@@ -110,7 +76,7 @@ class PsycopgSyncConfig(SyncDatabaseConfig[PsycopgSyncConnection, ConnectionPool
110
76
  supported_parameter_styles: ClassVar[tuple[str, ...]] = ("pyformat_positional", "pyformat_named")
111
77
  """Psycopg supports %s (positional) and %(name)s (named) parameter styles."""
112
78
 
113
- preferred_parameter_style: ClassVar[str] = "pyformat_positional"
79
+ default_parameter_style: ClassVar[str] = "pyformat_positional"
114
80
  """Psycopg's native parameter style is %s (pyformat positional)."""
115
81
 
116
82
  def __init__(
@@ -216,7 +182,6 @@ class PsycopgSyncConfig(SyncDatabaseConfig[PsycopgSyncConnection, ConnectionPool
216
182
  # Store other config
217
183
  self.statement_config = statement_config or SQLConfig()
218
184
  self.default_row_type = default_row_type
219
- self._dialect: DialectType = None
220
185
 
221
186
  super().__init__()
222
187
 
@@ -385,7 +350,7 @@ class PsycopgSyncConfig(SyncDatabaseConfig[PsycopgSyncConnection, ConnectionPool
385
350
  statement_config = replace(
386
351
  statement_config,
387
352
  allowed_parameter_styles=self.supported_parameter_styles,
388
- target_parameter_style=self.preferred_parameter_style,
353
+ default_parameter_style=self.default_parameter_style,
389
354
  )
390
355
  driver = self.driver_type(connection=conn, config=statement_config)
391
356
  yield driver
@@ -400,43 +365,23 @@ class PsycopgSyncConfig(SyncDatabaseConfig[PsycopgSyncConnection, ConnectionPool
400
365
  self.pool_instance = self.create_pool()
401
366
  return self.pool_instance
402
367
 
368
+ def get_signature_namespace(self) -> "dict[str, type[Any]]":
369
+ """Get the signature namespace for Psycopg types.
370
+
371
+ This provides all Psycopg-specific types that Litestar needs to recognize
372
+ to avoid serialization attempts.
373
+
374
+ Returns:
375
+ Dictionary mapping type names to types.
376
+ """
377
+ namespace = super().get_signature_namespace()
378
+ namespace.update({"PsycopgSyncConnection": PsycopgSyncConnection})
379
+ return namespace
380
+
403
381
 
404
382
  class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnectionPool, PsycopgAsyncDriver]):
405
383
  """Configuration for Psycopg asynchronous database connections with direct field-based configuration."""
406
384
 
407
- __slots__ = (
408
- "_dialect",
409
- "application_name",
410
- "autocommit",
411
- "configure",
412
- "connect_timeout",
413
- "conninfo",
414
- "dbname",
415
- "default_row_type",
416
- "extras",
417
- "host",
418
- "kwargs",
419
- "max_idle",
420
- "max_lifetime",
421
- "max_size",
422
- "max_waiting",
423
- "min_size",
424
- "name",
425
- "num_workers",
426
- "options",
427
- "password",
428
- "pool_instance",
429
- "port",
430
- "reconnect_timeout",
431
- "sslcert",
432
- "sslkey",
433
- "sslmode",
434
- "sslrootcert",
435
- "statement_config",
436
- "timeout",
437
- "user",
438
- )
439
-
440
385
  is_async: ClassVar[bool] = True
441
386
  supports_connection_pooling: ClassVar[bool] = True
442
387
 
@@ -448,7 +393,7 @@ class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnec
448
393
  supported_parameter_styles: ClassVar[tuple[str, ...]] = ("pyformat_positional", "pyformat_named")
449
394
  """Psycopg supports %s (pyformat_positional) and %(name)s (pyformat_named) parameter styles."""
450
395
 
451
- preferred_parameter_style: ClassVar[str] = "pyformat_positional"
396
+ default_parameter_style: ClassVar[str] = "pyformat_positional"
452
397
  """Psycopg's preferred parameter style is %s (pyformat_positional)."""
453
398
 
454
399
  def __init__(
@@ -554,7 +499,6 @@ class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnec
554
499
  # Store other config
555
500
  self.statement_config = statement_config or SQLConfig()
556
501
  self.default_row_type = default_row_type
557
- self._dialect: DialectType = None
558
502
 
559
503
  super().__init__()
560
504
 
@@ -713,7 +657,7 @@ class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnec
713
657
  statement_config = replace(
714
658
  statement_config,
715
659
  allowed_parameter_styles=self.supported_parameter_styles,
716
- target_parameter_style=self.preferred_parameter_style,
660
+ default_parameter_style=self.default_parameter_style,
717
661
  )
718
662
  driver = self.driver_type(connection=conn, config=statement_config)
719
663
  yield driver
@@ -727,3 +671,16 @@ class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnec
727
671
  if not self.pool_instance:
728
672
  self.pool_instance = await self.create_pool()
729
673
  return self.pool_instance
674
+
675
+ def get_signature_namespace(self) -> "dict[str, type[Any]]":
676
+ """Get the signature namespace for Psycopg async types.
677
+
678
+ This provides all Psycopg async-specific types that Litestar needs to recognize
679
+ to avoid serialization attempts.
680
+
681
+ Returns:
682
+ Dictionary mapping type names to types.
683
+ """
684
+ namespace = super().get_signature_namespace()
685
+ namespace.update({"PsycopgAsyncConnection": PsycopgAsyncConnection})
686
+ return namespace
@@ -13,15 +13,17 @@ from sqlglot.dialects.dialect import DialectType
13
13
  from sqlspec.driver import AsyncDriverAdapterProtocol, SyncDriverAdapterProtocol
14
14
  from sqlspec.driver.connection import managed_transaction_async, managed_transaction_sync
15
15
  from sqlspec.driver.mixins import (
16
+ AsyncAdapterCacheMixin,
16
17
  AsyncPipelinedExecutionMixin,
17
18
  AsyncStorageMixin,
18
19
  SQLTranslatorMixin,
20
+ SyncAdapterCacheMixin,
19
21
  SyncPipelinedExecutionMixin,
20
22
  SyncStorageMixin,
21
23
  ToSchemaMixin,
22
24
  TypeCoercionMixin,
23
25
  )
24
- from sqlspec.driver.parameters import normalize_parameter_sequence
26
+ from sqlspec.driver.parameters import convert_parameter_sequence
25
27
  from sqlspec.exceptions import PipelineExecutionError
26
28
  from sqlspec.statement.parameters import ParameterStyle, ParameterValidator
27
29
  from sqlspec.statement.result import ArrowResult, SQLResult
@@ -43,6 +45,7 @@ PsycopgAsyncConnection = AsyncConnection[PsycopgDictRow]
43
45
 
44
46
  class PsycopgSyncDriver(
45
47
  SyncDriverAdapterProtocol[PsycopgSyncConnection, RowT],
48
+ SyncAdapterCacheMixin,
46
49
  SQLTranslatorMixin,
47
50
  TypeCoercionMixin,
48
51
  SyncStorageMixin,
@@ -57,7 +60,6 @@ class PsycopgSyncDriver(
57
60
  ParameterStyle.NAMED_PYFORMAT,
58
61
  )
59
62
  default_parameter_style: ParameterStyle = ParameterStyle.POSITIONAL_PYFORMAT
60
- __slots__ = ()
61
63
 
62
64
  def __init__(
63
65
  self,
@@ -77,7 +79,7 @@ class PsycopgSyncDriver(
77
79
  self, statement: SQL, connection: Optional[PsycopgSyncConnection] = None, **kwargs: Any
78
80
  ) -> SQLResult[RowT]:
79
81
  if statement.is_script:
80
- sql, _ = statement.compile(placeholder_style=ParameterStyle.STATIC)
82
+ sql, _ = self._get_compiled_sql(statement, ParameterStyle.STATIC)
81
83
  return self._execute_script(sql, connection=connection, **kwargs)
82
84
 
83
85
  detected_styles = set()
@@ -105,7 +107,7 @@ class PsycopgSyncDriver(
105
107
  sql = statement.to_sql(placeholder_style=target_style)
106
108
  params = kwargs_params
107
109
  else:
108
- sql, params = statement.compile(placeholder_style=target_style)
110
+ sql, params = self._get_compiled_sql(statement, target_style)
109
111
  if params is not None:
110
112
  processed_params = [self._process_parameters(param_set) for param_set in params]
111
113
  params = processed_params
@@ -120,7 +122,7 @@ class PsycopgSyncDriver(
120
122
  sql = statement.to_sql(placeholder_style=target_style)
121
123
  params = kwargs_params
122
124
  else:
123
- sql, params = statement.compile(placeholder_style=target_style)
125
+ sql, params = self._get_compiled_sql(statement, target_style)
124
126
  params = self._process_parameters(params)
125
127
 
126
128
  # Fix over-nested parameters for Psycopg
@@ -231,8 +233,8 @@ class PsycopgSyncDriver(
231
233
 
232
234
  with managed_transaction_sync(conn, auto_commit=True) as txn_conn:
233
235
  # Normalize parameter list using consolidated utility
234
- normalized_param_list = normalize_parameter_sequence(param_list)
235
- final_param_list = normalized_param_list or []
236
+ converted_param_list = convert_parameter_sequence(param_list)
237
+ final_param_list = converted_param_list or []
236
238
 
237
239
  with self._get_cursor(txn_conn) as cursor:
238
240
  cursor.executemany(sql, final_param_list)
@@ -256,15 +258,37 @@ class PsycopgSyncDriver(
256
258
  conn = connection if connection is not None else self._connection(None)
257
259
 
258
260
  with managed_transaction_sync(conn, auto_commit=True) as txn_conn, self._get_cursor(txn_conn) as cursor:
259
- cursor.execute(script)
261
+ # Split script into individual statements for validation
262
+ statements = self._split_script_statements(script)
263
+ suppress_warnings = kwargs.get("_suppress_warnings", False)
264
+
265
+ executed_count = 0
266
+ total_rows = 0
267
+ last_status = None
268
+
269
+ # Execute each statement individually for better control and validation
270
+ for statement in statements:
271
+ if statement.strip():
272
+ # Validate each statement unless warnings suppressed
273
+ if not suppress_warnings:
274
+ # Run validation through pipeline
275
+ temp_sql = SQL(statement, config=self.config)
276
+ temp_sql._ensure_processed()
277
+ # Validation errors are logged as warnings by default
278
+
279
+ cursor.execute(statement)
280
+ executed_count += 1
281
+ total_rows += cursor.rowcount or 0
282
+ last_status = cursor.statusmessage
283
+
260
284
  return SQLResult(
261
285
  statement=SQL(script, _dialect=self.dialect).as_script(),
262
286
  data=[],
263
- rows_affected=0,
287
+ rows_affected=total_rows,
264
288
  operation_type="SCRIPT",
265
- metadata={"status_message": cursor.statusmessage or "SCRIPT EXECUTED"},
266
- total_statements=1,
267
- successful_statements=1,
289
+ metadata={"status_message": last_status or "SCRIPT EXECUTED"},
290
+ total_statements=executed_count,
291
+ successful_statements=executed_count,
268
292
  )
269
293
 
270
294
  def _ingest_arrow_table(self, table: "Any", table_name: str, mode: str = "append", **options: Any) -> int:
@@ -471,6 +495,7 @@ class PsycopgSyncDriver(
471
495
 
472
496
  class PsycopgAsyncDriver(
473
497
  AsyncDriverAdapterProtocol[PsycopgAsyncConnection, RowT],
498
+ AsyncAdapterCacheMixin,
474
499
  SQLTranslatorMixin,
475
500
  TypeCoercionMixin,
476
501
  AsyncStorageMixin,
@@ -485,7 +510,6 @@ class PsycopgAsyncDriver(
485
510
  ParameterStyle.NAMED_PYFORMAT,
486
511
  )
487
512
  default_parameter_style: ParameterStyle = ParameterStyle.POSITIONAL_PYFORMAT
488
- __slots__ = ()
489
513
 
490
514
  def __init__(
491
515
  self,
@@ -505,7 +529,7 @@ class PsycopgAsyncDriver(
505
529
  self, statement: SQL, connection: Optional[PsycopgAsyncConnection] = None, **kwargs: Any
506
530
  ) -> SQLResult[RowT]:
507
531
  if statement.is_script:
508
- sql, _ = statement.compile(placeholder_style=ParameterStyle.STATIC)
532
+ sql, _ = self._get_compiled_sql(statement, ParameterStyle.STATIC)
509
533
  return await self._execute_script(sql, connection=connection, **kwargs)
510
534
 
511
535
  detected_styles = set()
@@ -535,8 +559,7 @@ class PsycopgAsyncDriver(
535
559
  sql = statement.to_sql(placeholder_style=target_style)
536
560
  params = kwargs_params
537
561
  else:
538
- sql, _ = statement.compile(placeholder_style=target_style)
539
- params = statement.parameters
562
+ sql, params = self._get_compiled_sql(statement, target_style)
540
563
  if params is not None:
541
564
  processed_params = [self._process_parameters(param_set) for param_set in params]
542
565
  params = processed_params
@@ -560,7 +583,7 @@ class PsycopgAsyncDriver(
560
583
  sql = statement.to_sql(placeholder_style=target_style)
561
584
  params = kwargs_params
562
585
  else:
563
- sql, params = statement.compile(placeholder_style=target_style)
586
+ sql, params = self._get_compiled_sql(statement, target_style)
564
587
  params = self._process_parameters(params)
565
588
 
566
589
  # Fix over-nested parameters for Psycopg
@@ -683,8 +706,8 @@ class PsycopgAsyncDriver(
683
706
 
684
707
  async with managed_transaction_async(conn, auto_commit=True) as txn_conn:
685
708
  # Normalize parameter list using consolidated utility
686
- normalized_param_list = normalize_parameter_sequence(param_list)
687
- final_param_list = normalized_param_list or []
709
+ converted_param_list = convert_parameter_sequence(param_list)
710
+ final_param_list = converted_param_list or []
688
711
 
689
712
  async with txn_conn.cursor() as cursor:
690
713
  await cursor.executemany(cast("Query", sql), final_param_list)
@@ -703,15 +726,37 @@ class PsycopgAsyncDriver(
703
726
  conn = connection if connection is not None else self._connection(None)
704
727
 
705
728
  async with managed_transaction_async(conn, auto_commit=True) as txn_conn, txn_conn.cursor() as cursor:
706
- await cursor.execute(cast("Query", script))
729
+ # Split script into individual statements for validation
730
+ statements = self._split_script_statements(script)
731
+ suppress_warnings = kwargs.get("_suppress_warnings", False)
732
+
733
+ executed_count = 0
734
+ total_rows = 0
735
+ last_status = None
736
+
737
+ # Execute each statement individually for better control and validation
738
+ for statement in statements:
739
+ if statement.strip():
740
+ # Validate each statement unless warnings suppressed
741
+ if not suppress_warnings:
742
+ # Run validation through pipeline
743
+ temp_sql = SQL(statement, config=self.config)
744
+ temp_sql._ensure_processed()
745
+ # Validation errors are logged as warnings by default
746
+
747
+ await cursor.execute(cast("Query", statement))
748
+ executed_count += 1
749
+ total_rows += cursor.rowcount or 0
750
+ last_status = cursor.statusmessage
751
+
707
752
  return SQLResult(
708
753
  statement=SQL(script, _dialect=self.dialect).as_script(),
709
754
  data=[],
710
- rows_affected=0,
755
+ rows_affected=total_rows,
711
756
  operation_type="SCRIPT",
712
- metadata={"status_message": cursor.statusmessage or "SCRIPT EXECUTED"},
713
- total_statements=1,
714
- successful_statements=1,
757
+ metadata={"status_message": last_status or "SCRIPT EXECUTED"},
758
+ total_statements=executed_count,
759
+ successful_statements=executed_count,
715
760
  )
716
761
 
717
762
  async def _fetch_arrow_table(self, sql: SQL, connection: "Optional[Any]" = None, **kwargs: Any) -> "ArrowResult":
@@ -13,7 +13,6 @@ from sqlspec.typing import DictRow
13
13
  if TYPE_CHECKING:
14
14
  from collections.abc import Generator
15
15
 
16
- from sqlglot.dialects.dialect import DialectType
17
16
 
18
17
  logger = logging.getLogger(__name__)
19
18
 
@@ -36,26 +35,10 @@ __all__ = ("CONNECTION_FIELDS", "SqliteConfig", "sqlite3")
36
35
  class SqliteConfig(NoPoolSyncConfig[SqliteConnection, SqliteDriver]):
37
36
  """Configuration for SQLite database connections with direct field-based configuration."""
38
37
 
39
- __slots__ = (
40
- "_dialect",
41
- "cached_statements",
42
- "check_same_thread",
43
- "database",
44
- "default_row_type",
45
- "detect_types",
46
- "extras",
47
- "factory",
48
- "isolation_level",
49
- "pool_instance",
50
- "statement_config",
51
- "timeout",
52
- "uri",
53
- )
54
-
55
38
  driver_type: type[SqliteDriver] = SqliteDriver
56
39
  connection_type: type[SqliteConnection] = SqliteConnection
57
40
  supported_parameter_styles: ClassVar[tuple[str, ...]] = ("qmark", "named_colon")
58
- preferred_parameter_style: ClassVar[str] = "qmark"
41
+ default_parameter_style: ClassVar[str] = "qmark"
59
42
 
60
43
  def __init__(
61
44
  self,
@@ -65,7 +48,7 @@ class SqliteConfig(NoPoolSyncConfig[SqliteConnection, SqliteDriver]):
65
48
  # SQLite connection parameters
66
49
  timeout: Optional[float] = None,
67
50
  detect_types: Optional[int] = None,
68
- isolation_level: Optional[Union[str, None]] = None,
51
+ isolation_level: Union[None, str] = None,
69
52
  check_same_thread: Optional[bool] = None,
70
53
  factory: Optional[type[SqliteConnection]] = None,
71
54
  cached_statements: Optional[int] = None,
@@ -106,7 +89,6 @@ class SqliteConfig(NoPoolSyncConfig[SqliteConnection, SqliteDriver]):
106
89
  # Store other config
107
90
  self.statement_config = statement_config or SQLConfig()
108
91
  self.default_row_type = default_row_type
109
- self._dialect: DialectType = None
110
92
  super().__init__()
111
93
 
112
94
  @property
@@ -169,6 +151,6 @@ class SqliteConfig(NoPoolSyncConfig[SqliteConnection, SqliteDriver]):
169
151
  statement_config = replace(
170
152
  statement_config,
171
153
  allowed_parameter_styles=self.supported_parameter_styles,
172
- target_parameter_style=self.preferred_parameter_style,
154
+ default_parameter_style=self.default_parameter_style,
173
155
  )
174
156
  yield self.driver_type(connection=connection, config=statement_config)