sqlspec 0.62.2__py3-none-any.whl → 0.63.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.
Files changed (171) hide show
  1. sqlspec/__init__.py +5 -0
  2. sqlspec/_typing.py +0 -2
  3. sqlspec/adapters/adbc/config.py +36 -24
  4. sqlspec/adapters/adbc/core.py +29 -28
  5. sqlspec/adapters/adbc/driver.py +33 -7
  6. sqlspec/adapters/adbc/type_converter.py +10 -2
  7. sqlspec/adapters/aiomysql/config.py +12 -22
  8. sqlspec/adapters/aiosqlite/config.py +4 -0
  9. sqlspec/adapters/aiosqlite/core.py +7 -2
  10. sqlspec/adapters/arrow_odbc/config.py +5 -0
  11. sqlspec/adapters/asyncmy/_typing.py +78 -2
  12. sqlspec/adapters/asyncmy/config.py +13 -13
  13. sqlspec/adapters/asyncmy/core.py +54 -0
  14. sqlspec/adapters/asyncmy/driver.py +41 -11
  15. sqlspec/adapters/asyncpg/config.py +22 -14
  16. sqlspec/adapters/asyncpg/core.py +3 -0
  17. sqlspec/adapters/asyncpg/data_dictionary.py +3 -2
  18. sqlspec/adapters/asyncpg/driver.py +6 -1
  19. sqlspec/adapters/bigquery/adk/store.py +0 -5
  20. sqlspec/adapters/bigquery/config.py +12 -2
  21. sqlspec/adapters/bigquery/core.py +81 -1
  22. sqlspec/adapters/bigquery/driver.py +77 -1
  23. sqlspec/adapters/bigquery/litestar/store.py +6 -6
  24. sqlspec/adapters/cockroach_asyncpg/config.py +40 -1
  25. sqlspec/adapters/cockroach_asyncpg/core.py +94 -1
  26. sqlspec/adapters/cockroach_asyncpg/data_dictionary.py +3 -2
  27. sqlspec/adapters/cockroach_asyncpg/driver.py +106 -0
  28. sqlspec/adapters/cockroach_psycopg/config.py +57 -1
  29. sqlspec/adapters/cockroach_psycopg/core.py +91 -0
  30. sqlspec/adapters/cockroach_psycopg/data_dictionary.py +6 -4
  31. sqlspec/adapters/cockroach_psycopg/driver.py +219 -0
  32. sqlspec/adapters/duckdb/config.py +29 -3
  33. sqlspec/adapters/duckdb/core.py +308 -1
  34. sqlspec/adapters/duckdb/driver.py +125 -4
  35. sqlspec/adapters/duckdb/pool.py +199 -28
  36. sqlspec/adapters/mssql_python/adk/__init__.py +2 -2
  37. sqlspec/adapters/mssql_python/adk/store.py +212 -30
  38. sqlspec/adapters/mssql_python/config.py +5 -0
  39. sqlspec/adapters/mssql_python/data_dictionary.py +9 -8
  40. sqlspec/adapters/mssql_python/driver.py +45 -1
  41. sqlspec/adapters/mssql_python/litestar/store.py +7 -7
  42. sqlspec/adapters/mysqlconnector/_typing.py +2 -6
  43. sqlspec/adapters/mysqlconnector/config.py +14 -4
  44. sqlspec/adapters/oracledb/config.py +7 -0
  45. sqlspec/adapters/oracledb/driver.py +9 -0
  46. sqlspec/adapters/oracledb/type_converter.py +2 -6
  47. sqlspec/adapters/psqlpy/config.py +21 -6
  48. sqlspec/adapters/psqlpy/core.py +5 -15
  49. sqlspec/adapters/psqlpy/data_dictionary.py +3 -2
  50. sqlspec/adapters/psqlpy/driver.py +13 -3
  51. sqlspec/adapters/psycopg/_typing.py +4 -4
  52. sqlspec/adapters/psycopg/config.py +35 -31
  53. sqlspec/adapters/psycopg/core.py +3 -0
  54. sqlspec/adapters/psycopg/data_dictionary.py +6 -4
  55. sqlspec/adapters/pymssql/adk/store.py +11 -10
  56. sqlspec/adapters/pymssql/config.py +7 -1
  57. sqlspec/adapters/pymssql/core.py +3 -1
  58. sqlspec/adapters/pymssql/data_dictionary.py +9 -8
  59. sqlspec/adapters/pymssql/driver.py +78 -6
  60. sqlspec/adapters/pymssql/litestar/store.py +1 -1
  61. sqlspec/adapters/pymysql/config.py +10 -11
  62. sqlspec/adapters/spanner/config.py +4 -1
  63. sqlspec/adapters/spanner/driver.py +27 -0
  64. sqlspec/adapters/spanner/type_converter.py +1 -1
  65. sqlspec/adapters/sqlite/config.py +4 -0
  66. sqlspec/adapters/sqlite/core.py +7 -2
  67. sqlspec/adapters/sqlite/driver.py +8 -16
  68. sqlspec/base.py +5 -3
  69. sqlspec/builder/__init__.py +3 -1
  70. sqlspec/builder/_base.py +277 -45
  71. sqlspec/builder/_ddl.py +45 -13
  72. sqlspec/builder/_delete.py +2 -12
  73. sqlspec/builder/_dml.py +110 -14
  74. sqlspec/builder/_explain.py +8 -11
  75. sqlspec/builder/_factory.py +87 -122
  76. sqlspec/builder/_insert.py +1 -12
  77. sqlspec/builder/_locking.py +5 -4
  78. sqlspec/builder/_merge.py +0 -10
  79. sqlspec/builder/_parsing_utils.py +32 -0
  80. sqlspec/builder/_select.py +54 -170
  81. sqlspec/builder/_update.py +7 -8
  82. sqlspec/builder/_values.py +237 -0
  83. sqlspec/builder/_vector_distance.py +23 -23
  84. sqlspec/cli.py +40 -30
  85. sqlspec/config.py +72 -25
  86. sqlspec/core/__init__.py +16 -77
  87. sqlspec/core/cache.py +8 -2
  88. sqlspec/core/capabilities.py +67 -0
  89. sqlspec/core/compiler.py +21 -55
  90. sqlspec/core/config_runtime.py +29 -0
  91. sqlspec/core/hashing.py +1 -1
  92. sqlspec/core/parameters/__init__.py +11 -1
  93. sqlspec/core/parameters/_alignment.py +1 -9
  94. sqlspec/core/parameters/_converter.py +201 -138
  95. sqlspec/core/parameters/_processor.py +57 -96
  96. sqlspec/core/parameters/_transformers.py +2 -3
  97. sqlspec/core/parameters/_types.py +6 -0
  98. sqlspec/core/splitter.py +2 -0
  99. sqlspec/core/statement.py +2 -2
  100. sqlspec/data_dictionary/_loader.py +0 -8
  101. sqlspec/data_dictionary/_types.py +37 -3
  102. sqlspec/data_dictionary/dialects/bigquery/config.py +6 -0
  103. sqlspec/data_dictionary/dialects/cockroachdb/config.py +6 -0
  104. sqlspec/data_dictionary/dialects/cockroachdb/sql/columns.sql +49 -15
  105. sqlspec/data_dictionary/dialects/duckdb/config.py +6 -0
  106. sqlspec/data_dictionary/dialects/duckdb/sql/columns.sql +128 -35
  107. sqlspec/data_dictionary/dialects/mariadb/sql/columns.sql +1 -0
  108. sqlspec/data_dictionary/dialects/mssql/config.py +6 -0
  109. sqlspec/data_dictionary/dialects/mssql/sql/schemas.sql +4 -0
  110. sqlspec/data_dictionary/dialects/mysql/config.py +8 -0
  111. sqlspec/data_dictionary/dialects/mysql/sql/columns.sql +6 -1
  112. sqlspec/data_dictionary/dialects/oracle/config.py +9 -0
  113. sqlspec/data_dictionary/dialects/postgres/config.py +6 -0
  114. sqlspec/data_dictionary/dialects/postgres/sql/columns.sql +13 -2
  115. sqlspec/data_dictionary/dialects/spanner/config.py +6 -0
  116. sqlspec/data_dictionary/dialects/sqlite/config.py +6 -0
  117. sqlspec/dialects/__init__.py +4 -3
  118. sqlspec/dialects/postgres/__init__.py +2 -1
  119. sqlspec/dialects/postgres/_generators.py +13 -3
  120. sqlspec/dialects/postgres/_operators.py +16 -2
  121. sqlspec/dialects/postgres/_paradedb.py +11 -6
  122. sqlspec/dialects/postgres/_pg_textsearch.py +38 -0
  123. sqlspec/driver/_async.py +126 -68
  124. sqlspec/driver/_common.py +44 -63
  125. sqlspec/driver/_query_cache.py +0 -4
  126. sqlspec/driver/_sync.py +131 -62
  127. sqlspec/exceptions.py +54 -29
  128. sqlspec/extensions/adk/artifact/service.py +138 -159
  129. sqlspec/extensions/adk/memory/store.py +0 -18
  130. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +1 -1
  131. sqlspec/extensions/adk/migrations/0002_create_mssql_python_memory.py +54 -0
  132. sqlspec/extensions/events/__init__.py +5 -0
  133. sqlspec/extensions/events/_channel.py +36 -3
  134. sqlspec/extensions/events/_queue.py +19 -24
  135. sqlspec/extensions/events/primitives.py +82 -0
  136. sqlspec/extensions/litestar/__init__.py +4 -0
  137. sqlspec/extensions/litestar/channels.py +52 -5
  138. sqlspec/extensions/litestar/config.py +2 -95
  139. sqlspec/extensions/litestar/plugin.py +142 -20
  140. sqlspec/extensions/litestar/store.py +1 -0
  141. sqlspec/loader.py +948 -253
  142. sqlspec/migrations/base.py +5 -15
  143. sqlspec/migrations/commands.py +33 -79
  144. sqlspec/migrations/loaders.py +13 -11
  145. sqlspec/migrations/runner.py +68 -18
  146. sqlspec/migrations/tracker.py +3 -0
  147. sqlspec/protocols.py +2 -2
  148. sqlspec/service.py +499 -85
  149. sqlspec/storage/__init__.py +2 -4
  150. sqlspec/storage/_paths.py +157 -1
  151. sqlspec/storage/backends/local.py +8 -5
  152. sqlspec/storage/backends/obstore.py +123 -64
  153. sqlspec/storage/pipeline.py +47 -94
  154. sqlspec/typing.py +1 -3
  155. sqlspec/utils/arrow_helpers.py +106 -111
  156. sqlspec/utils/correlation.py +7 -7
  157. sqlspec/utils/deprecation.py +19 -4
  158. sqlspec/utils/fixtures.py +915 -3
  159. sqlspec/utils/logging.py +2 -2
  160. sqlspec/utils/schema.py +102 -119
  161. sqlspec/utils/text.py +1 -1
  162. sqlspec/utils/type_converters.py +1 -3
  163. sqlspec/utils/type_guards.py +4 -2
  164. sqlspec/utils/uuids.py +1 -1
  165. {sqlspec-0.62.2.dist-info → sqlspec-0.63.0.dist-info}/METADATA +7 -5
  166. {sqlspec-0.62.2.dist-info → sqlspec-0.63.0.dist-info}/RECORD +170 -166
  167. {sqlspec-0.62.2.dist-info → sqlspec-0.63.0.dist-info}/entry_points.txt +1 -0
  168. sqlspec/utils/singleton.py +0 -41
  169. /sqlspec/core/{pipeline.py → _pipeline.py} +0 -0
  170. {sqlspec-0.62.2.dist-info → sqlspec-0.63.0.dist-info}/WHEEL +0 -0
  171. {sqlspec-0.62.2.dist-info → sqlspec-0.63.0.dist-info}/licenses/LICENSE +0 -0
sqlspec/__init__.py CHANGED
@@ -82,6 +82,7 @@ from sqlspec.observability import (
82
82
  )
83
83
  from sqlspec.typing import ConnectionT, PoolT, SchemaT, StatementParameters, SupportedSchemaModel
84
84
  from sqlspec.utils.logging import suppress_erroneous_sqlglot_log_messages
85
+ from sqlspec.utils.uuids import nanoid, uuid4, uuid6, uuid7
85
86
 
86
87
  if TYPE_CHECKING:
87
88
  from sqlspec import dialects
@@ -159,11 +160,15 @@ __all__ = (
159
160
  "loader",
160
161
  "matches_param_type",
161
162
  "migrations",
163
+ "nanoid",
162
164
  "register_param_type",
163
165
  "resolve_param_type",
164
166
  "sql",
165
167
  "typing",
166
168
  "utils",
169
+ "uuid4",
170
+ "uuid6",
171
+ "uuid7",
167
172
  )
168
173
 
169
174
  suppress_erroneous_sqlglot_log_messages()
sqlspec/_typing.py CHANGED
@@ -102,7 +102,6 @@ __all__ = (
102
102
  "Struct",
103
103
  "StructStub",
104
104
  "T",
105
- "T_co",
106
105
  "Tracer",
107
106
  "TypeAdapter",
108
107
  "TypeAdapterStub",
@@ -139,7 +138,6 @@ class DataclassProtocol(Protocol):
139
138
 
140
139
 
141
140
  T = TypeVar("T")
142
- T_co = TypeVar("T_co", covariant=True)
143
141
 
144
142
  # Always define stub types for type checking
145
143
 
@@ -8,19 +8,23 @@ from sqlspec.adapters.adbc._typing import AdbcConnection, AdbcCursor, AdbcSessio
8
8
  from sqlspec.adapters.adbc.core import (
9
9
  apply_driver_features,
10
10
  build_connection_config,
11
- build_postgres_extension_probe_names,
12
11
  detect_postgres_extensions,
13
12
  get_statement_config,
14
13
  is_postgres_dialect,
15
14
  resolve_dialect_from_config,
16
15
  resolve_dialect_name,
17
16
  resolve_driver_connect_func,
18
- resolve_postgres_extension_state,
19
- resolve_runtime_statement_config,
20
17
  )
21
18
  from sqlspec.adapters.adbc.driver import AdbcDriver, AdbcExceptionHandler
22
19
  from sqlspec.config import ExtensionConfigs, NoPoolSyncConfig
23
20
  from sqlspec.core import StatementConfig
21
+ from sqlspec.core.capabilities import TypeCoercionCapabilities
22
+ from sqlspec.core.config_runtime import (
23
+ build_postgres_extension_probe_names,
24
+ is_postgres_extension_active,
25
+ resolve_postgres_extension_state,
26
+ resolve_runtime_statement_config,
27
+ )
24
28
  from sqlspec.driver._sync import SyncPoolConnectionContext, SyncPoolSessionFactory
25
29
  from sqlspec.exceptions import ImproperConfigurationError
26
30
  from sqlspec.extensions.events import EventRuntimeHints
@@ -81,20 +85,20 @@ class AdbcDriverFeatures(TypedDict):
81
85
  Attributes:
82
86
  json_serializer: JSON serialization function to use.
83
87
  Callable that takes Any and returns str (JSON string).
84
- Default: sqlspec.utils.serializers.to_json
88
+ Default: sqlspec.utils.serializers.to_json
85
89
  enable_cast_detection: Enable cast-aware parameter processing.
86
90
  When True, detects SQL casts and applies appropriate
87
91
  serialization. Currently used for PostgreSQL JSONB handling.
88
- Default: True
92
+ Default: True
89
93
  enable_strict_type_coercion: Enforce strict type coercion rules.
90
94
  When True, raises errors for unsupported type conversions.
91
95
  When False, attempts best-effort conversion.
92
- Default: False
96
+ Default: False
93
97
  strict_type_coercion: Alias for enable_strict_type_coercion.
94
98
  enable_arrow_extension_types: Enable PyArrow extension type support.
95
99
  When True, preserves Arrow extension type metadata when reading data.
96
100
  When False, falls back to storage types.
97
- Default: True
101
+ Default: True
98
102
  arrow_extension_types: Alias for enable_arrow_extension_types.
99
103
  enable_pgvector: Enable automatic pgvector extension detection.
100
104
  When True and the resolved dialect is PostgreSQL, queries ``pg_extension``
@@ -104,6 +108,10 @@ class AdbcDriverFeatures(TypedDict):
104
108
  When True and the resolved dialect is PostgreSQL, queries ``pg_extension``
105
109
  on the first connection to check for the ``pg_search`` extension.
106
110
  Defaults to True. Independent of enable_pgvector.
111
+ enable_pg_textsearch: Enable pg_textsearch extension detection for BM25 search.
112
+ When True and the resolved dialect is PostgreSQL, queries ``pg_extension``
113
+ on the first connection to check for the ``pg_textsearch`` extension.
114
+ Defaults to True.
107
115
  enable_events: Enable database event channel support.
108
116
  Defaults to True when extension_config["events"] is configured.
109
117
  Provides pub/sub capabilities via table-backed queue (ADBC has no native pub/sub).
@@ -111,7 +119,7 @@ class AdbcDriverFeatures(TypedDict):
111
119
  on_connection_create: Callback executed when a connection is created.
112
120
  Receives the raw ADBC connection for low-level driver configuration.
113
121
  events_backend: Event channel backend selection.
114
- Only option: "poll_queue" (durable table-backed queue with lease-based retries and acknowledgements).
122
+ Only option: "poll_queue" (durable table-backed queue with lease-based retries and acknowledgements).
115
123
  ADBC does not have native pub/sub, so poll_queue is the only backend.
116
124
  Defaults to "poll_queue".
117
125
  """
@@ -124,6 +132,7 @@ class AdbcDriverFeatures(TypedDict):
124
132
  arrow_extension_types: NotRequired[bool]
125
133
  enable_pgvector: NotRequired[bool]
126
134
  enable_paradedb: NotRequired[bool]
135
+ enable_pg_textsearch: NotRequired[bool]
127
136
  enable_events: NotRequired[bool]
128
137
  on_connection_create: "NotRequired[Callable[[AdbcConnection], None]]"
129
138
  events_backend: NotRequired[Literal["poll_queue"]]
@@ -188,7 +197,11 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
188
197
  supports_native_row_streaming: "ClassVar[bool]" = True
189
198
  supports_native_parquet_export: "ClassVar[bool]" = True
190
199
  supports_native_parquet_import: "ClassVar[bool]" = True
200
+ supports_reliable_rowcount: ClassVar[bool] = False
191
201
  storage_partition_strategies: "ClassVar[tuple[str, ...]]" = ("fixed", "rows_per_chunk")
202
+ type_coercion_capabilities: "ClassVar[TypeCoercionCapabilities]" = TypeCoercionCapabilities(
203
+ datetime_binding="native", timestamp_precision="microsecond", json_columns_decoded=False, uuid_binding="native"
204
+ )
192
205
  _connection_context_class: "ClassVar[type[AdbcConnectionContext]]" = AdbcConnectionContext
193
206
  _session_factory_class: "ClassVar[type[_AdbcSessionConnectionHandler]]" = _AdbcSessionConnectionHandler
194
207
  _session_context_class: "ClassVar[type[AdbcSessionContext]]" = AdbcSessionContext
@@ -196,6 +209,7 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
196
209
  __slots__ = (
197
210
  "_default_session_config",
198
211
  "_paradedb_available",
212
+ "_pg_textsearch_available",
199
213
  "_pgvector_available",
200
214
  "_resolved_dialect",
201
215
  "_user_connection_hook",
@@ -230,6 +244,7 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
230
244
  self.connection_config = normalize_connection_config(connection_config)
231
245
  self._pgvector_available: bool | None = None
232
246
  self._paradedb_available: bool | None = None
247
+ self._pg_textsearch_available: bool | None = None
233
248
 
234
249
  self._resolved_dialect = resolve_dialect_from_config(self.connection_config)
235
250
 
@@ -284,20 +299,10 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
284
299
  msg = f"Could not configure connection using driver '{err_driver_name}'. Error: {e}"
285
300
  raise ImproperConfigurationError(msg) from e
286
301
 
287
- def _update_dialect_for_extensions(self) -> None:
288
- """Update statement_config dialect based on detected extensions.
289
-
290
- Priority: paradedb > pgvector > postgres (default).
291
- Only switches when current dialect is ``postgres``.
292
- """
293
- current_dialect = self.statement_config.dialect or "postgres"
294
- if current_dialect != "postgres":
295
- return
296
-
297
- if self._paradedb_available:
298
- self.statement_config = self.statement_config.replace(dialect="paradedb")
299
- elif self._pgvector_available:
300
- self.statement_config = self.statement_config.replace(dialect="pgvector")
302
+ @property
303
+ def pg_textsearch_available(self) -> bool:
304
+ """Return True if the pg_textsearch extension is available."""
305
+ return bool(self._pg_textsearch_available)
301
306
 
302
307
  def _detect_extensions_if_needed(self) -> None:
303
308
  """Detect postgres extensions on first call, caching results.
@@ -312,13 +317,17 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
312
317
  if not is_postgres_dialect(dialect):
313
318
  self._pgvector_available = False
314
319
  self._paradedb_available = False
320
+ self._pg_textsearch_available = False
315
321
  return
316
322
 
317
323
  connection = self.create_connection()
318
324
  try:
319
325
  probe_names = build_postgres_extension_probe_names(self.driver_features)
320
- pgvector_available, paradedb_available = detect_postgres_extensions(
321
- connection, enable_pgvector="vector" in probe_names, enable_paradedb="pg_search" in probe_names
326
+ pgvector_available, paradedb_available, pg_textsearch_available = detect_postgres_extensions(
327
+ connection,
328
+ enable_pgvector="vector" in probe_names,
329
+ enable_paradedb="pg_search" in probe_names,
330
+ enable_pg_textsearch="pg_textsearch" in probe_names,
322
331
  )
323
332
  finally:
324
333
  connection.close()
@@ -328,9 +337,12 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
328
337
  detected_extensions.add("vector")
329
338
  if paradedb_available:
330
339
  detected_extensions.add("pg_search")
340
+ if pg_textsearch_available:
341
+ detected_extensions.add("pg_textsearch")
331
342
  self.statement_config, self._pgvector_available, self._paradedb_available = resolve_postgres_extension_state(
332
343
  self.statement_config, self.driver_features, detected_extensions
333
344
  )
345
+ self._pg_textsearch_available = is_postgres_extension_active(self.driver_features, "pg_textsearch")
334
346
 
335
347
  def provide_connection(self, *args: Any, **kwargs: Any) -> "AdbcConnectionContext":
336
348
  """Provide a connection context manager.
@@ -21,9 +21,11 @@ from sqlspec.core import (
21
21
  )
22
22
  from sqlspec.core.config_runtime import (
23
23
  build_postgres_extension_probe_names,
24
+ is_postgres_extension_active,
24
25
  resolve_postgres_extension_state,
25
26
  resolve_runtime_statement_config,
26
27
  )
28
+ from sqlspec.core.parameters import type_coercion_dispatcher
27
29
  from sqlspec.exceptions import (
28
30
  CheckViolationError,
29
31
  DatabaseConnectionError,
@@ -44,7 +46,6 @@ from sqlspec.exceptions import (
44
46
  map_sqlstate_to_exception,
45
47
  )
46
48
  from sqlspec.typing import PGVECTOR_INSTALLED, Empty
47
- from sqlspec.utils.dispatch import TypeDispatcher
48
49
  from sqlspec.utils.module_loader import import_string
49
50
  from sqlspec.utils.serializers import to_json
50
51
  from sqlspec.utils.type_converters import build_uuid_coercions
@@ -55,6 +56,7 @@ if TYPE_CHECKING:
55
56
  from collections.abc import Callable, Mapping
56
57
 
57
58
  from sqlspec.core import SQL
59
+ from sqlspec.utils.dispatch import TypeDispatcher
58
60
 
59
61
  __all__ = (
60
62
  "_prepare_batch_with_casts",
@@ -73,6 +75,7 @@ __all__ = (
73
75
  "get_statement_config",
74
76
  "handle_postgres_rollback",
75
77
  "is_postgres_dialect",
78
+ "is_postgres_extension_active",
76
79
  "normalize_driver_path",
77
80
  "normalize_postgres_empty_parameters",
78
81
  "normalize_script_rowcount",
@@ -171,7 +174,6 @@ _BIGQUERY_DB_KWARGS_FIELDS: "tuple[str, ...]" = ("project_id", "dataset_id", "to
171
174
  _FLIGHTSQL_DB_KWARGS_FIELDS: "tuple[str, ...]" = ("username", "password")
172
175
  _FLIGHTSQL_TLS_SKIP_VERIFY_KEY: Final[str] = "adbc.flight.sql.client_option.tls_skip_verify"
173
176
  _FLIGHTSQL_AUTHORIZATION_HEADER_KEY: Final[str] = "adbc.flight.sql.authorization_header"
174
- _TYPE_COERCION_DISPATCHERS: "dict[tuple[tuple[type, Callable[[Any], Any]], ...], TypeDispatcher[Callable[[Any], Any]]]" = {}
175
177
  _SQLSTATE_CLASS_CODE_LEN = 2
176
178
  _SQLSTATE_DESCRIPTIONS: dict[str, str] = {
177
179
  "23": "integrity constraint violation",
@@ -231,29 +233,32 @@ def detect_dialect(connection: Any, logger: Any | None = None, *, fallback_diale
231
233
 
232
234
 
233
235
  def detect_postgres_extensions(
234
- connection: Any, *, enable_pgvector: bool = False, enable_paradedb: bool = False
235
- ) -> "tuple[bool, bool]":
236
- """Detect pgvector and paradedb extensions on a postgres connection.
236
+ connection: Any, *, enable_pgvector: bool = False, enable_paradedb: bool = False, enable_pg_textsearch: bool = False
237
+ ) -> "tuple[bool, bool, bool]":
238
+ """Detect pgvector, paradedb, and pg_textsearch extensions on a postgres connection.
237
239
 
238
- Queries ``pg_extension`` for the ``vector`` and ``pg_search`` extensions.
240
+ Queries ``pg_extension`` for the ``vector``, ``pg_search``, and ``pg_textsearch`` extensions.
239
241
  Returns cached-friendly booleans suitable for storing on the config instance.
240
242
 
241
243
  Args:
242
244
  connection: ADBC connection to a PostgreSQL database.
243
245
  enable_pgvector: Whether to check for the pgvector extension.
244
246
  enable_paradedb: Whether to check for the pg_search extension.
247
+ enable_pg_textsearch: Whether to check for the pg_textsearch extension.
245
248
 
246
249
  Returns:
247
- Tuple of ``(pgvector_available, paradedb_available)``.
250
+ Tuple of ``(pgvector_available, paradedb_available, pg_textsearch_available)``.
248
251
  """
249
252
  extensions: list[str] = []
250
253
  if enable_pgvector:
251
254
  extensions.append("vector")
252
255
  if enable_paradedb:
253
256
  extensions.append("pg_search")
257
+ if enable_pg_textsearch:
258
+ extensions.append("pg_textsearch")
254
259
 
255
260
  if not extensions:
256
- return False, False
261
+ return False, False, False
257
262
 
258
263
  try:
259
264
  cursor = connection.cursor()
@@ -261,11 +266,11 @@ def detect_postgres_extensions(
261
266
  cursor.execute("SELECT extname FROM pg_extension WHERE extname = ANY($1::text[])", [extensions])
262
267
  rows = cursor.fetchall()
263
268
  detected: set[str] = {row[0] for row in rows} if rows else set()
264
- return "vector" in detected, "pg_search" in detected
269
+ return "vector" in detected, "pg_search" in detected, "pg_textsearch" in detected
265
270
  finally:
266
271
  cursor.close()
267
272
  except Exception:
268
- return False, False
273
+ return False, False, False
269
274
 
270
275
 
271
276
  def normalize_driver_path(driver_name: str) -> str:
@@ -455,15 +460,21 @@ def resolve_dialect_name(dialect: Any) -> str:
455
460
  """Return the normalized dialect name string."""
456
461
  if dialect is None:
457
462
  return ""
463
+ if isinstance(dialect, str):
464
+ return dialect.lower()
465
+ if isinstance(dialect, type) and issubclass(dialect, sqlglot.Dialect):
466
+ return dialect.__name__.lower()
467
+ if isinstance(dialect, sqlglot.Dialect):
468
+ return type(dialect).__name__.lower()
458
469
  return str(dialect)
459
470
 
460
471
 
461
472
  def is_postgres_dialect(dialect_name: str) -> bool:
462
473
  """Return True when the dialect indicates PostgreSQL.
463
474
 
464
- Includes pgvector and paradedb which are PostgreSQL extension dialects.
475
+ Includes pgvector, paradedb, and pg_textsearch extension dialects.
465
476
  """
466
- return dialect_name in {"postgres", "postgresql", "pgvector", "paradedb"}
477
+ return dialect_name in {"postgres", "postgresql", "pgvector", "paradedb", "pg_textsearch", "pgtextsearch"}
467
478
 
468
479
 
469
480
  def handle_postgres_rollback(dialect: str, cursor: Any, logger: Any | None = None) -> None:
@@ -726,7 +737,8 @@ def build_profile() -> "DriverParameterProfile":
726
737
  def get_statement_config(detected_dialect: str) -> StatementConfig:
727
738
  """Create statement configuration for the specified dialect."""
728
739
  default_style, supported_styles = DIALECT_PARAMETER_STYLES.get(
729
- detected_dialect, (ParameterStyle.QMARK, [ParameterStyle.QMARK])
740
+ "postgres" if is_postgres_dialect(detected_dialect) else detected_dialect,
741
+ (ParameterStyle.QMARK, [ParameterStyle.QMARK]),
730
742
  )
731
743
 
732
744
  sqlglot_dialect = "postgres" if detected_dialect == "postgresql" else detected_dialect
@@ -744,7 +756,7 @@ def get_statement_config(detected_dialect: str) -> StatementConfig:
744
756
  parameter_overrides["preserve_parameter_format"] = False
745
757
  parameter_overrides["supported_execution_parameter_styles"] = {ParameterStyle.QMARK, ParameterStyle.NUMERIC}
746
758
 
747
- if detected_dialect in {"postgres", "postgresql"}:
759
+ if is_postgres_dialect(detected_dialect):
748
760
  parameter_overrides["ast_transformer"] = build_null_pruning_transform(dialect=sqlglot_dialect)
749
761
 
750
762
  return build_statement_config_from_profile(
@@ -775,6 +787,7 @@ def apply_driver_features(
775
787
  processed_features.setdefault("enable_arrow_extension_types", processed_features["arrow_extension_types"])
776
788
  processed_features.setdefault("enable_pgvector", PGVECTOR_INSTALLED)
777
789
  processed_features.setdefault("enable_paradedb", True)
790
+ processed_features.setdefault("enable_pg_textsearch", True)
778
791
 
779
792
  if json_serializer is not None:
780
793
  statement_config = _apply_adbc_json_serializer(statement_config, json_serializer)
@@ -898,7 +911,7 @@ def prepare_parameters_with_casts(
898
911
  if isinstance(parameters, (list, tuple)):
899
912
  converter = get_adbc_type_converter(dialect)
900
913
  type_map = statement_config.parameter_config.type_coercion_map
901
- dispatcher = _type_coercion_dispatcher(type_map) if type_map else None
914
+ dispatcher = type_coercion_dispatcher(tuple(type_map.items())) if type_map else None
902
915
  return _prepare_parameter_sequence_with_casts(
903
916
  parameters, parameter_casts, type_map, dispatcher, converter, json_encoder
904
917
  )
@@ -1211,18 +1224,6 @@ def _apply_adbc_json_serializer(
1211
1224
  return statement_config.replace(parameter_config=updated_parameter_config.replace(type_coercion_map=updated_map))
1212
1225
 
1213
1226
 
1214
- def _type_coercion_dispatcher(type_map: "dict[type, Callable[[Any], Any]]") -> "TypeDispatcher[Callable[[Any], Any]]":
1215
- fallback_items = tuple(type_map.items())
1216
- dispatcher = _TYPE_COERCION_DISPATCHERS.get(fallback_items)
1217
- if dispatcher is not None:
1218
- return dispatcher
1219
-
1220
- dispatcher = TypeDispatcher["Callable[[Any], Any]"]()
1221
- dispatcher.register_all(fallback_items)
1222
- _TYPE_COERCION_DISPATCHERS[fallback_items] = dispatcher
1223
- return dispatcher
1224
-
1225
-
1226
1227
  def _prepare_parameter_sequence_with_casts(
1227
1228
  parameters: "list[Any] | tuple[Any, ...]",
1228
1229
  parameter_casts: "dict[int, str]",
@@ -1287,7 +1288,7 @@ def _prepare_batch_with_casts(
1287
1288
  json_encoder = statement_config.parameter_config.json_serializer or json_serializer
1288
1289
  converter = get_adbc_type_converter(dialect)
1289
1290
  type_map = statement_config.parameter_config.type_coercion_map
1290
- dispatcher = _type_coercion_dispatcher(type_map) if type_map else None
1291
+ dispatcher = type_coercion_dispatcher(tuple(type_map.items())) if type_map else None
1291
1292
  return [
1292
1293
  _prepare_parameter_sequence_with_casts(row, parameter_casts, type_map, dispatcher, converter, json_encoder)
1293
1294
  if isinstance(row, (list, tuple))
@@ -59,6 +59,7 @@ if TYPE_CHECKING:
59
59
  __all__ = ("AdbcCursor", "AdbcDriver", "AdbcExceptionHandler", "AdbcSessionContext")
60
60
 
61
61
  logger = get_logger("sqlspec.adapters.adbc")
62
+ _DIALECTS_WITHOUT_SAVEPOINTS = frozenset({"bigquery", "duckdb", "snowflake"})
62
63
 
63
64
  _MULTI_ROW_BIND_UNSUPPORTED = "Binding multiple rows at once is not supported"
64
65
 
@@ -377,6 +378,33 @@ class AdbcDriver(SyncDriverAdapterBase):
377
378
  finally:
378
379
  self._transaction_active = False
379
380
 
381
+ def create_savepoint(self, name: str) -> None:
382
+ """Create a savepoint within the current transaction.
383
+
384
+ Raises:
385
+ NotImplementedError: If the connected database does not support savepoints.
386
+ """
387
+ self._require_savepoints()
388
+ super().create_savepoint(name)
389
+
390
+ def release_savepoint(self, name: str) -> None:
391
+ """Release a previously created savepoint.
392
+
393
+ Raises:
394
+ NotImplementedError: If the connected database does not support savepoints.
395
+ """
396
+ self._require_savepoints()
397
+ super().release_savepoint(name)
398
+
399
+ def rollback_to_savepoint(self, name: str) -> None:
400
+ """Roll back the current transaction to a previously created savepoint.
401
+
402
+ Raises:
403
+ NotImplementedError: If the connected database does not support savepoints.
404
+ """
405
+ self._require_savepoints()
406
+ super().rollback_to_savepoint(name)
407
+
380
408
  def set_migration_session_schema(self, schema: str) -> None:
381
409
  """Set the PostgreSQL search path for migration SQL when using ADBC PostgreSQL."""
382
410
  if not self._is_postgres:
@@ -516,19 +544,13 @@ class AdbcDriver(SyncDriverAdapterBase):
516
544
  arrow_schema=arrow_schema,
517
545
  )
518
546
 
519
- # Use ADBC cursor for native Arrow
520
547
  with self.with_cursor(self.connection) as cursor, exc_handler:
521
548
  if cursor is None:
522
549
  msg = "Failed to create cursor"
523
550
  raise DatabaseConnectionError(msg)
524
551
 
525
- # Get compiled SQL and parameters
526
552
  sql, driver_params = self._compiled_sql(prepared_statement, config)
527
-
528
- # Execute query
529
553
  cursor.execute(sql, driver_params or ())
530
-
531
- # Fetch as Arrow table (zero-copy!)
532
554
  arrow_table = cursor.fetch_arrow_table()
533
555
 
534
556
  arrow_result = build_arrow_result_from_table(
@@ -566,7 +588,6 @@ class AdbcDriver(SyncDriverAdapterBase):
566
588
  ) -> "StorageBridgeJob":
567
589
  """Stream query results to storage via the Arrow fast path."""
568
590
 
569
- _ = kwargs
570
591
  self._require_capability("arrow_export_enabled")
571
592
  arrow_result = self.select_to_arrow(statement, *parameters, statement_config=statement_config, **kwargs)
572
593
  sync_pipeline = self._storage_pipeline()
@@ -729,6 +750,11 @@ class AdbcDriver(SyncDriverAdapterBase):
729
750
  """
730
751
  return self._transaction_active
731
752
 
753
+ def _require_savepoints(self) -> None:
754
+ if self._dialect_name in _DIALECTS_WITHOUT_SAVEPOINTS:
755
+ msg = f"ADBC {self._dialect_name} connections do not support savepoints."
756
+ raise NotImplementedError(msg)
757
+
732
758
  @staticmethod
733
759
  def _detect_flightsql_connection(connection: "AdbcConnection") -> bool:
734
760
  try:
@@ -34,7 +34,15 @@ class ADBCOutputConverter:
34
34
  Returns:
35
35
  Converted value appropriate for the dialect.
36
36
  """
37
- if self.dialect in {"postgres", "postgresql", "pgvector", "paradedb", "bigquery"}:
37
+ if self.dialect in {
38
+ "postgres",
39
+ "postgresql",
40
+ "pgvector",
41
+ "paradedb",
42
+ "pg_textsearch",
43
+ "pgtextsearch",
44
+ "bigquery",
45
+ }:
38
46
  return to_json(value)
39
47
  return value
40
48
 
@@ -51,7 +59,7 @@ class ADBCOutputConverter:
51
59
  Converted list parameter appropriate for the dialect.
52
60
  """
53
61
  items = list(value)
54
- if self.dialect in {"postgres", "postgresql", "pgvector", "paradedb"}:
62
+ if self.dialect in {"postgres", "postgresql", "pgvector", "paradedb", "pg_textsearch", "pgtextsearch"}:
55
63
  return [item if item is not None else None for item in items]
56
64
  return items
57
65
 
@@ -18,6 +18,7 @@ from sqlspec.adapters.aiomysql._typing import (
18
18
  from sqlspec.adapters.aiomysql.core import apply_driver_features, default_statement_config
19
19
  from sqlspec.adapters.aiomysql.driver import AiomysqlDriver, AiomysqlExceptionHandler
20
20
  from sqlspec.config import AsyncDatabaseConfig, ExtensionConfigs
21
+ from sqlspec.core import TypeCoercionCapabilities
21
22
  from sqlspec.driver._async import AsyncPoolConnectionContext, AsyncPoolSessionFactory
22
23
  from sqlspec.exceptions import ImproperConfigurationError
23
24
  from sqlspec.extensions.events import EventRuntimeHints
@@ -35,7 +36,6 @@ if TYPE_CHECKING:
35
36
  __all__ = ("AiomysqlConfig", "AiomysqlConnectionParams", "AiomysqlDriverFeatures", "AiomysqlPoolParams")
36
37
 
37
38
  _POOL_ONLY_CONFIG_KEYS = frozenset({"maxsize", "minsize", "pool_recycle"})
38
- _AIOMYSQL_LOCAL_INFILE_GATE = "allow_local_infile"
39
39
  aiomysql: "AiomysqlModule" = cast("AiomysqlModule", AiomysqlModule)
40
40
 
41
41
 
@@ -84,28 +84,19 @@ class AiomysqlPoolParams(AiomysqlConnectionParams):
84
84
  pool_recycle: NotRequired[int]
85
85
 
86
86
 
87
- def _normalize_local_infile(connection_config: "Mapping[str, Any]", *, strip_consent_gate: bool) -> "dict[str, Any]":
88
- """Normalize aiomysql local-infile settings and SQLSpec's consent gate."""
87
+ def _normalize_local_infile(connection_config: "Mapping[str, Any]") -> "dict[str, Any]":
88
+ """Normalize aiomysql local-infile aliases to the native connection flag."""
89
89
  config = dict(connection_config)
90
90
 
91
91
  config.pop("enable_local_infile", None)
92
- allow_local_infile = bool(config.get(_AIOMYSQL_LOCAL_INFILE_GATE, False))
93
- local_infile = bool(config.get("local_infile", False))
94
- if local_infile and not allow_local_infile:
95
- msg = (
96
- "Aiomysql local_infile=True requires allow_local_infile=True because "
97
- "LOAD DATA LOCAL INFILE can read client files."
98
- )
99
- raise ImproperConfigurationError(msg)
100
- config["local_infile"] = bool(local_infile and allow_local_infile)
101
- if strip_consent_gate:
102
- config.pop(_AIOMYSQL_LOCAL_INFILE_GATE, None)
92
+ allow_local_infile = bool(config.pop("allow_local_infile", False))
93
+ config["local_infile"] = bool(config.get("local_infile", False) or allow_local_infile)
103
94
  return config
104
95
 
105
96
 
106
97
  def _normalize_connection_kwargs(connection_config: "Mapping[str, Any]") -> "dict[str, Any]":
107
98
  """Build aiomysql.connect-compatible kwargs from SQLSpec connection config."""
108
- config = _normalize_local_infile(connection_config, strip_consent_gate=True)
99
+ config = _normalize_local_infile(connection_config)
109
100
 
110
101
  for key in _POOL_ONLY_CONFIG_KEYS:
111
102
  config.pop(key, None)
@@ -222,6 +213,9 @@ class AiomysqlConfig(AsyncDatabaseConfig[AiomysqlConnection, "AiomysqlPool", Aio
222
213
  supports_native_arrow_import: ClassVar[bool] = True
223
214
  supports_native_parquet_import: ClassVar[bool] = True
224
215
  supports_native_row_streaming: ClassVar[bool] = True
216
+ type_coercion_capabilities: ClassVar[TypeCoercionCapabilities] = TypeCoercionCapabilities(
217
+ datetime_binding="native", timestamp_precision="microsecond", json_columns_decoded=False, uuid_binding="text"
218
+ )
225
219
  _connection_context_class: "ClassVar[type[AiomysqlConnectionContext]]" = AiomysqlConnectionContext
226
220
  _session_factory_class: "ClassVar[type[_AiomysqlSessionFactory]]" = _AiomysqlSessionFactory
227
221
  _session_context_class: "ClassVar[type[AiomysqlSessionContext]]" = AiomysqlSessionContext
@@ -253,9 +247,7 @@ class AiomysqlConfig(AsyncDatabaseConfig[AiomysqlConnection, "AiomysqlPool", Aio
253
247
  observability_config: Adapter-level observability overrides for lifecycle hooks and observers
254
248
  **kwargs: Additional keyword arguments
255
249
  """
256
- connection_config = _normalize_local_infile(
257
- normalize_connection_config(connection_config), strip_consent_gate=False
258
- )
250
+ connection_config = _normalize_local_infile(normalize_connection_config(connection_config))
259
251
 
260
252
  connection_config.setdefault("host", "localhost")
261
253
  connection_config.setdefault("port", 3306)
@@ -271,11 +263,9 @@ class AiomysqlConfig(AsyncDatabaseConfig[AiomysqlConnection, "AiomysqlPool", Aio
271
263
  # Track initialized connections to ensure callback runs exactly once per physical connection
272
264
  self._initialized_connections: WeakSet[Any] = WeakSet()
273
265
 
266
+ features_dict.setdefault("enable_local_infile_bulk_load", connection_config["local_infile"])
274
267
  if features_dict.get("enable_local_infile_bulk_load") and not connection_config.get("local_infile"):
275
- msg = (
276
- "enable_local_infile_bulk_load requires local_infile=True and "
277
- "allow_local_infile=True in connection_config."
278
- )
268
+ msg = "enable_local_infile_bulk_load requires local_infile=True or allow_local_infile=True in connection_config."
279
269
  raise ImproperConfigurationError(msg)
280
270
 
281
271
  super().__init__(
@@ -23,6 +23,7 @@ from sqlspec.adapters.aiosqlite.pool import (
23
23
  )
24
24
  from sqlspec.adapters.aiosqlite.type_converter import register_type_handlers
25
25
  from sqlspec.config import AsyncDatabaseConfig, ExtensionConfigs
26
+ from sqlspec.core.capabilities import TypeCoercionCapabilities
26
27
  from sqlspec.driver._async import AsyncPoolConnectionContext, AsyncPoolSessionFactory
27
28
  from sqlspec.exceptions import ImproperConfigurationError
28
29
  from sqlspec.utils.config_tools import normalize_connection_config
@@ -251,6 +252,9 @@ class AiosqliteConfig(AsyncDatabaseConfig["AiosqliteConnection", AiosqliteConnec
251
252
  supports_native_parquet_export: "ClassVar[bool]" = True
252
253
  supports_native_parquet_import: "ClassVar[bool]" = True
253
254
  supports_native_row_streaming: "ClassVar[bool]" = True
255
+ type_coercion_capabilities: "ClassVar[TypeCoercionCapabilities]" = TypeCoercionCapabilities(
256
+ datetime_binding="iso_text", timestamp_precision="microsecond", json_columns_decoded=False, uuid_binding="text"
257
+ )
254
258
  _connection_context_class: "ClassVar[type[AiosqliteConnectionContext]]" = AiosqliteConnectionContext
255
259
  _session_factory_class: "ClassVar[type[_AiosqliteSessionFactory]]" = _AiosqliteSessionFactory
256
260
  _session_context_class: "ClassVar[type[AiosqliteSessionContext]]" = AiosqliteSessionContext
@@ -68,6 +68,7 @@ _TIME_TO_ISO = time_iso_convert
68
68
  _DECIMAL_TO_STRING = build_decimal_converter(mode="string")
69
69
 
70
70
  SQLITE_CONSTRAINT_UNIQUE_CODE = 2067
71
+ SQLITE_CONSTRAINT_PRIMARYKEY_CODE = 1555
71
72
  SQLITE_CONSTRAINT_FOREIGNKEY_CODE = 787
72
73
  SQLITE_CONSTRAINT_NOTNULL_CODE = 1811
73
74
  SQLITE_CONSTRAINT_CHECK_CODE = 531
@@ -348,6 +349,12 @@ def create_mapped_exception(error: BaseException, *, logger: Any | None = None)
348
349
  error_name = None
349
350
  error_msg = str(error).lower()
350
351
 
352
+ if error_code in (SQLITE_CONSTRAINT_UNIQUE_CODE, SQLITE_CONSTRAINT_PRIMARYKEY_CODE) or error_name in (
353
+ "SQLITE_CONSTRAINT_UNIQUE",
354
+ "SQLITE_CONSTRAINT_PRIMARYKEY",
355
+ ):
356
+ return _create_aiosqlite_error(error, error_code, UniqueViolationError, "unique constraint violation")
357
+
351
358
  # Check for busy/locked conditions first (deadlock-like scenarios in SQLite)
352
359
  # SQLITE_BUSY means another process has the database locked
353
360
  # SQLITE_LOCKED means another connection has the table/rows locked
@@ -385,8 +392,6 @@ def create_mapped_exception(error: BaseException, *, logger: Any | None = None)
385
392
  return _create_aiosqlite_error(error, None, SQLParsingError, "SQL syntax error")
386
393
  return _create_aiosqlite_error(error, None, SQLSpecError, "database error")
387
394
 
388
- if error_code == SQLITE_CONSTRAINT_UNIQUE_CODE or error_name == "SQLITE_CONSTRAINT_UNIQUE":
389
- return _create_aiosqlite_error(error, error_code, UniqueViolationError, "unique constraint violation")
390
395
  if error_code == SQLITE_CONSTRAINT_FOREIGNKEY_CODE or error_name == "SQLITE_CONSTRAINT_FOREIGNKEY":
391
396
  return _create_aiosqlite_error(error, error_code, ForeignKeyViolationError, "foreign key constraint violation")
392
397
  if error_code == SQLITE_CONSTRAINT_NOTNULL_CODE or error_name == "SQLITE_CONSTRAINT_NOTNULL":
@@ -14,6 +14,7 @@ from sqlspec.adapters.arrow_odbc.core import (
14
14
  )
15
15
  from sqlspec.adapters.arrow_odbc.driver import ArrowOdbcDriver
16
16
  from sqlspec.config import ExtensionConfigs, NoPoolSyncConfig
17
+ from sqlspec.core.capabilities import TypeCoercionCapabilities
17
18
  from sqlspec.driver._sync import SyncPoolConnectionContext, SyncPoolSessionFactory
18
19
  from sqlspec.exceptions import ImproperConfigurationError
19
20
  from sqlspec.extensions.events import EventRuntimeHints
@@ -137,6 +138,10 @@ class ArrowOdbcConfig(NoPoolSyncConfig[ArrowOdbcConnection, ArrowOdbcDriver]):
137
138
  supports_native_row_streaming: "ClassVar[bool]" = True
138
139
  supports_native_parquet_export: "ClassVar[bool]" = False
139
140
  supports_native_parquet_import: "ClassVar[bool]" = False
141
+ supports_reliable_rowcount: "ClassVar[bool]" = False
142
+ type_coercion_capabilities: "ClassVar[TypeCoercionCapabilities]" = TypeCoercionCapabilities(
143
+ datetime_binding="iso_text", timestamp_precision="microsecond", json_columns_decoded=False, uuid_binding="text"
144
+ )
140
145
  _connection_context_class: "ClassVar[type[ArrowOdbcConnectionContext]]" = ArrowOdbcConnectionContext
141
146
  _session_factory_class: "ClassVar[type[_ArrowOdbcSessionConnectionHandler]]" = _ArrowOdbcSessionConnectionHandler
142
147
  _session_context_class: "ClassVar[type[ArrowOdbcSessionContext]]" = ArrowOdbcSessionContext