sqlspec 0.13.1__py3-none-any.whl → 0.14.1__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 (112) hide show
  1. sqlspec/__init__.py +39 -1
  2. sqlspec/__main__.py +12 -0
  3. sqlspec/adapters/adbc/config.py +16 -40
  4. sqlspec/adapters/adbc/driver.py +43 -16
  5. sqlspec/adapters/adbc/transformers.py +108 -0
  6. sqlspec/adapters/aiosqlite/config.py +2 -20
  7. sqlspec/adapters/aiosqlite/driver.py +36 -18
  8. sqlspec/adapters/asyncmy/config.py +2 -33
  9. sqlspec/adapters/asyncmy/driver.py +23 -16
  10. sqlspec/adapters/asyncpg/config.py +5 -39
  11. sqlspec/adapters/asyncpg/driver.py +41 -18
  12. sqlspec/adapters/bigquery/config.py +2 -43
  13. sqlspec/adapters/bigquery/driver.py +26 -14
  14. sqlspec/adapters/duckdb/config.py +2 -49
  15. sqlspec/adapters/duckdb/driver.py +35 -16
  16. sqlspec/adapters/oracledb/config.py +4 -83
  17. sqlspec/adapters/oracledb/driver.py +54 -27
  18. sqlspec/adapters/psqlpy/config.py +2 -55
  19. sqlspec/adapters/psqlpy/driver.py +28 -8
  20. sqlspec/adapters/psycopg/config.py +4 -73
  21. sqlspec/adapters/psycopg/driver.py +69 -24
  22. sqlspec/adapters/sqlite/config.py +3 -21
  23. sqlspec/adapters/sqlite/driver.py +50 -26
  24. sqlspec/cli.py +248 -0
  25. sqlspec/config.py +18 -20
  26. sqlspec/driver/_async.py +28 -10
  27. sqlspec/driver/_common.py +5 -4
  28. sqlspec/driver/_sync.py +28 -10
  29. sqlspec/driver/mixins/__init__.py +6 -0
  30. sqlspec/driver/mixins/_cache.py +114 -0
  31. sqlspec/driver/mixins/_pipeline.py +0 -4
  32. sqlspec/{service/base.py → driver/mixins/_query_tools.py} +86 -421
  33. sqlspec/driver/mixins/_result_utils.py +0 -2
  34. sqlspec/driver/mixins/_sql_translator.py +0 -2
  35. sqlspec/driver/mixins/_storage.py +4 -18
  36. sqlspec/driver/mixins/_type_coercion.py +0 -2
  37. sqlspec/driver/parameters.py +4 -4
  38. sqlspec/extensions/aiosql/adapter.py +4 -4
  39. sqlspec/extensions/litestar/__init__.py +2 -1
  40. sqlspec/extensions/litestar/cli.py +48 -0
  41. sqlspec/extensions/litestar/plugin.py +3 -0
  42. sqlspec/loader.py +1 -1
  43. sqlspec/migrations/__init__.py +23 -0
  44. sqlspec/migrations/base.py +390 -0
  45. sqlspec/migrations/commands.py +525 -0
  46. sqlspec/migrations/runner.py +215 -0
  47. sqlspec/migrations/tracker.py +153 -0
  48. sqlspec/migrations/utils.py +89 -0
  49. sqlspec/protocols.py +37 -3
  50. sqlspec/statement/builder/__init__.py +8 -8
  51. sqlspec/statement/builder/{column.py → _column.py} +82 -52
  52. sqlspec/statement/builder/{ddl.py → _ddl.py} +5 -5
  53. sqlspec/statement/builder/_ddl_utils.py +1 -1
  54. sqlspec/statement/builder/{delete.py → _delete.py} +1 -1
  55. sqlspec/statement/builder/{insert.py → _insert.py} +1 -1
  56. sqlspec/statement/builder/{merge.py → _merge.py} +1 -1
  57. sqlspec/statement/builder/_parsing_utils.py +5 -3
  58. sqlspec/statement/builder/{select.py → _select.py} +59 -61
  59. sqlspec/statement/builder/{update.py → _update.py} +2 -2
  60. sqlspec/statement/builder/mixins/__init__.py +24 -30
  61. sqlspec/statement/builder/mixins/{_set_ops.py → _cte_and_set_ops.py} +86 -2
  62. sqlspec/statement/builder/mixins/{_delete_from.py → _delete_operations.py} +2 -0
  63. sqlspec/statement/builder/mixins/{_insert_values.py → _insert_operations.py} +70 -1
  64. sqlspec/statement/builder/mixins/{_merge_clauses.py → _merge_operations.py} +2 -0
  65. sqlspec/statement/builder/mixins/_order_limit_operations.py +123 -0
  66. sqlspec/statement/builder/mixins/{_pivot.py → _pivot_operations.py} +71 -2
  67. sqlspec/statement/builder/mixins/_select_operations.py +612 -0
  68. sqlspec/statement/builder/mixins/{_update_set.py → _update_operations.py} +73 -2
  69. sqlspec/statement/builder/mixins/_where_clause.py +536 -0
  70. sqlspec/statement/cache.py +50 -0
  71. sqlspec/statement/filters.py +37 -8
  72. sqlspec/statement/parameters.py +143 -54
  73. sqlspec/statement/pipelines/__init__.py +1 -1
  74. sqlspec/statement/pipelines/context.py +4 -10
  75. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +3 -3
  76. sqlspec/statement/pipelines/validators/_parameter_style.py +22 -22
  77. sqlspec/statement/pipelines/validators/_performance.py +1 -5
  78. sqlspec/statement/sql.py +246 -176
  79. sqlspec/utils/__init__.py +2 -1
  80. sqlspec/utils/statement_hashing.py +203 -0
  81. sqlspec/utils/type_guards.py +32 -0
  82. {sqlspec-0.13.1.dist-info → sqlspec-0.14.1.dist-info}/METADATA +1 -1
  83. sqlspec-0.14.1.dist-info/RECORD +145 -0
  84. sqlspec-0.14.1.dist-info/entry_points.txt +2 -0
  85. sqlspec/service/__init__.py +0 -4
  86. sqlspec/service/_util.py +0 -147
  87. sqlspec/service/pagination.py +0 -26
  88. sqlspec/statement/builder/mixins/_aggregate_functions.py +0 -250
  89. sqlspec/statement/builder/mixins/_case_builder.py +0 -91
  90. sqlspec/statement/builder/mixins/_common_table_expr.py +0 -90
  91. sqlspec/statement/builder/mixins/_from.py +0 -63
  92. sqlspec/statement/builder/mixins/_group_by.py +0 -118
  93. sqlspec/statement/builder/mixins/_having.py +0 -35
  94. sqlspec/statement/builder/mixins/_insert_from_select.py +0 -47
  95. sqlspec/statement/builder/mixins/_insert_into.py +0 -36
  96. sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
  97. sqlspec/statement/builder/mixins/_order_by.py +0 -46
  98. sqlspec/statement/builder/mixins/_returning.py +0 -37
  99. sqlspec/statement/builder/mixins/_select_columns.py +0 -61
  100. sqlspec/statement/builder/mixins/_unpivot.py +0 -77
  101. sqlspec/statement/builder/mixins/_update_from.py +0 -55
  102. sqlspec/statement/builder/mixins/_update_table.py +0 -29
  103. sqlspec/statement/builder/mixins/_where.py +0 -401
  104. sqlspec/statement/builder/mixins/_window_functions.py +0 -86
  105. sqlspec/statement/parameter_manager.py +0 -220
  106. sqlspec/statement/sql_compiler.py +0 -140
  107. sqlspec-0.13.1.dist-info/RECORD +0 -150
  108. /sqlspec/statement/builder/{base.py → _base.py} +0 -0
  109. /sqlspec/statement/builder/mixins/{_join.py → _join_operations.py} +0 -0
  110. {sqlspec-0.13.1.dist-info → sqlspec-0.14.1.dist-info}/WHEEL +0 -0
  111. {sqlspec-0.13.1.dist-info → sqlspec-0.14.1.dist-info}/licenses/LICENSE +0 -0
  112. {sqlspec-0.13.1.dist-info → sqlspec-0.14.1.dist-info}/licenses/NOTICE +0 -0
@@ -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
@@ -417,39 +382,6 @@ class PsycopgSyncConfig(SyncDatabaseConfig[PsycopgSyncConnection, ConnectionPool
417
382
  class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnectionPool, PsycopgAsyncDriver]):
418
383
  """Configuration for Psycopg asynchronous database connections with direct field-based configuration."""
419
384
 
420
- __slots__ = (
421
- "_dialect",
422
- "application_name",
423
- "autocommit",
424
- "configure",
425
- "connect_timeout",
426
- "conninfo",
427
- "dbname",
428
- "default_row_type",
429
- "extras",
430
- "host",
431
- "kwargs",
432
- "max_idle",
433
- "max_lifetime",
434
- "max_size",
435
- "max_waiting",
436
- "min_size",
437
- "name",
438
- "num_workers",
439
- "options",
440
- "password",
441
- "pool_instance",
442
- "port",
443
- "reconnect_timeout",
444
- "sslcert",
445
- "sslkey",
446
- "sslmode",
447
- "sslrootcert",
448
- "statement_config",
449
- "timeout",
450
- "user",
451
- )
452
-
453
385
  is_async: ClassVar[bool] = True
454
386
  supports_connection_pooling: ClassVar[bool] = True
455
387
 
@@ -461,7 +393,7 @@ class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnec
461
393
  supported_parameter_styles: ClassVar[tuple[str, ...]] = ("pyformat_positional", "pyformat_named")
462
394
  """Psycopg supports %s (pyformat_positional) and %(name)s (pyformat_named) parameter styles."""
463
395
 
464
- preferred_parameter_style: ClassVar[str] = "pyformat_positional"
396
+ default_parameter_style: ClassVar[str] = "pyformat_positional"
465
397
  """Psycopg's preferred parameter style is %s (pyformat_positional)."""
466
398
 
467
399
  def __init__(
@@ -567,7 +499,6 @@ class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnec
567
499
  # Store other config
568
500
  self.statement_config = statement_config or SQLConfig()
569
501
  self.default_row_type = default_row_type
570
- self._dialect: DialectType = None
571
502
 
572
503
  super().__init__()
573
504
 
@@ -726,7 +657,7 @@ class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnec
726
657
  statement_config = replace(
727
658
  statement_config,
728
659
  allowed_parameter_styles=self.supported_parameter_styles,
729
- target_parameter_style=self.preferred_parameter_style,
660
+ default_parameter_style=self.default_parameter_style,
730
661
  )
731
662
  driver = self.driver_type(connection=conn, config=statement_config)
732
663
  yield driver
@@ -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)
@@ -12,12 +12,14 @@ from sqlspec.driver import SyncDriverAdapterProtocol
12
12
  from sqlspec.driver.connection import managed_transaction_sync
13
13
  from sqlspec.driver.mixins import (
14
14
  SQLTranslatorMixin,
15
+ SyncAdapterCacheMixin,
15
16
  SyncPipelinedExecutionMixin,
17
+ SyncQueryMixin,
16
18
  SyncStorageMixin,
17
19
  ToSchemaMixin,
18
20
  TypeCoercionMixin,
19
21
  )
20
- from sqlspec.driver.parameters import normalize_parameter_sequence
22
+ from sqlspec.driver.parameters import convert_parameter_sequence
21
23
  from sqlspec.statement.parameters import ParameterStyle, ParameterValidator
22
24
  from sqlspec.statement.result import SQLResult
23
25
  from sqlspec.statement.sql import SQL, SQLConfig
@@ -37,10 +39,12 @@ SqliteConnection: TypeAlias = sqlite3.Connection
37
39
 
38
40
  class SqliteDriver(
39
41
  SyncDriverAdapterProtocol[SqliteConnection, RowT],
42
+ SyncAdapterCacheMixin,
40
43
  SQLTranslatorMixin,
41
44
  TypeCoercionMixin,
42
45
  SyncStorageMixin,
43
46
  SyncPipelinedExecutionMixin,
47
+ SyncQueryMixin,
44
48
  ToSchemaMixin,
45
49
  ):
46
50
  """SQLite Sync Driver Adapter with Arrow/Parquet export support.
@@ -49,8 +53,6 @@ class SqliteDriver(
49
53
  instrumentation standards following the psycopg pattern.
50
54
  """
51
55
 
52
- __slots__ = ()
53
-
54
56
  dialect: "DialectType" = "sqlite"
55
57
  supported_parameter_styles: "tuple[ParameterStyle, ...]" = (ParameterStyle.QMARK, ParameterStyle.NAMED_COLON)
56
58
  default_parameter_style: ParameterStyle = ParameterStyle.QMARK
@@ -106,7 +108,7 @@ class SqliteDriver(
106
108
  self, statement: SQL, connection: Optional[SqliteConnection] = None, **kwargs: Any
107
109
  ) -> SQLResult[RowT]:
108
110
  if statement.is_script:
109
- sql, _ = statement.compile(placeholder_style=ParameterStyle.STATIC)
111
+ sql, _ = self._get_compiled_sql(statement, ParameterStyle.STATIC)
110
112
  return self._execute_script(sql, connection=connection, statement=statement, **kwargs)
111
113
 
112
114
  detected_styles = set()
@@ -126,17 +128,17 @@ class SqliteDriver(
126
128
  target_style = self.default_parameter_style
127
129
  elif detected_styles:
128
130
  # Single style detected - use it if supported
129
- single_style = next(iter(detected_styles))
130
- if single_style in self.supported_parameter_styles:
131
- target_style = single_style
131
+ detected_style = next(iter(detected_styles))
132
+ if detected_style.value in self.supported_parameter_styles:
133
+ target_style = detected_style
132
134
  else:
133
135
  target_style = self.default_parameter_style
134
136
 
135
137
  if statement.is_many:
136
- sql, params = statement.compile(placeholder_style=target_style)
138
+ sql, params = self._get_compiled_sql(statement, target_style)
137
139
  return self._execute_many(sql, params, connection=connection, statement=statement, **kwargs)
138
140
 
139
- sql, params = statement.compile(placeholder_style=target_style)
141
+ sql, params = self._get_compiled_sql(statement, target_style)
140
142
 
141
143
  params = self._process_parameters(params)
142
144
 
@@ -153,18 +155,18 @@ class SqliteDriver(
153
155
  # Use provided connection or driver's default connection
154
156
  conn = connection if connection is not None else self._connection(None)
155
157
  with managed_transaction_sync(conn, auto_commit=True) as txn_conn, self._get_cursor(txn_conn) as cursor:
156
- # Normalize parameters using consolidated utility
157
- normalized_params_list = normalize_parameter_sequence(parameters)
158
+ # Convert parameters using consolidated utility
159
+ converted_params_list = convert_parameter_sequence(parameters)
158
160
  params_for_execute: Any
159
- if normalized_params_list and len(normalized_params_list) == 1:
161
+ if converted_params_list and len(converted_params_list) == 1:
160
162
  # Single parameter should be tuple for SQLite
161
- if not isinstance(normalized_params_list[0], (tuple, list, dict)):
162
- params_for_execute = (normalized_params_list[0],)
163
+ if not isinstance(converted_params_list[0], (tuple, list, dict)):
164
+ params_for_execute = (converted_params_list[0],)
163
165
  else:
164
- params_for_execute = normalized_params_list[0]
166
+ params_for_execute = converted_params_list[0]
165
167
  else:
166
168
  # Multiple parameters
167
- params_for_execute = tuple(normalized_params_list) if normalized_params_list else ()
169
+ params_for_execute = tuple(converted_params_list) if converted_params_list else ()
168
170
 
169
171
  cursor.execute(sql, params_for_execute)
170
172
  if self.returns_rows(statement.expression):
@@ -199,10 +201,10 @@ class SqliteDriver(
199
201
  conn = connection if connection is not None else self._connection(None)
200
202
  with managed_transaction_sync(conn, auto_commit=True) as txn_conn:
201
203
  # Normalize parameter list using consolidated utility
202
- normalized_param_list = normalize_parameter_sequence(param_list)
204
+ converted_param_list = convert_parameter_sequence(param_list)
203
205
  formatted_params: list[tuple[Any, ...]] = []
204
- if normalized_param_list:
205
- for param_set in normalized_param_list:
206
+ if converted_param_list:
207
+ for param_set in converted_param_list:
206
208
  if isinstance(param_set, (list, tuple)):
207
209
  formatted_params.append(tuple(param_set))
208
210
  elif param_set is None:
@@ -227,12 +229,34 @@ class SqliteDriver(
227
229
  def _execute_script(
228
230
  self, script: str, connection: Optional[SqliteConnection] = None, statement: Optional[SQL] = None, **kwargs: Any
229
231
  ) -> SQLResult[RowT]:
230
- """Execute a script on the SQLite connection."""
231
- # Use provided connection or driver's default connection
232
+ """Execute script using splitter for per-statement validation."""
233
+ from sqlspec.statement.splitter import split_sql_script
234
+
232
235
  conn = connection if connection is not None else self._connection(None)
236
+ statements = split_sql_script(script, dialect="sqlite")
237
+
238
+ total_rows = 0
239
+ successful = 0
240
+ suppress_warnings = kwargs.get("_suppress_warnings", False)
241
+
233
242
  with self._get_cursor(conn) as cursor:
234
- cursor.executescript(script)
235
- # executescript doesn't auto-commit in some cases - force commit
243
+ for stmt in statements:
244
+ try:
245
+ # Validate each statement unless warnings suppressed
246
+ if not suppress_warnings and statement:
247
+ # Run validation through pipeline
248
+ temp_sql = SQL(stmt, config=statement._config)
249
+ temp_sql._ensure_processed()
250
+ # Validation errors are logged as warnings by default
251
+
252
+ cursor.execute(stmt)
253
+ successful += 1
254
+ total_rows += cursor.rowcount or 0
255
+ except Exception as e: # noqa: PERF203
256
+ if not kwargs.get("continue_on_error", False):
257
+ raise
258
+ logger.warning("Script statement failed: %s", e)
259
+
236
260
  conn.commit()
237
261
 
238
262
  if statement is None:
@@ -241,10 +265,10 @@ class SqliteDriver(
241
265
  return SQLResult(
242
266
  statement=statement,
243
267
  data=[],
244
- rows_affected=-1, # Unknown for scripts
268
+ rows_affected=total_rows,
245
269
  operation_type="SCRIPT",
246
- total_statements=-1, # SQLite doesn't provide this info
247
- successful_statements=-1,
270
+ total_statements=len(statements),
271
+ successful_statements=successful,
248
272
  metadata={"status_message": "SCRIPT EXECUTED"},
249
273
  )
250
274