sqlspec 0.26.0__py3-none-any.whl → 0.28.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 (212) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +55 -25
  3. sqlspec/_typing.py +155 -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 +880 -0
  7. sqlspec/adapters/adbc/config.py +62 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +74 -2
  9. sqlspec/adapters/adbc/driver.py +226 -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 +44 -50
  13. sqlspec/adapters/aiosqlite/_types.py +1 -1
  14. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  15. sqlspec/adapters/aiosqlite/adk/store.py +536 -0
  16. sqlspec/adapters/aiosqlite/config.py +86 -16
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +34 -2
  18. sqlspec/adapters/aiosqlite/driver.py +127 -38
  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 +1 -1
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +503 -0
  26. sqlspec/adapters/asyncmy/config.py +59 -17
  27. sqlspec/adapters/asyncmy/data_dictionary.py +41 -2
  28. sqlspec/adapters/asyncmy/driver.py +293 -62
  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 +460 -0
  36. sqlspec/adapters/asyncpg/config.py +57 -36
  37. sqlspec/adapters/asyncpg/data_dictionary.py +48 -2
  38. sqlspec/adapters/asyncpg/driver.py +153 -23
  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 +585 -0
  44. sqlspec/adapters/bigquery/config.py +36 -11
  45. sqlspec/adapters/bigquery/data_dictionary.py +42 -2
  46. sqlspec/adapters/bigquery/driver.py +489 -144
  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 +55 -23
  50. sqlspec/adapters/duckdb/_types.py +2 -2
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +563 -0
  53. sqlspec/adapters/duckdb/config.py +79 -21
  54. sqlspec/adapters/duckdb/data_dictionary.py +41 -2
  55. sqlspec/adapters/duckdb/driver.py +225 -44
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +5 -5
  59. sqlspec/adapters/duckdb/type_converter.py +51 -21
  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 +1628 -0
  64. sqlspec/adapters/oracledb/config.py +120 -36
  65. sqlspec/adapters/oracledb/data_dictionary.py +87 -20
  66. sqlspec/adapters/oracledb/driver.py +475 -86
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +765 -0
  69. sqlspec/adapters/oracledb/migrations.py +316 -25
  70. sqlspec/adapters/oracledb/type_converter.py +91 -16
  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 +483 -0
  75. sqlspec/adapters/psqlpy/config.py +45 -19
  76. sqlspec/adapters/psqlpy/data_dictionary.py +48 -2
  77. sqlspec/adapters/psqlpy/driver.py +108 -41
  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 +40 -11
  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 +962 -0
  85. sqlspec/adapters/psycopg/config.py +65 -37
  86. sqlspec/adapters/psycopg/data_dictionary.py +91 -3
  87. sqlspec/adapters/psycopg/driver.py +200 -78
  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 +582 -0
  95. sqlspec/adapters/sqlite/config.py +85 -16
  96. sqlspec/adapters/sqlite/data_dictionary.py +34 -2
  97. sqlspec/adapters/sqlite/driver.py +120 -52
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +5 -5
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +91 -58
  104. sqlspec/builder/_column.py +5 -5
  105. sqlspec/builder/_ddl.py +98 -89
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +41 -44
  109. sqlspec/builder/_insert.py +5 -82
  110. sqlspec/builder/{mixins/_join_operations.py → _join.py} +145 -143
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +9 -11
  113. sqlspec/builder/_select.py +1313 -25
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +76 -69
  116. sqlspec/config.py +331 -62
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +18 -18
  119. sqlspec/core/compiler.py +6 -8
  120. sqlspec/core/filters.py +55 -47
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +76 -45
  123. sqlspec/core/result.py +234 -47
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +32 -31
  126. sqlspec/core/type_conversion.py +3 -2
  127. sqlspec/driver/__init__.py +1 -3
  128. sqlspec/driver/_async.py +183 -160
  129. sqlspec/driver/_common.py +197 -109
  130. sqlspec/driver/_sync.py +189 -161
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +70 -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 +69 -61
  142. sqlspec/extensions/fastapi/__init__.py +21 -0
  143. sqlspec/extensions/fastapi/extension.py +331 -0
  144. sqlspec/extensions/fastapi/providers.py +543 -0
  145. sqlspec/extensions/flask/__init__.py +36 -0
  146. sqlspec/extensions/flask/_state.py +71 -0
  147. sqlspec/extensions/flask/_utils.py +40 -0
  148. sqlspec/extensions/flask/extension.py +389 -0
  149. sqlspec/extensions/litestar/__init__.py +21 -4
  150. sqlspec/extensions/litestar/cli.py +54 -10
  151. sqlspec/extensions/litestar/config.py +56 -266
  152. sqlspec/extensions/litestar/handlers.py +46 -17
  153. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  154. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  155. sqlspec/extensions/litestar/plugin.py +349 -224
  156. sqlspec/extensions/litestar/providers.py +25 -25
  157. sqlspec/extensions/litestar/store.py +265 -0
  158. sqlspec/extensions/starlette/__init__.py +10 -0
  159. sqlspec/extensions/starlette/_state.py +25 -0
  160. sqlspec/extensions/starlette/_utils.py +52 -0
  161. sqlspec/extensions/starlette/extension.py +254 -0
  162. sqlspec/extensions/starlette/middleware.py +154 -0
  163. sqlspec/loader.py +30 -49
  164. sqlspec/migrations/base.py +200 -76
  165. sqlspec/migrations/commands.py +591 -62
  166. sqlspec/migrations/context.py +6 -9
  167. sqlspec/migrations/fix.py +199 -0
  168. sqlspec/migrations/loaders.py +47 -19
  169. sqlspec/migrations/runner.py +241 -75
  170. sqlspec/migrations/tracker.py +237 -21
  171. sqlspec/migrations/utils.py +51 -3
  172. sqlspec/migrations/validation.py +177 -0
  173. sqlspec/protocols.py +106 -36
  174. sqlspec/storage/_utils.py +85 -0
  175. sqlspec/storage/backends/fsspec.py +133 -107
  176. sqlspec/storage/backends/local.py +78 -51
  177. sqlspec/storage/backends/obstore.py +276 -168
  178. sqlspec/storage/registry.py +75 -39
  179. sqlspec/typing.py +30 -84
  180. sqlspec/utils/__init__.py +25 -4
  181. sqlspec/utils/arrow_helpers.py +81 -0
  182. sqlspec/utils/config_resolver.py +6 -6
  183. sqlspec/utils/correlation.py +4 -5
  184. sqlspec/utils/data_transformation.py +3 -2
  185. sqlspec/utils/deprecation.py +9 -8
  186. sqlspec/utils/fixtures.py +4 -4
  187. sqlspec/utils/logging.py +46 -6
  188. sqlspec/utils/module_loader.py +205 -5
  189. sqlspec/utils/portal.py +311 -0
  190. sqlspec/utils/schema.py +288 -0
  191. sqlspec/utils/serializers.py +113 -4
  192. sqlspec/utils/sync_tools.py +36 -22
  193. sqlspec/utils/text.py +1 -2
  194. sqlspec/utils/type_guards.py +136 -20
  195. sqlspec/utils/version.py +433 -0
  196. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/METADATA +41 -22
  197. sqlspec-0.28.0.dist-info/RECORD +221 -0
  198. sqlspec/builder/mixins/__init__.py +0 -55
  199. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -253
  200. sqlspec/builder/mixins/_delete_operations.py +0 -50
  201. sqlspec/builder/mixins/_insert_operations.py +0 -282
  202. sqlspec/builder/mixins/_merge_operations.py +0 -698
  203. sqlspec/builder/mixins/_order_limit_operations.py +0 -145
  204. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  205. sqlspec/builder/mixins/_select_operations.py +0 -930
  206. sqlspec/builder/mixins/_update_operations.py +0 -199
  207. sqlspec/builder/mixins/_where_clause.py +0 -1298
  208. sqlspec-0.26.0.dist-info/RECORD +0 -157
  209. sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
  210. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/WHEEL +0 -0
  211. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/entry_points.txt +0 -0
  212. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,11 +3,12 @@
3
3
  import contextlib
4
4
  import logging
5
5
  from contextlib import asynccontextmanager
6
- from typing import TYPE_CHECKING, Any, ClassVar, Optional, TypedDict, Union, cast
6
+ from typing import TYPE_CHECKING, Any, ClassVar, TypedDict, cast
7
7
 
8
8
  import oracledb
9
9
  from typing_extensions import NotRequired
10
10
 
11
+ from sqlspec.adapters.oracledb._numpy_handlers import register_numpy_handlers
11
12
  from sqlspec.adapters.oracledb._types import (
12
13
  OracleAsyncConnection,
13
14
  OracleAsyncConnectionPool,
@@ -23,6 +24,7 @@ from sqlspec.adapters.oracledb.driver import (
23
24
  )
24
25
  from sqlspec.adapters.oracledb.migrations import OracleAsyncMigrationTracker, OracleSyncMigrationTracker
25
26
  from sqlspec.config import AsyncDatabaseConfig, SyncDatabaseConfig
27
+ from sqlspec.typing import NUMPY_INSTALLED
26
28
 
27
29
  if TYPE_CHECKING:
28
30
  from collections.abc import AsyncGenerator, Callable, Generator
@@ -32,12 +34,18 @@ if TYPE_CHECKING:
32
34
  from sqlspec.core.statement import StatementConfig
33
35
 
34
36
 
35
- __all__ = ("OracleAsyncConfig", "OracleConnectionParams", "OraclePoolParams", "OracleSyncConfig")
37
+ __all__ = (
38
+ "OracleAsyncConfig",
39
+ "OracleConnectionParams",
40
+ "OracleDriverFeatures",
41
+ "OraclePoolParams",
42
+ "OracleSyncConfig",
43
+ )
36
44
 
37
45
  logger = logging.getLogger(__name__)
38
46
 
39
47
 
40
- class OracleConnectionParams(TypedDict, total=False):
48
+ class OracleConnectionParams(TypedDict):
41
49
  """OracleDB connection parameters."""
42
50
 
43
51
  dsn: NotRequired[str]
@@ -58,7 +66,7 @@ class OracleConnectionParams(TypedDict, total=False):
58
66
  edition: NotRequired[str]
59
67
 
60
68
 
61
- class OraclePoolParams(OracleConnectionParams, total=False):
69
+ class OraclePoolParams(OracleConnectionParams):
62
70
  """OracleDB pool parameters."""
63
71
 
64
72
  min: NotRequired[int]
@@ -77,6 +85,23 @@ class OraclePoolParams(OracleConnectionParams, total=False):
77
85
  extra: NotRequired[dict[str, Any]]
78
86
 
79
87
 
88
+ class OracleDriverFeatures(TypedDict):
89
+ """Oracle driver feature flags.
90
+
91
+ enable_numpy_vectors: Enable automatic NumPy array ↔ Oracle VECTOR conversion.
92
+ Requires NumPy and Oracle Database 23ai or higher with VECTOR data type support.
93
+ Defaults to True when NumPy is installed.
94
+ Provides automatic bidirectional conversion between NumPy ndarrays and Oracle VECTOR columns.
95
+ Supports float32, float64, int8, and uint8 dtypes.
96
+ enable_lowercase_column_names: Normalize implicit Oracle uppercase column names to lowercase.
97
+ Targets unquoted Oracle identifiers that default to uppercase while preserving quoted case-sensitive aliases.
98
+ Defaults to True for compatibility with schema libraries expecting snake_case fields.
99
+ """
100
+
101
+ enable_numpy_vectors: NotRequired[bool]
102
+ enable_lowercase_column_names: NotRequired[bool]
103
+
104
+
80
105
  class OracleSyncConfig(SyncDatabaseConfig[OracleSyncConnection, "OracleSyncConnectionPool", OracleSyncDriver]):
81
106
  """Configuration for Oracle synchronous database connections."""
82
107
 
@@ -85,26 +110,29 @@ class OracleSyncConfig(SyncDatabaseConfig[OracleSyncConnection, "OracleSyncConne
85
110
  driver_type: ClassVar[type[OracleSyncDriver]] = OracleSyncDriver
86
111
  connection_type: "ClassVar[type[OracleSyncConnection]]" = OracleSyncConnection
87
112
  migration_tracker_type: "ClassVar[type[OracleSyncMigrationTracker]]" = OracleSyncMigrationTracker
113
+ supports_transactional_ddl: ClassVar[bool] = False
88
114
 
89
115
  def __init__(
90
116
  self,
91
117
  *,
92
- pool_config: "Optional[Union[OraclePoolParams, dict[str, Any]]]" = None,
93
- pool_instance: "Optional[OracleSyncConnectionPool]" = None,
94
- migration_config: Optional[dict[str, Any]] = None,
95
- statement_config: "Optional[StatementConfig]" = None,
96
- driver_features: "Optional[dict[str, Any]]" = None,
97
- bind_key: "Optional[str]" = None,
118
+ pool_config: "OraclePoolParams | dict[str, Any] | None" = None,
119
+ pool_instance: "OracleSyncConnectionPool | None" = None,
120
+ migration_config: dict[str, Any] | None = None,
121
+ statement_config: "StatementConfig | None" = None,
122
+ driver_features: "OracleDriverFeatures | dict[str, Any] | None" = None,
123
+ bind_key: "str | None" = None,
124
+ extension_config: "dict[str, dict[str, Any]] | None" = None,
98
125
  ) -> None:
99
126
  """Initialize Oracle synchronous configuration.
100
127
 
101
128
  Args:
102
- pool_config: Pool configuration parameters
103
- pool_instance: Existing pool instance to use
104
- migration_config: Migration configuration
105
- statement_config: Default SQL statement configuration
106
- driver_features: Optional driver feature configuration
107
- bind_key: Optional unique identifier for this configuration
129
+ pool_config: Pool configuration parameters.
130
+ pool_instance: Existing pool instance to use.
131
+ migration_config: Migration configuration.
132
+ statement_config: Default SQL statement configuration.
133
+ driver_features: Optional driver feature configuration (TypedDict or dict).
134
+ bind_key: Optional unique identifier for this configuration.
135
+ extension_config: Extension-specific configuration (e.g., Litestar plugin settings).
108
136
  """
109
137
 
110
138
  processed_pool_config: dict[str, Any] = dict(pool_config) if pool_config else {}
@@ -112,19 +140,43 @@ class OracleSyncConfig(SyncDatabaseConfig[OracleSyncConnection, "OracleSyncConne
112
140
  extras = processed_pool_config.pop("extra")
113
141
  processed_pool_config.update(extras)
114
142
  statement_config = statement_config or oracledb_statement_config
143
+
144
+ processed_driver_features: dict[str, Any] = dict(driver_features) if driver_features else {}
145
+ if "enable_numpy_vectors" not in processed_driver_features:
146
+ processed_driver_features["enable_numpy_vectors"] = NUMPY_INSTALLED
147
+ if "enable_lowercase_column_names" not in processed_driver_features:
148
+ processed_driver_features["enable_lowercase_column_names"] = True
149
+
115
150
  super().__init__(
116
151
  pool_config=processed_pool_config,
117
152
  pool_instance=pool_instance,
118
153
  migration_config=migration_config,
119
154
  statement_config=statement_config,
120
- driver_features=driver_features or {},
155
+ driver_features=processed_driver_features,
121
156
  bind_key=bind_key,
157
+ extension_config=extension_config,
122
158
  )
123
159
 
124
160
  def _create_pool(self) -> "OracleSyncConnectionPool":
125
161
  """Create the actual connection pool."""
162
+ config = dict(self.pool_config)
163
+
164
+ if self.driver_features.get("enable_numpy_vectors", False):
165
+ config["session_callback"] = self._init_connection
126
166
 
127
- return oracledb.create_pool(**dict(self.pool_config))
167
+ return oracledb.create_pool(**config)
168
+
169
+ def _init_connection(self, connection: "OracleSyncConnection", tag: str) -> None:
170
+ """Initialize connection with optional NumPy vector support.
171
+
172
+ Args:
173
+ connection: Oracle connection to initialize.
174
+ tag: Connection tag for session state (unused).
175
+ """
176
+ if self.driver_features.get("enable_numpy_vectors", False):
177
+ from sqlspec.adapters.oracledb._numpy_handlers import register_numpy_handlers
178
+
179
+ register_numpy_handlers(connection)
128
180
 
129
181
  def _close_pool(self) -> None:
130
182
  """Close the actual connection pool."""
@@ -158,7 +210,7 @@ class OracleSyncConfig(SyncDatabaseConfig[OracleSyncConnection, "OracleSyncConne
158
210
 
159
211
  @contextlib.contextmanager
160
212
  def provide_session(
161
- self, *args: Any, statement_config: "Optional[StatementConfig]" = None, **kwargs: Any
213
+ self, *args: Any, statement_config: "StatementConfig | None" = None, **kwargs: Any
162
214
  ) -> "Generator[OracleSyncDriver, None, None]":
163
215
  """Provide a driver session context manager.
164
216
 
@@ -172,7 +224,11 @@ class OracleSyncConfig(SyncDatabaseConfig[OracleSyncConnection, "OracleSyncConne
172
224
  """
173
225
  _ = (args, kwargs) # Mark as intentionally unused
174
226
  with self.provide_connection() as conn:
175
- yield self.driver_type(connection=conn, statement_config=statement_config or self.statement_config)
227
+ yield self.driver_type(
228
+ connection=conn,
229
+ statement_config=statement_config or self.statement_config,
230
+ driver_features=self.driver_features,
231
+ )
176
232
 
177
233
  def provide_pool(self) -> "OracleSyncConnectionPool":
178
234
  """Provide pool instance.
@@ -214,26 +270,29 @@ class OracleAsyncConfig(AsyncDatabaseConfig[OracleAsyncConnection, "OracleAsyncC
214
270
  connection_type: "ClassVar[type[OracleAsyncConnection]]" = OracleAsyncConnection
215
271
  driver_type: ClassVar[type[OracleAsyncDriver]] = OracleAsyncDriver
216
272
  migration_tracker_type: "ClassVar[type[OracleAsyncMigrationTracker]]" = OracleAsyncMigrationTracker
273
+ supports_transactional_ddl: ClassVar[bool] = False
217
274
 
218
275
  def __init__(
219
276
  self,
220
277
  *,
221
- pool_config: "Optional[Union[OraclePoolParams, dict[str, Any]]]" = None,
222
- pool_instance: "Optional[OracleAsyncConnectionPool]" = None,
223
- migration_config: Optional[dict[str, Any]] = None,
224
- statement_config: "Optional[StatementConfig]" = None,
225
- driver_features: "Optional[dict[str, Any]]" = None,
226
- bind_key: "Optional[str]" = None,
278
+ pool_config: "OraclePoolParams | dict[str, Any] | None" = None,
279
+ pool_instance: "OracleAsyncConnectionPool | None" = None,
280
+ migration_config: dict[str, Any] | None = None,
281
+ statement_config: "StatementConfig | None" = None,
282
+ driver_features: "OracleDriverFeatures | dict[str, Any] | None" = None,
283
+ bind_key: "str | None" = None,
284
+ extension_config: "dict[str, dict[str, Any]] | None" = None,
227
285
  ) -> None:
228
286
  """Initialize Oracle asynchronous configuration.
229
287
 
230
288
  Args:
231
- pool_config: Pool configuration parameters
232
- pool_instance: Existing pool instance to use
233
- migration_config: Migration configuration
234
- statement_config: Default SQL statement configuration
235
- driver_features: Optional driver feature configuration
236
- bind_key: Optional unique identifier for this configuration
289
+ pool_config: Pool configuration parameters.
290
+ pool_instance: Existing pool instance to use.
291
+ migration_config: Migration configuration.
292
+ statement_config: Default SQL statement configuration.
293
+ driver_features: Optional driver feature configuration (TypedDict or dict).
294
+ bind_key: Optional unique identifier for this configuration.
295
+ extension_config: Extension-specific configuration (e.g., Litestar plugin settings).
237
296
  """
238
297
 
239
298
  processed_pool_config: dict[str, Any] = dict(pool_config) if pool_config else {}
@@ -241,19 +300,40 @@ class OracleAsyncConfig(AsyncDatabaseConfig[OracleAsyncConnection, "OracleAsyncC
241
300
  extras = processed_pool_config.pop("extra")
242
301
  processed_pool_config.update(extras)
243
302
 
303
+ processed_driver_features: dict[str, Any] = dict(driver_features) if driver_features else {}
304
+ if "enable_numpy_vectors" not in processed_driver_features:
305
+ processed_driver_features["enable_numpy_vectors"] = NUMPY_INSTALLED
306
+ if "enable_lowercase_column_names" not in processed_driver_features:
307
+ processed_driver_features["enable_lowercase_column_names"] = True
308
+
244
309
  super().__init__(
245
310
  pool_config=processed_pool_config,
246
311
  pool_instance=pool_instance,
247
312
  migration_config=migration_config,
248
313
  statement_config=statement_config or oracledb_statement_config,
249
- driver_features=driver_features or {},
314
+ driver_features=processed_driver_features,
250
315
  bind_key=bind_key,
316
+ extension_config=extension_config,
251
317
  )
252
318
 
253
319
  async def _create_pool(self) -> "OracleAsyncConnectionPool":
254
320
  """Create the actual async connection pool."""
321
+ config = dict(self.pool_config)
322
+
323
+ if self.driver_features.get("enable_numpy_vectors", False):
324
+ config["session_callback"] = self._init_connection
255
325
 
256
- return oracledb.create_pool_async(**dict(self.pool_config))
326
+ return oracledb.create_pool_async(**config)
327
+
328
+ async def _init_connection(self, connection: "OracleAsyncConnection", tag: str) -> None:
329
+ """Initialize async connection with optional NumPy vector support.
330
+
331
+ Args:
332
+ connection: Oracle async connection to initialize.
333
+ tag: Connection tag for session state (unused).
334
+ """
335
+ if self.driver_features.get("enable_numpy_vectors", False):
336
+ register_numpy_handlers(connection)
257
337
 
258
338
  async def _close_pool(self) -> None:
259
339
  """Close the actual async connection pool."""
@@ -291,7 +371,7 @@ class OracleAsyncConfig(AsyncDatabaseConfig[OracleAsyncConnection, "OracleAsyncC
291
371
 
292
372
  @asynccontextmanager
293
373
  async def provide_session(
294
- self, *args: Any, statement_config: "Optional[StatementConfig]" = None, **kwargs: Any
374
+ self, *args: Any, statement_config: "StatementConfig | None" = None, **kwargs: Any
295
375
  ) -> "AsyncGenerator[OracleAsyncDriver, None]":
296
376
  """Provide an async driver session context manager.
297
377
 
@@ -305,7 +385,11 @@ class OracleAsyncConfig(AsyncDatabaseConfig[OracleAsyncConnection, "OracleAsyncC
305
385
  """
306
386
  _ = (args, kwargs) # Mark as intentionally unused
307
387
  async with self.provide_connection() as conn:
308
- yield self.driver_type(connection=conn, statement_config=statement_config or self.statement_config)
388
+ yield self.driver_type(
389
+ connection=conn,
390
+ statement_config=statement_config or self.statement_config,
391
+ driver_features=self.driver_features,
392
+ )
309
393
 
310
394
  async def provide_pool(self) -> "OracleAsyncConnectionPool":
311
395
  """Provide async pool instance.
@@ -3,7 +3,7 @@
3
3
 
4
4
  import re
5
5
  from contextlib import suppress
6
- from typing import TYPE_CHECKING, Callable, Optional, cast
6
+ from typing import TYPE_CHECKING, Any, cast
7
7
 
8
8
  from sqlspec.driver import (
9
9
  AsyncDataDictionaryBase,
@@ -15,6 +15,8 @@ from sqlspec.driver import (
15
15
  from sqlspec.utils.logging import get_logger
16
16
 
17
17
  if TYPE_CHECKING:
18
+ from collections.abc import Callable
19
+
18
20
  from sqlspec.adapters.oracledb.driver import OracleAsyncDriver, OracleSyncDriver
19
21
 
20
22
  logger = get_logger("adapters.oracledb.data_dictionary")
@@ -35,12 +37,7 @@ class OracleVersionInfo(VersionInfo):
35
37
  """Oracle database version information."""
36
38
 
37
39
  def __init__(
38
- self,
39
- major: int,
40
- minor: int = 0,
41
- patch: int = 0,
42
- compatible: "Optional[str]" = None,
43
- is_autonomous: bool = False,
40
+ self, major: int, minor: int = 0, patch: int = 0, compatible: "str | None" = None, is_autonomous: bool = False
44
41
  ) -> None:
45
42
  """Initialize Oracle version info.
46
43
 
@@ -56,7 +53,7 @@ class OracleVersionInfo(VersionInfo):
56
53
  self.is_autonomous = is_autonomous
57
54
 
58
55
  @property
59
- def compatible_major(self) -> "Optional[int]":
56
+ def compatible_major(self) -> "int | None":
60
57
  """Get major version from compatible parameter."""
61
58
  if not self.compatible:
62
59
  return None
@@ -109,7 +106,31 @@ class OracleDataDictionaryMixin:
109
106
 
110
107
  __slots__ = ()
111
108
 
112
- def _get_oracle_version(self, driver: "OracleAsyncDriver | OracleSyncDriver") -> "Optional[OracleVersionInfo]":
109
+ def _get_columns_sql(self, table: str, schema: "str | None" = None) -> str:
110
+ """Get SQL to query column metadata from Oracle data dictionary.
111
+
112
+ Uses USER_TAB_COLUMNS which returns column names in UPPERCASE.
113
+
114
+ Args:
115
+ table: Table name to query columns for
116
+ schema: Schema name (unused for USER_TAB_COLUMNS)
117
+
118
+ Returns:
119
+ SQL string for Oracle's USER_TAB_COLUMNS query
120
+ """
121
+ _ = schema
122
+ return f"""
123
+ SELECT
124
+ column_name AS "column_name",
125
+ data_type AS "data_type",
126
+ data_length AS "data_length",
127
+ nullable AS "nullable"
128
+ FROM user_tab_columns
129
+ WHERE table_name = '{table.upper()}'
130
+ ORDER BY column_id
131
+ """
132
+
133
+ def _get_oracle_version(self, driver: "OracleAsyncDriver | OracleSyncDriver") -> "OracleVersionInfo | None":
113
134
  """Get Oracle database version information.
114
135
 
115
136
  Args:
@@ -118,7 +139,7 @@ class OracleDataDictionaryMixin:
118
139
  Returns:
119
140
  Oracle version information or None if detection fails
120
141
  """
121
- banner = driver.select_value("SELECT banner FROM v$version WHERE banner LIKE 'Oracle%'")
142
+ banner = driver.select_value("SELECT banner AS \"banner\" FROM v$version WHERE banner LIKE 'Oracle%'")
122
143
 
123
144
  # Parse version from banner like "Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production"
124
145
  # or "Oracle Database 19c Standard Edition 2 Release 19.0.0.0.0 - Production"
@@ -143,7 +164,7 @@ class OracleDataDictionaryMixin:
143
164
  logger.debug("Detected Oracle version: %s", version_info)
144
165
  return version_info
145
166
 
146
- def _get_oracle_compatible(self, driver: "OracleAsyncDriver | OracleSyncDriver") -> "Optional[str]":
167
+ def _get_oracle_compatible(self, driver: "OracleAsyncDriver | OracleSyncDriver") -> "str | None":
147
168
  """Get Oracle compatible parameter value.
148
169
 
149
170
  Args:
@@ -153,14 +174,14 @@ class OracleDataDictionaryMixin:
153
174
  Compatible parameter value or None if detection fails
154
175
  """
155
176
  try:
156
- compatible = driver.select_value("SELECT value FROM v$parameter WHERE name = 'compatible'")
177
+ compatible = driver.select_value("SELECT value AS \"value\" FROM v$parameter WHERE name = 'compatible'")
157
178
  logger.debug("Detected Oracle compatible parameter: %s", compatible)
158
179
  return str(compatible)
159
180
  except Exception:
160
181
  logger.warning("Compatible parameter not found")
161
182
  return None
162
183
 
163
- def _get_oracle_json_type(self, version_info: "Optional[OracleVersionInfo]") -> str:
184
+ def _get_oracle_json_type(self, version_info: "OracleVersionInfo | None") -> str:
164
185
  """Determine the appropriate JSON column type for Oracle.
165
186
 
166
187
  Args:
@@ -199,10 +220,10 @@ class OracleSyncDataDictionary(OracleDataDictionaryMixin, SyncDataDictionaryBase
199
220
  Returns:
200
221
  True if this is an Autonomous Database, False otherwise
201
222
  """
202
- result = driver.select_value_or_none("SELECT COUNT(*) as cnt FROM v$pdbs WHERE cloud_identity IS NOT NULL")
223
+ result = driver.select_value_or_none('SELECT COUNT(1) AS "cnt" FROM v$pdbs WHERE cloud_identity IS NOT NULL')
203
224
  return bool(result and int(result) > 0)
204
225
 
205
- def get_version(self, driver: SyncDriverAdapterBase) -> "Optional[OracleVersionInfo]":
226
+ def get_version(self, driver: SyncDriverAdapterBase) -> "OracleVersionInfo | None":
206
227
  """Get Oracle database version information.
207
228
 
208
229
  Args:
@@ -275,6 +296,28 @@ class OracleSyncDataDictionary(OracleDataDictionaryMixin, SyncDataDictionaryBase
275
296
  }
276
297
  return type_map.get(type_category, "VARCHAR2(255)")
277
298
 
299
+ def get_columns(
300
+ self, driver: SyncDriverAdapterBase, table: str, schema: "str | None" = None
301
+ ) -> "list[dict[str, Any]]":
302
+ """Get column information for a table from Oracle data dictionary.
303
+
304
+ Args:
305
+ driver: Database driver instance
306
+ table: Table name to query columns for
307
+ schema: Schema name (ignored for USER_TAB_COLUMNS)
308
+
309
+ Returns:
310
+ List of column metadata dictionaries with keys:
311
+ - column_name: Name of the column
312
+ - data_type: Oracle data type
313
+ - data_length: Maximum length (for character types)
314
+ - nullable: 'Y' or 'N'
315
+ """
316
+
317
+ oracle_driver = cast("OracleSyncDriver", driver)
318
+ result = oracle_driver.execute(self._get_columns_sql(table, schema))
319
+ return result.get_data()
320
+
278
321
  def list_available_features(self) -> "list[str]":
279
322
  """List available Oracle feature flags.
280
323
 
@@ -296,7 +339,7 @@ class OracleSyncDataDictionary(OracleDataDictionaryMixin, SyncDataDictionaryBase
296
339
  class OracleAsyncDataDictionary(OracleDataDictionaryMixin, AsyncDataDictionaryBase):
297
340
  """Oracle-specific async data dictionary."""
298
341
 
299
- async def get_version(self, driver: AsyncDriverAdapterBase) -> "Optional[OracleVersionInfo]":
342
+ async def get_version(self, driver: AsyncDriverAdapterBase) -> "OracleVersionInfo | None":
300
343
  """Get Oracle database version information.
301
344
 
302
345
  Args:
@@ -306,7 +349,7 @@ class OracleAsyncDataDictionary(OracleDataDictionaryMixin, AsyncDataDictionaryBa
306
349
  Oracle version information or None if detection fails
307
350
  """
308
351
  banner = await cast("OracleAsyncDriver", driver).select_value(
309
- "SELECT banner FROM v$version WHERE banner LIKE 'Oracle%'"
352
+ "SELECT banner AS \"banner\" FROM v$version WHERE banner LIKE 'Oracle%'"
310
353
  )
311
354
 
312
355
  version_match = ORACLE_VERSION_PATTERN.search(str(banner))
@@ -336,7 +379,7 @@ class OracleAsyncDataDictionary(OracleDataDictionaryMixin, AsyncDataDictionaryBa
336
379
  logger.debug("Detected Oracle version: %s", version_info)
337
380
  return version_info
338
381
 
339
- async def _get_oracle_compatible_async(self, driver: "OracleAsyncDriver") -> "Optional[str]":
382
+ async def _get_oracle_compatible_async(self, driver: "OracleAsyncDriver") -> "str | None":
340
383
  """Get Oracle compatible parameter value (async version).
341
384
 
342
385
  Args:
@@ -346,7 +389,9 @@ class OracleAsyncDataDictionary(OracleDataDictionaryMixin, AsyncDataDictionaryBa
346
389
  Compatible parameter value or None if detection fails
347
390
  """
348
391
  try:
349
- compatible = await driver.select_value("SELECT value FROM v$parameter WHERE name = 'compatible'")
392
+ compatible = await driver.select_value(
393
+ "SELECT value AS \"value\" FROM v$parameter WHERE name = 'compatible'"
394
+ )
350
395
  logger.debug("Detected Oracle compatible parameter: %s", compatible)
351
396
  return str(compatible)
352
397
  except Exception:
@@ -364,7 +409,7 @@ class OracleAsyncDataDictionary(OracleDataDictionaryMixin, AsyncDataDictionaryBa
364
409
  """
365
410
  # Check for cloud_identity in v$pdbs (most reliable for Autonomous)
366
411
  with suppress(Exception):
367
- result = await driver.execute("SELECT COUNT(*) as cnt FROM v$pdbs WHERE cloud_identity IS NOT NULL")
412
+ result = await driver.execute('SELECT COUNT(1) AS "cnt" FROM v$pdbs WHERE cloud_identity IS NOT NULL')
368
413
  if result.data:
369
414
  count = result.data[0]["cnt"] if isinstance(result.data[0], dict) else result.data[0][0]
370
415
  if int(count) > 0:
@@ -424,6 +469,28 @@ class OracleAsyncDataDictionary(OracleDataDictionaryMixin, AsyncDataDictionaryBa
424
469
  type_map = {"uuid": "RAW(16)", "boolean": "NUMBER(1)", "timestamp": "TIMESTAMP", "text": "CLOB", "blob": "BLOB"}
425
470
  return type_map.get(type_category, "VARCHAR2(255)")
426
471
 
472
+ async def get_columns(
473
+ self, driver: AsyncDriverAdapterBase, table: str, schema: "str | None" = None
474
+ ) -> "list[dict[str, Any]]":
475
+ """Get column information for a table from Oracle data dictionary.
476
+
477
+ Args:
478
+ driver: Async database driver instance
479
+ table: Table name to query columns for
480
+ schema: Schema name (ignored for USER_TAB_COLUMNS)
481
+
482
+ Returns:
483
+ List of column metadata dictionaries with keys:
484
+ - column_name: Name of the column
485
+ - data_type: Oracle data type
486
+ - data_length: Maximum length (for character types)
487
+ - nullable: 'Y' or 'N'
488
+ """
489
+
490
+ oracle_driver = cast("OracleAsyncDriver", driver)
491
+ result = await oracle_driver.execute(self._get_columns_sql(table, schema))
492
+ return result.get_data()
493
+
427
494
  def list_available_features(self) -> "list[str]":
428
495
  """List available Oracle feature flags.
429
496