jetbase 0.7.0__py3-none-any.whl → 0.12.1__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.
- jetbase/cli/main.py +146 -4
- jetbase/commands/current.py +20 -0
- jetbase/commands/fix_checksums.py +172 -0
- jetbase/commands/fix_files.py +133 -0
- jetbase/commands/history.py +53 -0
- jetbase/commands/init.py +29 -0
- jetbase/commands/lock_status.py +25 -0
- jetbase/commands/new.py +65 -0
- jetbase/commands/rollback.py +172 -0
- jetbase/commands/status.py +212 -0
- jetbase/commands/unlock.py +29 -0
- jetbase/commands/upgrade.py +248 -0
- jetbase/commands/validators.py +37 -0
- jetbase/config.py +304 -25
- jetbase/constants.py +10 -2
- jetbase/database/connection.py +40 -0
- jetbase/database/queries/base.py +353 -0
- jetbase/database/queries/default_queries.py +215 -0
- jetbase/database/queries/postgres.py +14 -0
- jetbase/database/queries/query_loader.py +87 -0
- jetbase/database/queries/sqlite.py +197 -0
- jetbase/engine/checksum.py +25 -0
- jetbase/engine/dry_run.py +105 -0
- jetbase/engine/file_parser.py +324 -0
- jetbase/engine/formatters.py +61 -0
- jetbase/engine/lock.py +65 -0
- jetbase/engine/repeatable.py +125 -0
- jetbase/engine/validation.py +238 -0
- jetbase/engine/version.py +144 -0
- jetbase/enums.py +37 -1
- jetbase/exceptions.py +87 -0
- jetbase/models.py +45 -0
- jetbase/repositories/lock_repo.py +129 -0
- jetbase/repositories/migrations_repo.py +451 -0
- jetbase-0.12.1.dist-info/METADATA +135 -0
- jetbase-0.12.1.dist-info/RECORD +39 -0
- {jetbase-0.7.0.dist-info → jetbase-0.12.1.dist-info}/WHEEL +1 -1
- jetbase/core/dry_run.py +0 -38
- jetbase/core/file_parser.py +0 -199
- jetbase/core/initialize.py +0 -33
- jetbase/core/repository.py +0 -169
- jetbase/core/rollback.py +0 -67
- jetbase/core/upgrade.py +0 -75
- jetbase/core/version.py +0 -163
- jetbase/queries.py +0 -72
- jetbase-0.7.0.dist-info/METADATA +0 -12
- jetbase-0.7.0.dist-info/RECORD +0 -17
- {jetbase-0.7.0.dist-info → jetbase-0.12.1.dist-info}/entry_points.txt +0 -0
- {jetbase-0.7.0.dist-info → jetbase-0.12.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import TextClause, text
|
|
4
|
+
from sqlalchemy.engine import make_url
|
|
5
|
+
|
|
6
|
+
from jetbase.database.queries import default_queries
|
|
7
|
+
from jetbase.enums import DatabaseType, MigrationType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseQueries:
|
|
11
|
+
"""
|
|
12
|
+
Base class for database-specific SQL queries.
|
|
13
|
+
|
|
14
|
+
Provides default implementations for all migration-related SQL queries.
|
|
15
|
+
Database-specific subclasses can override methods to provide optimized
|
|
16
|
+
or syntax-compatible versions.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def latest_version_query() -> TextClause:
|
|
21
|
+
"""
|
|
22
|
+
Get query to fetch the latest migration version.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
TextClause: SQLAlchemy text clause for the query.
|
|
26
|
+
"""
|
|
27
|
+
return default_queries.LATEST_VERSION_QUERY
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def create_migrations_table_stmt() -> TextClause:
|
|
31
|
+
"""
|
|
32
|
+
Get statement to create the jetbase_migrations table.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
TextClause: SQLAlchemy text clause for the CREATE TABLE statement.
|
|
36
|
+
"""
|
|
37
|
+
return default_queries.CREATE_MIGRATIONS_TABLE_STMT
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def insert_version_stmt() -> TextClause:
|
|
41
|
+
"""
|
|
42
|
+
Get statement to insert a new migration record.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
TextClause: SQLAlchemy text clause with :version, :description,
|
|
46
|
+
:filename, :migration_type, and :checksum parameters.
|
|
47
|
+
"""
|
|
48
|
+
return default_queries.INSERT_VERSION_STMT
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def delete_version_stmt() -> TextClause:
|
|
52
|
+
"""
|
|
53
|
+
Get statement to delete a migration record by version.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
TextClause: SQLAlchemy text clause with :version parameter.
|
|
57
|
+
"""
|
|
58
|
+
return default_queries.DELETE_VERSION_STMT
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def latest_versions_query() -> TextClause:
|
|
62
|
+
"""
|
|
63
|
+
Get query to fetch the latest N migration versions.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
TextClause: SQLAlchemy text clause with :limit parameter.
|
|
67
|
+
"""
|
|
68
|
+
return default_queries.LATEST_VERSIONS_QUERY
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def latest_versions_by_starting_version_query() -> TextClause:
|
|
72
|
+
"""
|
|
73
|
+
Get query to fetch all versions applied after a starting version.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
TextClause: SQLAlchemy text clause with :starting_version parameter.
|
|
77
|
+
"""
|
|
78
|
+
return default_queries.LATEST_VERSIONS_BY_STARTING_VERSION_QUERY
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def check_if_version_exists_query() -> TextClause:
|
|
82
|
+
"""
|
|
83
|
+
Get query to check if a specific version exists in the database.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
TextClause: SQLAlchemy text clause with :version parameter.
|
|
87
|
+
"""
|
|
88
|
+
return default_queries.CHECK_IF_VERSION_EXISTS_QUERY
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def check_if_migrations_table_exists_query() -> TextClause:
|
|
92
|
+
"""
|
|
93
|
+
Get query to check if the jetbase_migrations table exists.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
TextClause: SQLAlchemy text clause that returns a boolean.
|
|
97
|
+
"""
|
|
98
|
+
return default_queries.CHECK_IF_MIGRATIONS_TABLE_EXISTS_QUERY
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def check_if_lock_table_exists_query() -> TextClause:
|
|
102
|
+
"""
|
|
103
|
+
Get query to check if the jetbase_lock table exists.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
TextClause: SQLAlchemy text clause that returns a boolean.
|
|
107
|
+
"""
|
|
108
|
+
return default_queries.CHECK_IF_LOCK_TABLE_EXISTS_QUERY
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def migration_records_query(
|
|
112
|
+
ascending: bool = True,
|
|
113
|
+
all_repeatables: bool = False,
|
|
114
|
+
migration_type: MigrationType | None = None,
|
|
115
|
+
) -> TextClause:
|
|
116
|
+
"""
|
|
117
|
+
Get query to fetch migration records with optional filters.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
ascending (bool): If True, order by applied_at ascending.
|
|
121
|
+
Defaults to True.
|
|
122
|
+
all_repeatables (bool): If True, filter to only repeatable
|
|
123
|
+
migrations. Defaults to False.
|
|
124
|
+
migration_type (MigrationType | None): If provided, filter to
|
|
125
|
+
only this migration type. Defaults to None.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
TextClause: SQLAlchemy text clause for the filtered query.
|
|
129
|
+
"""
|
|
130
|
+
query: str = f"""
|
|
131
|
+
SELECT
|
|
132
|
+
order_executed,
|
|
133
|
+
version,
|
|
134
|
+
description,
|
|
135
|
+
filename,
|
|
136
|
+
migration_type,
|
|
137
|
+
applied_at,
|
|
138
|
+
checksum
|
|
139
|
+
FROM
|
|
140
|
+
jetbase_migrations
|
|
141
|
+
{"WHERE migration_type = " + f"'{migration_type.value}'" if migration_type else ""}
|
|
142
|
+
{"WHERE migration_type IN ('RUNS_ON_CHANGE', 'RUNS_ALWAYS')" if all_repeatables else ""}
|
|
143
|
+
ORDER BY
|
|
144
|
+
applied_at {"ASC" if ascending else "DESC"}
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
return text(query)
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def create_lock_table_stmt() -> TextClause:
|
|
151
|
+
"""
|
|
152
|
+
Get statement to create the jetbase_lock table.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
TextClause: SQLAlchemy text clause for the CREATE TABLE statement.
|
|
156
|
+
"""
|
|
157
|
+
return default_queries.CREATE_LOCK_TABLE_STMT
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def initialize_lock_record_stmt() -> TextClause:
|
|
161
|
+
"""
|
|
162
|
+
Get statement to initialize the lock record with an unlocked state.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
TextClause: SQLAlchemy text clause for the INSERT statement.
|
|
166
|
+
"""
|
|
167
|
+
return default_queries.INITIALIZE_LOCK_RECORD_STMT
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
def check_lock_status_stmt() -> TextClause:
|
|
171
|
+
"""
|
|
172
|
+
Get statement to check the current lock status.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
TextClause: SQLAlchemy text clause that returns is_locked
|
|
176
|
+
and locked_at columns.
|
|
177
|
+
"""
|
|
178
|
+
return default_queries.CHECK_LOCK_STATUS_STMT
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def acquire_lock_stmt() -> TextClause:
|
|
182
|
+
"""
|
|
183
|
+
Get statement to atomically acquire the migration lock.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
TextClause: SQLAlchemy text clause with :process_id parameter.
|
|
187
|
+
"""
|
|
188
|
+
return default_queries.ACQUIRE_LOCK_STMT
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def release_lock_stmt() -> TextClause:
|
|
192
|
+
"""
|
|
193
|
+
Get statement to release the migration lock for a specific process.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
TextClause: SQLAlchemy text clause with :process_id parameter.
|
|
197
|
+
"""
|
|
198
|
+
return default_queries.RELEASE_LOCK_STMT
|
|
199
|
+
|
|
200
|
+
@staticmethod
|
|
201
|
+
def force_unlock_stmt() -> TextClause:
|
|
202
|
+
"""
|
|
203
|
+
Get statement to force release the migration lock unconditionally.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
TextClause: SQLAlchemy text clause for the UPDATE statement.
|
|
207
|
+
"""
|
|
208
|
+
return default_queries.FORCE_UNLOCK_STMT
|
|
209
|
+
|
|
210
|
+
@staticmethod
|
|
211
|
+
def get_version_checksums_query() -> TextClause:
|
|
212
|
+
"""
|
|
213
|
+
Get query to fetch version and checksum pairs for all migrations.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
TextClause: SQLAlchemy text clause that returns version
|
|
217
|
+
and checksum columns.
|
|
218
|
+
"""
|
|
219
|
+
return default_queries.GET_VERSION_CHECKSUMS_QUERY
|
|
220
|
+
|
|
221
|
+
@staticmethod
|
|
222
|
+
def repair_migration_checksum_stmt() -> TextClause:
|
|
223
|
+
"""
|
|
224
|
+
Get statement to update the checksum for a specific version.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
TextClause: SQLAlchemy text clause with :version and
|
|
228
|
+
:checksum parameters.
|
|
229
|
+
"""
|
|
230
|
+
return default_queries.REPAIR_MIGRATION_CHECKSUM_STMT
|
|
231
|
+
|
|
232
|
+
@staticmethod
|
|
233
|
+
def get_runs_on_change_migrations_query() -> TextClause:
|
|
234
|
+
"""
|
|
235
|
+
Get query to fetch all runs-on-change migration records.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
TextClause: SQLAlchemy text clause that returns filename
|
|
239
|
+
and checksum columns.
|
|
240
|
+
"""
|
|
241
|
+
return default_queries.GET_RUNS_ON_CHANGE_MIGRATIONS_QUERY
|
|
242
|
+
|
|
243
|
+
@staticmethod
|
|
244
|
+
def get_repeatable_always_migrations_query() -> TextClause:
|
|
245
|
+
"""
|
|
246
|
+
Get query to fetch all runs-always migration records.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
TextClause: SQLAlchemy text clause that returns filename column.
|
|
250
|
+
"""
|
|
251
|
+
return default_queries.GET_RUNS_ALWAYS_MIGRATIONS_QUERY
|
|
252
|
+
|
|
253
|
+
@staticmethod
|
|
254
|
+
def get_repeatable_migrations_query() -> TextClause:
|
|
255
|
+
"""
|
|
256
|
+
Get query to fetch all repeatable migration records.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
TextClause: SQLAlchemy text clause for the SELECT query.
|
|
260
|
+
"""
|
|
261
|
+
return default_queries.GET_REPEATABLE_MIGRATIONS_QUERY
|
|
262
|
+
|
|
263
|
+
@staticmethod
|
|
264
|
+
def update_repeatable_migration_stmt() -> TextClause:
|
|
265
|
+
"""
|
|
266
|
+
Get statement to update a repeatable migration's checksum and timestamp.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
TextClause: SQLAlchemy text clause with :checksum, :filename,
|
|
270
|
+
and :migration_type parameters.
|
|
271
|
+
"""
|
|
272
|
+
return default_queries.UPDATE_REPEATABLE_MIGRATION_STMT
|
|
273
|
+
|
|
274
|
+
@staticmethod
|
|
275
|
+
def delete_missing_version_stmt() -> TextClause:
|
|
276
|
+
"""
|
|
277
|
+
Get statement to delete a versioned migration record.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
TextClause: SQLAlchemy text clause with :version parameter.
|
|
281
|
+
"""
|
|
282
|
+
return default_queries.DELETE_MISSING_VERSION_STMT
|
|
283
|
+
|
|
284
|
+
@staticmethod
|
|
285
|
+
def delete_missing_repeatable_stmt() -> TextClause:
|
|
286
|
+
"""
|
|
287
|
+
Get statement to delete a repeatable migration record by filename.
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
TextClause: SQLAlchemy text clause with :filename parameter.
|
|
291
|
+
"""
|
|
292
|
+
return default_queries.DELETE_MISSING_REPEATABLE_STMT
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def detect_db(sqlalchemy_url: str) -> DatabaseType:
|
|
296
|
+
"""
|
|
297
|
+
Detect the database type from a SQLAlchemy connection URL.
|
|
298
|
+
|
|
299
|
+
Parses the URL to determine the database backend and returns
|
|
300
|
+
the corresponding DatabaseType enum value.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
sqlalchemy_url (str): A SQLAlchemy database connection URL.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
DatabaseType: The detected database type (postgresql or sqlite).
|
|
307
|
+
|
|
308
|
+
Raises:
|
|
309
|
+
ValueError: If the database type is not supported by Jetbase.
|
|
310
|
+
"""
|
|
311
|
+
url = make_url(sqlalchemy_url)
|
|
312
|
+
backend_name = url.get_backend_name()
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
database_type: DatabaseType = DatabaseType(backend_name)
|
|
316
|
+
except ValueError:
|
|
317
|
+
raise ValueError(
|
|
318
|
+
f"Unsupported database: {backend_name}. "
|
|
319
|
+
f"Supported databases are: {', '.join(db.value for db in DatabaseType)}"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
return database_type
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
class QueryMethod(Enum):
|
|
326
|
+
"""Enum for all available query methods in BaseQueries"""
|
|
327
|
+
|
|
328
|
+
LATEST_VERSION_QUERY = "latest_version_query"
|
|
329
|
+
CREATE_MIGRATIONS_TABLE_STMT = "create_migrations_table_stmt"
|
|
330
|
+
INSERT_VERSION_STMT = "insert_version_stmt"
|
|
331
|
+
DELETE_VERSION_STMT = "delete_version_stmt"
|
|
332
|
+
LATEST_VERSIONS_QUERY = "latest_versions_query"
|
|
333
|
+
LATEST_VERSIONS_BY_STARTING_VERSION_QUERY = (
|
|
334
|
+
"latest_versions_by_starting_version_query"
|
|
335
|
+
)
|
|
336
|
+
CHECK_IF_VERSION_EXISTS_QUERY = "check_if_version_exists_query"
|
|
337
|
+
CHECK_IF_MIGRATIONS_TABLE_EXISTS_QUERY = "check_if_migrations_table_exists_query"
|
|
338
|
+
CHECK_IF_LOCK_TABLE_EXISTS_QUERY = "check_if_lock_table_exists_query"
|
|
339
|
+
MIGRATION_RECORDS_QUERY = "migration_records_query"
|
|
340
|
+
CREATE_LOCK_TABLE_STMT = "create_lock_table_stmt"
|
|
341
|
+
INITIALIZE_LOCK_RECORD_STMT = "initialize_lock_record_stmt"
|
|
342
|
+
CHECK_LOCK_STATUS_STMT = "check_lock_status_stmt"
|
|
343
|
+
ACQUIRE_LOCK_STMT = "acquire_lock_stmt"
|
|
344
|
+
RELEASE_LOCK_STMT = "release_lock_stmt"
|
|
345
|
+
FORCE_UNLOCK_STMT = "force_unlock_stmt"
|
|
346
|
+
GET_VERSION_CHECKSUMS_QUERY = "get_version_checksums_query"
|
|
347
|
+
REPAIR_MIGRATION_CHECKSUM_STMT = "repair_migration_checksum_stmt"
|
|
348
|
+
GET_RUNS_ON_CHANGE_MIGRATIONS_QUERY = "get_runs_on_change_migrations_query"
|
|
349
|
+
GET_RUNS_ALWAYS_MIGRATIONS_QUERY = "get_repeatable_always_migrations_query"
|
|
350
|
+
GET_REPEATABLE_MIGRATIONS_QUERY = "get_repeatable_migrations_query"
|
|
351
|
+
UPDATE_REPEATABLE_MIGRATION_STMT = "update_repeatable_migration_stmt"
|
|
352
|
+
DELETE_MISSING_VERSION_STMT = "delete_missing_version_stmt"
|
|
353
|
+
DELETE_MISSING_REPEATABLE_STMT = "delete_missing_repeatable_stmt"
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from sqlalchemy import TextClause, text
|
|
2
|
+
|
|
3
|
+
from jetbase.enums import MigrationType
|
|
4
|
+
|
|
5
|
+
LATEST_VERSION_QUERY: TextClause = text(f"""
|
|
6
|
+
SELECT
|
|
7
|
+
version
|
|
8
|
+
FROM
|
|
9
|
+
jetbase_migrations
|
|
10
|
+
WHERE
|
|
11
|
+
migration_type = '{MigrationType.VERSIONED.value}'
|
|
12
|
+
ORDER BY
|
|
13
|
+
applied_at DESC
|
|
14
|
+
LIMIT 1
|
|
15
|
+
""")
|
|
16
|
+
|
|
17
|
+
CREATE_MIGRATIONS_TABLE_STMT: TextClause = text("""
|
|
18
|
+
CREATE TABLE IF NOT EXISTS jetbase_migrations (
|
|
19
|
+
order_executed INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
20
|
+
version VARCHAR(255),
|
|
21
|
+
description VARCHAR(500) NOT NULL,
|
|
22
|
+
filename VARCHAR(512) NOT NULL,
|
|
23
|
+
migration_type VARCHAR(32) NOT NULL,
|
|
24
|
+
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
25
|
+
checksum VARCHAR(64) NOT NULL
|
|
26
|
+
)
|
|
27
|
+
""")
|
|
28
|
+
|
|
29
|
+
INSERT_VERSION_STMT: TextClause = text("""
|
|
30
|
+
INSERT INTO jetbase_migrations (version, description, filename, migration_type, checksum)
|
|
31
|
+
VALUES (:version, :description, :filename, :migration_type, :checksum)
|
|
32
|
+
""")
|
|
33
|
+
|
|
34
|
+
DELETE_VERSION_STMT: TextClause = text(f"""
|
|
35
|
+
DELETE FROM jetbase_migrations
|
|
36
|
+
WHERE version = :version
|
|
37
|
+
AND migration_type = '{MigrationType.VERSIONED.value}'
|
|
38
|
+
""")
|
|
39
|
+
|
|
40
|
+
LATEST_VERSIONS_QUERY: TextClause = text(f"""
|
|
41
|
+
SELECT
|
|
42
|
+
version
|
|
43
|
+
FROM
|
|
44
|
+
jetbase_migrations
|
|
45
|
+
WHERE
|
|
46
|
+
migration_type = '{MigrationType.VERSIONED.value}'
|
|
47
|
+
ORDER BY
|
|
48
|
+
applied_at DESC
|
|
49
|
+
LIMIT :limit
|
|
50
|
+
""")
|
|
51
|
+
|
|
52
|
+
LATEST_VERSIONS_BY_STARTING_VERSION_QUERY: TextClause = text(f"""
|
|
53
|
+
SELECT
|
|
54
|
+
version
|
|
55
|
+
FROM
|
|
56
|
+
jetbase_migrations
|
|
57
|
+
WHERE applied_at >
|
|
58
|
+
(select applied_at from jetbase_migrations
|
|
59
|
+
where version = :starting_version AND migration_type = '{MigrationType.VERSIONED.value}')
|
|
60
|
+
AND migration_type = '{MigrationType.VERSIONED.value}'
|
|
61
|
+
ORDER BY
|
|
62
|
+
applied_at DESC
|
|
63
|
+
""")
|
|
64
|
+
|
|
65
|
+
CHECK_IF_VERSION_EXISTS_QUERY: TextClause = text(f"""
|
|
66
|
+
SELECT
|
|
67
|
+
COUNT(*)
|
|
68
|
+
FROM
|
|
69
|
+
jetbase_migrations
|
|
70
|
+
WHERE
|
|
71
|
+
version = :version
|
|
72
|
+
AND
|
|
73
|
+
migration_type = '{MigrationType.VERSIONED.value}'
|
|
74
|
+
""")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
CHECK_IF_MIGRATIONS_TABLE_EXISTS_QUERY: TextClause = text("""
|
|
78
|
+
SELECT EXISTS (
|
|
79
|
+
SELECT 1
|
|
80
|
+
FROM information_schema.tables
|
|
81
|
+
WHERE table_schema = 'public'
|
|
82
|
+
AND table_name = 'jetbase_migrations'
|
|
83
|
+
)
|
|
84
|
+
""")
|
|
85
|
+
|
|
86
|
+
CHECK_IF_LOCK_TABLE_EXISTS_QUERY: TextClause = text("""
|
|
87
|
+
SELECT EXISTS (
|
|
88
|
+
SELECT 1
|
|
89
|
+
FROM information_schema.tables
|
|
90
|
+
WHERE table_schema = 'public'
|
|
91
|
+
AND table_name = 'jetbase_lock'
|
|
92
|
+
)
|
|
93
|
+
""")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
CREATE_LOCK_TABLE_STMT: TextClause = text("""
|
|
97
|
+
CREATE TABLE IF NOT EXISTS jetbase_lock (
|
|
98
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
99
|
+
is_locked BOOLEAN NOT NULL DEFAULT FALSE,
|
|
100
|
+
locked_at TIMESTAMP,
|
|
101
|
+
process_id VARCHAR(36)
|
|
102
|
+
)
|
|
103
|
+
""")
|
|
104
|
+
|
|
105
|
+
INITIALIZE_LOCK_RECORD_STMT: TextClause = text("""
|
|
106
|
+
INSERT INTO jetbase_lock (id, is_locked)
|
|
107
|
+
SELECT 1, FALSE
|
|
108
|
+
WHERE NOT EXISTS (SELECT 1 FROM jetbase_lock WHERE id = 1)
|
|
109
|
+
""")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
CHECK_LOCK_STATUS_STMT: TextClause = text("""
|
|
113
|
+
SELECT is_locked, locked_at
|
|
114
|
+
FROM jetbase_lock
|
|
115
|
+
WHERE id = 1
|
|
116
|
+
""")
|
|
117
|
+
|
|
118
|
+
ACQUIRE_LOCK_STMT: TextClause = text("""
|
|
119
|
+
UPDATE jetbase_lock
|
|
120
|
+
SET is_locked = TRUE,
|
|
121
|
+
locked_at = CURRENT_TIMESTAMP,
|
|
122
|
+
process_id = :process_id
|
|
123
|
+
WHERE id = 1 AND is_locked = FALSE
|
|
124
|
+
""")
|
|
125
|
+
|
|
126
|
+
RELEASE_LOCK_STMT: TextClause = text("""
|
|
127
|
+
UPDATE jetbase_lock
|
|
128
|
+
SET is_locked = FALSE,
|
|
129
|
+
locked_at = NULL,
|
|
130
|
+
process_id = NULL
|
|
131
|
+
WHERE id = 1 AND process_id = :process_id
|
|
132
|
+
""")
|
|
133
|
+
|
|
134
|
+
FORCE_UNLOCK_STMT: TextClause = text("""
|
|
135
|
+
UPDATE jetbase_lock
|
|
136
|
+
SET is_locked = FALSE,
|
|
137
|
+
locked_at = NULL,
|
|
138
|
+
process_id = NULL
|
|
139
|
+
WHERE id = 1
|
|
140
|
+
""")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
GET_VERSION_CHECKSUMS_QUERY: TextClause = text(f"""
|
|
144
|
+
SELECT
|
|
145
|
+
version, checksum
|
|
146
|
+
FROM
|
|
147
|
+
jetbase_migrations
|
|
148
|
+
WHERE
|
|
149
|
+
migration_type = '{MigrationType.VERSIONED.value}'
|
|
150
|
+
ORDER BY
|
|
151
|
+
order_executed ASC
|
|
152
|
+
""")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
REPAIR_MIGRATION_CHECKSUM_STMT: TextClause = text(f"""
|
|
156
|
+
UPDATE jetbase_migrations
|
|
157
|
+
SET checksum = :checksum
|
|
158
|
+
WHERE version = :version
|
|
159
|
+
AND migration_type = '{MigrationType.VERSIONED.value}'
|
|
160
|
+
""")
|
|
161
|
+
|
|
162
|
+
GET_RUNS_ON_CHANGE_MIGRATIONS_QUERY: TextClause = text(f"""
|
|
163
|
+
SELECT
|
|
164
|
+
filename, checksum
|
|
165
|
+
FROM
|
|
166
|
+
jetbase_migrations
|
|
167
|
+
WHERE
|
|
168
|
+
migration_type = '{MigrationType.RUNS_ON_CHANGE.value}'
|
|
169
|
+
ORDER BY
|
|
170
|
+
filename ASC
|
|
171
|
+
""")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
GET_RUNS_ALWAYS_MIGRATIONS_QUERY: TextClause = text(f"""
|
|
175
|
+
SELECT
|
|
176
|
+
filename
|
|
177
|
+
FROM
|
|
178
|
+
jetbase_migrations
|
|
179
|
+
WHERE
|
|
180
|
+
migration_type = '{MigrationType.RUNS_ALWAYS.value}'
|
|
181
|
+
ORDER BY
|
|
182
|
+
filename ASC
|
|
183
|
+
""")
|
|
184
|
+
|
|
185
|
+
GET_REPEATABLE_MIGRATIONS_QUERY: TextClause = text(f"""
|
|
186
|
+
SELECT
|
|
187
|
+
filename
|
|
188
|
+
FROM
|
|
189
|
+
jetbase_migrations
|
|
190
|
+
WHERE
|
|
191
|
+
migration_type in ('{MigrationType.RUNS_ALWAYS.value}', '{MigrationType.RUNS_ON_CHANGE.value}')
|
|
192
|
+
ORDER BY
|
|
193
|
+
filename ASC
|
|
194
|
+
""")
|
|
195
|
+
|
|
196
|
+
UPDATE_REPEATABLE_MIGRATION_STMT: TextClause = text("""
|
|
197
|
+
UPDATE jetbase_migrations
|
|
198
|
+
SET checksum = :checksum,
|
|
199
|
+
applied_at = CURRENT_TIMESTAMP
|
|
200
|
+
WHERE filename = :filename
|
|
201
|
+
AND migration_type = :migration_type
|
|
202
|
+
""")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
DELETE_MISSING_VERSION_STMT: TextClause = text(f"""
|
|
206
|
+
DELETE FROM jetbase_migrations
|
|
207
|
+
WHERE version = :version
|
|
208
|
+
AND migration_type = '{MigrationType.VERSIONED.value}'
|
|
209
|
+
""")
|
|
210
|
+
|
|
211
|
+
DELETE_MISSING_REPEATABLE_STMT: TextClause = text(f"""
|
|
212
|
+
DELETE FROM jetbase_migrations
|
|
213
|
+
WHERE filename = :filename
|
|
214
|
+
AND migration_type in ('{MigrationType.RUNS_ALWAYS.value}', '{MigrationType.RUNS_ON_CHANGE.value}')
|
|
215
|
+
""")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from jetbase.database.queries.base import BaseQueries
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PostgresQueries(BaseQueries):
|
|
5
|
+
"""
|
|
6
|
+
PostgreSQL-specific SQL queries.
|
|
7
|
+
|
|
8
|
+
This class inherits all methods from BaseQueries without modification
|
|
9
|
+
because the default queries in BaseQueries are already PostgreSQL-compatible.
|
|
10
|
+
It exists to maintain consistency in the query loading pattern and to allow
|
|
11
|
+
future PostgreSQL-specific optimizations.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
pass
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import Engine, TextClause, create_engine
|
|
4
|
+
|
|
5
|
+
from jetbase.config import get_config
|
|
6
|
+
from jetbase.database.queries.base import BaseQueries, QueryMethod
|
|
7
|
+
from jetbase.database.queries.postgres import PostgresQueries
|
|
8
|
+
from jetbase.database.queries.sqlite import SQLiteQueries
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DatabaseType(Enum):
|
|
12
|
+
POSTGRESQL = "postgresql"
|
|
13
|
+
SQLITE = "sqlite"
|
|
14
|
+
MYSQL = "mysql"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_database_type() -> DatabaseType:
|
|
18
|
+
"""
|
|
19
|
+
Detect the database type from the configured SQLAlchemy URL.
|
|
20
|
+
|
|
21
|
+
Reads the sqlalchemy_url from configuration and determines the
|
|
22
|
+
database backend type.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
DatabaseType: The detected database type (postgresql or sqlite).
|
|
26
|
+
|
|
27
|
+
Raises:
|
|
28
|
+
ValueError: If the database type is not supported.
|
|
29
|
+
"""
|
|
30
|
+
sqlalchemy_url: str = get_config(required={"sqlalchemy_url"}).sqlalchemy_url
|
|
31
|
+
engine: Engine = create_engine(url=sqlalchemy_url)
|
|
32
|
+
dialect_name: str = engine.dialect.name.lower()
|
|
33
|
+
|
|
34
|
+
if dialect_name.startswith("postgres"):
|
|
35
|
+
return DatabaseType.POSTGRESQL
|
|
36
|
+
elif dialect_name == "sqlite":
|
|
37
|
+
return DatabaseType.SQLITE
|
|
38
|
+
else:
|
|
39
|
+
raise ValueError(f"Unsupported database type: {dialect_name}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_queries() -> type[BaseQueries]:
|
|
43
|
+
"""
|
|
44
|
+
Get the appropriate query class for the current database type.
|
|
45
|
+
|
|
46
|
+
Returns the PostgresQueries or SQLiteQueries class based on the
|
|
47
|
+
detected database type.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
type[BaseQueries]: The database-specific query class.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
ValueError: If the database type is not supported.
|
|
54
|
+
"""
|
|
55
|
+
db_type = get_database_type()
|
|
56
|
+
|
|
57
|
+
if db_type == DatabaseType.POSTGRESQL:
|
|
58
|
+
return PostgresQueries
|
|
59
|
+
elif db_type == DatabaseType.SQLITE:
|
|
60
|
+
return SQLiteQueries
|
|
61
|
+
else:
|
|
62
|
+
raise ValueError(f"Unsupported database type: {db_type}")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# Convenience function to get specific queries
|
|
66
|
+
def get_query(query_name: QueryMethod, **kwargs) -> TextClause:
|
|
67
|
+
"""
|
|
68
|
+
Get a specific query for the current database type.
|
|
69
|
+
|
|
70
|
+
Retrieves the appropriate database-specific query by looking up the
|
|
71
|
+
query method on the correct query class for the current database.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
query_name (QueryMethod): The enum value identifying which query
|
|
75
|
+
to retrieve.
|
|
76
|
+
**kwargs: Additional arguments to pass to the query method.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
TextClause: The database-specific SQLAlchemy TextClause query.
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
>>> get_query(QueryMethod.LATEST_VERSION_QUERY)
|
|
83
|
+
<sqlalchemy.sql.elements.TextClause>
|
|
84
|
+
"""
|
|
85
|
+
queries = get_queries()
|
|
86
|
+
method = getattr(queries, query_name.value)
|
|
87
|
+
return method(**kwargs)
|