sqlspec 0.13.1__py3-none-any.whl → 0.16.2__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 (185) hide show
  1. sqlspec/__init__.py +71 -8
  2. sqlspec/__main__.py +12 -0
  3. sqlspec/__metadata__.py +1 -3
  4. sqlspec/_serialization.py +1 -2
  5. sqlspec/_sql.py +930 -136
  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 +116 -285
  10. sqlspec/adapters/adbc/driver.py +462 -340
  11. sqlspec/adapters/aiosqlite/__init__.py +18 -3
  12. sqlspec/adapters/aiosqlite/_types.py +13 -0
  13. sqlspec/adapters/aiosqlite/config.py +202 -150
  14. sqlspec/adapters/aiosqlite/driver.py +226 -247
  15. sqlspec/adapters/asyncmy/__init__.py +18 -3
  16. sqlspec/adapters/asyncmy/_types.py +12 -0
  17. sqlspec/adapters/asyncmy/config.py +80 -199
  18. sqlspec/adapters/asyncmy/driver.py +257 -215
  19. sqlspec/adapters/asyncpg/__init__.py +19 -4
  20. sqlspec/adapters/asyncpg/_types.py +17 -0
  21. sqlspec/adapters/asyncpg/config.py +81 -214
  22. sqlspec/adapters/asyncpg/driver.py +284 -359
  23. sqlspec/adapters/bigquery/__init__.py +17 -3
  24. sqlspec/adapters/bigquery/_types.py +12 -0
  25. sqlspec/adapters/bigquery/config.py +191 -299
  26. sqlspec/adapters/bigquery/driver.py +474 -634
  27. sqlspec/adapters/duckdb/__init__.py +14 -3
  28. sqlspec/adapters/duckdb/_types.py +12 -0
  29. sqlspec/adapters/duckdb/config.py +414 -397
  30. sqlspec/adapters/duckdb/driver.py +342 -393
  31. sqlspec/adapters/oracledb/__init__.py +19 -5
  32. sqlspec/adapters/oracledb/_types.py +14 -0
  33. sqlspec/adapters/oracledb/config.py +123 -458
  34. sqlspec/adapters/oracledb/driver.py +505 -531
  35. sqlspec/adapters/psqlpy/__init__.py +13 -3
  36. sqlspec/adapters/psqlpy/_types.py +11 -0
  37. sqlspec/adapters/psqlpy/config.py +93 -307
  38. sqlspec/adapters/psqlpy/driver.py +504 -213
  39. sqlspec/adapters/psycopg/__init__.py +19 -5
  40. sqlspec/adapters/psycopg/_types.py +17 -0
  41. sqlspec/adapters/psycopg/config.py +143 -472
  42. sqlspec/adapters/psycopg/driver.py +704 -825
  43. sqlspec/adapters/sqlite/__init__.py +14 -3
  44. sqlspec/adapters/sqlite/_types.py +11 -0
  45. sqlspec/adapters/sqlite/config.py +208 -142
  46. sqlspec/adapters/sqlite/driver.py +263 -278
  47. sqlspec/base.py +105 -9
  48. sqlspec/{statement/builder → builder}/__init__.py +12 -14
  49. sqlspec/{statement/builder/base.py → builder/_base.py} +184 -86
  50. sqlspec/{statement/builder/column.py → builder/_column.py} +97 -60
  51. sqlspec/{statement/builder/ddl.py → builder/_ddl.py} +61 -131
  52. sqlspec/{statement/builder → builder}/_ddl_utils.py +4 -10
  53. sqlspec/{statement/builder/delete.py → builder/_delete.py} +10 -30
  54. sqlspec/builder/_insert.py +421 -0
  55. sqlspec/builder/_merge.py +71 -0
  56. sqlspec/{statement/builder → builder}/_parsing_utils.py +49 -26
  57. sqlspec/builder/_select.py +170 -0
  58. sqlspec/{statement/builder/update.py → builder/_update.py} +16 -20
  59. sqlspec/builder/mixins/__init__.py +55 -0
  60. sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
  61. sqlspec/{statement/builder/mixins/_delete_from.py → builder/mixins/_delete_operations.py} +8 -1
  62. sqlspec/builder/mixins/_insert_operations.py +244 -0
  63. sqlspec/{statement/builder/mixins/_join.py → builder/mixins/_join_operations.py} +45 -13
  64. sqlspec/{statement/builder/mixins/_merge_clauses.py → builder/mixins/_merge_operations.py} +188 -30
  65. sqlspec/builder/mixins/_order_limit_operations.py +135 -0
  66. sqlspec/builder/mixins/_pivot_operations.py +153 -0
  67. sqlspec/builder/mixins/_select_operations.py +604 -0
  68. sqlspec/builder/mixins/_update_operations.py +202 -0
  69. sqlspec/builder/mixins/_where_clause.py +644 -0
  70. sqlspec/cli.py +247 -0
  71. sqlspec/config.py +183 -138
  72. sqlspec/core/__init__.py +63 -0
  73. sqlspec/core/cache.py +871 -0
  74. sqlspec/core/compiler.py +417 -0
  75. sqlspec/core/filters.py +830 -0
  76. sqlspec/core/hashing.py +310 -0
  77. sqlspec/core/parameters.py +1237 -0
  78. sqlspec/core/result.py +677 -0
  79. sqlspec/{statement → core}/splitter.py +321 -191
  80. sqlspec/core/statement.py +676 -0
  81. sqlspec/driver/__init__.py +7 -10
  82. sqlspec/driver/_async.py +422 -163
  83. sqlspec/driver/_common.py +545 -287
  84. sqlspec/driver/_sync.py +426 -160
  85. sqlspec/driver/mixins/__init__.py +2 -13
  86. sqlspec/driver/mixins/_result_tools.py +193 -0
  87. sqlspec/driver/mixins/_sql_translator.py +65 -14
  88. sqlspec/exceptions.py +5 -252
  89. sqlspec/extensions/aiosql/adapter.py +93 -96
  90. sqlspec/extensions/litestar/__init__.py +2 -1
  91. sqlspec/extensions/litestar/cli.py +48 -0
  92. sqlspec/extensions/litestar/config.py +0 -1
  93. sqlspec/extensions/litestar/handlers.py +15 -26
  94. sqlspec/extensions/litestar/plugin.py +21 -16
  95. sqlspec/extensions/litestar/providers.py +17 -52
  96. sqlspec/loader.py +423 -104
  97. sqlspec/migrations/__init__.py +35 -0
  98. sqlspec/migrations/base.py +414 -0
  99. sqlspec/migrations/commands.py +443 -0
  100. sqlspec/migrations/loaders.py +402 -0
  101. sqlspec/migrations/runner.py +213 -0
  102. sqlspec/migrations/tracker.py +140 -0
  103. sqlspec/migrations/utils.py +129 -0
  104. sqlspec/protocols.py +51 -186
  105. sqlspec/storage/__init__.py +1 -1
  106. sqlspec/storage/backends/base.py +37 -40
  107. sqlspec/storage/backends/fsspec.py +136 -112
  108. sqlspec/storage/backends/obstore.py +138 -160
  109. sqlspec/storage/capabilities.py +5 -4
  110. sqlspec/storage/registry.py +57 -106
  111. sqlspec/typing.py +136 -115
  112. sqlspec/utils/__init__.py +2 -2
  113. sqlspec/utils/correlation.py +0 -3
  114. sqlspec/utils/deprecation.py +6 -6
  115. sqlspec/utils/fixtures.py +6 -6
  116. sqlspec/utils/logging.py +0 -2
  117. sqlspec/utils/module_loader.py +7 -12
  118. sqlspec/utils/singleton.py +0 -1
  119. sqlspec/utils/sync_tools.py +17 -38
  120. sqlspec/utils/text.py +12 -51
  121. sqlspec/utils/type_guards.py +482 -235
  122. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/METADATA +7 -2
  123. sqlspec-0.16.2.dist-info/RECORD +134 -0
  124. sqlspec-0.16.2.dist-info/entry_points.txt +2 -0
  125. sqlspec/driver/connection.py +0 -207
  126. sqlspec/driver/mixins/_csv_writer.py +0 -91
  127. sqlspec/driver/mixins/_pipeline.py +0 -512
  128. sqlspec/driver/mixins/_result_utils.py +0 -140
  129. sqlspec/driver/mixins/_storage.py +0 -926
  130. sqlspec/driver/mixins/_type_coercion.py +0 -130
  131. sqlspec/driver/parameters.py +0 -138
  132. sqlspec/service/__init__.py +0 -4
  133. sqlspec/service/_util.py +0 -147
  134. sqlspec/service/base.py +0 -1131
  135. sqlspec/service/pagination.py +0 -26
  136. sqlspec/statement/__init__.py +0 -21
  137. sqlspec/statement/builder/insert.py +0 -288
  138. sqlspec/statement/builder/merge.py +0 -95
  139. sqlspec/statement/builder/mixins/__init__.py +0 -65
  140. sqlspec/statement/builder/mixins/_aggregate_functions.py +0 -250
  141. sqlspec/statement/builder/mixins/_case_builder.py +0 -91
  142. sqlspec/statement/builder/mixins/_common_table_expr.py +0 -90
  143. sqlspec/statement/builder/mixins/_from.py +0 -63
  144. sqlspec/statement/builder/mixins/_group_by.py +0 -118
  145. sqlspec/statement/builder/mixins/_having.py +0 -35
  146. sqlspec/statement/builder/mixins/_insert_from_select.py +0 -47
  147. sqlspec/statement/builder/mixins/_insert_into.py +0 -36
  148. sqlspec/statement/builder/mixins/_insert_values.py +0 -67
  149. sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
  150. sqlspec/statement/builder/mixins/_order_by.py +0 -46
  151. sqlspec/statement/builder/mixins/_pivot.py +0 -79
  152. sqlspec/statement/builder/mixins/_returning.py +0 -37
  153. sqlspec/statement/builder/mixins/_select_columns.py +0 -61
  154. sqlspec/statement/builder/mixins/_set_ops.py +0 -122
  155. sqlspec/statement/builder/mixins/_unpivot.py +0 -77
  156. sqlspec/statement/builder/mixins/_update_from.py +0 -55
  157. sqlspec/statement/builder/mixins/_update_set.py +0 -94
  158. sqlspec/statement/builder/mixins/_update_table.py +0 -29
  159. sqlspec/statement/builder/mixins/_where.py +0 -401
  160. sqlspec/statement/builder/mixins/_window_functions.py +0 -86
  161. sqlspec/statement/builder/select.py +0 -221
  162. sqlspec/statement/filters.py +0 -596
  163. sqlspec/statement/parameter_manager.py +0 -220
  164. sqlspec/statement/parameters.py +0 -867
  165. sqlspec/statement/pipelines/__init__.py +0 -210
  166. sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
  167. sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
  168. sqlspec/statement/pipelines/context.py +0 -115
  169. sqlspec/statement/pipelines/transformers/__init__.py +0 -7
  170. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
  171. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
  172. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
  173. sqlspec/statement/pipelines/validators/__init__.py +0 -23
  174. sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
  175. sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
  176. sqlspec/statement/pipelines/validators/_performance.py +0 -718
  177. sqlspec/statement/pipelines/validators/_security.py +0 -967
  178. sqlspec/statement/result.py +0 -435
  179. sqlspec/statement/sql.py +0 -1704
  180. sqlspec/statement/sql_compiler.py +0 -140
  181. sqlspec/utils/cached_property.py +0 -25
  182. sqlspec-0.13.1.dist-info/RECORD +0 -150
  183. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/WHEEL +0 -0
  184. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/LICENSE +0 -0
  185. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/NOTICE +0 -0
@@ -1,269 +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
- AsyncPipelinedExecutionMixin,
14
- AsyncStorageMixin,
15
- SQLTranslatorMixin,
16
- ToSchemaMixin,
17
- TypeCoercionMixin,
18
- )
19
- from sqlspec.driver.parameters import normalize_parameter_sequence
20
- from sqlspec.statement.parameters import ParameterStyle, ParameterValidator
21
- from sqlspec.statement.result import SQLResult
22
- from sqlspec.statement.sql import SQL, SQLConfig
23
- 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
24
22
  from sqlspec.utils.serializers import to_json
25
23
 
26
24
  if TYPE_CHECKING:
27
- 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
+ )
60
+
61
+
62
+ class AiosqliteCursor:
63
+ """Async context manager for AIOSQLite cursor management."""
28
64
 
29
- __all__ = ("AiosqliteConnection", "AiosqliteDriver")
65
+ __slots__ = ("connection", "cursor")
30
66
 
31
- logger = logging.getLogger("sqlspec")
67
+ def __init__(self, connection: "AiosqliteConnection") -> None:
68
+ self.connection = connection
69
+ self.cursor: Optional[aiosqlite.Cursor] = None
32
70
 
33
- AiosqliteConnection = aiosqlite.Connection
71
+ async def __aenter__(self) -> "aiosqlite.Cursor":
72
+ self.cursor = await self.connection.cursor()
73
+ return self.cursor
34
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__ = ()
35
86
 
36
- class AiosqliteDriver(
37
- AsyncDriverAdapterProtocol[AiosqliteConnection, RowT],
38
- SQLTranslatorMixin,
39
- TypeCoercionMixin,
40
- AsyncStorageMixin,
41
- AsyncPipelinedExecutionMixin,
42
- ToSchemaMixin,
43
- ):
44
- """Aiosqlite SQLite Driver Adapter. Modern protocol implementation."""
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
+ """
45
134
 
46
- dialect: "DialectType" = "sqlite"
47
- supported_parameter_styles: "tuple[ParameterStyle, ...]" = (ParameterStyle.QMARK, ParameterStyle.NAMED_COLON)
48
- default_parameter_style: ParameterStyle = ParameterStyle.QMARK
49
135
  __slots__ = ()
136
+ dialect = "sqlite"
50
137
 
51
138
  def __init__(
52
139
  self,
53
- connection: AiosqliteConnection,
54
- config: "Optional[SQLConfig]" = None,
55
- default_row_type: "type[DictRow]" = DictRow,
140
+ connection: "AiosqliteConnection",
141
+ statement_config: "Optional[StatementConfig]" = None,
142
+ driver_features: "Optional[dict[str, Any]]" = None,
56
143
  ) -> None:
57
- super().__init__(connection=connection, config=config, default_row_type=default_row_type)
58
-
59
- # AIOSQLite-specific type coercion overrides (same as SQLite)
60
- def _coerce_boolean(self, value: Any) -> Any:
61
- """AIOSQLite/SQLite stores booleans as integers (0/1)."""
62
- if isinstance(value, bool):
63
- return 1 if value else 0
64
- return value
65
-
66
- def _coerce_decimal(self, value: Any) -> Any:
67
- """AIOSQLite/SQLite stores decimals as strings to preserve precision."""
68
- if isinstance(value, str):
69
- return value # Already a string
70
- from decimal import Decimal
71
-
72
- if isinstance(value, Decimal):
73
- return str(value)
74
- return value
75
-
76
- def _coerce_json(self, value: Any) -> Any:
77
- """AIOSQLite/SQLite stores JSON as strings (requires JSON1 extension)."""
78
- if isinstance(value, (dict, list)):
79
- return to_json(value)
80
- return value
81
-
82
- def _coerce_array(self, value: Any) -> Any:
83
- """AIOSQLite/SQLite doesn't have native arrays - store as JSON strings."""
84
- if isinstance(value, (list, tuple)):
85
- return to_json(list(value))
86
- return value
87
-
88
- @asynccontextmanager
89
- async def _get_cursor(
90
- self, connection: Optional[AiosqliteConnection] = None
91
- ) -> AsyncGenerator[aiosqlite.Cursor, None]:
92
- conn_to_use = connection or self.connection
93
- conn_to_use.row_factory = aiosqlite.Row
94
- cursor = await conn_to_use.cursor()
95
- try:
96
- yield cursor
97
- finally:
98
- await cursor.close()
99
-
100
- async def _execute_statement(
101
- self, statement: SQL, connection: Optional[AiosqliteConnection] = None, **kwargs: Any
102
- ) -> SQLResult[RowT]:
103
- if statement.is_script:
104
- sql, _ = statement.compile(placeholder_style=ParameterStyle.STATIC)
105
- return await self._execute_script(sql, connection=connection, **kwargs)
106
-
107
- detected_styles = set()
108
- sql_str = statement.to_sql(placeholder_style=None) # Get raw SQL
109
- validator = self.config.parameter_validator if self.config else ParameterValidator()
110
- param_infos = validator.extract_parameters(sql_str)
111
- if param_infos:
112
- detected_styles = {p.style for p in param_infos}
113
-
114
- target_style = self.default_parameter_style
115
-
116
- unsupported_styles = detected_styles - set(self.supported_parameter_styles)
117
- if unsupported_styles:
118
- target_style = self.default_parameter_style
119
- elif detected_styles:
120
- # Prefer the first supported style found
121
- for style in detected_styles:
122
- if style in self.supported_parameter_styles:
123
- target_style = style
124
- break
125
-
126
- if statement.is_many:
127
- sql, params = statement.compile(placeholder_style=target_style)
128
-
129
- params = self._process_parameters(params)
130
-
131
- return await self._execute_many(sql, params, connection=connection, **kwargs)
132
-
133
- sql, params = statement.compile(placeholder_style=target_style)
134
-
135
- params = self._process_parameters(params)
136
-
137
- return await self._execute(sql, params, statement, connection=connection, **kwargs)
138
-
139
- async def _execute(
140
- self, sql: str, parameters: Any, statement: SQL, connection: Optional[AiosqliteConnection] = None, **kwargs: Any
141
- ) -> SQLResult[RowT]:
142
- conn = self._connection(connection)
143
-
144
- async with managed_transaction_async(conn, auto_commit=True) as txn_conn:
145
- normalized_params = normalize_parameter_sequence(parameters)
146
-
147
- # Extract the actual parameters from the normalized list
148
- if normalized_params and len(normalized_params) == 1:
149
- actual_params = normalized_params[0]
150
- else:
151
- actual_params = normalized_params
152
-
153
- # AIOSQLite expects tuple or dict - handle parameter conversion
154
- if ":param_" in sql or (isinstance(actual_params, dict)):
155
- # SQL has named placeholders, ensure params are dict
156
- converted_params = self._convert_parameters_to_driver_format(
157
- sql, actual_params, target_style=ParameterStyle.NAMED_COLON
158
- )
159
- else:
160
- # SQL has positional placeholders, ensure params are list/tuple
161
- converted_params = self._convert_parameters_to_driver_format(
162
- sql, actual_params, target_style=ParameterStyle.QMARK
163
- )
164
-
165
- async with self._get_cursor(txn_conn) as cursor:
166
- # Aiosqlite handles both dict and tuple parameters
167
- await cursor.execute(sql, converted_params or ())
168
- if self.returns_rows(statement.expression):
169
- fetched_data = await cursor.fetchall()
170
- column_names = [desc[0] for desc in cursor.description or []]
171
- data_list: list[Any] = list(fetched_data) if fetched_data else []
172
- return SQLResult(
173
- statement=statement,
174
- data=data_list,
175
- column_names=column_names,
176
- rows_affected=len(data_list),
177
- operation_type="SELECT",
178
- )
179
-
180
- return SQLResult(
181
- statement=statement,
182
- data=[],
183
- rows_affected=cursor.rowcount,
184
- operation_type=self._determine_operation_type(statement),
185
- metadata={"status_message": "OK"},
186
- )
187
-
188
- async def _execute_many(
189
- self, sql: str, param_list: Any, connection: Optional[AiosqliteConnection] = None, **kwargs: Any
190
- ) -> SQLResult[RowT]:
191
- # Use provided connection or driver's default connection
192
- conn = connection if connection is not None else self._connection(None)
193
-
194
- async with managed_transaction_async(conn, auto_commit=True) as txn_conn:
195
- # Normalize parameter list using consolidated utility
196
- normalized_param_list = normalize_parameter_sequence(param_list)
197
-
198
- params_list: list[tuple[Any, ...]] = []
199
- if normalized_param_list and isinstance(normalized_param_list, Sequence):
200
- for param_set in normalized_param_list:
201
- if isinstance(param_set, (list, tuple)):
202
- params_list.append(tuple(param_set))
203
- elif param_set is None:
204
- params_list.append(())
205
-
206
- async with self._get_cursor(txn_conn) as cursor:
207
- await cursor.executemany(sql, params_list)
208
- return SQLResult(
209
- statement=SQL(sql, _dialect=self.dialect),
210
- data=[],
211
- rows_affected=cursor.rowcount,
212
- operation_type="EXECUTE",
213
- metadata={"status_message": "OK"},
214
- )
215
-
216
- async def _execute_script(
217
- self, script: str, connection: Optional[AiosqliteConnection] = None, **kwargs: Any
218
- ) -> SQLResult[RowT]:
219
- # Use provided connection or driver's default connection
220
- conn = connection if connection is not None else self._connection(None)
221
-
222
- async with managed_transaction_async(conn, auto_commit=True) as txn_conn:
223
- async with self._get_cursor(txn_conn) as cursor:
224
- await cursor.executescript(script)
225
- return SQLResult(
226
- statement=SQL(script, _dialect=self.dialect).as_script(),
227
- data=[],
228
- rows_affected=0,
229
- operation_type="SCRIPT",
230
- metadata={"status_message": "SCRIPT EXECUTED"},
231
- total_statements=-1, # AIOSQLite doesn't provide this info
232
- successful_statements=-1,
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
153
+
154
+ super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
155
+
156
+ def with_cursor(self, connection: "AiosqliteConnection") -> "AiosqliteCursor":
157
+ """Create async context manager for AIOSQLite cursor."""
158
+ return AiosqliteCursor(connection)
159
+
160
+ def handle_database_exceptions(self) -> "AbstractAsyncContextManager[None]":
161
+ """Handle AIOSQLite-specific exceptions."""
162
+ return AiosqliteExceptionHandler()
163
+
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
233
220
  )
234
221
 
235
- async def _bulk_load_file(self, file_path: Path, table_name: str, format: str, mode: str, **options: Any) -> int:
236
- """Database-specific bulk load implementation using storage backend."""
237
- if format != "csv":
238
- msg = f"aiosqlite driver only supports CSV for bulk loading, not {format}."
239
- 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
240
241
 
241
- conn = await self._create_connection() # type: ignore[attr-defined]
242
+ async def commit(self) -> None:
243
+ """Commit the current transaction."""
242
244
  try:
243
- async with self._get_cursor(conn) as cursor:
244
- if mode == "replace":
245
- await cursor.execute(f"DELETE FROM {table_name}")
246
-
247
- # Use async storage backend to read the file
248
- file_path_str = str(file_path)
249
- backend = self._get_storage_backend(file_path_str)
250
- content = await backend.read_text_async(file_path_str, encoding="utf-8")
251
- # Parse CSV content
252
- import io
253
-
254
- csv_file = io.StringIO(content)
255
- reader = csv.reader(csv_file, **options)
256
- header = next(reader) # Skip header
257
- placeholders = ", ".join("?" for _ in header)
258
- sql = f"INSERT INTO {table_name} VALUES ({placeholders})"
259
- data_iter = list(reader)
260
- await cursor.executemany(sql, data_iter)
261
- rowcount = cursor.rowcount
262
- await conn.commit()
263
- return rowcount
264
- finally:
265
- await conn.close()
266
-
267
- def _connection(self, connection: Optional[AiosqliteConnection] = None) -> AiosqliteConnection:
268
- """Get the connection to use for the operation."""
269
- 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",)