sqlspec 0.16.2__cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.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.
- 51ff5a9eadfdefd49f98__mypyc.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/__init__.py +92 -0
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +14 -0
- sqlspec/_serialization.py +77 -0
- sqlspec/_sql.py +1782 -0
- sqlspec/_typing.py +680 -0
- sqlspec/adapters/__init__.py +0 -0
- sqlspec/adapters/adbc/__init__.py +5 -0
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +361 -0
- sqlspec/adapters/adbc/driver.py +512 -0
- sqlspec/adapters/aiosqlite/__init__.py +19 -0
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +253 -0
- sqlspec/adapters/aiosqlite/driver.py +248 -0
- sqlspec/adapters/asyncmy/__init__.py +19 -0
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +180 -0
- sqlspec/adapters/asyncmy/driver.py +274 -0
- sqlspec/adapters/asyncpg/__init__.py +21 -0
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +229 -0
- sqlspec/adapters/asyncpg/driver.py +344 -0
- sqlspec/adapters/bigquery/__init__.py +18 -0
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +298 -0
- sqlspec/adapters/bigquery/driver.py +558 -0
- sqlspec/adapters/duckdb/__init__.py +22 -0
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +504 -0
- sqlspec/adapters/duckdb/driver.py +368 -0
- sqlspec/adapters/oracledb/__init__.py +32 -0
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +317 -0
- sqlspec/adapters/oracledb/driver.py +538 -0
- sqlspec/adapters/psqlpy/__init__.py +16 -0
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +214 -0
- sqlspec/adapters/psqlpy/driver.py +530 -0
- sqlspec/adapters/psycopg/__init__.py +32 -0
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +426 -0
- sqlspec/adapters/psycopg/driver.py +796 -0
- sqlspec/adapters/sqlite/__init__.py +15 -0
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +240 -0
- sqlspec/adapters/sqlite/driver.py +294 -0
- sqlspec/base.py +571 -0
- sqlspec/builder/__init__.py +62 -0
- sqlspec/builder/_base.py +473 -0
- sqlspec/builder/_column.py +320 -0
- sqlspec/builder/_ddl.py +1346 -0
- sqlspec/builder/_ddl_utils.py +103 -0
- sqlspec/builder/_delete.py +76 -0
- sqlspec/builder/_insert.py +421 -0
- sqlspec/builder/_merge.py +71 -0
- sqlspec/builder/_parsing_utils.py +164 -0
- sqlspec/builder/_select.py +170 -0
- sqlspec/builder/_update.py +188 -0
- sqlspec/builder/mixins/__init__.py +55 -0
- sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
- sqlspec/builder/mixins/_delete_operations.py +41 -0
- sqlspec/builder/mixins/_insert_operations.py +244 -0
- sqlspec/builder/mixins/_join_operations.py +149 -0
- sqlspec/builder/mixins/_merge_operations.py +562 -0
- sqlspec/builder/mixins/_order_limit_operations.py +135 -0
- sqlspec/builder/mixins/_pivot_operations.py +153 -0
- sqlspec/builder/mixins/_select_operations.py +604 -0
- sqlspec/builder/mixins/_update_operations.py +202 -0
- sqlspec/builder/mixins/_where_clause.py +644 -0
- sqlspec/cli.py +247 -0
- sqlspec/config.py +395 -0
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/core/cache.py +871 -0
- sqlspec/core/compiler.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/core/compiler.py +417 -0
- sqlspec/core/filters.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/core/filters.py +830 -0
- sqlspec/core/hashing.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters.py +1237 -0
- sqlspec/core/result.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/core/result.py +677 -0
- sqlspec/core/splitter.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/core/splitter.py +819 -0
- sqlspec/core/statement.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/core/statement.py +676 -0
- sqlspec/driver/__init__.py +19 -0
- sqlspec/driver/_async.py +502 -0
- sqlspec/driver/_common.py +631 -0
- sqlspec/driver/_sync.py +503 -0
- sqlspec/driver/mixins/__init__.py +6 -0
- sqlspec/driver/mixins/_result_tools.py +193 -0
- sqlspec/driver/mixins/_sql_translator.py +86 -0
- sqlspec/exceptions.py +193 -0
- sqlspec/extensions/__init__.py +0 -0
- sqlspec/extensions/aiosql/__init__.py +10 -0
- sqlspec/extensions/aiosql/adapter.py +461 -0
- sqlspec/extensions/litestar/__init__.py +6 -0
- sqlspec/extensions/litestar/_utils.py +52 -0
- sqlspec/extensions/litestar/cli.py +48 -0
- sqlspec/extensions/litestar/config.py +92 -0
- sqlspec/extensions/litestar/handlers.py +260 -0
- sqlspec/extensions/litestar/plugin.py +145 -0
- sqlspec/extensions/litestar/providers.py +454 -0
- sqlspec/loader.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/loader.py +760 -0
- sqlspec/migrations/__init__.py +35 -0
- sqlspec/migrations/base.py +414 -0
- sqlspec/migrations/commands.py +443 -0
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +213 -0
- sqlspec/migrations/tracker.py +140 -0
- sqlspec/migrations/utils.py +129 -0
- sqlspec/protocols.py +407 -0
- sqlspec/py.typed +0 -0
- sqlspec/storage/__init__.py +23 -0
- sqlspec/storage/backends/__init__.py +0 -0
- sqlspec/storage/backends/base.py +163 -0
- sqlspec/storage/backends/fsspec.py +386 -0
- sqlspec/storage/backends/obstore.py +459 -0
- sqlspec/storage/capabilities.py +102 -0
- sqlspec/storage/registry.py +239 -0
- sqlspec/typing.py +299 -0
- sqlspec/utils/__init__.py +3 -0
- sqlspec/utils/correlation.py +150 -0
- sqlspec/utils/deprecation.py +106 -0
- sqlspec/utils/fixtures.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/fixtures.py +58 -0
- sqlspec/utils/logging.py +127 -0
- sqlspec/utils/module_loader.py +89 -0
- sqlspec/utils/serializers.py +4 -0
- sqlspec/utils/singleton.py +32 -0
- sqlspec/utils/sync_tools.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/sync_tools.py +237 -0
- sqlspec/utils/text.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/text.py +96 -0
- sqlspec/utils/type_guards.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/type_guards.py +1139 -0
- sqlspec-0.16.2.dist-info/METADATA +365 -0
- sqlspec-0.16.2.dist-info/RECORD +148 -0
- sqlspec-0.16.2.dist-info/WHEEL +7 -0
- sqlspec-0.16.2.dist-info/entry_points.txt +2 -0
- sqlspec-0.16.2.dist-info/licenses/LICENSE +21 -0
- sqlspec-0.16.2.dist-info/licenses/NOTICE +29 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""SQLSpec Migration Tool.
|
|
2
|
+
|
|
3
|
+
A native migration system for SQLSpec that leverages the SQLFileLoader
|
|
4
|
+
and driver architecture for database versioning.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from sqlspec.migrations.commands import AsyncMigrationCommands, MigrationCommands, SyncMigrationCommands
|
|
8
|
+
from sqlspec.migrations.loaders import (
|
|
9
|
+
BaseMigrationLoader,
|
|
10
|
+
MigrationLoadError,
|
|
11
|
+
PythonFileLoader,
|
|
12
|
+
SQLFileLoader,
|
|
13
|
+
get_migration_loader,
|
|
14
|
+
)
|
|
15
|
+
from sqlspec.migrations.runner import AsyncMigrationRunner, SyncMigrationRunner
|
|
16
|
+
from sqlspec.migrations.tracker import AsyncMigrationTracker, SyncMigrationTracker
|
|
17
|
+
from sqlspec.migrations.utils import create_migration_file, drop_all, get_author
|
|
18
|
+
|
|
19
|
+
__all__ = (
|
|
20
|
+
"AsyncMigrationCommands",
|
|
21
|
+
"AsyncMigrationRunner",
|
|
22
|
+
"AsyncMigrationTracker",
|
|
23
|
+
"BaseMigrationLoader",
|
|
24
|
+
"MigrationCommands",
|
|
25
|
+
"MigrationLoadError",
|
|
26
|
+
"PythonFileLoader",
|
|
27
|
+
"SQLFileLoader",
|
|
28
|
+
"SyncMigrationCommands",
|
|
29
|
+
"SyncMigrationRunner",
|
|
30
|
+
"SyncMigrationTracker",
|
|
31
|
+
"create_migration_file",
|
|
32
|
+
"drop_all",
|
|
33
|
+
"get_author",
|
|
34
|
+
"get_migration_loader",
|
|
35
|
+
)
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
"""Base classes for SQLSpec migrations.
|
|
2
|
+
|
|
3
|
+
This module provides abstract base classes for migration components.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import operator
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Generic, Optional, TypeVar
|
|
10
|
+
|
|
11
|
+
from sqlspec._sql import sql
|
|
12
|
+
from sqlspec.builder._ddl import CreateTable
|
|
13
|
+
from sqlspec.core.statement import SQL
|
|
14
|
+
from sqlspec.loader import SQLFileLoader
|
|
15
|
+
from sqlspec.migrations.loaders import get_migration_loader
|
|
16
|
+
from sqlspec.utils.logging import get_logger
|
|
17
|
+
from sqlspec.utils.sync_tools import run_
|
|
18
|
+
|
|
19
|
+
__all__ = ("BaseMigrationCommands", "BaseMigrationRunner", "BaseMigrationTracker")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
logger = get_logger("migrations.base")
|
|
23
|
+
|
|
24
|
+
DriverT = TypeVar("DriverT")
|
|
25
|
+
ConfigT = TypeVar("ConfigT")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BaseMigrationTracker(ABC, Generic[DriverT]):
|
|
29
|
+
"""Base class for migration version tracking."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, version_table_name: str = "ddl_migrations") -> None:
|
|
32
|
+
"""Initialize the migration tracker.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
version_table_name: Name of the table to track migrations.
|
|
36
|
+
"""
|
|
37
|
+
self.version_table = version_table_name
|
|
38
|
+
|
|
39
|
+
def _get_create_table_sql(self) -> SQL:
|
|
40
|
+
"""Get SQL for creating the tracking table.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
SQL object for table creation.
|
|
44
|
+
"""
|
|
45
|
+
builder = CreateTable(self.version_table)
|
|
46
|
+
if not hasattr(builder, "_columns"):
|
|
47
|
+
builder._columns = []
|
|
48
|
+
if not hasattr(builder, "_constraints"):
|
|
49
|
+
builder._constraints = []
|
|
50
|
+
if not hasattr(builder, "_table_options"):
|
|
51
|
+
builder._table_options = {}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
builder.if_not_exists()
|
|
55
|
+
.column("version_num", "VARCHAR(32)", primary_key=True)
|
|
56
|
+
.column("description", "TEXT")
|
|
57
|
+
.column("applied_at", "TIMESTAMP", not_null=True, default="CURRENT_TIMESTAMP")
|
|
58
|
+
.column("execution_time_ms", "INTEGER")
|
|
59
|
+
.column("checksum", "VARCHAR(64)")
|
|
60
|
+
.column("applied_by", "VARCHAR(255)")
|
|
61
|
+
).to_statement()
|
|
62
|
+
|
|
63
|
+
def _get_current_version_sql(self) -> SQL:
|
|
64
|
+
"""Get SQL for retrieving current version.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
SQL object for version query.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
sql.select("version_num").from_(self.version_table).order_by("version_num DESC").limit(1)
|
|
72
|
+
).to_statement()
|
|
73
|
+
|
|
74
|
+
def _get_applied_migrations_sql(self) -> SQL:
|
|
75
|
+
"""Get SQL for retrieving all applied migrations.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
SQL object for migrations query.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
return (sql.select("*").from_(self.version_table).order_by("version_num")).to_statement()
|
|
82
|
+
|
|
83
|
+
def _get_record_migration_sql(
|
|
84
|
+
self, version: str, description: str, execution_time_ms: int, checksum: str, applied_by: str
|
|
85
|
+
) -> SQL:
|
|
86
|
+
"""Get SQL for recording a migration.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
version: Version number of the migration.
|
|
90
|
+
description: Description of the migration.
|
|
91
|
+
execution_time_ms: Execution time in milliseconds.
|
|
92
|
+
checksum: MD5 checksum of the migration content.
|
|
93
|
+
applied_by: User who applied the migration.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
SQL object for insert.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
sql.insert(self.version_table)
|
|
101
|
+
.columns("version_num", "description", "execution_time_ms", "checksum", "applied_by")
|
|
102
|
+
.values(version, description, execution_time_ms, checksum, applied_by)
|
|
103
|
+
).to_statement()
|
|
104
|
+
|
|
105
|
+
def _get_remove_migration_sql(self, version: str) -> SQL:
|
|
106
|
+
"""Get SQL for removing a migration record.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
version: Version number to remove.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
SQL object for delete.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
return (sql.delete().from_(self.version_table).where(sql.version_num == version)).to_statement()
|
|
116
|
+
|
|
117
|
+
@abstractmethod
|
|
118
|
+
def ensure_tracking_table(self, driver: DriverT) -> Any:
|
|
119
|
+
"""Create the migration tracking table if it doesn't exist."""
|
|
120
|
+
...
|
|
121
|
+
|
|
122
|
+
@abstractmethod
|
|
123
|
+
def get_current_version(self, driver: DriverT) -> Any:
|
|
124
|
+
"""Get the latest applied migration version."""
|
|
125
|
+
...
|
|
126
|
+
|
|
127
|
+
@abstractmethod
|
|
128
|
+
def get_applied_migrations(self, driver: DriverT) -> Any:
|
|
129
|
+
"""Get all applied migrations in order."""
|
|
130
|
+
...
|
|
131
|
+
|
|
132
|
+
@abstractmethod
|
|
133
|
+
def record_migration(
|
|
134
|
+
self, driver: DriverT, version: str, description: str, execution_time_ms: int, checksum: str
|
|
135
|
+
) -> Any:
|
|
136
|
+
"""Record a successfully applied migration."""
|
|
137
|
+
...
|
|
138
|
+
|
|
139
|
+
@abstractmethod
|
|
140
|
+
def remove_migration(self, driver: DriverT, version: str) -> Any:
|
|
141
|
+
"""Remove a migration record."""
|
|
142
|
+
...
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class BaseMigrationRunner(ABC, Generic[DriverT]):
|
|
146
|
+
"""Base class for migration execution."""
|
|
147
|
+
|
|
148
|
+
def __init__(self, migrations_path: Path) -> None:
|
|
149
|
+
"""Initialize the migration runner.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
migrations_path: Path to the directory containing migration files.
|
|
153
|
+
"""
|
|
154
|
+
self.migrations_path = migrations_path
|
|
155
|
+
self.loader = SQLFileLoader()
|
|
156
|
+
self.project_root: Optional[Path] = None
|
|
157
|
+
|
|
158
|
+
def _extract_version(self, filename: str) -> Optional[str]:
|
|
159
|
+
"""Extract version from filename.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
filename: The migration filename.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
The extracted version string or None.
|
|
166
|
+
"""
|
|
167
|
+
parts = filename.split("_", 1)
|
|
168
|
+
return parts[0].zfill(4) if parts and parts[0].isdigit() else None
|
|
169
|
+
|
|
170
|
+
def _calculate_checksum(self, content: str) -> str:
|
|
171
|
+
"""Calculate MD5 checksum of migration content.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
content: The migration file content.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
MD5 checksum hex string.
|
|
178
|
+
"""
|
|
179
|
+
import hashlib
|
|
180
|
+
|
|
181
|
+
return hashlib.md5(content.encode()).hexdigest() # noqa: S324
|
|
182
|
+
|
|
183
|
+
def _get_migration_files_sync(self) -> "list[tuple[str, Path]]":
|
|
184
|
+
"""Get all migration files sorted by version.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
List of tuples containing (version, file_path).
|
|
188
|
+
"""
|
|
189
|
+
if not self.migrations_path.exists():
|
|
190
|
+
return []
|
|
191
|
+
|
|
192
|
+
migrations = []
|
|
193
|
+
for pattern in ["*.sql", "*.py"]:
|
|
194
|
+
for file_path in self.migrations_path.glob(pattern):
|
|
195
|
+
if file_path.name.startswith("."):
|
|
196
|
+
continue
|
|
197
|
+
version = self._extract_version(file_path.name)
|
|
198
|
+
if version:
|
|
199
|
+
migrations.append((version, file_path))
|
|
200
|
+
|
|
201
|
+
return sorted(migrations, key=operator.itemgetter(0))
|
|
202
|
+
|
|
203
|
+
def _load_migration_metadata(self, file_path: Path) -> "dict[str, Any]":
|
|
204
|
+
"""Load migration metadata from file.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
file_path: Path to the migration file.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Migration metadata dictionary.
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
loader = get_migration_loader(file_path, self.migrations_path, self.project_root)
|
|
214
|
+
loader.validate_migration_file(file_path)
|
|
215
|
+
content = file_path.read_text(encoding="utf-8")
|
|
216
|
+
checksum = self._calculate_checksum(content)
|
|
217
|
+
version = self._extract_version(file_path.name)
|
|
218
|
+
description = file_path.stem.split("_", 1)[1] if "_" in file_path.stem else ""
|
|
219
|
+
|
|
220
|
+
has_upgrade, has_downgrade = True, False
|
|
221
|
+
|
|
222
|
+
if file_path.suffix == ".sql":
|
|
223
|
+
up_query, down_query = f"migrate-{version}-up", f"migrate-{version}-down"
|
|
224
|
+
self.loader.clear_cache()
|
|
225
|
+
self.loader.load_sql(file_path)
|
|
226
|
+
has_upgrade, has_downgrade = self.loader.has_query(up_query), self.loader.has_query(down_query)
|
|
227
|
+
else:
|
|
228
|
+
try:
|
|
229
|
+
has_downgrade = bool(run_(loader.get_down_sql)(file_path))
|
|
230
|
+
except Exception:
|
|
231
|
+
has_downgrade = False
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
"version": version,
|
|
235
|
+
"description": description,
|
|
236
|
+
"file_path": file_path,
|
|
237
|
+
"checksum": checksum,
|
|
238
|
+
"has_upgrade": has_upgrade,
|
|
239
|
+
"has_downgrade": has_downgrade,
|
|
240
|
+
"loader": loader,
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
def _get_migration_sql(self, migration: "dict[str, Any]", direction: str) -> Optional[SQL]:
|
|
244
|
+
"""Get migration SQL for given direction.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
migration: Migration metadata.
|
|
248
|
+
direction: Either 'up' or 'down'.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
SQL object for the migration.
|
|
252
|
+
"""
|
|
253
|
+
if not migration.get(f"has_{direction}grade"):
|
|
254
|
+
if direction == "down":
|
|
255
|
+
logger.warning("Migration %s has no downgrade query", migration["version"])
|
|
256
|
+
return None
|
|
257
|
+
msg = f"Migration {migration['version']} has no upgrade query"
|
|
258
|
+
raise ValueError(msg)
|
|
259
|
+
|
|
260
|
+
file_path, loader = migration["file_path"], migration["loader"]
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
method = loader.get_up_sql if direction == "up" else loader.get_down_sql
|
|
264
|
+
sql_statements = run_(method)(file_path)
|
|
265
|
+
|
|
266
|
+
except Exception as e:
|
|
267
|
+
if direction == "down":
|
|
268
|
+
logger.warning("Failed to load downgrade for migration %s: %s", migration["version"], e)
|
|
269
|
+
return None
|
|
270
|
+
msg = f"Failed to load upgrade for migration {migration['version']}: {e}"
|
|
271
|
+
raise ValueError(msg) from e
|
|
272
|
+
else:
|
|
273
|
+
if sql_statements:
|
|
274
|
+
return SQL(sql_statements[0])
|
|
275
|
+
return None
|
|
276
|
+
|
|
277
|
+
@abstractmethod
|
|
278
|
+
def get_migration_files(self) -> Any:
|
|
279
|
+
"""Get all migration files sorted by version."""
|
|
280
|
+
...
|
|
281
|
+
|
|
282
|
+
@abstractmethod
|
|
283
|
+
def load_migration(self, file_path: Path) -> Any:
|
|
284
|
+
"""Load a migration file and extract its components."""
|
|
285
|
+
...
|
|
286
|
+
|
|
287
|
+
@abstractmethod
|
|
288
|
+
def execute_upgrade(self, driver: DriverT, migration: "dict[str, Any]") -> Any:
|
|
289
|
+
"""Execute an upgrade migration."""
|
|
290
|
+
...
|
|
291
|
+
|
|
292
|
+
@abstractmethod
|
|
293
|
+
def execute_downgrade(self, driver: DriverT, migration: "dict[str, Any]") -> Any:
|
|
294
|
+
"""Execute a downgrade migration."""
|
|
295
|
+
...
|
|
296
|
+
|
|
297
|
+
@abstractmethod
|
|
298
|
+
def load_all_migrations(self) -> Any:
|
|
299
|
+
"""Load all migrations into a single namespace for bulk operations."""
|
|
300
|
+
...
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class BaseMigrationCommands(ABC, Generic[ConfigT, DriverT]):
|
|
304
|
+
"""Base class for migration commands."""
|
|
305
|
+
|
|
306
|
+
def __init__(self, config: ConfigT) -> None:
|
|
307
|
+
"""Initialize migration commands.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
config: The SQLSpec configuration.
|
|
311
|
+
"""
|
|
312
|
+
self.config = config
|
|
313
|
+
migration_config = getattr(self.config, "migration_config", {}) or {}
|
|
314
|
+
|
|
315
|
+
self.version_table = migration_config.get("version_table_name", "sqlspec_migrations")
|
|
316
|
+
self.migrations_path = Path(migration_config.get("script_location", "migrations"))
|
|
317
|
+
self.project_root = Path(migration_config["project_root"]) if "project_root" in migration_config else None
|
|
318
|
+
|
|
319
|
+
def _get_init_readme_content(self) -> str:
|
|
320
|
+
"""Get README content for migration directory initialization.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
README markdown content.
|
|
324
|
+
"""
|
|
325
|
+
return """# SQLSpec Migrations
|
|
326
|
+
|
|
327
|
+
This directory contains database migration files.
|
|
328
|
+
|
|
329
|
+
## File Format
|
|
330
|
+
|
|
331
|
+
Migration files use SQLFileLoader's named query syntax with versioned names:
|
|
332
|
+
|
|
333
|
+
```sql
|
|
334
|
+
-- name: migrate-0001-up
|
|
335
|
+
CREATE TABLE example (
|
|
336
|
+
id INTEGER PRIMARY KEY,
|
|
337
|
+
name TEXT NOT NULL
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
-- name: migrate-0001-down
|
|
341
|
+
DROP TABLE example;
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Naming Conventions
|
|
345
|
+
|
|
346
|
+
### File Names
|
|
347
|
+
|
|
348
|
+
Format: `{version}_{description}.sql`
|
|
349
|
+
|
|
350
|
+
- Version: Zero-padded 4-digit number (0001, 0002, etc.)
|
|
351
|
+
- Description: Brief description using underscores
|
|
352
|
+
- Example: `0001_create_users_table.sql`
|
|
353
|
+
|
|
354
|
+
### Query Names
|
|
355
|
+
|
|
356
|
+
- Upgrade: `migrate-{version}-up`
|
|
357
|
+
- Downgrade: `migrate-{version}-down`
|
|
358
|
+
|
|
359
|
+
This naming ensures proper sorting and avoids conflicts when loading multiple files.
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
def init_directory(self, directory: str, package: bool = True) -> None:
|
|
363
|
+
"""Initialize migration directory structure.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
directory: Directory to initialize migrations in.
|
|
367
|
+
package: Whether to create __init__.py file.
|
|
368
|
+
"""
|
|
369
|
+
from rich.console import Console
|
|
370
|
+
|
|
371
|
+
console = Console()
|
|
372
|
+
|
|
373
|
+
migrations_dir = Path(directory)
|
|
374
|
+
migrations_dir.mkdir(parents=True, exist_ok=True)
|
|
375
|
+
|
|
376
|
+
if package:
|
|
377
|
+
(migrations_dir / "__init__.py").touch()
|
|
378
|
+
|
|
379
|
+
readme = migrations_dir / "README.md"
|
|
380
|
+
readme.write_text(self._get_init_readme_content())
|
|
381
|
+
|
|
382
|
+
(migrations_dir / ".gitkeep").touch()
|
|
383
|
+
|
|
384
|
+
console.print(f"[green]Initialized migrations in {directory}[/]")
|
|
385
|
+
|
|
386
|
+
@abstractmethod
|
|
387
|
+
def init(self, directory: str, package: bool = True) -> Any:
|
|
388
|
+
"""Initialize migration directory structure."""
|
|
389
|
+
...
|
|
390
|
+
|
|
391
|
+
@abstractmethod
|
|
392
|
+
def current(self, verbose: bool = False) -> Any:
|
|
393
|
+
"""Show current migration version."""
|
|
394
|
+
...
|
|
395
|
+
|
|
396
|
+
@abstractmethod
|
|
397
|
+
def upgrade(self, revision: str = "head") -> Any:
|
|
398
|
+
"""Upgrade to a target revision."""
|
|
399
|
+
...
|
|
400
|
+
|
|
401
|
+
@abstractmethod
|
|
402
|
+
def downgrade(self, revision: str = "-1") -> Any:
|
|
403
|
+
"""Downgrade to a target revision."""
|
|
404
|
+
...
|
|
405
|
+
|
|
406
|
+
@abstractmethod
|
|
407
|
+
def stamp(self, revision: str) -> Any:
|
|
408
|
+
"""Mark database as being at a specific revision without running migrations."""
|
|
409
|
+
...
|
|
410
|
+
|
|
411
|
+
@abstractmethod
|
|
412
|
+
def revision(self, message: str, file_type: str = "sql") -> Any:
|
|
413
|
+
"""Create a new migration file."""
|
|
414
|
+
...
|