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.
Files changed (49) hide show
  1. jetbase/cli/main.py +146 -4
  2. jetbase/commands/current.py +20 -0
  3. jetbase/commands/fix_checksums.py +172 -0
  4. jetbase/commands/fix_files.py +133 -0
  5. jetbase/commands/history.py +53 -0
  6. jetbase/commands/init.py +29 -0
  7. jetbase/commands/lock_status.py +25 -0
  8. jetbase/commands/new.py +65 -0
  9. jetbase/commands/rollback.py +172 -0
  10. jetbase/commands/status.py +212 -0
  11. jetbase/commands/unlock.py +29 -0
  12. jetbase/commands/upgrade.py +248 -0
  13. jetbase/commands/validators.py +37 -0
  14. jetbase/config.py +304 -25
  15. jetbase/constants.py +10 -2
  16. jetbase/database/connection.py +40 -0
  17. jetbase/database/queries/base.py +353 -0
  18. jetbase/database/queries/default_queries.py +215 -0
  19. jetbase/database/queries/postgres.py +14 -0
  20. jetbase/database/queries/query_loader.py +87 -0
  21. jetbase/database/queries/sqlite.py +197 -0
  22. jetbase/engine/checksum.py +25 -0
  23. jetbase/engine/dry_run.py +105 -0
  24. jetbase/engine/file_parser.py +324 -0
  25. jetbase/engine/formatters.py +61 -0
  26. jetbase/engine/lock.py +65 -0
  27. jetbase/engine/repeatable.py +125 -0
  28. jetbase/engine/validation.py +238 -0
  29. jetbase/engine/version.py +144 -0
  30. jetbase/enums.py +37 -1
  31. jetbase/exceptions.py +87 -0
  32. jetbase/models.py +45 -0
  33. jetbase/repositories/lock_repo.py +129 -0
  34. jetbase/repositories/migrations_repo.py +451 -0
  35. jetbase-0.12.1.dist-info/METADATA +135 -0
  36. jetbase-0.12.1.dist-info/RECORD +39 -0
  37. {jetbase-0.7.0.dist-info → jetbase-0.12.1.dist-info}/WHEEL +1 -1
  38. jetbase/core/dry_run.py +0 -38
  39. jetbase/core/file_parser.py +0 -199
  40. jetbase/core/initialize.py +0 -33
  41. jetbase/core/repository.py +0 -169
  42. jetbase/core/rollback.py +0 -67
  43. jetbase/core/upgrade.py +0 -75
  44. jetbase/core/version.py +0 -163
  45. jetbase/queries.py +0 -72
  46. jetbase-0.7.0.dist-info/METADATA +0 -12
  47. jetbase-0.7.0.dist-info/RECORD +0 -17
  48. {jetbase-0.7.0.dist-info → jetbase-0.12.1.dist-info}/entry_points.txt +0 -0
  49. {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)