sqlspec 0.25.0__py3-none-any.whl → 0.27.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 (199) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +256 -24
  3. sqlspec/_typing.py +71 -52
  4. sqlspec/adapters/adbc/_types.py +1 -1
  5. sqlspec/adapters/adbc/adk/__init__.py +5 -0
  6. sqlspec/adapters/adbc/adk/store.py +870 -0
  7. sqlspec/adapters/adbc/config.py +69 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +340 -0
  9. sqlspec/adapters/adbc/driver.py +266 -58
  10. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  11. sqlspec/adapters/adbc/litestar/store.py +504 -0
  12. sqlspec/adapters/adbc/type_converter.py +153 -0
  13. sqlspec/adapters/aiosqlite/_types.py +1 -1
  14. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  15. sqlspec/adapters/aiosqlite/adk/store.py +527 -0
  16. sqlspec/adapters/aiosqlite/config.py +88 -15
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
  18. sqlspec/adapters/aiosqlite/driver.py +143 -40
  19. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  20. sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
  21. sqlspec/adapters/aiosqlite/pool.py +7 -7
  22. sqlspec/adapters/asyncmy/__init__.py +7 -1
  23. sqlspec/adapters/asyncmy/_types.py +2 -2
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +493 -0
  26. sqlspec/adapters/asyncmy/config.py +68 -23
  27. sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
  28. sqlspec/adapters/asyncmy/driver.py +313 -58
  29. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  30. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  31. sqlspec/adapters/asyncpg/__init__.py +2 -1
  32. sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
  33. sqlspec/adapters/asyncpg/_types.py +11 -7
  34. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  35. sqlspec/adapters/asyncpg/adk/store.py +450 -0
  36. sqlspec/adapters/asyncpg/config.py +59 -35
  37. sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
  38. sqlspec/adapters/asyncpg/driver.py +170 -25
  39. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  40. sqlspec/adapters/asyncpg/litestar/store.py +253 -0
  41. sqlspec/adapters/bigquery/_types.py +1 -1
  42. sqlspec/adapters/bigquery/adk/__init__.py +5 -0
  43. sqlspec/adapters/bigquery/adk/store.py +576 -0
  44. sqlspec/adapters/bigquery/config.py +27 -10
  45. sqlspec/adapters/bigquery/data_dictionary.py +149 -0
  46. sqlspec/adapters/bigquery/driver.py +368 -142
  47. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  48. sqlspec/adapters/bigquery/litestar/store.py +327 -0
  49. sqlspec/adapters/bigquery/type_converter.py +125 -0
  50. sqlspec/adapters/duckdb/_types.py +1 -1
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +553 -0
  53. sqlspec/adapters/duckdb/config.py +80 -20
  54. sqlspec/adapters/duckdb/data_dictionary.py +163 -0
  55. sqlspec/adapters/duckdb/driver.py +167 -45
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +4 -4
  59. sqlspec/adapters/duckdb/type_converter.py +133 -0
  60. sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
  61. sqlspec/adapters/oracledb/_types.py +20 -2
  62. sqlspec/adapters/oracledb/adk/__init__.py +5 -0
  63. sqlspec/adapters/oracledb/adk/store.py +1745 -0
  64. sqlspec/adapters/oracledb/config.py +122 -32
  65. sqlspec/adapters/oracledb/data_dictionary.py +509 -0
  66. sqlspec/adapters/oracledb/driver.py +353 -91
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +767 -0
  69. sqlspec/adapters/oracledb/migrations.py +348 -73
  70. sqlspec/adapters/oracledb/type_converter.py +207 -0
  71. sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
  72. sqlspec/adapters/psqlpy/_types.py +2 -1
  73. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  74. sqlspec/adapters/psqlpy/adk/store.py +482 -0
  75. sqlspec/adapters/psqlpy/config.py +46 -17
  76. sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
  77. sqlspec/adapters/psqlpy/driver.py +123 -209
  78. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  79. sqlspec/adapters/psqlpy/litestar/store.py +272 -0
  80. sqlspec/adapters/psqlpy/type_converter.py +102 -0
  81. sqlspec/adapters/psycopg/_type_handlers.py +80 -0
  82. sqlspec/adapters/psycopg/_types.py +2 -1
  83. sqlspec/adapters/psycopg/adk/__init__.py +5 -0
  84. sqlspec/adapters/psycopg/adk/store.py +944 -0
  85. sqlspec/adapters/psycopg/config.py +69 -35
  86. sqlspec/adapters/psycopg/data_dictionary.py +331 -0
  87. sqlspec/adapters/psycopg/driver.py +238 -81
  88. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  89. sqlspec/adapters/psycopg/litestar/store.py +554 -0
  90. sqlspec/adapters/sqlite/__init__.py +2 -1
  91. sqlspec/adapters/sqlite/_type_handlers.py +86 -0
  92. sqlspec/adapters/sqlite/_types.py +1 -1
  93. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  94. sqlspec/adapters/sqlite/adk/store.py +572 -0
  95. sqlspec/adapters/sqlite/config.py +87 -15
  96. sqlspec/adapters/sqlite/data_dictionary.py +149 -0
  97. sqlspec/adapters/sqlite/driver.py +137 -54
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +18 -9
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +162 -89
  104. sqlspec/builder/_column.py +62 -29
  105. sqlspec/builder/_ddl.py +180 -121
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +53 -94
  109. sqlspec/builder/_insert.py +32 -131
  110. sqlspec/builder/_join.py +375 -0
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +111 -17
  113. sqlspec/builder/_select.py +1457 -24
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +307 -194
  116. sqlspec/config.py +252 -67
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +17 -17
  119. sqlspec/core/compiler.py +62 -9
  120. sqlspec/core/filters.py +37 -37
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +83 -48
  123. sqlspec/core/result.py +102 -46
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +36 -30
  126. sqlspec/core/type_conversion.py +235 -0
  127. sqlspec/driver/__init__.py +7 -6
  128. sqlspec/driver/_async.py +188 -151
  129. sqlspec/driver/_common.py +285 -80
  130. sqlspec/driver/_sync.py +188 -152
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +75 -7
  134. sqlspec/extensions/adk/__init__.py +53 -0
  135. sqlspec/extensions/adk/_types.py +51 -0
  136. sqlspec/extensions/adk/converters.py +172 -0
  137. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
  138. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  139. sqlspec/extensions/adk/service.py +181 -0
  140. sqlspec/extensions/adk/store.py +536 -0
  141. sqlspec/extensions/aiosql/adapter.py +73 -53
  142. sqlspec/extensions/litestar/__init__.py +21 -4
  143. sqlspec/extensions/litestar/cli.py +54 -10
  144. sqlspec/extensions/litestar/config.py +59 -266
  145. sqlspec/extensions/litestar/handlers.py +46 -17
  146. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  147. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  148. sqlspec/extensions/litestar/plugin.py +324 -223
  149. sqlspec/extensions/litestar/providers.py +25 -25
  150. sqlspec/extensions/litestar/store.py +265 -0
  151. sqlspec/loader.py +30 -49
  152. sqlspec/migrations/__init__.py +4 -3
  153. sqlspec/migrations/base.py +302 -39
  154. sqlspec/migrations/commands.py +611 -144
  155. sqlspec/migrations/context.py +142 -0
  156. sqlspec/migrations/fix.py +199 -0
  157. sqlspec/migrations/loaders.py +68 -23
  158. sqlspec/migrations/runner.py +543 -107
  159. sqlspec/migrations/tracker.py +237 -21
  160. sqlspec/migrations/utils.py +51 -3
  161. sqlspec/migrations/validation.py +177 -0
  162. sqlspec/protocols.py +66 -36
  163. sqlspec/storage/_utils.py +98 -0
  164. sqlspec/storage/backends/fsspec.py +134 -106
  165. sqlspec/storage/backends/local.py +78 -51
  166. sqlspec/storage/backends/obstore.py +278 -162
  167. sqlspec/storage/registry.py +75 -39
  168. sqlspec/typing.py +16 -84
  169. sqlspec/utils/config_resolver.py +153 -0
  170. sqlspec/utils/correlation.py +4 -5
  171. sqlspec/utils/data_transformation.py +3 -2
  172. sqlspec/utils/deprecation.py +9 -8
  173. sqlspec/utils/fixtures.py +4 -4
  174. sqlspec/utils/logging.py +46 -6
  175. sqlspec/utils/module_loader.py +2 -2
  176. sqlspec/utils/schema.py +288 -0
  177. sqlspec/utils/serializers.py +50 -2
  178. sqlspec/utils/sync_tools.py +21 -17
  179. sqlspec/utils/text.py +1 -2
  180. sqlspec/utils/type_guards.py +111 -20
  181. sqlspec/utils/version.py +433 -0
  182. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
  183. sqlspec-0.27.0.dist-info/RECORD +207 -0
  184. sqlspec/builder/mixins/__init__.py +0 -55
  185. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
  186. sqlspec/builder/mixins/_delete_operations.py +0 -50
  187. sqlspec/builder/mixins/_insert_operations.py +0 -282
  188. sqlspec/builder/mixins/_join_operations.py +0 -389
  189. sqlspec/builder/mixins/_merge_operations.py +0 -592
  190. sqlspec/builder/mixins/_order_limit_operations.py +0 -152
  191. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  192. sqlspec/builder/mixins/_select_operations.py +0 -936
  193. sqlspec/builder/mixins/_update_operations.py +0 -218
  194. sqlspec/builder/mixins/_where_clause.py +0 -1304
  195. sqlspec-0.25.0.dist-info/RECORD +0 -139
  196. sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
  197. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
  198. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
  199. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,9 @@
1
1
  """ADBC database configuration."""
2
2
 
3
3
  import logging
4
+ from collections.abc import Callable
4
5
  from contextlib import contextmanager
5
- from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional, TypedDict, Union
6
+ from typing import TYPE_CHECKING, Any, ClassVar, TypedDict
6
7
 
7
8
  from typing_extensions import NotRequired
8
9
 
@@ -22,7 +23,7 @@ if TYPE_CHECKING:
22
23
  logger = logging.getLogger("sqlspec.adapters.adbc")
23
24
 
24
25
 
25
- class AdbcConnectionParams(TypedDict, total=False):
26
+ class AdbcConnectionParams(TypedDict):
26
27
  """ADBC connection parameters."""
27
28
 
28
29
  uri: NotRequired[str]
@@ -54,7 +55,37 @@ class AdbcConnectionParams(TypedDict, total=False):
54
55
  extra: NotRequired[dict[str, Any]]
55
56
 
56
57
 
57
- __all__ = ("AdbcConfig", "AdbcConnectionParams")
58
+ class AdbcDriverFeatures(TypedDict):
59
+ """ADBC driver feature configuration.
60
+
61
+ Controls optional type handling and serialization behavior for the ADBC adapter.
62
+ These features configure how data is converted between Python and Arrow types.
63
+
64
+ Attributes:
65
+ json_serializer: JSON serialization function to use.
66
+ Callable that takes Any and returns str (JSON string).
67
+ Default: sqlspec.utils.serializers.to_json
68
+ enable_cast_detection: Enable cast-aware parameter processing.
69
+ When True, detects SQL casts (e.g., ::JSONB) and applies appropriate
70
+ serialization. Currently used for PostgreSQL JSONB handling.
71
+ Default: True
72
+ strict_type_coercion: Enforce strict type coercion rules.
73
+ When True, raises errors for unsupported type conversions.
74
+ When False, attempts best-effort conversion.
75
+ Default: False
76
+ arrow_extension_types: Enable PyArrow extension type support.
77
+ When True, preserves Arrow extension type metadata when reading data.
78
+ When False, falls back to storage types.
79
+ Default: True
80
+ """
81
+
82
+ json_serializer: "NotRequired[Callable[[Any], str]]"
83
+ enable_cast_detection: NotRequired[bool]
84
+ strict_type_coercion: NotRequired[bool]
85
+ arrow_extension_types: NotRequired[bool]
86
+
87
+
88
+ __all__ = ("AdbcConfig", "AdbcConnectionParams", "AdbcDriverFeatures")
58
89
 
59
90
 
60
91
  class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
@@ -69,14 +100,17 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
69
100
 
70
101
  driver_type: ClassVar[type[AdbcDriver]] = AdbcDriver
71
102
  connection_type: "ClassVar[type[AdbcConnection]]" = AdbcConnection
103
+ supports_transactional_ddl: ClassVar[bool] = False
72
104
 
73
105
  def __init__(
74
106
  self,
75
107
  *,
76
- connection_config: Optional[Union[AdbcConnectionParams, dict[str, Any]]] = None,
77
- migration_config: Optional[dict[str, Any]] = None,
78
- statement_config: Optional[StatementConfig] = None,
79
- driver_features: Optional[dict[str, Any]] = None,
108
+ connection_config: AdbcConnectionParams | dict[str, Any] | None = None,
109
+ migration_config: dict[str, Any] | None = None,
110
+ statement_config: StatementConfig | None = None,
111
+ driver_features: "AdbcDriverFeatures | dict[str, Any] | None" = None,
112
+ bind_key: str | None = None,
113
+ extension_config: "dict[str, dict[str, Any]] | None" = None,
80
114
  ) -> None:
81
115
  """Initialize configuration.
82
116
 
@@ -84,7 +118,9 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
84
118
  connection_config: Connection configuration parameters
85
119
  migration_config: Migration configuration
86
120
  statement_config: Default SQL statement configuration
87
- driver_features: Driver feature configuration
121
+ driver_features: Driver feature configuration (AdbcDriverFeatures)
122
+ bind_key: Optional unique identifier for this configuration
123
+ extension_config: Extension-specific configuration (e.g., Litestar plugin settings)
88
124
  """
89
125
  if connection_config is None:
90
126
  connection_config = {}
@@ -99,11 +135,26 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
99
135
  detected_dialect = str(self._get_dialect() or "sqlite")
100
136
  statement_config = get_adbc_statement_config(detected_dialect)
101
137
 
138
+ from sqlspec.utils.serializers import to_json
139
+
140
+ if driver_features is None:
141
+ driver_features = {}
142
+ if "json_serializer" not in driver_features:
143
+ driver_features["json_serializer"] = to_json
144
+ if "enable_cast_detection" not in driver_features:
145
+ driver_features["enable_cast_detection"] = True
146
+ if "strict_type_coercion" not in driver_features:
147
+ driver_features["strict_type_coercion"] = False
148
+ if "arrow_extension_types" not in driver_features:
149
+ driver_features["arrow_extension_types"] = True
150
+
102
151
  super().__init__(
103
152
  connection_config=self.connection_config,
104
153
  migration_config=migration_config,
105
154
  statement_config=statement_config,
106
- driver_features=driver_features or {},
155
+ driver_features=dict(driver_features),
156
+ bind_key=bind_key,
157
+ extension_config=extension_config,
107
158
  )
108
159
 
109
160
  def _resolve_driver_name(self) -> str:
@@ -174,7 +225,11 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
174
225
  try:
175
226
  connect_func = import_string(driver_path)
176
227
  except ImportError as e:
177
- driver_path_with_suffix = f"{driver_path}.dbapi.connect"
228
+ # Only add .dbapi.connect if it's not already there
229
+ if not driver_path.endswith(".dbapi.connect"):
230
+ driver_path_with_suffix = f"{driver_path}.dbapi.connect"
231
+ else:
232
+ driver_path_with_suffix = driver_path
178
233
  try:
179
234
  connect_func = import_string(driver_path_with_suffix)
180
235
  except ImportError as e2:
@@ -277,7 +332,7 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
277
332
  connection.close()
278
333
 
279
334
  def provide_session(
280
- self, *args: Any, statement_config: "Optional[StatementConfig]" = None, **kwargs: Any
335
+ self, *args: Any, statement_config: "StatementConfig | None" = None, **kwargs: Any
281
336
  ) -> "AbstractContextManager[AdbcDriver]":
282
337
  """Provide a driver session context manager.
283
338
 
@@ -298,7 +353,9 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
298
353
  or self.statement_config
299
354
  or get_adbc_statement_config(str(self._get_dialect() or "sqlite"))
300
355
  )
301
- yield self.driver_type(connection=connection, statement_config=final_statement_config)
356
+ yield self.driver_type(
357
+ connection=connection, statement_config=final_statement_config, driver_features=self.driver_features
358
+ )
302
359
 
303
360
  return session_manager()
304
361
 
@@ -0,0 +1,340 @@
1
+ """ADBC multi-dialect data dictionary for metadata queries."""
2
+
3
+ import re
4
+ from typing import TYPE_CHECKING, Any, cast
5
+
6
+ from sqlspec.driver import SyncDataDictionaryBase, SyncDriverAdapterBase, VersionInfo
7
+ from sqlspec.utils.logging import get_logger
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Callable
11
+
12
+ from sqlspec.adapters.adbc.driver import AdbcDriver
13
+
14
+ logger = get_logger("adapters.adbc.data_dictionary")
15
+
16
+ POSTGRES_VERSION_PATTERN = re.compile(r"PostgreSQL (\d+)\.(\d+)(?:\.(\d+))?")
17
+ SQLITE_VERSION_PATTERN = re.compile(r"(\d+)\.(\d+)\.(\d+)")
18
+ DUCKDB_VERSION_PATTERN = re.compile(r"v?(\d+)\.(\d+)\.(\d+)")
19
+ MYSQL_VERSION_PATTERN = re.compile(r"(\d+)\.(\d+)\.(\d+)")
20
+
21
+ __all__ = ("AdbcDataDictionary",)
22
+
23
+
24
+ class AdbcDataDictionary(SyncDataDictionaryBase):
25
+ """ADBC multi-dialect data dictionary.
26
+
27
+ Delegates to appropriate dialect-specific logic based on the driver's dialect.
28
+ """
29
+
30
+ def _get_dialect(self, driver: SyncDriverAdapterBase) -> str:
31
+ """Get dialect from ADBC driver.
32
+
33
+ Args:
34
+ driver: ADBC driver instance
35
+
36
+ Returns:
37
+ Dialect name
38
+ """
39
+ return str(cast("AdbcDriver", driver).dialect)
40
+
41
+ def get_version(self, driver: SyncDriverAdapterBase) -> "VersionInfo | None":
42
+ """Get database version information based on detected dialect.
43
+
44
+ Args:
45
+ driver: ADBC driver instance
46
+
47
+ Returns:
48
+ Database version information or None if detection fails
49
+ """
50
+ dialect = self._get_dialect(driver)
51
+ adbc_driver = cast("AdbcDriver", driver)
52
+
53
+ try:
54
+ if dialect == "postgres":
55
+ version_str = adbc_driver.select_value("SELECT version()")
56
+ if version_str:
57
+ match = POSTGRES_VERSION_PATTERN.search(str(version_str))
58
+ if match:
59
+ major = int(match.group(1))
60
+ minor = int(match.group(2))
61
+ patch = int(match.group(3)) if match.group(3) else 0
62
+ return VersionInfo(major, minor, patch)
63
+
64
+ elif dialect == "sqlite":
65
+ version_str = adbc_driver.select_value("SELECT sqlite_version()")
66
+ if version_str:
67
+ match = SQLITE_VERSION_PATTERN.match(str(version_str))
68
+ if match:
69
+ major, minor, patch = map(int, match.groups())
70
+ return VersionInfo(major, minor, patch)
71
+
72
+ elif dialect == "duckdb":
73
+ version_str = adbc_driver.select_value("SELECT version()")
74
+ if version_str:
75
+ match = DUCKDB_VERSION_PATTERN.search(str(version_str))
76
+ if match:
77
+ major, minor, patch = map(int, match.groups())
78
+ return VersionInfo(major, minor, patch)
79
+
80
+ elif dialect == "mysql":
81
+ version_str = adbc_driver.select_value("SELECT VERSION()")
82
+ if version_str:
83
+ match = MYSQL_VERSION_PATTERN.search(str(version_str))
84
+ if match:
85
+ major, minor, patch = map(int, match.groups())
86
+ return VersionInfo(major, minor, patch)
87
+
88
+ elif dialect == "bigquery":
89
+ return VersionInfo(1, 0, 0)
90
+
91
+ except Exception:
92
+ logger.warning("Failed to get %s version", dialect)
93
+
94
+ return None
95
+
96
+ def get_feature_flag(self, driver: SyncDriverAdapterBase, feature: str) -> bool:
97
+ """Check if database supports a specific feature based on detected dialect.
98
+
99
+ Args:
100
+ driver: ADBC driver instance
101
+ feature: Feature name to check
102
+
103
+ Returns:
104
+ True if feature is supported, False otherwise
105
+ """
106
+ dialect = self._get_dialect(driver)
107
+ version_info = self.get_version(driver)
108
+
109
+ if dialect == "postgres":
110
+ feature_checks: dict[str, Callable[..., bool]] = {
111
+ "supports_json": lambda v: v and v >= VersionInfo(9, 2, 0),
112
+ "supports_jsonb": lambda v: v and v >= VersionInfo(9, 4, 0),
113
+ "supports_uuid": lambda _: True,
114
+ "supports_arrays": lambda _: True,
115
+ "supports_returning": lambda v: v and v >= VersionInfo(8, 2, 0),
116
+ "supports_upsert": lambda v: v and v >= VersionInfo(9, 5, 0),
117
+ "supports_window_functions": lambda v: v and v >= VersionInfo(8, 4, 0),
118
+ "supports_cte": lambda v: v and v >= VersionInfo(8, 4, 0),
119
+ "supports_transactions": lambda _: True,
120
+ "supports_prepared_statements": lambda _: True,
121
+ "supports_schemas": lambda _: True,
122
+ }
123
+ elif dialect == "sqlite":
124
+ feature_checks = {
125
+ "supports_json": lambda v: v and v >= VersionInfo(3, 38, 0),
126
+ "supports_returning": lambda v: v and v >= VersionInfo(3, 35, 0),
127
+ "supports_upsert": lambda v: v and v >= VersionInfo(3, 24, 0),
128
+ "supports_window_functions": lambda v: v and v >= VersionInfo(3, 25, 0),
129
+ "supports_cte": lambda v: v and v >= VersionInfo(3, 8, 3),
130
+ "supports_transactions": lambda _: True,
131
+ "supports_prepared_statements": lambda _: True,
132
+ "supports_schemas": lambda _: False,
133
+ "supports_arrays": lambda _: False,
134
+ "supports_uuid": lambda _: False,
135
+ }
136
+ elif dialect == "duckdb":
137
+ feature_checks = {
138
+ "supports_json": lambda _: True,
139
+ "supports_arrays": lambda _: True,
140
+ "supports_uuid": lambda _: True,
141
+ "supports_returning": lambda v: v and v >= VersionInfo(0, 8, 0),
142
+ "supports_upsert": lambda v: v and v >= VersionInfo(0, 8, 0),
143
+ "supports_window_functions": lambda _: True,
144
+ "supports_cte": lambda _: True,
145
+ "supports_transactions": lambda _: True,
146
+ "supports_prepared_statements": lambda _: True,
147
+ "supports_schemas": lambda _: True,
148
+ }
149
+ elif dialect == "mysql":
150
+ feature_checks = {
151
+ "supports_json": lambda v: v and v >= VersionInfo(5, 7, 8),
152
+ "supports_cte": lambda v: v and v >= VersionInfo(8, 0, 1),
153
+ "supports_returning": lambda _: False,
154
+ "supports_upsert": lambda _: True,
155
+ "supports_window_functions": lambda v: v and v >= VersionInfo(8, 0, 2),
156
+ "supports_transactions": lambda _: True,
157
+ "supports_prepared_statements": lambda _: True,
158
+ "supports_schemas": lambda _: True,
159
+ "supports_uuid": lambda _: False,
160
+ "supports_arrays": lambda _: False,
161
+ }
162
+ elif dialect == "bigquery":
163
+ feature_checks = {
164
+ "supports_json": lambda _: True,
165
+ "supports_arrays": lambda _: True,
166
+ "supports_structs": lambda _: True,
167
+ "supports_returning": lambda _: False,
168
+ "supports_upsert": lambda _: True,
169
+ "supports_window_functions": lambda _: True,
170
+ "supports_cte": lambda _: True,
171
+ "supports_transactions": lambda _: False,
172
+ "supports_prepared_statements": lambda _: True,
173
+ "supports_schemas": lambda _: True,
174
+ "supports_uuid": lambda _: False,
175
+ }
176
+ else:
177
+ feature_checks = {
178
+ "supports_transactions": lambda _: True,
179
+ "supports_prepared_statements": lambda _: True,
180
+ "supports_window_functions": lambda _: True,
181
+ "supports_cte": lambda _: True,
182
+ }
183
+
184
+ if feature in feature_checks:
185
+ return bool(feature_checks[feature](version_info))
186
+
187
+ return False
188
+
189
+ def get_optimal_type(self, driver: SyncDriverAdapterBase, type_category: str) -> str:
190
+ """Get optimal database type for a category based on detected dialect.
191
+
192
+ Args:
193
+ driver: ADBC driver instance
194
+ type_category: Type category
195
+
196
+ Returns:
197
+ Database-specific type name
198
+ """
199
+ dialect = self._get_dialect(driver)
200
+ version_info = self.get_version(driver)
201
+
202
+ if dialect == "postgres":
203
+ if type_category == "json":
204
+ if version_info and version_info >= VersionInfo(9, 4, 0):
205
+ return "JSONB"
206
+ if version_info and version_info >= VersionInfo(9, 2, 0):
207
+ return "JSON"
208
+ return "TEXT"
209
+ type_map = {
210
+ "uuid": "UUID",
211
+ "boolean": "BOOLEAN",
212
+ "timestamp": "TIMESTAMP WITH TIME ZONE",
213
+ "text": "TEXT",
214
+ "blob": "BYTEA",
215
+ "array": "ARRAY",
216
+ }
217
+
218
+ elif dialect == "sqlite":
219
+ if type_category == "json":
220
+ if version_info and version_info >= VersionInfo(3, 38, 0):
221
+ return "JSON"
222
+ return "TEXT"
223
+ type_map = {"uuid": "TEXT", "boolean": "INTEGER", "timestamp": "TIMESTAMP", "text": "TEXT", "blob": "BLOB"}
224
+
225
+ elif dialect == "duckdb":
226
+ type_map = {
227
+ "json": "JSON",
228
+ "uuid": "UUID",
229
+ "boolean": "BOOLEAN",
230
+ "timestamp": "TIMESTAMP",
231
+ "text": "TEXT",
232
+ "blob": "BLOB",
233
+ "array": "LIST",
234
+ }
235
+
236
+ elif dialect == "mysql":
237
+ if type_category == "json":
238
+ if version_info and version_info >= VersionInfo(5, 7, 8):
239
+ return "JSON"
240
+ return "TEXT"
241
+ type_map = {
242
+ "uuid": "VARCHAR(36)",
243
+ "boolean": "TINYINT(1)",
244
+ "timestamp": "TIMESTAMP",
245
+ "text": "TEXT",
246
+ "blob": "BLOB",
247
+ }
248
+
249
+ elif dialect == "bigquery":
250
+ type_map = {
251
+ "json": "JSON",
252
+ "uuid": "STRING",
253
+ "boolean": "BOOL",
254
+ "timestamp": "TIMESTAMP",
255
+ "text": "STRING",
256
+ "blob": "BYTES",
257
+ "array": "ARRAY",
258
+ }
259
+ else:
260
+ type_map = {
261
+ "json": "TEXT",
262
+ "uuid": "VARCHAR(36)",
263
+ "boolean": "INTEGER",
264
+ "timestamp": "TIMESTAMP",
265
+ "text": "TEXT",
266
+ "blob": "BLOB",
267
+ }
268
+
269
+ return type_map.get(type_category, "TEXT")
270
+
271
+ def get_columns(
272
+ self, driver: SyncDriverAdapterBase, table: str, schema: "str | None" = None
273
+ ) -> "list[dict[str, Any]]":
274
+ """Get column information for a table based on detected dialect.
275
+
276
+ Args:
277
+ driver: ADBC driver instance
278
+ table: Table name to query columns for
279
+ schema: Schema name (None for default)
280
+
281
+ Returns:
282
+ List of column metadata dictionaries with keys:
283
+ - column_name: Name of the column
284
+ - data_type: Database data type
285
+ - is_nullable or nullable: Whether column allows NULL
286
+ - column_default or default_value: Default value if any
287
+ """
288
+ dialect = self._get_dialect(driver)
289
+ adbc_driver = cast("AdbcDriver", driver)
290
+
291
+ if dialect == "sqlite":
292
+ result = adbc_driver.execute(f"PRAGMA table_info({table})")
293
+ return [
294
+ {
295
+ "column_name": row["name"] if isinstance(row, dict) else row[1],
296
+ "data_type": row["type"] if isinstance(row, dict) else row[2],
297
+ "nullable": not (row["notnull"] if isinstance(row, dict) else row[3]),
298
+ "default_value": row["dflt_value"] if isinstance(row, dict) else row[4],
299
+ }
300
+ for row in result.data or []
301
+ ]
302
+
303
+ if schema:
304
+ sql = f"""
305
+ SELECT column_name, data_type, is_nullable, column_default
306
+ FROM information_schema.columns
307
+ WHERE table_name = '{table}' AND table_schema = '{schema}'
308
+ ORDER BY ordinal_position
309
+ """
310
+ else:
311
+ sql = f"""
312
+ SELECT column_name, data_type, is_nullable, column_default
313
+ FROM information_schema.columns
314
+ WHERE table_name = '{table}'
315
+ ORDER BY ordinal_position
316
+ """
317
+
318
+ result = adbc_driver.execute(sql)
319
+ return result.data or []
320
+
321
+ def list_available_features(self) -> "list[str]":
322
+ """List available feature flags across all supported dialects.
323
+
324
+ Returns:
325
+ List of supported feature names
326
+ """
327
+ return [
328
+ "supports_json",
329
+ "supports_jsonb",
330
+ "supports_uuid",
331
+ "supports_arrays",
332
+ "supports_structs",
333
+ "supports_returning",
334
+ "supports_upsert",
335
+ "supports_window_functions",
336
+ "supports_cte",
337
+ "supports_transactions",
338
+ "supports_prepared_statements",
339
+ "supports_schemas",
340
+ ]