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
@@ -3,7 +3,7 @@
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
  from psycopg.rows import dict_row
9
9
  from psycopg_pool import AsyncConnectionPool, ConnectionPool
@@ -18,6 +18,7 @@ from sqlspec.adapters.psycopg.driver import (
18
18
  psycopg_statement_config,
19
19
  )
20
20
  from sqlspec.config import AsyncDatabaseConfig, SyncDatabaseConfig
21
+ from sqlspec.typing import PGVECTOR_INSTALLED
21
22
 
22
23
  if TYPE_CHECKING:
23
24
  from collections.abc import AsyncGenerator, Callable, Generator
@@ -28,7 +29,7 @@ if TYPE_CHECKING:
28
29
  logger = logging.getLogger("sqlspec.adapters.psycopg")
29
30
 
30
31
 
31
- class PsycopgConnectionParams(TypedDict, total=False):
32
+ class PsycopgConnectionParams(TypedDict):
32
33
  """Psycopg connection parameters."""
33
34
 
34
35
  conninfo: NotRequired[str]
@@ -48,7 +49,7 @@ class PsycopgConnectionParams(TypedDict, total=False):
48
49
  extra: NotRequired[dict[str, Any]]
49
50
 
50
51
 
51
- class PsycopgPoolParams(PsycopgConnectionParams, total=False):
52
+ class PsycopgPoolParams(PsycopgConnectionParams):
52
53
  """Psycopg pool parameters."""
53
54
 
54
55
  min_size: NotRequired[int]
@@ -64,10 +65,25 @@ class PsycopgPoolParams(PsycopgConnectionParams, total=False):
64
65
  kwargs: NotRequired[dict[str, Any]]
65
66
 
66
67
 
68
+ class PsycopgDriverFeatures(TypedDict):
69
+ """Psycopg driver feature flags.
70
+
71
+ enable_pgvector: Enable automatic pgvector extension support for vector similarity search.
72
+ Requires pgvector-python package (pip install pgvector) and PostgreSQL with pgvector extension.
73
+ Defaults to True when pgvector-python is installed.
74
+ Provides automatic conversion between Python objects and PostgreSQL vector types.
75
+ Enables vector similarity operations and index support.
76
+ Set to False to disable pgvector support even when package is available.
77
+ """
78
+
79
+ enable_pgvector: NotRequired[bool]
80
+
81
+
67
82
  __all__ = (
68
83
  "PsycopgAsyncConfig",
69
84
  "PsycopgAsyncCursor",
70
85
  "PsycopgConnectionParams",
86
+ "PsycopgDriverFeatures",
71
87
  "PsycopgPoolParams",
72
88
  "PsycopgSyncConfig",
73
89
  "PsycopgSyncCursor",
@@ -79,15 +95,18 @@ class PsycopgSyncConfig(SyncDatabaseConfig[PsycopgSyncConnection, ConnectionPool
79
95
 
80
96
  driver_type: "ClassVar[type[PsycopgSyncDriver]]" = PsycopgSyncDriver
81
97
  connection_type: "ClassVar[type[PsycopgSyncConnection]]" = PsycopgSyncConnection
98
+ supports_transactional_ddl: "ClassVar[bool]" = True
82
99
 
83
100
  def __init__(
84
101
  self,
85
102
  *,
86
- pool_config: "Optional[Union[PsycopgPoolParams, dict[str, Any]]]" = None,
87
- pool_instance: Optional["ConnectionPool"] = None,
88
- migration_config: Optional[dict[str, Any]] = None,
89
- statement_config: "Optional[StatementConfig]" = None,
90
- driver_features: "Optional[dict[str, Any]]" = None,
103
+ pool_config: "PsycopgPoolParams | dict[str, Any] | None" = None,
104
+ pool_instance: "ConnectionPool | None" = None,
105
+ migration_config: dict[str, Any] | None = None,
106
+ statement_config: "StatementConfig | None" = None,
107
+ driver_features: "dict[str, Any] | None" = None,
108
+ bind_key: "str | None" = None,
109
+ extension_config: "dict[str, dict[str, Any]] | None" = None,
91
110
  ) -> None:
92
111
  """Initialize Psycopg synchronous configuration.
93
112
 
@@ -97,18 +116,27 @@ class PsycopgSyncConfig(SyncDatabaseConfig[PsycopgSyncConnection, ConnectionPool
97
116
  migration_config: Migration configuration
98
117
  statement_config: Default SQL statement configuration
99
118
  driver_features: Optional driver feature configuration
119
+ bind_key: Optional unique identifier for this configuration
120
+ extension_config: Extension-specific configuration (e.g., Litestar plugin settings)
100
121
  """
101
122
  processed_pool_config: dict[str, Any] = dict(pool_config) if pool_config else {}
102
123
  if "extra" in processed_pool_config:
103
124
  extras = processed_pool_config.pop("extra")
104
125
  processed_pool_config.update(extras)
105
126
 
127
+ if driver_features is None:
128
+ driver_features = {}
129
+ if "enable_pgvector" not in driver_features:
130
+ driver_features["enable_pgvector"] = PGVECTOR_INSTALLED
131
+
106
132
  super().__init__(
107
133
  pool_config=processed_pool_config,
108
134
  pool_instance=pool_instance,
109
135
  migration_config=migration_config,
110
136
  statement_config=statement_config or psycopg_statement_config,
111
- driver_features=driver_features or {},
137
+ driver_features=driver_features,
138
+ bind_key=bind_key,
139
+ extension_config=extension_config,
112
140
  )
113
141
 
114
142
  def _create_pool(self) -> "ConnectionPool":
@@ -137,15 +165,10 @@ class PsycopgSyncConfig(SyncDatabaseConfig[PsycopgSyncConnection, ConnectionPool
137
165
  if autocommit_setting is not None:
138
166
  conn.autocommit = autocommit_setting
139
167
 
140
- try:
141
- import pgvector.psycopg
168
+ if self.driver_features.get("enable_pgvector", False):
169
+ from sqlspec.adapters.psycopg._type_handlers import register_pgvector_sync
142
170
 
143
- pgvector.psycopg.register_vector(conn)
144
- logger.debug("pgvector registered successfully for psycopg sync connection")
145
- except ImportError:
146
- pass
147
- except Exception as e:
148
- logger.debug("Failed to register pgvector for psycopg sync: %s", e)
171
+ register_pgvector_sync(conn)
149
172
 
150
173
  pool_parameters["configure"] = all_config.pop("configure", configure_connection)
151
174
 
@@ -216,7 +239,7 @@ class PsycopgSyncConfig(SyncDatabaseConfig[PsycopgSyncConnection, ConnectionPool
216
239
 
217
240
  @contextlib.contextmanager
218
241
  def provide_session(
219
- self, *args: Any, statement_config: "Optional[StatementConfig]" = None, **kwargs: Any
242
+ self, *args: Any, statement_config: "StatementConfig | None" = None, **kwargs: Any
220
243
  ) -> "Generator[PsycopgSyncDriver, None, None]":
221
244
  """Provide a driver session context manager.
222
245
 
@@ -230,7 +253,9 @@ class PsycopgSyncConfig(SyncDatabaseConfig[PsycopgSyncConnection, ConnectionPool
230
253
  """
231
254
  with self.provide_connection(*args, **kwargs) as conn:
232
255
  final_statement_config = statement_config or self.statement_config
233
- yield self.driver_type(connection=conn, statement_config=final_statement_config)
256
+ yield self.driver_type(
257
+ connection=conn, statement_config=final_statement_config, driver_features=self.driver_features
258
+ )
234
259
 
235
260
  def provide_pool(self, *args: Any, **kwargs: Any) -> "ConnectionPool":
236
261
  """Provide pool instance.
@@ -261,15 +286,18 @@ class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnec
261
286
 
262
287
  driver_type: ClassVar[type[PsycopgAsyncDriver]] = PsycopgAsyncDriver
263
288
  connection_type: "ClassVar[type[PsycopgAsyncConnection]]" = PsycopgAsyncConnection
289
+ supports_transactional_ddl: "ClassVar[bool]" = True
264
290
 
265
291
  def __init__(
266
292
  self,
267
293
  *,
268
- pool_config: "Optional[Union[PsycopgPoolParams, dict[str, Any]]]" = None,
269
- pool_instance: "Optional[AsyncConnectionPool]" = None,
270
- migration_config: "Optional[dict[str, Any]]" = None,
271
- statement_config: "Optional[StatementConfig]" = None,
272
- driver_features: "Optional[dict[str, Any]]" = None,
294
+ pool_config: "PsycopgPoolParams | dict[str, Any] | None" = None,
295
+ pool_instance: "AsyncConnectionPool | None" = None,
296
+ migration_config: "dict[str, Any] | None" = None,
297
+ statement_config: "StatementConfig | None" = None,
298
+ driver_features: "dict[str, Any] | None" = None,
299
+ bind_key: "str | None" = None,
300
+ extension_config: "dict[str, dict[str, Any]] | None" = None,
273
301
  ) -> None:
274
302
  """Initialize Psycopg asynchronous configuration.
275
303
 
@@ -279,18 +307,27 @@ class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnec
279
307
  migration_config: Migration configuration
280
308
  statement_config: Default SQL statement configuration
281
309
  driver_features: Optional driver feature configuration
310
+ bind_key: Optional unique identifier for this configuration
311
+ extension_config: Extension-specific configuration (e.g., Litestar plugin settings)
282
312
  """
283
313
  processed_pool_config: dict[str, Any] = dict(pool_config) if pool_config else {}
284
314
  if "extra" in processed_pool_config:
285
315
  extras = processed_pool_config.pop("extra")
286
316
  processed_pool_config.update(extras)
287
317
 
318
+ if driver_features is None:
319
+ driver_features = {}
320
+ if "enable_pgvector" not in driver_features:
321
+ driver_features["enable_pgvector"] = PGVECTOR_INSTALLED
322
+
288
323
  super().__init__(
289
324
  pool_config=processed_pool_config,
290
325
  pool_instance=pool_instance,
291
326
  migration_config=migration_config,
292
327
  statement_config=statement_config or psycopg_statement_config,
293
- driver_features=driver_features or {},
328
+ driver_features=driver_features,
329
+ bind_key=bind_key,
330
+ extension_config=extension_config,
294
331
  )
295
332
 
296
333
  async def _create_pool(self) -> "AsyncConnectionPool":
@@ -317,15 +354,10 @@ class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnec
317
354
  if autocommit_setting is not None:
318
355
  await conn.set_autocommit(autocommit_setting)
319
356
 
320
- try:
321
- from pgvector.psycopg import register_vector_async
357
+ if self.driver_features.get("enable_pgvector", False):
358
+ from sqlspec.adapters.psycopg._type_handlers import register_pgvector_async
322
359
 
323
- await register_vector_async(conn)
324
- logger.debug("pgvector registered successfully for psycopg async connection")
325
- except ImportError:
326
- pass
327
- except Exception as e:
328
- logger.debug("Failed to register pgvector for psycopg async: %s", e)
360
+ await register_pgvector_async(conn)
329
361
 
330
362
  pool_parameters["configure"] = all_config.pop("configure", configure_connection)
331
363
 
@@ -388,7 +420,7 @@ class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnec
388
420
 
389
421
  @asynccontextmanager
390
422
  async def provide_session(
391
- self, *args: Any, statement_config: "Optional[StatementConfig]" = None, **kwargs: Any
423
+ self, *args: Any, statement_config: "StatementConfig | None" = None, **kwargs: Any
392
424
  ) -> "AsyncGenerator[PsycopgAsyncDriver, None]":
393
425
  """Provide an async driver session context manager.
394
426
 
@@ -402,7 +434,9 @@ class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnec
402
434
  """
403
435
  async with self.provide_connection(*args, **kwargs) as conn:
404
436
  final_statement_config = statement_config or psycopg_statement_config
405
- yield self.driver_type(connection=conn, statement_config=final_statement_config)
437
+ yield self.driver_type(
438
+ connection=conn, statement_config=final_statement_config, driver_features=self.driver_features
439
+ )
406
440
 
407
441
  async def provide_pool(self, *args: Any, **kwargs: Any) -> "AsyncConnectionPool":
408
442
  """Provide async pool instance.
@@ -0,0 +1,331 @@
1
+ """PostgreSQL-specific data dictionary for metadata queries via psycopg."""
2
+
3
+ import re
4
+ from typing import TYPE_CHECKING, Any, cast
5
+
6
+ from sqlspec.driver import (
7
+ AsyncDataDictionaryBase,
8
+ AsyncDriverAdapterBase,
9
+ SyncDataDictionaryBase,
10
+ SyncDriverAdapterBase,
11
+ VersionInfo,
12
+ )
13
+ from sqlspec.utils.logging import get_logger
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Callable
17
+
18
+ from sqlspec.adapters.psycopg.driver import PsycopgAsyncDriver, PsycopgSyncDriver
19
+
20
+ logger = get_logger("adapters.psycopg.data_dictionary")
21
+
22
+ # Compiled regex patterns
23
+ POSTGRES_VERSION_PATTERN = re.compile(r"PostgreSQL (\d+)\.(\d+)(?:\.(\d+))?")
24
+
25
+ __all__ = ("PostgresAsyncDataDictionary", "PostgresSyncDataDictionary")
26
+
27
+
28
+ class PostgresSyncDataDictionary(SyncDataDictionaryBase):
29
+ """PostgreSQL-specific sync data dictionary."""
30
+
31
+ def get_version(self, driver: SyncDriverAdapterBase) -> "VersionInfo | None":
32
+ """Get PostgreSQL database version information.
33
+
34
+ Args:
35
+ driver: Sync database driver instance
36
+
37
+ Returns:
38
+ PostgreSQL version information or None if detection fails
39
+ """
40
+ version_str = cast("PsycopgSyncDriver", driver).select_value("SELECT version()")
41
+ if not version_str:
42
+ logger.warning("No PostgreSQL version information found")
43
+ return None
44
+
45
+ # Parse version like "PostgreSQL 15.3 on x86_64-pc-linux-gnu..."
46
+ version_match = POSTGRES_VERSION_PATTERN.search(str(version_str))
47
+ if not version_match:
48
+ logger.warning("Could not parse PostgreSQL version: %s", version_str)
49
+ return None
50
+
51
+ major = int(version_match.group(1))
52
+ minor = int(version_match.group(2))
53
+ patch = int(version_match.group(3)) if version_match.group(3) else 0
54
+
55
+ version_info = VersionInfo(major, minor, patch)
56
+ logger.debug("Detected PostgreSQL version: %s", version_info)
57
+ return version_info
58
+
59
+ def get_feature_flag(self, driver: SyncDriverAdapterBase, feature: str) -> bool:
60
+ """Check if PostgreSQL database supports a specific feature.
61
+
62
+ Args:
63
+ driver: Sync database driver instance
64
+ feature: Feature name to check
65
+
66
+ Returns:
67
+ True if feature is supported, False otherwise
68
+ """
69
+ version_info = self.get_version(driver)
70
+ if not version_info:
71
+ return False
72
+
73
+ feature_checks: dict[str, Callable[[VersionInfo], bool]] = {
74
+ "supports_json": lambda v: v >= VersionInfo(9, 2, 0),
75
+ "supports_jsonb": lambda v: v >= VersionInfo(9, 4, 0),
76
+ "supports_uuid": lambda _: True, # UUID extension widely available
77
+ "supports_arrays": lambda _: True, # PostgreSQL has excellent array support
78
+ "supports_returning": lambda v: v >= VersionInfo(8, 2, 0),
79
+ "supports_upsert": lambda v: v >= VersionInfo(9, 5, 0), # ON CONFLICT
80
+ "supports_window_functions": lambda v: v >= VersionInfo(8, 4, 0),
81
+ "supports_cte": lambda v: v >= VersionInfo(8, 4, 0),
82
+ "supports_transactions": lambda _: True,
83
+ "supports_prepared_statements": lambda _: True,
84
+ "supports_schemas": lambda _: True,
85
+ "supports_partitioning": lambda v: v >= VersionInfo(10, 0, 0),
86
+ }
87
+
88
+ if feature in feature_checks:
89
+ return bool(feature_checks[feature](version_info))
90
+
91
+ return False
92
+
93
+ def get_optimal_type(self, driver: SyncDriverAdapterBase, type_category: str) -> str:
94
+ """Get optimal PostgreSQL type for a category.
95
+
96
+ Args:
97
+ driver: Sync database driver instance
98
+ type_category: Type category
99
+
100
+ Returns:
101
+ PostgreSQL-specific type name
102
+ """
103
+ version_info = self.get_version(driver)
104
+
105
+ if type_category == "json":
106
+ if version_info and version_info >= VersionInfo(9, 4, 0):
107
+ return "JSONB" # Prefer JSONB over JSON
108
+ if version_info and version_info >= VersionInfo(9, 2, 0):
109
+ return "JSON"
110
+ return "TEXT"
111
+
112
+ type_map = {
113
+ "uuid": "UUID",
114
+ "boolean": "BOOLEAN",
115
+ "timestamp": "TIMESTAMP WITH TIME ZONE",
116
+ "text": "TEXT",
117
+ "blob": "BYTEA",
118
+ "array": "ARRAY",
119
+ }
120
+ return type_map.get(type_category, "TEXT")
121
+
122
+ def get_columns(
123
+ self, driver: SyncDriverAdapterBase, table: str, schema: "str | None" = None
124
+ ) -> "list[dict[str, Any]]":
125
+ """Get column information for a table using information_schema.
126
+
127
+ Args:
128
+ driver: Psycopg sync driver instance
129
+ table: Table name to query columns for
130
+ schema: Schema name (None for default 'public')
131
+
132
+ Returns:
133
+ List of column metadata dictionaries with keys:
134
+ - column_name: Name of the column
135
+ - data_type: PostgreSQL data type
136
+ - is_nullable: Whether column allows NULL (YES/NO)
137
+ - column_default: Default value if any
138
+ """
139
+ psycopg_driver = cast("PsycopgSyncDriver", driver)
140
+
141
+ if schema:
142
+ sql = f"""
143
+ SELECT column_name, data_type, is_nullable, column_default
144
+ FROM information_schema.columns
145
+ WHERE table_name = '{table}' AND table_schema = '{schema}'
146
+ ORDER BY ordinal_position
147
+ """
148
+ else:
149
+ sql = f"""
150
+ SELECT column_name, data_type, is_nullable, column_default
151
+ FROM information_schema.columns
152
+ WHERE table_name = '{table}' AND table_schema = 'public'
153
+ ORDER BY ordinal_position
154
+ """
155
+
156
+ result = psycopg_driver.execute(sql)
157
+ return result.data or []
158
+
159
+ def list_available_features(self) -> "list[str]":
160
+ """List available PostgreSQL feature flags.
161
+
162
+ Returns:
163
+ List of supported feature names
164
+ """
165
+ return [
166
+ "supports_json",
167
+ "supports_jsonb",
168
+ "supports_uuid",
169
+ "supports_arrays",
170
+ "supports_returning",
171
+ "supports_upsert",
172
+ "supports_window_functions",
173
+ "supports_cte",
174
+ "supports_transactions",
175
+ "supports_prepared_statements",
176
+ "supports_schemas",
177
+ "supports_partitioning",
178
+ ]
179
+
180
+
181
+ class PostgresAsyncDataDictionary(AsyncDataDictionaryBase):
182
+ """PostgreSQL-specific async data dictionary."""
183
+
184
+ async def get_version(self, driver: AsyncDriverAdapterBase) -> "VersionInfo | None":
185
+ """Get PostgreSQL database version information.
186
+
187
+ Args:
188
+ driver: Async database driver instance
189
+
190
+ Returns:
191
+ PostgreSQL version information or None if detection fails
192
+ """
193
+ version_str = await cast("PsycopgAsyncDriver", driver).select_value("SELECT version()")
194
+ if not version_str:
195
+ logger.warning("No PostgreSQL version information found")
196
+ return None
197
+
198
+ # Parse version like "PostgreSQL 15.3 on x86_64-pc-linux-gnu..."
199
+ version_match = POSTGRES_VERSION_PATTERN.search(str(version_str))
200
+ if not version_match:
201
+ logger.warning("Could not parse PostgreSQL version: %s", version_str)
202
+ return None
203
+
204
+ major = int(version_match.group(1))
205
+ minor = int(version_match.group(2))
206
+ patch = int(version_match.group(3)) if version_match.group(3) else 0
207
+
208
+ version_info = VersionInfo(major, minor, patch)
209
+ logger.debug("Detected PostgreSQL version: %s", version_info)
210
+ return version_info
211
+
212
+ async def get_feature_flag(self, driver: AsyncDriverAdapterBase, feature: str) -> bool:
213
+ """Check if PostgreSQL database supports a specific feature.
214
+
215
+ Args:
216
+ driver: Async database driver instance
217
+ feature: Feature name to check
218
+
219
+ Returns:
220
+ True if feature is supported, False otherwise
221
+ """
222
+ version_info = await self.get_version(driver)
223
+ if not version_info:
224
+ return False
225
+
226
+ feature_checks: dict[str, Callable[[VersionInfo], bool]] = {
227
+ "supports_json": lambda v: v >= VersionInfo(9, 2, 0),
228
+ "supports_jsonb": lambda v: v >= VersionInfo(9, 4, 0),
229
+ "supports_uuid": lambda _: True, # UUID extension widely available
230
+ "supports_arrays": lambda _: True, # PostgreSQL has excellent array support
231
+ "supports_returning": lambda v: v >= VersionInfo(8, 2, 0),
232
+ "supports_upsert": lambda v: v >= VersionInfo(9, 5, 0), # ON CONFLICT
233
+ "supports_window_functions": lambda v: v >= VersionInfo(8, 4, 0),
234
+ "supports_cte": lambda v: v >= VersionInfo(8, 4, 0),
235
+ "supports_transactions": lambda _: True,
236
+ "supports_prepared_statements": lambda _: True,
237
+ "supports_schemas": lambda _: True,
238
+ "supports_partitioning": lambda v: v >= VersionInfo(10, 0, 0),
239
+ }
240
+
241
+ if feature in feature_checks:
242
+ return bool(feature_checks[feature](version_info))
243
+
244
+ return False
245
+
246
+ async def get_optimal_type(self, driver: AsyncDriverAdapterBase, type_category: str) -> str:
247
+ """Get optimal PostgreSQL type for a category.
248
+
249
+ Args:
250
+ driver: Async database driver instance
251
+ type_category: Type category
252
+
253
+ Returns:
254
+ PostgreSQL-specific type name
255
+ """
256
+ version_info = await self.get_version(driver)
257
+
258
+ if type_category == "json":
259
+ if version_info and version_info >= VersionInfo(9, 4, 0):
260
+ return "JSONB" # Prefer JSONB over JSON
261
+ if version_info and version_info >= VersionInfo(9, 2, 0):
262
+ return "JSON"
263
+ return "TEXT"
264
+
265
+ type_map = {
266
+ "uuid": "UUID",
267
+ "boolean": "BOOLEAN",
268
+ "timestamp": "TIMESTAMP WITH TIME ZONE",
269
+ "text": "TEXT",
270
+ "blob": "BYTEA",
271
+ "array": "ARRAY",
272
+ }
273
+ return type_map.get(type_category, "TEXT")
274
+
275
+ async def get_columns(
276
+ self, driver: AsyncDriverAdapterBase, table: str, schema: "str | None" = None
277
+ ) -> "list[dict[str, Any]]":
278
+ """Get column information for a table using information_schema.
279
+
280
+ Args:
281
+ driver: Psycopg async driver instance
282
+ table: Table name to query columns for
283
+ schema: Schema name (None for default 'public')
284
+
285
+ Returns:
286
+ List of column metadata dictionaries with keys:
287
+ - column_name: Name of the column
288
+ - data_type: PostgreSQL data type
289
+ - is_nullable: Whether column allows NULL (YES/NO)
290
+ - column_default: Default value if any
291
+ """
292
+ psycopg_driver = cast("PsycopgAsyncDriver", driver)
293
+
294
+ if schema:
295
+ sql = f"""
296
+ SELECT column_name, data_type, is_nullable, column_default
297
+ FROM information_schema.columns
298
+ WHERE table_name = '{table}' AND table_schema = '{schema}'
299
+ ORDER BY ordinal_position
300
+ """
301
+ else:
302
+ sql = f"""
303
+ SELECT column_name, data_type, is_nullable, column_default
304
+ FROM information_schema.columns
305
+ WHERE table_name = '{table}' AND table_schema = 'public'
306
+ ORDER BY ordinal_position
307
+ """
308
+
309
+ result = await psycopg_driver.execute(sql)
310
+ return result.data or []
311
+
312
+ def list_available_features(self) -> "list[str]":
313
+ """List available PostgreSQL feature flags.
314
+
315
+ Returns:
316
+ List of supported feature names
317
+ """
318
+ return [
319
+ "supports_json",
320
+ "supports_jsonb",
321
+ "supports_uuid",
322
+ "supports_arrays",
323
+ "supports_returning",
324
+ "supports_upsert",
325
+ "supports_window_functions",
326
+ "supports_cte",
327
+ "supports_transactions",
328
+ "supports_prepared_statements",
329
+ "supports_schemas",
330
+ "supports_partitioning",
331
+ ]