sqlspec 0.14.0__py3-none-any.whl → 0.15.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 (158) hide show
  1. sqlspec/__init__.py +50 -25
  2. sqlspec/__main__.py +12 -0
  3. sqlspec/__metadata__.py +1 -3
  4. sqlspec/_serialization.py +1 -2
  5. sqlspec/_sql.py +256 -120
  6. sqlspec/_typing.py +278 -142
  7. sqlspec/adapters/adbc/__init__.py +4 -3
  8. sqlspec/adapters/adbc/_types.py +12 -0
  9. sqlspec/adapters/adbc/config.py +115 -248
  10. sqlspec/adapters/adbc/driver.py +462 -353
  11. sqlspec/adapters/aiosqlite/__init__.py +18 -3
  12. sqlspec/adapters/aiosqlite/_types.py +13 -0
  13. sqlspec/adapters/aiosqlite/config.py +199 -129
  14. sqlspec/adapters/aiosqlite/driver.py +230 -269
  15. sqlspec/adapters/asyncmy/__init__.py +18 -3
  16. sqlspec/adapters/asyncmy/_types.py +12 -0
  17. sqlspec/adapters/asyncmy/config.py +80 -168
  18. sqlspec/adapters/asyncmy/driver.py +260 -225
  19. sqlspec/adapters/asyncpg/__init__.py +19 -4
  20. sqlspec/adapters/asyncpg/_types.py +17 -0
  21. sqlspec/adapters/asyncpg/config.py +82 -181
  22. sqlspec/adapters/asyncpg/driver.py +285 -383
  23. sqlspec/adapters/bigquery/__init__.py +17 -3
  24. sqlspec/adapters/bigquery/_types.py +12 -0
  25. sqlspec/adapters/bigquery/config.py +191 -258
  26. sqlspec/adapters/bigquery/driver.py +474 -646
  27. sqlspec/adapters/duckdb/__init__.py +14 -3
  28. sqlspec/adapters/duckdb/_types.py +12 -0
  29. sqlspec/adapters/duckdb/config.py +415 -351
  30. sqlspec/adapters/duckdb/driver.py +343 -413
  31. sqlspec/adapters/oracledb/__init__.py +19 -5
  32. sqlspec/adapters/oracledb/_types.py +14 -0
  33. sqlspec/adapters/oracledb/config.py +123 -379
  34. sqlspec/adapters/oracledb/driver.py +507 -560
  35. sqlspec/adapters/psqlpy/__init__.py +13 -3
  36. sqlspec/adapters/psqlpy/_types.py +11 -0
  37. sqlspec/adapters/psqlpy/config.py +93 -254
  38. sqlspec/adapters/psqlpy/driver.py +505 -234
  39. sqlspec/adapters/psycopg/__init__.py +19 -5
  40. sqlspec/adapters/psycopg/_types.py +17 -0
  41. sqlspec/adapters/psycopg/config.py +143 -403
  42. sqlspec/adapters/psycopg/driver.py +706 -872
  43. sqlspec/adapters/sqlite/__init__.py +14 -3
  44. sqlspec/adapters/sqlite/_types.py +11 -0
  45. sqlspec/adapters/sqlite/config.py +202 -118
  46. sqlspec/adapters/sqlite/driver.py +264 -303
  47. sqlspec/base.py +105 -9
  48. sqlspec/{statement/builder → builder}/__init__.py +12 -14
  49. sqlspec/{statement/builder → builder}/_base.py +120 -55
  50. sqlspec/{statement/builder → builder}/_column.py +17 -6
  51. sqlspec/{statement/builder → builder}/_ddl.py +46 -79
  52. sqlspec/{statement/builder → builder}/_ddl_utils.py +5 -10
  53. sqlspec/{statement/builder → builder}/_delete.py +6 -25
  54. sqlspec/{statement/builder → builder}/_insert.py +6 -64
  55. sqlspec/builder/_merge.py +56 -0
  56. sqlspec/{statement/builder → builder}/_parsing_utils.py +3 -10
  57. sqlspec/{statement/builder → builder}/_select.py +11 -56
  58. sqlspec/{statement/builder → builder}/_update.py +12 -18
  59. sqlspec/{statement/builder → builder}/mixins/__init__.py +10 -14
  60. sqlspec/{statement/builder → builder}/mixins/_cte_and_set_ops.py +48 -59
  61. sqlspec/{statement/builder → builder}/mixins/_insert_operations.py +22 -16
  62. sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
  63. sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +3 -5
  64. sqlspec/{statement/builder → builder}/mixins/_order_limit_operations.py +3 -3
  65. sqlspec/{statement/builder → builder}/mixins/_pivot_operations.py +4 -8
  66. sqlspec/{statement/builder → builder}/mixins/_select_operations.py +21 -36
  67. sqlspec/{statement/builder → builder}/mixins/_update_operations.py +3 -14
  68. sqlspec/{statement/builder → builder}/mixins/_where_clause.py +52 -79
  69. sqlspec/cli.py +4 -5
  70. sqlspec/config.py +180 -133
  71. sqlspec/core/__init__.py +63 -0
  72. sqlspec/core/cache.py +873 -0
  73. sqlspec/core/compiler.py +396 -0
  74. sqlspec/core/filters.py +828 -0
  75. sqlspec/core/hashing.py +310 -0
  76. sqlspec/core/parameters.py +1209 -0
  77. sqlspec/core/result.py +664 -0
  78. sqlspec/{statement → core}/splitter.py +321 -191
  79. sqlspec/core/statement.py +651 -0
  80. sqlspec/driver/__init__.py +7 -10
  81. sqlspec/driver/_async.py +387 -176
  82. sqlspec/driver/_common.py +527 -289
  83. sqlspec/driver/_sync.py +390 -172
  84. sqlspec/driver/mixins/__init__.py +2 -19
  85. sqlspec/driver/mixins/_result_tools.py +168 -0
  86. sqlspec/driver/mixins/_sql_translator.py +6 -3
  87. sqlspec/exceptions.py +5 -252
  88. sqlspec/extensions/aiosql/adapter.py +93 -96
  89. sqlspec/extensions/litestar/config.py +0 -1
  90. sqlspec/extensions/litestar/handlers.py +15 -26
  91. sqlspec/extensions/litestar/plugin.py +16 -14
  92. sqlspec/extensions/litestar/providers.py +17 -52
  93. sqlspec/loader.py +424 -105
  94. sqlspec/migrations/__init__.py +12 -0
  95. sqlspec/migrations/base.py +92 -68
  96. sqlspec/migrations/commands.py +24 -106
  97. sqlspec/migrations/loaders.py +402 -0
  98. sqlspec/migrations/runner.py +49 -51
  99. sqlspec/migrations/tracker.py +31 -44
  100. sqlspec/migrations/utils.py +64 -24
  101. sqlspec/protocols.py +7 -183
  102. sqlspec/storage/__init__.py +1 -1
  103. sqlspec/storage/backends/base.py +37 -40
  104. sqlspec/storage/backends/fsspec.py +136 -112
  105. sqlspec/storage/backends/obstore.py +138 -160
  106. sqlspec/storage/capabilities.py +5 -4
  107. sqlspec/storage/registry.py +57 -106
  108. sqlspec/typing.py +136 -115
  109. sqlspec/utils/__init__.py +2 -3
  110. sqlspec/utils/correlation.py +0 -3
  111. sqlspec/utils/deprecation.py +6 -6
  112. sqlspec/utils/fixtures.py +6 -6
  113. sqlspec/utils/logging.py +0 -2
  114. sqlspec/utils/module_loader.py +7 -12
  115. sqlspec/utils/singleton.py +0 -1
  116. sqlspec/utils/sync_tools.py +16 -37
  117. sqlspec/utils/text.py +12 -51
  118. sqlspec/utils/type_guards.py +443 -232
  119. {sqlspec-0.14.0.dist-info → sqlspec-0.15.0.dist-info}/METADATA +7 -2
  120. sqlspec-0.15.0.dist-info/RECORD +134 -0
  121. sqlspec-0.15.0.dist-info/entry_points.txt +2 -0
  122. sqlspec/driver/connection.py +0 -207
  123. sqlspec/driver/mixins/_cache.py +0 -114
  124. sqlspec/driver/mixins/_csv_writer.py +0 -91
  125. sqlspec/driver/mixins/_pipeline.py +0 -508
  126. sqlspec/driver/mixins/_query_tools.py +0 -796
  127. sqlspec/driver/mixins/_result_utils.py +0 -138
  128. sqlspec/driver/mixins/_storage.py +0 -912
  129. sqlspec/driver/mixins/_type_coercion.py +0 -128
  130. sqlspec/driver/parameters.py +0 -138
  131. sqlspec/statement/__init__.py +0 -21
  132. sqlspec/statement/builder/_merge.py +0 -95
  133. sqlspec/statement/cache.py +0 -50
  134. sqlspec/statement/filters.py +0 -625
  135. sqlspec/statement/parameters.py +0 -996
  136. sqlspec/statement/pipelines/__init__.py +0 -210
  137. sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
  138. sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
  139. sqlspec/statement/pipelines/context.py +0 -115
  140. sqlspec/statement/pipelines/transformers/__init__.py +0 -7
  141. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
  142. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
  143. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
  144. sqlspec/statement/pipelines/validators/__init__.py +0 -23
  145. sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
  146. sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
  147. sqlspec/statement/pipelines/validators/_performance.py +0 -714
  148. sqlspec/statement/pipelines/validators/_security.py +0 -967
  149. sqlspec/statement/result.py +0 -435
  150. sqlspec/statement/sql.py +0 -1774
  151. sqlspec/utils/cached_property.py +0 -25
  152. sqlspec/utils/statement_hashing.py +0 -203
  153. sqlspec-0.14.0.dist-info/RECORD +0 -143
  154. sqlspec-0.14.0.dist-info/entry_points.txt +0 -2
  155. /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
  156. {sqlspec-0.14.0.dist-info → sqlspec-0.15.0.dist-info}/WHEEL +0 -0
  157. {sqlspec-0.14.0.dist-info → sqlspec-0.15.0.dist-info}/licenses/LICENSE +0 -0
  158. {sqlspec-0.14.0.dist-info → sqlspec-0.15.0.dist-info}/licenses/NOTICE +0 -0
@@ -1,287 +1,248 @@
1
- import csv
2
- import logging
3
- from collections.abc import AsyncGenerator, Sequence
4
- from contextlib import asynccontextmanager
5
- from pathlib import Path
1
+ """AIOSQLite driver implementation for async SQLite operations.
2
+
3
+ Provides async SQLite database connectivity with:
4
+ - Async parameter processing with type coercion
5
+ - Thread-safe caching system
6
+ - Context management for resource handling
7
+ - SQLite-specific optimizations
8
+ """
9
+
10
+ import contextlib
11
+ import datetime
12
+ from decimal import Decimal
6
13
  from typing import TYPE_CHECKING, Any, Optional
7
14
 
8
15
  import aiosqlite
9
16
 
10
- from sqlspec.driver import AsyncDriverAdapterProtocol
11
- from sqlspec.driver.connection import managed_transaction_async
12
- from sqlspec.driver.mixins import (
13
- AsyncAdapterCacheMixin,
14
- AsyncPipelinedExecutionMixin,
15
- AsyncStorageMixin,
16
- SQLTranslatorMixin,
17
- ToSchemaMixin,
18
- TypeCoercionMixin,
19
- )
20
- from sqlspec.driver.parameters import convert_parameter_sequence
21
- from sqlspec.statement.parameters import ParameterStyle, ParameterValidator
22
- from sqlspec.statement.result import SQLResult
23
- from sqlspec.statement.sql import SQL, SQLConfig
24
- from sqlspec.typing import DictRow, RowT
17
+ from sqlspec.core.cache import get_cache_config
18
+ from sqlspec.core.parameters import ParameterStyle, ParameterStyleConfig
19
+ from sqlspec.core.statement import StatementConfig
20
+ from sqlspec.driver import AsyncDriverAdapterBase
21
+ from sqlspec.exceptions import SQLParsingError, SQLSpecError
25
22
  from sqlspec.utils.serializers import to_json
26
23
 
27
24
  if TYPE_CHECKING:
28
- from sqlglot.dialects.dialect import DialectType
25
+ from contextlib import AbstractAsyncContextManager
26
+
27
+ from sqlspec.adapters.aiosqlite._types import AiosqliteConnection
28
+ from sqlspec.core.result import SQLResult
29
+ from sqlspec.core.statement import SQL
30
+ from sqlspec.driver import ExecutionResult
31
+
32
+ __all__ = ("AiosqliteCursor", "AiosqliteDriver", "AiosqliteExceptionHandler", "aiosqlite_statement_config")
33
+
34
+
35
+ aiosqlite_statement_config = StatementConfig(
36
+ dialect="sqlite",
37
+ parameter_config=ParameterStyleConfig(
38
+ default_parameter_style=ParameterStyle.QMARK,
39
+ supported_parameter_styles={ParameterStyle.QMARK},
40
+ default_execution_parameter_style=ParameterStyle.QMARK,
41
+ supported_execution_parameter_styles={ParameterStyle.QMARK},
42
+ type_coercion_map={
43
+ bool: int,
44
+ datetime.datetime: lambda v: v.isoformat(),
45
+ datetime.date: lambda v: v.isoformat(),
46
+ Decimal: str,
47
+ dict: to_json,
48
+ list: to_json,
49
+ tuple: lambda v: to_json(list(v)),
50
+ },
51
+ has_native_list_expansion=False,
52
+ needs_static_script_compilation=False,
53
+ preserve_parameter_format=True,
54
+ ),
55
+ enable_parsing=True,
56
+ enable_validation=True,
57
+ enable_caching=True,
58
+ enable_parameter_type_wrapping=True,
59
+ )
29
60
 
30
- __all__ = ("AiosqliteConnection", "AiosqliteDriver")
31
61
 
32
- logger = logging.getLogger("sqlspec")
62
+ class AiosqliteCursor:
63
+ """Async context manager for AIOSQLite cursor management."""
64
+
65
+ __slots__ = ("connection", "cursor")
66
+
67
+ def __init__(self, connection: "AiosqliteConnection") -> None:
68
+ self.connection = connection
69
+ self.cursor: Optional[aiosqlite.Cursor] = None
70
+
71
+ async def __aenter__(self) -> "aiosqlite.Cursor":
72
+ self.cursor = await self.connection.cursor()
73
+ return self.cursor
74
+
75
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
76
+ _ = (exc_type, exc_val, exc_tb)
77
+ if self.cursor is not None:
78
+ with contextlib.suppress(Exception):
79
+ await self.cursor.close()
80
+
81
+
82
+ class AiosqliteExceptionHandler:
83
+ """Custom async context manager for handling AIOSQLite database exceptions."""
84
+
85
+ __slots__ = ()
86
+
87
+ async def __aenter__(self) -> None:
88
+ return None
89
+
90
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
91
+ if exc_type is None:
92
+ return
93
+ if issubclass(exc_type, aiosqlite.IntegrityError):
94
+ e = exc_val
95
+ msg = f"AIOSQLite integrity constraint violation: {e}"
96
+ raise SQLSpecError(msg) from e
97
+ if issubclass(exc_type, aiosqlite.OperationalError):
98
+ e = exc_val
99
+ error_msg = str(e).lower()
100
+ if "locked" in error_msg:
101
+ msg = f"AIOSQLite database locked: {e}. Consider enabling WAL mode or reducing concurrency."
102
+ raise SQLSpecError(msg) from e
103
+ if "syntax" in error_msg or "malformed" in error_msg:
104
+ msg = f"AIOSQLite SQL syntax error: {e}"
105
+ raise SQLParsingError(msg) from e
106
+ msg = f"AIOSQLite operational error: {e}"
107
+ raise SQLSpecError(msg) from e
108
+ if issubclass(exc_type, aiosqlite.DatabaseError):
109
+ e = exc_val
110
+ msg = f"AIOSQLite database error: {e}"
111
+ raise SQLSpecError(msg) from e
112
+ if issubclass(exc_type, aiosqlite.Error):
113
+ e = exc_val
114
+ msg = f"AIOSQLite error: {e}"
115
+ raise SQLSpecError(msg) from e
116
+ if issubclass(exc_type, Exception):
117
+ e = exc_val
118
+ error_msg = str(e).lower()
119
+ if "parse" in error_msg or "syntax" in error_msg:
120
+ msg = f"SQL parsing failed: {e}"
121
+ raise SQLParsingError(msg) from e
122
+ msg = f"Unexpected async database operation error: {e}"
123
+ raise SQLSpecError(msg) from e
124
+
125
+
126
+ class AiosqliteDriver(AsyncDriverAdapterBase):
127
+ """AIOSQLite driver for async SQLite database operations.
128
+
129
+ Provides async SQLite connectivity with:
130
+ - Statement processing and parameter handling
131
+ - Cursor management and resource cleanup
132
+ - Exception handling for SQLite operations
133
+ """
134
+
135
+ __slots__ = ()
136
+ dialect = "sqlite"
33
137
 
34
- AiosqliteConnection = aiosqlite.Connection
138
+ def __init__(
139
+ self,
140
+ connection: "AiosqliteConnection",
141
+ statement_config: "Optional[StatementConfig]" = None,
142
+ driver_features: "Optional[dict[str, Any]]" = None,
143
+ ) -> None:
144
+ if statement_config is None:
145
+ cache_config = get_cache_config()
146
+ enhanced_config = aiosqlite_statement_config.replace(
147
+ enable_caching=cache_config.compiled_cache_enabled,
148
+ enable_parsing=True,
149
+ enable_validation=True,
150
+ dialect="sqlite",
151
+ )
152
+ statement_config = enhanced_config
35
153
 
154
+ super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
36
155
 
37
- class AiosqliteDriver(
38
- AsyncDriverAdapterProtocol[AiosqliteConnection, RowT],
39
- AsyncAdapterCacheMixin,
40
- SQLTranslatorMixin,
41
- TypeCoercionMixin,
42
- AsyncStorageMixin,
43
- AsyncPipelinedExecutionMixin,
44
- ToSchemaMixin,
45
- ):
46
- """Aiosqlite SQLite Driver Adapter. Modern protocol implementation."""
156
+ def with_cursor(self, connection: "AiosqliteConnection") -> "AiosqliteCursor":
157
+ """Create async context manager for AIOSQLite cursor."""
158
+ return AiosqliteCursor(connection)
47
159
 
48
- dialect: "DialectType" = "sqlite"
49
- supported_parameter_styles: "tuple[ParameterStyle, ...]" = (ParameterStyle.QMARK, ParameterStyle.NAMED_COLON)
50
- default_parameter_style: ParameterStyle = ParameterStyle.QMARK
160
+ def handle_database_exceptions(self) -> "AbstractAsyncContextManager[None]":
161
+ """Handle AIOSQLite-specific exceptions."""
162
+ return AiosqliteExceptionHandler()
51
163
 
52
- def __init__(
53
- self,
54
- connection: AiosqliteConnection,
55
- config: "Optional[SQLConfig]" = None,
56
- default_row_type: "type[DictRow]" = DictRow,
57
- ) -> None:
58
- super().__init__(connection=connection, config=config, default_row_type=default_row_type)
59
-
60
- # AIOSQLite-specific type coercion overrides (same as SQLite)
61
- def _coerce_boolean(self, value: Any) -> Any:
62
- """AIOSQLite/SQLite stores booleans as integers (0/1)."""
63
- if isinstance(value, bool):
64
- return 1 if value else 0
65
- return value
66
-
67
- def _coerce_decimal(self, value: Any) -> Any:
68
- """AIOSQLite/SQLite stores decimals as strings to preserve precision."""
69
- if isinstance(value, str):
70
- return value # Already a string
71
- from decimal import Decimal
72
-
73
- if isinstance(value, Decimal):
74
- return str(value)
75
- return value
76
-
77
- def _coerce_json(self, value: Any) -> Any:
78
- """AIOSQLite/SQLite stores JSON as strings (requires JSON1 extension)."""
79
- if isinstance(value, (dict, list)):
80
- return to_json(value)
81
- return value
82
-
83
- def _coerce_array(self, value: Any) -> Any:
84
- """AIOSQLite/SQLite doesn't have native arrays - store as JSON strings."""
85
- if isinstance(value, (list, tuple)):
86
- return to_json(list(value))
87
- return value
88
-
89
- @asynccontextmanager
90
- async def _get_cursor(
91
- self, connection: Optional[AiosqliteConnection] = None
92
- ) -> AsyncGenerator[aiosqlite.Cursor, None]:
93
- conn_to_use = connection or self.connection
94
- conn_to_use.row_factory = aiosqlite.Row
95
- cursor = await conn_to_use.cursor()
96
- try:
97
- yield cursor
98
- finally:
99
- await cursor.close()
100
-
101
- async def _execute_statement(
102
- self, statement: SQL, connection: Optional[AiosqliteConnection] = None, **kwargs: Any
103
- ) -> SQLResult[RowT]:
104
- if statement.is_script:
105
- sql, _ = self._get_compiled_sql(statement, ParameterStyle.STATIC)
106
- return await self._execute_script(sql, connection=connection, **kwargs)
107
-
108
- detected_styles = set()
109
- sql_str = statement.to_sql(placeholder_style=None) # Get raw SQL
110
- validator = self.config.parameter_validator if self.config else ParameterValidator()
111
- param_infos = validator.extract_parameters(sql_str)
112
- if param_infos:
113
- detected_styles = {p.style for p in param_infos}
114
-
115
- target_style = self.default_parameter_style
116
-
117
- unsupported_styles = detected_styles - set(self.supported_parameter_styles)
118
- if unsupported_styles:
119
- target_style = self.default_parameter_style
120
- elif detected_styles:
121
- # Prefer the first supported style found
122
- for style in detected_styles:
123
- if style in self.supported_parameter_styles:
124
- target_style = style
125
- break
126
-
127
- if statement.is_many:
128
- sql, params = self._get_compiled_sql(statement, target_style)
129
-
130
- params = self._process_parameters(params)
131
-
132
- return await self._execute_many(sql, params, connection=connection, **kwargs)
133
-
134
- sql, params = self._get_compiled_sql(statement, target_style)
135
-
136
- params = self._process_parameters(params)
137
-
138
- return await self._execute(sql, params, statement, connection=connection, **kwargs)
139
-
140
- async def _execute(
141
- self, sql: str, parameters: Any, statement: SQL, connection: Optional[AiosqliteConnection] = None, **kwargs: Any
142
- ) -> SQLResult[RowT]:
143
- conn = self._connection(connection)
144
-
145
- async with managed_transaction_async(conn, auto_commit=True) as txn_conn:
146
- converted_params = convert_parameter_sequence(parameters)
147
-
148
- # Extract the actual parameters from the converted list
149
- actual_params = converted_params[0] if converted_params and len(converted_params) == 1 else converted_params
150
-
151
- # AIOSQLite expects tuple or dict - handle parameter conversion
152
- if ":param_" in sql or (isinstance(actual_params, dict)):
153
- # SQL has named placeholders, ensure params are dict
154
- converted_params = self._convert_parameters_to_driver_format(
155
- sql, actual_params, target_style=ParameterStyle.NAMED_COLON
156
- )
157
- else:
158
- # SQL has positional placeholders, ensure params are list/tuple
159
- converted_params = self._convert_parameters_to_driver_format(
160
- sql, actual_params, target_style=ParameterStyle.QMARK
161
- )
162
-
163
- async with self._get_cursor(txn_conn) as cursor:
164
- # Aiosqlite handles both dict and tuple parameters
165
- await cursor.execute(sql, converted_params or ())
166
- if self.returns_rows(statement.expression):
167
- fetched_data = await cursor.fetchall()
168
- column_names = [desc[0] for desc in cursor.description or []]
169
- data_list: list[Any] = list(fetched_data) if fetched_data else []
170
- return SQLResult(
171
- statement=statement,
172
- data=data_list,
173
- column_names=column_names,
174
- rows_affected=len(data_list),
175
- operation_type="SELECT",
176
- )
177
-
178
- return SQLResult(
179
- statement=statement,
180
- data=[],
181
- rows_affected=cursor.rowcount,
182
- operation_type=self._determine_operation_type(statement),
183
- metadata={"status_message": "OK"},
184
- )
185
-
186
- async def _execute_many(
187
- self, sql: str, param_list: Any, connection: Optional[AiosqliteConnection] = None, **kwargs: Any
188
- ) -> SQLResult[RowT]:
189
- # Use provided connection or driver's default connection
190
- conn = connection if connection is not None else self._connection(None)
191
-
192
- async with managed_transaction_async(conn, auto_commit=True) as txn_conn:
193
- # Normalize parameter list using consolidated utility
194
- converted_param_list = convert_parameter_sequence(param_list)
195
-
196
- params_list: list[tuple[Any, ...]] = []
197
- if converted_param_list and isinstance(converted_param_list, Sequence):
198
- for param_set in converted_param_list:
199
- if isinstance(param_set, (list, tuple)):
200
- params_list.append(tuple(param_set))
201
- elif param_set is None:
202
- params_list.append(())
203
-
204
- async with self._get_cursor(txn_conn) as cursor:
205
- await cursor.executemany(sql, params_list)
206
- return SQLResult(
207
- statement=SQL(sql, _dialect=self.dialect),
208
- data=[],
209
- rows_affected=cursor.rowcount,
210
- operation_type="EXECUTE",
211
- metadata={"status_message": "OK"},
212
- )
213
-
214
- async def _execute_script(
215
- self, script: str, connection: Optional[AiosqliteConnection] = None, **kwargs: Any
216
- ) -> SQLResult[RowT]:
217
- # Use provided connection or driver's default connection
218
- conn = connection if connection is not None else self._connection(None)
219
-
220
- async with managed_transaction_async(conn, auto_commit=True) as txn_conn:
221
- # Split script into individual statements for validation
222
- statements = self._split_script_statements(script)
223
- suppress_warnings = kwargs.get("_suppress_warnings", False)
224
-
225
- executed_count = 0
226
- total_rows = 0
227
-
228
- # Execute each statement individually for better control and validation
229
- async with self._get_cursor(txn_conn) as cursor:
230
- for statement in statements:
231
- if statement.strip():
232
- # Validate each statement unless warnings suppressed
233
- if not suppress_warnings:
234
- # Run validation through pipeline
235
- temp_sql = SQL(statement, config=self.config)
236
- temp_sql._ensure_processed()
237
- # Validation errors are logged as warnings by default
238
-
239
- await cursor.execute(statement)
240
- executed_count += 1
241
- total_rows += cursor.rowcount or 0
242
-
243
- return SQLResult(
244
- statement=SQL(script, _dialect=self.dialect).as_script(),
245
- data=[],
246
- rows_affected=total_rows,
247
- operation_type="SCRIPT",
248
- metadata={"status_message": "SCRIPT EXECUTED"},
249
- total_statements=executed_count,
250
- successful_statements=executed_count,
164
+ async def _try_special_handling(self, cursor: "aiosqlite.Cursor", statement: "SQL") -> "Optional[SQLResult]":
165
+ """Hook for AIOSQLite-specific special operations.
166
+
167
+ Args:
168
+ cursor: AIOSQLite cursor object
169
+ statement: SQL statement to analyze
170
+
171
+ Returns:
172
+ None - always proceeds with standard execution for AIOSQLite
173
+ """
174
+ _ = (cursor, statement)
175
+ return None
176
+
177
+ async def _execute_script(self, cursor: "aiosqlite.Cursor", statement: "SQL") -> "ExecutionResult":
178
+ """Execute SQL script using statement splitting and parameter handling."""
179
+ sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
180
+ statements = self.split_script_statements(sql, statement.statement_config, strip_trailing_semicolon=True)
181
+
182
+ successful_count = 0
183
+ last_cursor = cursor
184
+
185
+ for stmt in statements:
186
+ await cursor.execute(stmt, prepared_parameters or ())
187
+ successful_count += 1
188
+
189
+ return self.create_execution_result(
190
+ last_cursor, statement_count=len(statements), successful_statements=successful_count, is_script_result=True
191
+ )
192
+
193
+ async def _execute_many(self, cursor: "aiosqlite.Cursor", statement: "SQL") -> "ExecutionResult":
194
+ """Execute SQL with multiple parameter sets using async batch processing."""
195
+ sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
196
+
197
+ if not prepared_parameters:
198
+ msg = "execute_many requires parameters"
199
+ raise ValueError(msg)
200
+
201
+ await cursor.executemany(sql, prepared_parameters)
202
+
203
+ affected_rows = cursor.rowcount if cursor.rowcount and cursor.rowcount > 0 else 0
204
+
205
+ return self.create_execution_result(cursor, rowcount_override=affected_rows, is_many_result=True)
206
+
207
+ async def _execute_statement(self, cursor: "aiosqlite.Cursor", statement: "SQL") -> "ExecutionResult":
208
+ """Execute single SQL statement with async data handling."""
209
+ sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
210
+ await cursor.execute(sql, prepared_parameters or ())
211
+
212
+ if statement.returns_rows():
213
+ fetched_data = await cursor.fetchall()
214
+ column_names = [col[0] for col in cursor.description or []]
215
+
216
+ data = [dict(zip(column_names, row)) for row in fetched_data]
217
+
218
+ return self.create_execution_result(
219
+ cursor, selected_data=data, column_names=column_names, data_row_count=len(data), is_select_result=True
251
220
  )
252
221
 
253
- async def _bulk_load_file(self, file_path: Path, table_name: str, format: str, mode: str, **options: Any) -> int:
254
- """Database-specific bulk load implementation using storage backend."""
255
- if format != "csv":
256
- msg = f"aiosqlite driver only supports CSV for bulk loading, not {format}."
257
- raise NotImplementedError(msg)
222
+ affected_rows = cursor.rowcount if cursor.rowcount and cursor.rowcount > 0 else 0
223
+ return self.create_execution_result(cursor, rowcount_override=affected_rows)
224
+
225
+ async def begin(self) -> None:
226
+ """Begin a database transaction."""
227
+ try:
228
+ if not self.connection.in_transaction:
229
+ await self.connection.execute("BEGIN")
230
+ except aiosqlite.Error as e:
231
+ msg = f"Failed to begin transaction: {e}"
232
+ raise SQLSpecError(msg) from e
233
+
234
+ async def rollback(self) -> None:
235
+ """Rollback the current transaction."""
236
+ try:
237
+ await self.connection.rollback()
238
+ except aiosqlite.Error as e:
239
+ msg = f"Failed to rollback transaction: {e}"
240
+ raise SQLSpecError(msg) from e
258
241
 
259
- conn = await self._create_connection() # type: ignore[attr-defined]
242
+ async def commit(self) -> None:
243
+ """Commit the current transaction."""
260
244
  try:
261
- async with self._get_cursor(conn) as cursor:
262
- if mode == "replace":
263
- await cursor.execute(f"DELETE FROM {table_name}")
264
-
265
- # Use async storage backend to read the file
266
- file_path_str = str(file_path)
267
- backend = self._get_storage_backend(file_path_str)
268
- content = await backend.read_text_async(file_path_str, encoding="utf-8")
269
- # Parse CSV content
270
- import io
271
-
272
- csv_file = io.StringIO(content)
273
- reader = csv.reader(csv_file, **options)
274
- header = next(reader) # Skip header
275
- placeholders = ", ".join("?" for _ in header)
276
- sql = f"INSERT INTO {table_name} VALUES ({placeholders})"
277
- data_iter = list(reader)
278
- await cursor.executemany(sql, data_iter)
279
- rowcount = cursor.rowcount
280
- await conn.commit()
281
- return rowcount
282
- finally:
283
- await conn.close()
284
-
285
- def _connection(self, connection: Optional[AiosqliteConnection] = None) -> AiosqliteConnection:
286
- """Get the connection to use for the operation."""
287
- return connection or self.connection
245
+ await self.connection.commit()
246
+ except aiosqlite.Error as e:
247
+ msg = f"Failed to commit transaction: {e}"
248
+ raise SQLSpecError(msg) from e
@@ -1,4 +1,19 @@
1
- from sqlspec.adapters.asyncmy.config import CONNECTION_FIELDS, POOL_FIELDS, AsyncmyConfig
2
- from sqlspec.adapters.asyncmy.driver import AsyncmyConnection, AsyncmyDriver
1
+ from sqlspec.adapters.asyncmy._types import AsyncmyConnection
2
+ from sqlspec.adapters.asyncmy.config import AsyncmyConfig, AsyncmyConnectionParams, AsyncmyPoolParams
3
+ from sqlspec.adapters.asyncmy.driver import (
4
+ AsyncmyCursor,
5
+ AsyncmyDriver,
6
+ AsyncmyExceptionHandler,
7
+ asyncmy_statement_config,
8
+ )
3
9
 
4
- __all__ = ("CONNECTION_FIELDS", "POOL_FIELDS", "AsyncmyConfig", "AsyncmyConnection", "AsyncmyDriver")
10
+ __all__ = (
11
+ "AsyncmyConfig",
12
+ "AsyncmyConnection",
13
+ "AsyncmyConnectionParams",
14
+ "AsyncmyCursor",
15
+ "AsyncmyDriver",
16
+ "AsyncmyExceptionHandler",
17
+ "AsyncmyPoolParams",
18
+ "asyncmy_statement_config",
19
+ )
@@ -0,0 +1,12 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from asyncmy import Connection
4
+
5
+ if TYPE_CHECKING:
6
+ from typing_extensions import TypeAlias
7
+
8
+ AsyncmyConnection: TypeAlias = Connection
9
+ else:
10
+ AsyncmyConnection = Connection
11
+
12
+ __all__ = ("AsyncmyConnection",)