sqlspec 0.25.0__py3-none-any.whl → 0.26.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/_serialization.py +223 -21
- sqlspec/_sql.py +12 -50
- sqlspec/_typing.py +9 -0
- sqlspec/adapters/adbc/config.py +8 -1
- sqlspec/adapters/adbc/data_dictionary.py +290 -0
- sqlspec/adapters/adbc/driver.py +127 -18
- sqlspec/adapters/adbc/type_converter.py +159 -0
- sqlspec/adapters/aiosqlite/config.py +3 -0
- sqlspec/adapters/aiosqlite/data_dictionary.py +117 -0
- sqlspec/adapters/aiosqlite/driver.py +17 -3
- sqlspec/adapters/asyncmy/_types.py +1 -1
- sqlspec/adapters/asyncmy/config.py +11 -8
- sqlspec/adapters/asyncmy/data_dictionary.py +122 -0
- sqlspec/adapters/asyncmy/driver.py +31 -7
- sqlspec/adapters/asyncpg/config.py +3 -0
- sqlspec/adapters/asyncpg/data_dictionary.py +134 -0
- sqlspec/adapters/asyncpg/driver.py +19 -4
- sqlspec/adapters/bigquery/config.py +3 -0
- sqlspec/adapters/bigquery/data_dictionary.py +109 -0
- sqlspec/adapters/bigquery/driver.py +21 -3
- sqlspec/adapters/bigquery/type_converter.py +93 -0
- sqlspec/adapters/duckdb/_types.py +1 -1
- sqlspec/adapters/duckdb/config.py +2 -0
- sqlspec/adapters/duckdb/data_dictionary.py +124 -0
- sqlspec/adapters/duckdb/driver.py +32 -5
- sqlspec/adapters/duckdb/pool.py +1 -1
- sqlspec/adapters/duckdb/type_converter.py +103 -0
- sqlspec/adapters/oracledb/config.py +6 -0
- sqlspec/adapters/oracledb/data_dictionary.py +442 -0
- sqlspec/adapters/oracledb/driver.py +63 -9
- sqlspec/adapters/oracledb/migrations.py +51 -67
- sqlspec/adapters/oracledb/type_converter.py +132 -0
- sqlspec/adapters/psqlpy/config.py +3 -0
- sqlspec/adapters/psqlpy/data_dictionary.py +133 -0
- sqlspec/adapters/psqlpy/driver.py +23 -179
- sqlspec/adapters/psqlpy/type_converter.py +73 -0
- sqlspec/adapters/psycopg/config.py +6 -0
- sqlspec/adapters/psycopg/data_dictionary.py +257 -0
- sqlspec/adapters/psycopg/driver.py +40 -5
- sqlspec/adapters/sqlite/config.py +3 -0
- sqlspec/adapters/sqlite/data_dictionary.py +117 -0
- sqlspec/adapters/sqlite/driver.py +18 -3
- sqlspec/adapters/sqlite/pool.py +13 -4
- sqlspec/builder/_base.py +82 -42
- sqlspec/builder/_column.py +57 -24
- sqlspec/builder/_ddl.py +84 -34
- sqlspec/builder/_insert.py +30 -52
- sqlspec/builder/_parsing_utils.py +104 -8
- sqlspec/builder/_select.py +147 -2
- sqlspec/builder/mixins/_cte_and_set_ops.py +1 -2
- sqlspec/builder/mixins/_join_operations.py +14 -30
- sqlspec/builder/mixins/_merge_operations.py +167 -61
- sqlspec/builder/mixins/_order_limit_operations.py +3 -10
- sqlspec/builder/mixins/_select_operations.py +3 -9
- sqlspec/builder/mixins/_update_operations.py +3 -22
- sqlspec/builder/mixins/_where_clause.py +4 -10
- sqlspec/cli.py +246 -140
- sqlspec/config.py +33 -19
- sqlspec/core/cache.py +2 -2
- sqlspec/core/compiler.py +56 -1
- sqlspec/core/parameters.py +7 -3
- sqlspec/core/statement.py +5 -0
- sqlspec/core/type_conversion.py +234 -0
- sqlspec/driver/__init__.py +6 -3
- sqlspec/driver/_async.py +106 -3
- sqlspec/driver/_common.py +156 -4
- sqlspec/driver/_sync.py +106 -3
- sqlspec/exceptions.py +5 -0
- sqlspec/migrations/__init__.py +4 -3
- sqlspec/migrations/base.py +153 -14
- sqlspec/migrations/commands.py +34 -96
- sqlspec/migrations/context.py +145 -0
- sqlspec/migrations/loaders.py +25 -8
- sqlspec/migrations/runner.py +352 -82
- sqlspec/typing.py +2 -0
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/serializers.py +50 -2
- {sqlspec-0.25.0.dist-info → sqlspec-0.26.0.dist-info}/METADATA +1 -1
- sqlspec-0.26.0.dist-info/RECORD +157 -0
- sqlspec-0.25.0.dist-info/RECORD +0 -139
- {sqlspec-0.25.0.dist-info → sqlspec-0.26.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.26.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.26.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.26.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/driver/_async.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""Asynchronous driver protocol implementation."""
|
|
2
2
|
|
|
3
3
|
from abc import abstractmethod
|
|
4
|
-
from typing import TYPE_CHECKING, Any, Final, NoReturn, Optional, Union, cast, overload
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Final, NoReturn, Optional, TypeVar, Union, cast, overload
|
|
5
5
|
|
|
6
6
|
from sqlspec.core import SQL, Statement
|
|
7
|
-
from sqlspec.driver._common import CommonDriverAttributesMixin, ExecutionResult
|
|
7
|
+
from sqlspec.driver._common import CommonDriverAttributesMixin, DataDictionaryMixin, ExecutionResult, VersionInfo
|
|
8
8
|
from sqlspec.driver.mixins import SQLTranslatorMixin, ToSchemaMixin
|
|
9
9
|
from sqlspec.exceptions import NotFoundError
|
|
10
10
|
from sqlspec.utils.logging import get_logger
|
|
@@ -21,17 +21,28 @@ if TYPE_CHECKING:
|
|
|
21
21
|
_LOGGER_NAME: Final[str] = "sqlspec"
|
|
22
22
|
logger = get_logger(_LOGGER_NAME)
|
|
23
23
|
|
|
24
|
-
__all__ = ("AsyncDriverAdapterBase",)
|
|
24
|
+
__all__ = ("AsyncDataDictionaryBase", "AsyncDriverAdapterBase", "AsyncDriverT")
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
EMPTY_FILTERS: Final["list[StatementFilter]"] = []
|
|
28
28
|
|
|
29
|
+
AsyncDriverT = TypeVar("AsyncDriverT", bound="AsyncDriverAdapterBase")
|
|
30
|
+
|
|
29
31
|
|
|
30
32
|
class AsyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToSchemaMixin):
|
|
31
33
|
"""Base class for asynchronous database drivers."""
|
|
32
34
|
|
|
33
35
|
__slots__ = ()
|
|
34
36
|
|
|
37
|
+
@property
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def data_dictionary(self) -> "AsyncDataDictionaryBase":
|
|
40
|
+
"""Get the data dictionary for this driver.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Data dictionary instance for metadata queries
|
|
44
|
+
"""
|
|
45
|
+
|
|
35
46
|
async def dispatch_statement_execution(self, statement: "SQL", connection: "Any") -> "SQLResult":
|
|
36
47
|
"""Central execution dispatcher using the Template Method Pattern.
|
|
37
48
|
|
|
@@ -487,3 +498,95 @@ class AsyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, To
|
|
|
487
498
|
def _raise_cannot_extract_value_from_row_type(self, type_name: str) -> NoReturn:
|
|
488
499
|
msg = f"Cannot extract value from row type {type_name}"
|
|
489
500
|
raise TypeError(msg)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
class AsyncDataDictionaryBase(DataDictionaryMixin):
|
|
504
|
+
"""Base class for asynchronous data dictionary implementations."""
|
|
505
|
+
|
|
506
|
+
@abstractmethod
|
|
507
|
+
async def get_version(self, driver: "AsyncDriverAdapterBase") -> "Optional[VersionInfo]":
|
|
508
|
+
"""Get database version information.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
driver: Async database driver instance
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
Version information or None if detection fails
|
|
515
|
+
"""
|
|
516
|
+
|
|
517
|
+
@abstractmethod
|
|
518
|
+
async def get_feature_flag(self, driver: "AsyncDriverAdapterBase", feature: str) -> bool:
|
|
519
|
+
"""Check if database supports a specific feature.
|
|
520
|
+
|
|
521
|
+
Args:
|
|
522
|
+
driver: Async database driver instance
|
|
523
|
+
feature: Feature name to check
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
True if feature is supported, False otherwise
|
|
527
|
+
"""
|
|
528
|
+
|
|
529
|
+
@abstractmethod
|
|
530
|
+
async def get_optimal_type(self, driver: "AsyncDriverAdapterBase", type_category: str) -> str:
|
|
531
|
+
"""Get optimal database type for a category.
|
|
532
|
+
|
|
533
|
+
Args:
|
|
534
|
+
driver: Async database driver instance
|
|
535
|
+
type_category: Type category (e.g., 'json', 'uuid', 'boolean')
|
|
536
|
+
|
|
537
|
+
Returns:
|
|
538
|
+
Database-specific type name
|
|
539
|
+
"""
|
|
540
|
+
|
|
541
|
+
async def get_tables(self, driver: "AsyncDriverAdapterBase", schema: "Optional[str]" = None) -> "list[str]":
|
|
542
|
+
"""Get list of tables in schema.
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
driver: Async database driver instance
|
|
546
|
+
schema: Schema name (None for default)
|
|
547
|
+
|
|
548
|
+
Returns:
|
|
549
|
+
List of table names
|
|
550
|
+
"""
|
|
551
|
+
_ = driver, schema
|
|
552
|
+
return []
|
|
553
|
+
|
|
554
|
+
async def get_columns(
|
|
555
|
+
self, driver: "AsyncDriverAdapterBase", table: str, schema: "Optional[str]" = None
|
|
556
|
+
) -> "list[dict[str, Any]]":
|
|
557
|
+
"""Get column information for a table.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
driver: Async database driver instance
|
|
561
|
+
table: Table name
|
|
562
|
+
schema: Schema name (None for default)
|
|
563
|
+
|
|
564
|
+
Returns:
|
|
565
|
+
List of column metadata dictionaries
|
|
566
|
+
"""
|
|
567
|
+
_ = driver, table, schema
|
|
568
|
+
return []
|
|
569
|
+
|
|
570
|
+
async def get_indexes(
|
|
571
|
+
self, driver: "AsyncDriverAdapterBase", table: str, schema: "Optional[str]" = None
|
|
572
|
+
) -> "list[dict[str, Any]]":
|
|
573
|
+
"""Get index information for a table.
|
|
574
|
+
|
|
575
|
+
Args:
|
|
576
|
+
driver: Async database driver instance
|
|
577
|
+
table: Table name
|
|
578
|
+
schema: Schema name (None for default)
|
|
579
|
+
|
|
580
|
+
Returns:
|
|
581
|
+
List of index metadata dictionaries
|
|
582
|
+
"""
|
|
583
|
+
_ = driver, table, schema
|
|
584
|
+
return []
|
|
585
|
+
|
|
586
|
+
def list_available_features(self) -> "list[str]":
|
|
587
|
+
"""List all features that can be checked via get_feature_flag.
|
|
588
|
+
|
|
589
|
+
Returns:
|
|
590
|
+
List of feature names this data dictionary supports
|
|
591
|
+
"""
|
|
592
|
+
return self.get_default_features()
|
sqlspec/driver/_common.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Common driver attributes and utilities."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import re
|
|
4
|
+
from contextlib import suppress
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Final, NamedTuple, Optional, TypeVar, Union, cast
|
|
4
6
|
|
|
5
7
|
from mypy_extensions import trait
|
|
6
8
|
from sqlglot import exp
|
|
@@ -25,13 +27,158 @@ __all__ = (
|
|
|
25
27
|
"EXEC_ROWCOUNT_OVERRIDE",
|
|
26
28
|
"EXEC_SPECIAL_DATA",
|
|
27
29
|
"CommonDriverAttributesMixin",
|
|
30
|
+
"DataDictionaryMixin",
|
|
28
31
|
"ExecutionResult",
|
|
29
32
|
"ScriptExecutionResult",
|
|
33
|
+
"VersionInfo",
|
|
30
34
|
)
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
logger = get_logger("driver")
|
|
34
38
|
|
|
39
|
+
DriverT = TypeVar("DriverT")
|
|
40
|
+
VERSION_GROUPS_MIN_FOR_MINOR = 1
|
|
41
|
+
VERSION_GROUPS_MIN_FOR_PATCH = 2
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class VersionInfo:
|
|
45
|
+
"""Database version information."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, major: int, minor: int = 0, patch: int = 0) -> None:
|
|
48
|
+
"""Initialize version info.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
major: Major version number
|
|
52
|
+
minor: Minor version number
|
|
53
|
+
patch: Patch version number
|
|
54
|
+
"""
|
|
55
|
+
self.major = major
|
|
56
|
+
self.minor = minor
|
|
57
|
+
self.patch = patch
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def version_tuple(self) -> "tuple[int, int, int]":
|
|
61
|
+
"""Get version as tuple for comparison."""
|
|
62
|
+
return (self.major, self.minor, self.patch)
|
|
63
|
+
|
|
64
|
+
def __str__(self) -> str:
|
|
65
|
+
"""String representation of version info."""
|
|
66
|
+
return f"{self.major}.{self.minor}.{self.patch}"
|
|
67
|
+
|
|
68
|
+
def __repr__(self) -> str:
|
|
69
|
+
"""Detailed string representation."""
|
|
70
|
+
return f"VersionInfo({self.major}, {self.minor}, {self.patch})"
|
|
71
|
+
|
|
72
|
+
def __eq__(self, other: object) -> bool:
|
|
73
|
+
"""Check version equality."""
|
|
74
|
+
if not isinstance(other, VersionInfo):
|
|
75
|
+
return NotImplemented
|
|
76
|
+
return self.version_tuple == other.version_tuple
|
|
77
|
+
|
|
78
|
+
def __lt__(self, other: "VersionInfo") -> bool:
|
|
79
|
+
"""Check if this version is less than another."""
|
|
80
|
+
return self.version_tuple < other.version_tuple
|
|
81
|
+
|
|
82
|
+
def __le__(self, other: "VersionInfo") -> bool:
|
|
83
|
+
"""Check if this version is less than or equal to another."""
|
|
84
|
+
return self.version_tuple <= other.version_tuple
|
|
85
|
+
|
|
86
|
+
def __gt__(self, other: "VersionInfo") -> bool:
|
|
87
|
+
"""Check if this version is greater than another."""
|
|
88
|
+
return self.version_tuple > other.version_tuple
|
|
89
|
+
|
|
90
|
+
def __ge__(self, other: "VersionInfo") -> bool:
|
|
91
|
+
"""Check if this version is greater than or equal to another."""
|
|
92
|
+
return self.version_tuple >= other.version_tuple
|
|
93
|
+
|
|
94
|
+
def __hash__(self) -> int:
|
|
95
|
+
"""Make VersionInfo hashable based on version tuple."""
|
|
96
|
+
return hash(self.version_tuple)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@trait
|
|
100
|
+
class DataDictionaryMixin:
|
|
101
|
+
"""Mixin providing common data dictionary functionality."""
|
|
102
|
+
|
|
103
|
+
def parse_version_string(self, version_str: str) -> "Optional[VersionInfo]":
|
|
104
|
+
"""Parse version string into VersionInfo.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
version_str: Raw version string from database
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
VersionInfo instance or None if parsing fails
|
|
111
|
+
"""
|
|
112
|
+
# Try common version patterns
|
|
113
|
+
patterns = [
|
|
114
|
+
r"(\d+)\.(\d+)\.(\d+)", # x.y.z
|
|
115
|
+
r"(\d+)\.(\d+)", # x.y
|
|
116
|
+
r"(\d+)", # x
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
for pattern in patterns:
|
|
120
|
+
match = re.search(pattern, version_str)
|
|
121
|
+
if match:
|
|
122
|
+
groups = match.groups()
|
|
123
|
+
|
|
124
|
+
major = int(groups[0])
|
|
125
|
+
minor = int(groups[1]) if len(groups) > VERSION_GROUPS_MIN_FOR_MINOR else 0
|
|
126
|
+
patch = int(groups[2]) if len(groups) > VERSION_GROUPS_MIN_FOR_PATCH else 0
|
|
127
|
+
return VersionInfo(major, minor, patch)
|
|
128
|
+
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
def detect_version_with_queries(self, driver: Any, queries: "list[str]") -> "Optional[VersionInfo]":
|
|
132
|
+
"""Try multiple version queries to detect database version.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
driver: Database driver instance
|
|
136
|
+
queries: List of SQL queries to try
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Version information or None if detection fails
|
|
140
|
+
"""
|
|
141
|
+
for query in queries:
|
|
142
|
+
with suppress(Exception):
|
|
143
|
+
result = driver.execute(query)
|
|
144
|
+
if result.data:
|
|
145
|
+
version_str = str(result.data[0])
|
|
146
|
+
if isinstance(result.data[0], dict):
|
|
147
|
+
version_str = str(next(iter(result.data[0].values())))
|
|
148
|
+
elif isinstance(result.data[0], (list, tuple)):
|
|
149
|
+
version_str = str(result.data[0][0])
|
|
150
|
+
|
|
151
|
+
parsed_version = self.parse_version_string(version_str)
|
|
152
|
+
if parsed_version:
|
|
153
|
+
logger.debug("Detected database version: %s", parsed_version)
|
|
154
|
+
return parsed_version
|
|
155
|
+
|
|
156
|
+
logger.warning("Could not detect database version")
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
def get_default_type_mapping(self) -> "dict[str, str]":
|
|
160
|
+
"""Get default type mappings for common categories.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Dictionary mapping type categories to generic SQL types
|
|
164
|
+
"""
|
|
165
|
+
return {
|
|
166
|
+
"json": "TEXT",
|
|
167
|
+
"uuid": "VARCHAR(36)",
|
|
168
|
+
"boolean": "INTEGER",
|
|
169
|
+
"timestamp": "TIMESTAMP",
|
|
170
|
+
"text": "TEXT",
|
|
171
|
+
"blob": "BLOB",
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
def get_default_features(self) -> "list[str]":
|
|
175
|
+
"""Get default feature flags supported by most databases.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
List of commonly supported feature names
|
|
179
|
+
"""
|
|
180
|
+
return ["supports_transactions", "supports_prepared_statements"]
|
|
181
|
+
|
|
35
182
|
|
|
36
183
|
class ScriptExecutionResult(NamedTuple):
|
|
37
184
|
"""Result from script execution with statement count information."""
|
|
@@ -270,7 +417,11 @@ class CommonDriverAttributesMixin:
|
|
|
270
417
|
]
|
|
271
418
|
|
|
272
419
|
def prepare_driver_parameters(
|
|
273
|
-
self,
|
|
420
|
+
self,
|
|
421
|
+
parameters: Any,
|
|
422
|
+
statement_config: "StatementConfig",
|
|
423
|
+
is_many: bool = False,
|
|
424
|
+
prepared_statement: Optional[Any] = None, # pyright: ignore[reportUnusedParameter]
|
|
274
425
|
) -> Any:
|
|
275
426
|
"""Prepare parameters for database driver consumption.
|
|
276
427
|
|
|
@@ -281,6 +432,7 @@ class CommonDriverAttributesMixin:
|
|
|
281
432
|
parameters: Parameters in any format (dict, list, tuple, scalar, TypedParameter)
|
|
282
433
|
statement_config: Statement configuration for parameter style detection
|
|
283
434
|
is_many: If True, handle as executemany parameter sequence
|
|
435
|
+
prepared_statement: Optional prepared statement containing metadata for parameter processing
|
|
284
436
|
|
|
285
437
|
Returns:
|
|
286
438
|
Parameters with TypedParameter objects unwrapped to primitive values
|
|
@@ -422,7 +574,7 @@ class CommonDriverAttributesMixin:
|
|
|
422
574
|
compiled_sql, execution_parameters = prepared_statement.compile()
|
|
423
575
|
|
|
424
576
|
prepared_parameters = self.prepare_driver_parameters(
|
|
425
|
-
execution_parameters, statement_config, is_many=statement.is_many
|
|
577
|
+
execution_parameters, statement_config, is_many=statement.is_many, prepared_statement=statement
|
|
426
578
|
)
|
|
427
579
|
|
|
428
580
|
if statement_config.parameter_config.output_transformer:
|
|
@@ -438,7 +590,7 @@ class CommonDriverAttributesMixin:
|
|
|
438
590
|
if isinstance(prepared_parameters, list)
|
|
439
591
|
else (
|
|
440
592
|
prepared_parameters
|
|
441
|
-
if prepared_parameters is None
|
|
593
|
+
if prepared_parameters is None or isinstance(prepared_parameters, dict)
|
|
442
594
|
else (
|
|
443
595
|
tuple(prepared_parameters)
|
|
444
596
|
if not isinstance(prepared_parameters, tuple)
|
sqlspec/driver/_sync.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""Synchronous driver protocol implementation."""
|
|
2
2
|
|
|
3
3
|
from abc import abstractmethod
|
|
4
|
-
from typing import TYPE_CHECKING, Any, Final, NoReturn, Optional, Union, cast, overload
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Final, NoReturn, Optional, TypeVar, Union, cast, overload
|
|
5
5
|
|
|
6
6
|
from sqlspec.core import SQL
|
|
7
|
-
from sqlspec.driver._common import CommonDriverAttributesMixin, ExecutionResult
|
|
7
|
+
from sqlspec.driver._common import CommonDriverAttributesMixin, DataDictionaryMixin, ExecutionResult, VersionInfo
|
|
8
8
|
from sqlspec.driver.mixins import SQLTranslatorMixin, ToSchemaMixin
|
|
9
9
|
from sqlspec.exceptions import NotFoundError
|
|
10
10
|
from sqlspec.utils.logging import get_logger
|
|
@@ -21,17 +21,28 @@ if TYPE_CHECKING:
|
|
|
21
21
|
_LOGGER_NAME: Final[str] = "sqlspec"
|
|
22
22
|
logger = get_logger(_LOGGER_NAME)
|
|
23
23
|
|
|
24
|
-
__all__ = ("SyncDriverAdapterBase",)
|
|
24
|
+
__all__ = ("SyncDataDictionaryBase", "SyncDriverAdapterBase", "SyncDriverT")
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
EMPTY_FILTERS: Final["list[StatementFilter]"] = []
|
|
28
28
|
|
|
29
|
+
SyncDriverT = TypeVar("SyncDriverT", bound="SyncDriverAdapterBase")
|
|
30
|
+
|
|
29
31
|
|
|
30
32
|
class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToSchemaMixin):
|
|
31
33
|
"""Base class for synchronous database drivers."""
|
|
32
34
|
|
|
33
35
|
__slots__ = ()
|
|
34
36
|
|
|
37
|
+
@property
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def data_dictionary(self) -> "SyncDataDictionaryBase":
|
|
40
|
+
"""Get the data dictionary for this driver.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Data dictionary instance for metadata queries
|
|
44
|
+
"""
|
|
45
|
+
|
|
35
46
|
def dispatch_statement_execution(self, statement: "SQL", connection: "Any") -> "SQLResult":
|
|
36
47
|
"""Central execution dispatcher using the Template Method Pattern.
|
|
37
48
|
|
|
@@ -488,3 +499,95 @@ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
|
|
|
488
499
|
def _raise_cannot_extract_value_from_row_type(self, type_name: str) -> NoReturn:
|
|
489
500
|
msg = f"Cannot extract value from row type {type_name}"
|
|
490
501
|
raise TypeError(msg)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
class SyncDataDictionaryBase(DataDictionaryMixin):
|
|
505
|
+
"""Base class for synchronous data dictionary implementations."""
|
|
506
|
+
|
|
507
|
+
@abstractmethod
|
|
508
|
+
def get_version(self, driver: "SyncDriverAdapterBase") -> "Optional[VersionInfo]":
|
|
509
|
+
"""Get database version information.
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
driver: Sync database driver instance
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
Version information or None if detection fails
|
|
516
|
+
"""
|
|
517
|
+
|
|
518
|
+
@abstractmethod
|
|
519
|
+
def get_feature_flag(self, driver: "SyncDriverAdapterBase", feature: str) -> bool:
|
|
520
|
+
"""Check if database supports a specific feature.
|
|
521
|
+
|
|
522
|
+
Args:
|
|
523
|
+
driver: Sync database driver instance
|
|
524
|
+
feature: Feature name to check
|
|
525
|
+
|
|
526
|
+
Returns:
|
|
527
|
+
True if feature is supported, False otherwise
|
|
528
|
+
"""
|
|
529
|
+
|
|
530
|
+
@abstractmethod
|
|
531
|
+
def get_optimal_type(self, driver: "SyncDriverAdapterBase", type_category: str) -> str:
|
|
532
|
+
"""Get optimal database type for a category.
|
|
533
|
+
|
|
534
|
+
Args:
|
|
535
|
+
driver: Sync database driver instance
|
|
536
|
+
type_category: Type category (e.g., 'json', 'uuid', 'boolean')
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
Database-specific type name
|
|
540
|
+
"""
|
|
541
|
+
|
|
542
|
+
def get_tables(self, driver: "SyncDriverAdapterBase", schema: "Optional[str]" = None) -> "list[str]":
|
|
543
|
+
"""Get list of tables in schema.
|
|
544
|
+
|
|
545
|
+
Args:
|
|
546
|
+
driver: Sync database driver instance
|
|
547
|
+
schema: Schema name (None for default)
|
|
548
|
+
|
|
549
|
+
Returns:
|
|
550
|
+
List of table names
|
|
551
|
+
"""
|
|
552
|
+
_ = driver, schema
|
|
553
|
+
return []
|
|
554
|
+
|
|
555
|
+
def get_columns(
|
|
556
|
+
self, driver: "SyncDriverAdapterBase", table: str, schema: "Optional[str]" = None
|
|
557
|
+
) -> "list[dict[str, Any]]":
|
|
558
|
+
"""Get column information for a table.
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
driver: Sync database driver instance
|
|
562
|
+
table: Table name
|
|
563
|
+
schema: Schema name (None for default)
|
|
564
|
+
|
|
565
|
+
Returns:
|
|
566
|
+
List of column metadata dictionaries
|
|
567
|
+
"""
|
|
568
|
+
_ = driver, table, schema
|
|
569
|
+
return []
|
|
570
|
+
|
|
571
|
+
def get_indexes(
|
|
572
|
+
self, driver: "SyncDriverAdapterBase", table: str, schema: "Optional[str]" = None
|
|
573
|
+
) -> "list[dict[str, Any]]":
|
|
574
|
+
"""Get index information for a table.
|
|
575
|
+
|
|
576
|
+
Args:
|
|
577
|
+
driver: Sync database driver instance
|
|
578
|
+
table: Table name
|
|
579
|
+
schema: Schema name (None for default)
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
List of index metadata dictionaries
|
|
583
|
+
"""
|
|
584
|
+
_ = driver, table, schema
|
|
585
|
+
return []
|
|
586
|
+
|
|
587
|
+
def list_available_features(self) -> "list[str]":
|
|
588
|
+
"""List all features that can be checked via get_feature_flag.
|
|
589
|
+
|
|
590
|
+
Returns:
|
|
591
|
+
List of feature names this data dictionary supports
|
|
592
|
+
"""
|
|
593
|
+
return self.get_default_features()
|
sqlspec/exceptions.py
CHANGED
|
@@ -3,6 +3,7 @@ from contextlib import contextmanager
|
|
|
3
3
|
from typing import Any, Optional, Union
|
|
4
4
|
|
|
5
5
|
__all__ = (
|
|
6
|
+
"ConfigResolverError",
|
|
6
7
|
"FileNotFoundInStorageError",
|
|
7
8
|
"ImproperConfigurationError",
|
|
8
9
|
"IntegrityError",
|
|
@@ -69,6 +70,10 @@ class BackendNotRegisteredError(SQLSpecError):
|
|
|
69
70
|
super().__init__(f"Storage backend '{backend_key}' is not registered. Please register it before use.")
|
|
70
71
|
|
|
71
72
|
|
|
73
|
+
class ConfigResolverError(SQLSpecError):
|
|
74
|
+
"""Exception raised when config resolution fails."""
|
|
75
|
+
|
|
76
|
+
|
|
72
77
|
class SQLParsingError(SQLSpecError):
|
|
73
78
|
"""Issues parsing SQL statements."""
|
|
74
79
|
|
sqlspec/migrations/__init__.py
CHANGED
|
@@ -4,7 +4,7 @@ A native migration system for SQLSpec that leverages the SQLFileLoader
|
|
|
4
4
|
and driver system for database versioning.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from sqlspec.migrations.commands import AsyncMigrationCommands,
|
|
7
|
+
from sqlspec.migrations.commands import AsyncMigrationCommands, SyncMigrationCommands, create_migration_commands
|
|
8
8
|
from sqlspec.migrations.loaders import (
|
|
9
9
|
BaseMigrationLoader,
|
|
10
10
|
MigrationLoadError,
|
|
@@ -12,7 +12,7 @@ from sqlspec.migrations.loaders import (
|
|
|
12
12
|
SQLFileLoader,
|
|
13
13
|
get_migration_loader,
|
|
14
14
|
)
|
|
15
|
-
from sqlspec.migrations.runner import AsyncMigrationRunner, SyncMigrationRunner
|
|
15
|
+
from sqlspec.migrations.runner import AsyncMigrationRunner, SyncMigrationRunner, create_migration_runner
|
|
16
16
|
from sqlspec.migrations.tracker import AsyncMigrationTracker, SyncMigrationTracker
|
|
17
17
|
from sqlspec.migrations.utils import create_migration_file, drop_all, get_author
|
|
18
18
|
|
|
@@ -21,14 +21,15 @@ __all__ = (
|
|
|
21
21
|
"AsyncMigrationRunner",
|
|
22
22
|
"AsyncMigrationTracker",
|
|
23
23
|
"BaseMigrationLoader",
|
|
24
|
-
"MigrationCommands",
|
|
25
24
|
"MigrationLoadError",
|
|
26
25
|
"PythonFileLoader",
|
|
27
26
|
"SQLFileLoader",
|
|
28
27
|
"SyncMigrationCommands",
|
|
29
28
|
"SyncMigrationRunner",
|
|
30
29
|
"SyncMigrationTracker",
|
|
30
|
+
"create_migration_commands",
|
|
31
31
|
"create_migration_file",
|
|
32
|
+
"create_migration_runner",
|
|
32
33
|
"drop_all",
|
|
33
34
|
"get_author",
|
|
34
35
|
"get_migration_loader",
|