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.
- sqlspec/__init__.py +7 -15
- sqlspec/_serialization.py +256 -24
- sqlspec/_typing.py +71 -52
- sqlspec/adapters/adbc/_types.py +1 -1
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +870 -0
- sqlspec/adapters/adbc/config.py +69 -12
- sqlspec/adapters/adbc/data_dictionary.py +340 -0
- sqlspec/adapters/adbc/driver.py +266 -58
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +504 -0
- sqlspec/adapters/adbc/type_converter.py +153 -0
- sqlspec/adapters/aiosqlite/_types.py +1 -1
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +527 -0
- sqlspec/adapters/aiosqlite/config.py +88 -15
- sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
- sqlspec/adapters/aiosqlite/driver.py +143 -40
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
- sqlspec/adapters/aiosqlite/pool.py +7 -7
- sqlspec/adapters/asyncmy/__init__.py +7 -1
- sqlspec/adapters/asyncmy/_types.py +2 -2
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +493 -0
- sqlspec/adapters/asyncmy/config.py +68 -23
- sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
- sqlspec/adapters/asyncmy/driver.py +313 -58
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +2 -1
- sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
- sqlspec/adapters/asyncpg/_types.py +11 -7
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +450 -0
- sqlspec/adapters/asyncpg/config.py +59 -35
- sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
- sqlspec/adapters/asyncpg/driver.py +170 -25
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +253 -0
- sqlspec/adapters/bigquery/_types.py +1 -1
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +576 -0
- sqlspec/adapters/bigquery/config.py +27 -10
- sqlspec/adapters/bigquery/data_dictionary.py +149 -0
- sqlspec/adapters/bigquery/driver.py +368 -142
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +327 -0
- sqlspec/adapters/bigquery/type_converter.py +125 -0
- sqlspec/adapters/duckdb/_types.py +1 -1
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +553 -0
- sqlspec/adapters/duckdb/config.py +80 -20
- sqlspec/adapters/duckdb/data_dictionary.py +163 -0
- sqlspec/adapters/duckdb/driver.py +167 -45
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +4 -4
- sqlspec/adapters/duckdb/type_converter.py +133 -0
- sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
- sqlspec/adapters/oracledb/_types.py +20 -2
- sqlspec/adapters/oracledb/adk/__init__.py +5 -0
- sqlspec/adapters/oracledb/adk/store.py +1745 -0
- sqlspec/adapters/oracledb/config.py +122 -32
- sqlspec/adapters/oracledb/data_dictionary.py +509 -0
- sqlspec/adapters/oracledb/driver.py +353 -91
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +767 -0
- sqlspec/adapters/oracledb/migrations.py +348 -73
- sqlspec/adapters/oracledb/type_converter.py +207 -0
- sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
- sqlspec/adapters/psqlpy/_types.py +2 -1
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +482 -0
- sqlspec/adapters/psqlpy/config.py +46 -17
- sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
- sqlspec/adapters/psqlpy/driver.py +123 -209
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +272 -0
- sqlspec/adapters/psqlpy/type_converter.py +102 -0
- sqlspec/adapters/psycopg/_type_handlers.py +80 -0
- sqlspec/adapters/psycopg/_types.py +2 -1
- sqlspec/adapters/psycopg/adk/__init__.py +5 -0
- sqlspec/adapters/psycopg/adk/store.py +944 -0
- sqlspec/adapters/psycopg/config.py +69 -35
- sqlspec/adapters/psycopg/data_dictionary.py +331 -0
- sqlspec/adapters/psycopg/driver.py +238 -81
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/sqlite/__init__.py +2 -1
- sqlspec/adapters/sqlite/_type_handlers.py +86 -0
- sqlspec/adapters/sqlite/_types.py +1 -1
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +572 -0
- sqlspec/adapters/sqlite/config.py +87 -15
- sqlspec/adapters/sqlite/data_dictionary.py +149 -0
- sqlspec/adapters/sqlite/driver.py +137 -54
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +18 -9
- sqlspec/base.py +45 -26
- sqlspec/builder/__init__.py +73 -4
- sqlspec/builder/_base.py +162 -89
- sqlspec/builder/_column.py +62 -29
- sqlspec/builder/_ddl.py +180 -121
- sqlspec/builder/_delete.py +5 -4
- sqlspec/builder/_dml.py +388 -0
- sqlspec/{_sql.py → builder/_factory.py} +53 -94
- sqlspec/builder/_insert.py +32 -131
- sqlspec/builder/_join.py +375 -0
- sqlspec/builder/_merge.py +446 -11
- sqlspec/builder/_parsing_utils.py +111 -17
- sqlspec/builder/_select.py +1457 -24
- sqlspec/builder/_update.py +11 -42
- sqlspec/cli.py +307 -194
- sqlspec/config.py +252 -67
- sqlspec/core/__init__.py +5 -4
- sqlspec/core/cache.py +17 -17
- sqlspec/core/compiler.py +62 -9
- sqlspec/core/filters.py +37 -37
- sqlspec/core/hashing.py +9 -9
- sqlspec/core/parameters.py +83 -48
- sqlspec/core/result.py +102 -46
- sqlspec/core/splitter.py +16 -17
- sqlspec/core/statement.py +36 -30
- sqlspec/core/type_conversion.py +235 -0
- sqlspec/driver/__init__.py +7 -6
- sqlspec/driver/_async.py +188 -151
- sqlspec/driver/_common.py +285 -80
- sqlspec/driver/_sync.py +188 -152
- sqlspec/driver/mixins/_result_tools.py +20 -236
- sqlspec/driver/mixins/_sql_translator.py +4 -4
- sqlspec/exceptions.py +75 -7
- sqlspec/extensions/adk/__init__.py +53 -0
- sqlspec/extensions/adk/_types.py +51 -0
- sqlspec/extensions/adk/converters.py +172 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +181 -0
- sqlspec/extensions/adk/store.py +536 -0
- sqlspec/extensions/aiosql/adapter.py +73 -53
- sqlspec/extensions/litestar/__init__.py +21 -4
- sqlspec/extensions/litestar/cli.py +54 -10
- sqlspec/extensions/litestar/config.py +59 -266
- sqlspec/extensions/litestar/handlers.py +46 -17
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +324 -223
- sqlspec/extensions/litestar/providers.py +25 -25
- sqlspec/extensions/litestar/store.py +265 -0
- sqlspec/loader.py +30 -49
- sqlspec/migrations/__init__.py +4 -3
- sqlspec/migrations/base.py +302 -39
- sqlspec/migrations/commands.py +611 -144
- sqlspec/migrations/context.py +142 -0
- sqlspec/migrations/fix.py +199 -0
- sqlspec/migrations/loaders.py +68 -23
- sqlspec/migrations/runner.py +543 -107
- sqlspec/migrations/tracker.py +237 -21
- sqlspec/migrations/utils.py +51 -3
- sqlspec/migrations/validation.py +177 -0
- sqlspec/protocols.py +66 -36
- sqlspec/storage/_utils.py +98 -0
- sqlspec/storage/backends/fsspec.py +134 -106
- sqlspec/storage/backends/local.py +78 -51
- sqlspec/storage/backends/obstore.py +278 -162
- sqlspec/storage/registry.py +75 -39
- sqlspec/typing.py +16 -84
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/correlation.py +4 -5
- sqlspec/utils/data_transformation.py +3 -2
- sqlspec/utils/deprecation.py +9 -8
- sqlspec/utils/fixtures.py +4 -4
- sqlspec/utils/logging.py +46 -6
- sqlspec/utils/module_loader.py +2 -2
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +50 -2
- sqlspec/utils/sync_tools.py +21 -17
- sqlspec/utils/text.py +1 -2
- sqlspec/utils/type_guards.py +111 -20
- sqlspec/utils/version.py +433 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
- sqlspec-0.27.0.dist-info/RECORD +207 -0
- sqlspec/builder/mixins/__init__.py +0 -55
- sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
- sqlspec/builder/mixins/_delete_operations.py +0 -50
- sqlspec/builder/mixins/_insert_operations.py +0 -282
- sqlspec/builder/mixins/_join_operations.py +0 -389
- sqlspec/builder/mixins/_merge_operations.py +0 -592
- sqlspec/builder/mixins/_order_limit_operations.py +0 -152
- sqlspec/builder/mixins/_pivot_operations.py +0 -157
- sqlspec/builder/mixins/_select_operations.py +0 -936
- sqlspec/builder/mixins/_update_operations.py +0 -218
- sqlspec/builder/mixins/_where_clause.py +0 -1304
- sqlspec-0.25.0.dist-info/RECORD +0 -139
- sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
- {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,
|
|
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
|
|
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
|
|
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: "
|
|
87
|
-
pool_instance:
|
|
88
|
-
migration_config:
|
|
89
|
-
statement_config: "
|
|
90
|
-
driver_features: "
|
|
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
|
|
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
|
-
|
|
141
|
-
|
|
168
|
+
if self.driver_features.get("enable_pgvector", False):
|
|
169
|
+
from sqlspec.adapters.psycopg._type_handlers import register_pgvector_sync
|
|
142
170
|
|
|
143
|
-
|
|
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: "
|
|
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(
|
|
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: "
|
|
269
|
-
pool_instance: "
|
|
270
|
-
migration_config: "
|
|
271
|
-
statement_config: "
|
|
272
|
-
driver_features: "
|
|
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
|
|
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
|
-
|
|
321
|
-
from
|
|
357
|
+
if self.driver_features.get("enable_pgvector", False):
|
|
358
|
+
from sqlspec.adapters.psycopg._type_handlers import register_pgvector_async
|
|
322
359
|
|
|
323
|
-
await
|
|
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: "
|
|
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(
|
|
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
|
+
]
|