sqlspec 0.17.1__py3-none-any.whl → 0.18.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sqlspec might be problematic. Click here for more details.
- sqlspec/__init__.py +1 -1
- sqlspec/_sql.py +54 -159
- sqlspec/adapters/adbc/config.py +24 -30
- sqlspec/adapters/adbc/driver.py +42 -61
- sqlspec/adapters/aiosqlite/config.py +5 -10
- sqlspec/adapters/aiosqlite/driver.py +9 -25
- sqlspec/adapters/aiosqlite/pool.py +43 -35
- sqlspec/adapters/asyncmy/config.py +10 -7
- sqlspec/adapters/asyncmy/driver.py +18 -39
- sqlspec/adapters/asyncpg/config.py +4 -0
- sqlspec/adapters/asyncpg/driver.py +32 -79
- sqlspec/adapters/bigquery/config.py +12 -65
- sqlspec/adapters/bigquery/driver.py +39 -133
- sqlspec/adapters/duckdb/config.py +11 -15
- sqlspec/adapters/duckdb/driver.py +61 -85
- sqlspec/adapters/duckdb/pool.py +2 -5
- sqlspec/adapters/oracledb/_types.py +8 -1
- sqlspec/adapters/oracledb/config.py +55 -38
- sqlspec/adapters/oracledb/driver.py +35 -92
- sqlspec/adapters/oracledb/migrations.py +257 -0
- sqlspec/adapters/psqlpy/config.py +13 -9
- sqlspec/adapters/psqlpy/driver.py +28 -103
- sqlspec/adapters/psycopg/config.py +9 -5
- sqlspec/adapters/psycopg/driver.py +107 -175
- sqlspec/adapters/sqlite/config.py +7 -5
- sqlspec/adapters/sqlite/driver.py +37 -73
- sqlspec/adapters/sqlite/pool.py +3 -12
- sqlspec/base.py +1 -8
- sqlspec/builder/__init__.py +1 -1
- sqlspec/builder/_base.py +34 -20
- sqlspec/builder/_ddl.py +407 -183
- sqlspec/builder/_insert.py +1 -1
- sqlspec/builder/mixins/_insert_operations.py +26 -6
- sqlspec/builder/mixins/_merge_operations.py +1 -1
- sqlspec/builder/mixins/_select_operations.py +1 -5
- sqlspec/config.py +32 -13
- sqlspec/core/__init__.py +89 -14
- sqlspec/core/cache.py +57 -104
- sqlspec/core/compiler.py +57 -112
- sqlspec/core/filters.py +1 -21
- sqlspec/core/hashing.py +13 -47
- sqlspec/core/parameters.py +272 -261
- sqlspec/core/result.py +12 -27
- sqlspec/core/splitter.py +17 -21
- sqlspec/core/statement.py +150 -159
- sqlspec/driver/_async.py +2 -15
- sqlspec/driver/_common.py +16 -95
- sqlspec/driver/_sync.py +2 -15
- sqlspec/driver/mixins/_result_tools.py +8 -29
- sqlspec/driver/mixins/_sql_translator.py +6 -8
- sqlspec/exceptions.py +1 -2
- sqlspec/loader.py +43 -115
- sqlspec/migrations/__init__.py +1 -1
- sqlspec/migrations/base.py +34 -45
- sqlspec/migrations/commands.py +34 -15
- sqlspec/migrations/loaders.py +1 -1
- sqlspec/migrations/runner.py +104 -19
- sqlspec/migrations/tracker.py +49 -2
- sqlspec/protocols.py +3 -6
- sqlspec/storage/__init__.py +4 -4
- sqlspec/storage/backends/fsspec.py +5 -6
- sqlspec/storage/backends/obstore.py +7 -8
- sqlspec/storage/registry.py +3 -3
- sqlspec/utils/__init__.py +2 -2
- sqlspec/utils/logging.py +6 -10
- sqlspec/utils/sync_tools.py +27 -4
- sqlspec/utils/text.py +6 -1
- {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/METADATA +1 -1
- sqlspec-0.18.0.dist-info/RECORD +138 -0
- sqlspec/builder/_ddl_utils.py +0 -103
- sqlspec-0.17.1.dist-info/RECORD +0 -138
- {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -121,6 +121,7 @@ class OracleSyncExceptionHandler:
|
|
|
121
121
|
return None
|
|
122
122
|
|
|
123
123
|
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
124
|
+
_ = exc_tb # Mark as intentionally unused
|
|
124
125
|
if exc_type is None:
|
|
125
126
|
return
|
|
126
127
|
|
|
@@ -167,6 +168,7 @@ class OracleAsyncExceptionHandler:
|
|
|
167
168
|
return None
|
|
168
169
|
|
|
169
170
|
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
171
|
+
_ = exc_tb # Mark as intentionally unused
|
|
170
172
|
if exc_type is None:
|
|
171
173
|
return
|
|
172
174
|
|
|
@@ -205,36 +207,10 @@ class OracleAsyncExceptionHandler:
|
|
|
205
207
|
|
|
206
208
|
|
|
207
209
|
class OracleSyncDriver(SyncDriverAdapterBase):
|
|
208
|
-
"""
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
Performance Improvements:
|
|
213
|
-
- 5-10x faster SQL compilation through single-pass processing
|
|
214
|
-
- 40-60% memory reduction through __slots__ optimization
|
|
215
|
-
- Enhanced caching for repeated statement execution
|
|
216
|
-
- Zero-copy parameter processing where possible
|
|
217
|
-
- Sync-optimized resource management
|
|
218
|
-
- Optimized Oracle parameter style conversion (QMARK -> NAMED_COLON)
|
|
219
|
-
|
|
220
|
-
Oracle Features:
|
|
221
|
-
- Parameter style conversion (QMARK to NAMED_COLON/POSITIONAL_COLON)
|
|
222
|
-
- Oracle-specific type coercion and data handling
|
|
223
|
-
- Enhanced error categorization for Oracle database errors
|
|
224
|
-
- Transaction management with automatic commit/rollback
|
|
225
|
-
- Oracle-specific data handling and optimization
|
|
226
|
-
|
|
227
|
-
Core Integration Features:
|
|
228
|
-
- sqlspec.core.statement for enhanced SQL processing
|
|
229
|
-
- sqlspec.core.parameters for optimized parameter handling
|
|
230
|
-
- sqlspec.core.cache for unified statement caching
|
|
231
|
-
- sqlspec.core.config for centralized configuration management
|
|
232
|
-
|
|
233
|
-
Compatibility:
|
|
234
|
-
- 100% backward compatibility with existing Oracle driver interface
|
|
235
|
-
- All existing sync Oracle tests pass without modification
|
|
236
|
-
- Complete StatementConfig API compatibility
|
|
237
|
-
- Preserved cursor management and exception handling patterns
|
|
210
|
+
"""Synchronous Oracle Database driver.
|
|
211
|
+
|
|
212
|
+
Provides Oracle Database connectivity with parameter style conversion,
|
|
213
|
+
error handling, and transaction management.
|
|
238
214
|
"""
|
|
239
215
|
|
|
240
216
|
__slots__ = ()
|
|
@@ -246,14 +222,14 @@ class OracleSyncDriver(SyncDriverAdapterBase):
|
|
|
246
222
|
statement_config: "Optional[StatementConfig]" = None,
|
|
247
223
|
driver_features: "Optional[dict[str, Any]]" = None,
|
|
248
224
|
) -> None:
|
|
249
|
-
#
|
|
225
|
+
# Configure with default settings
|
|
250
226
|
if statement_config is None:
|
|
251
227
|
cache_config = get_cache_config()
|
|
252
228
|
enhanced_config = oracledb_statement_config.replace(
|
|
253
229
|
enable_caching=cache_config.compiled_cache_enabled,
|
|
254
|
-
enable_parsing=True,
|
|
255
|
-
enable_validation=True,
|
|
256
|
-
dialect="oracle",
|
|
230
|
+
enable_parsing=True,
|
|
231
|
+
enable_validation=True,
|
|
232
|
+
dialect="oracle",
|
|
257
233
|
)
|
|
258
234
|
statement_config = enhanced_config
|
|
259
235
|
|
|
@@ -310,14 +286,14 @@ class OracleSyncDriver(SyncDriverAdapterBase):
|
|
|
310
286
|
"""
|
|
311
287
|
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
312
288
|
|
|
313
|
-
#
|
|
289
|
+
# Parameter validation for executemany
|
|
314
290
|
if not prepared_parameters:
|
|
315
291
|
msg = "execute_many requires parameters"
|
|
316
292
|
raise ValueError(msg)
|
|
317
293
|
|
|
318
294
|
cursor.executemany(sql, prepared_parameters)
|
|
319
295
|
|
|
320
|
-
# Calculate affected rows based on parameter count
|
|
296
|
+
# Calculate affected rows based on parameter count
|
|
321
297
|
affected_rows = len(prepared_parameters) if prepared_parameters else 0
|
|
322
298
|
|
|
323
299
|
return self.create_execution_result(cursor, rowcount_override=affected_rows, is_many_result=True)
|
|
@@ -330,7 +306,7 @@ class OracleSyncDriver(SyncDriverAdapterBase):
|
|
|
330
306
|
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
331
307
|
cursor.execute(sql, prepared_parameters or {})
|
|
332
308
|
|
|
333
|
-
#
|
|
309
|
+
# SELECT result processing for Oracle
|
|
334
310
|
if statement.returns_rows():
|
|
335
311
|
fetched_data = cursor.fetchall()
|
|
336
312
|
column_names = [col[0] for col in cursor.description or []]
|
|
@@ -342,11 +318,11 @@ class OracleSyncDriver(SyncDriverAdapterBase):
|
|
|
342
318
|
cursor, selected_data=data, column_names=column_names, data_row_count=len(data), is_select_result=True
|
|
343
319
|
)
|
|
344
320
|
|
|
345
|
-
#
|
|
321
|
+
# Non-SELECT result processing
|
|
346
322
|
affected_rows = cursor.rowcount if cursor.rowcount is not None else 0
|
|
347
323
|
return self.create_execution_result(cursor, rowcount_override=affected_rows)
|
|
348
324
|
|
|
349
|
-
# Oracle transaction management
|
|
325
|
+
# Oracle transaction management
|
|
350
326
|
def begin(self) -> None:
|
|
351
327
|
"""Begin a database transaction with enhanced error handling.
|
|
352
328
|
|
|
@@ -372,36 +348,10 @@ class OracleSyncDriver(SyncDriverAdapterBase):
|
|
|
372
348
|
|
|
373
349
|
|
|
374
350
|
class OracleAsyncDriver(AsyncDriverAdapterBase):
|
|
375
|
-
"""
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
Performance Improvements:
|
|
380
|
-
- 5-10x faster SQL compilation through single-pass processing
|
|
381
|
-
- 40-60% memory reduction through __slots__ optimization
|
|
382
|
-
- Enhanced caching for repeated statement execution
|
|
383
|
-
- Zero-copy parameter processing where possible
|
|
384
|
-
- Async-optimized resource management
|
|
385
|
-
- Optimized Oracle parameter style conversion (QMARK -> NAMED_COLON)
|
|
386
|
-
|
|
387
|
-
Oracle Features:
|
|
388
|
-
- Parameter style conversion (QMARK to NAMED_COLON/POSITIONAL_COLON)
|
|
389
|
-
- Oracle-specific type coercion and data handling
|
|
390
|
-
- Enhanced error categorization for Oracle database errors
|
|
391
|
-
- Transaction management with automatic commit/rollback
|
|
392
|
-
- Oracle-specific data handling and optimization
|
|
393
|
-
|
|
394
|
-
Core Integration Features:
|
|
395
|
-
- sqlspec.core.statement for enhanced SQL processing
|
|
396
|
-
- sqlspec.core.parameters for optimized parameter handling
|
|
397
|
-
- sqlspec.core.cache for unified statement caching
|
|
398
|
-
- sqlspec.core.config for centralized configuration management
|
|
399
|
-
|
|
400
|
-
Compatibility:
|
|
401
|
-
- 100% backward compatibility with existing Oracle driver interface
|
|
402
|
-
- All existing async Oracle tests pass without modification
|
|
403
|
-
- Complete StatementConfig API compatibility
|
|
404
|
-
- Preserved async cursor management and exception handling patterns
|
|
351
|
+
"""Asynchronous Oracle Database driver.
|
|
352
|
+
|
|
353
|
+
Provides Oracle Database connectivity with parameter style conversion,
|
|
354
|
+
error handling, and transaction management for async operations.
|
|
405
355
|
"""
|
|
406
356
|
|
|
407
357
|
__slots__ = ()
|
|
@@ -413,21 +363,21 @@ class OracleAsyncDriver(AsyncDriverAdapterBase):
|
|
|
413
363
|
statement_config: "Optional[StatementConfig]" = None,
|
|
414
364
|
driver_features: "Optional[dict[str, Any]]" = None,
|
|
415
365
|
) -> None:
|
|
416
|
-
#
|
|
366
|
+
# Configure with default settings
|
|
417
367
|
if statement_config is None:
|
|
418
368
|
cache_config = get_cache_config()
|
|
419
369
|
enhanced_config = oracledb_statement_config.replace(
|
|
420
370
|
enable_caching=cache_config.compiled_cache_enabled,
|
|
421
|
-
enable_parsing=True,
|
|
422
|
-
enable_validation=True,
|
|
423
|
-
dialect="oracle",
|
|
371
|
+
enable_parsing=True,
|
|
372
|
+
enable_validation=True,
|
|
373
|
+
dialect="oracle",
|
|
424
374
|
)
|
|
425
375
|
statement_config = enhanced_config
|
|
426
376
|
|
|
427
377
|
super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
|
|
428
378
|
|
|
429
379
|
def with_cursor(self, connection: OracleAsyncConnection) -> OracleAsyncCursor:
|
|
430
|
-
"""Create async context manager for Oracle cursor
|
|
380
|
+
"""Create async context manager for Oracle cursor."""
|
|
431
381
|
return OracleAsyncCursor(connection)
|
|
432
382
|
|
|
433
383
|
def handle_database_exceptions(self) -> "AbstractAsyncContextManager[None]":
|
|
@@ -451,9 +401,8 @@ class OracleAsyncDriver(AsyncDriverAdapterBase):
|
|
|
451
401
|
return None
|
|
452
402
|
|
|
453
403
|
async def _execute_script(self, cursor: Any, statement: "SQL") -> "ExecutionResult":
|
|
454
|
-
"""Execute SQL script
|
|
404
|
+
"""Execute SQL script with statement splitting and parameter handling.
|
|
455
405
|
|
|
456
|
-
Uses core module optimization for statement parsing and parameter processing.
|
|
457
406
|
Parameters are embedded as static values for script execution compatibility.
|
|
458
407
|
"""
|
|
459
408
|
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
@@ -471,33 +420,27 @@ class OracleAsyncDriver(AsyncDriverAdapterBase):
|
|
|
471
420
|
)
|
|
472
421
|
|
|
473
422
|
async def _execute_many(self, cursor: Any, statement: "SQL") -> "ExecutionResult":
|
|
474
|
-
"""Execute SQL with multiple parameter sets using
|
|
475
|
-
|
|
476
|
-
Leverages core parameter processing for enhanced Oracle type handling and parameter conversion.
|
|
477
|
-
"""
|
|
423
|
+
"""Execute SQL with multiple parameter sets using Oracle batch processing."""
|
|
478
424
|
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
479
425
|
|
|
480
|
-
#
|
|
426
|
+
# Parameter validation for executemany
|
|
481
427
|
if not prepared_parameters:
|
|
482
428
|
msg = "execute_many requires parameters"
|
|
483
429
|
raise ValueError(msg)
|
|
484
430
|
|
|
485
431
|
await cursor.executemany(sql, prepared_parameters)
|
|
486
432
|
|
|
487
|
-
# Calculate affected rows based on parameter count
|
|
433
|
+
# Calculate affected rows based on parameter count
|
|
488
434
|
affected_rows = len(prepared_parameters) if prepared_parameters else 0
|
|
489
435
|
|
|
490
436
|
return self.create_execution_result(cursor, rowcount_override=affected_rows, is_many_result=True)
|
|
491
437
|
|
|
492
438
|
async def _execute_statement(self, cursor: Any, statement: "SQL") -> "ExecutionResult":
|
|
493
|
-
"""Execute single SQL statement with
|
|
494
|
-
|
|
495
|
-
Uses core processing for optimal parameter handling and Oracle result processing.
|
|
496
|
-
"""
|
|
439
|
+
"""Execute single SQL statement with Oracle data handling."""
|
|
497
440
|
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
498
441
|
await cursor.execute(sql, prepared_parameters or {})
|
|
499
442
|
|
|
500
|
-
#
|
|
443
|
+
# SELECT result processing for Oracle
|
|
501
444
|
if statement.returns_rows():
|
|
502
445
|
fetched_data = await cursor.fetchall()
|
|
503
446
|
column_names = [col[0] for col in cursor.description or []]
|
|
@@ -509,20 +452,20 @@ class OracleAsyncDriver(AsyncDriverAdapterBase):
|
|
|
509
452
|
cursor, selected_data=data, column_names=column_names, data_row_count=len(data), is_select_result=True
|
|
510
453
|
)
|
|
511
454
|
|
|
512
|
-
#
|
|
455
|
+
# Non-SELECT result processing
|
|
513
456
|
affected_rows = cursor.rowcount if cursor.rowcount is not None else 0
|
|
514
457
|
return self.create_execution_result(cursor, rowcount_override=affected_rows)
|
|
515
458
|
|
|
516
|
-
# Oracle transaction management
|
|
459
|
+
# Oracle transaction management
|
|
517
460
|
async def begin(self) -> None:
|
|
518
|
-
"""Begin a database transaction
|
|
461
|
+
"""Begin a database transaction.
|
|
519
462
|
|
|
520
463
|
Oracle handles transactions automatically, so this is a no-op.
|
|
521
464
|
"""
|
|
522
465
|
# Oracle handles transactions implicitly
|
|
523
466
|
|
|
524
467
|
async def rollback(self) -> None:
|
|
525
|
-
"""Rollback the current transaction
|
|
468
|
+
"""Rollback the current transaction."""
|
|
526
469
|
try:
|
|
527
470
|
await self.connection.rollback()
|
|
528
471
|
except oracledb.Error as e:
|
|
@@ -530,7 +473,7 @@ class OracleAsyncDriver(AsyncDriverAdapterBase):
|
|
|
530
473
|
raise SQLSpecError(msg) from e
|
|
531
474
|
|
|
532
475
|
async def commit(self) -> None:
|
|
533
|
-
"""Commit the current transaction
|
|
476
|
+
"""Commit the current transaction."""
|
|
534
477
|
try:
|
|
535
478
|
await self.connection.commit()
|
|
536
479
|
except oracledb.Error as e:
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""Oracle-specific migration implementations.
|
|
2
|
+
|
|
3
|
+
This module provides Oracle Database-specific overrides for migration functionality
|
|
4
|
+
to handle Oracle's unique SQL syntax requirements.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import getpass
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Optional, cast
|
|
9
|
+
|
|
10
|
+
from sqlspec._sql import sql
|
|
11
|
+
from sqlspec.builder._ddl import CreateTable
|
|
12
|
+
from sqlspec.migrations.base import BaseMigrationTracker
|
|
13
|
+
from sqlspec.utils.logging import get_logger
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
|
|
17
|
+
|
|
18
|
+
__all__ = ("OracleAsyncMigrationTracker", "OracleSyncMigrationTracker")
|
|
19
|
+
|
|
20
|
+
logger = get_logger("migrations.oracle")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class OracleMigrationTrackerMixin:
|
|
24
|
+
"""Mixin providing Oracle-specific migration table creation."""
|
|
25
|
+
|
|
26
|
+
__slots__ = ()
|
|
27
|
+
|
|
28
|
+
version_table: str
|
|
29
|
+
|
|
30
|
+
def _get_create_table_sql(self) -> CreateTable:
|
|
31
|
+
"""Get Oracle-specific SQL builder for creating the tracking table.
|
|
32
|
+
|
|
33
|
+
Oracle doesn't support:
|
|
34
|
+
- CREATE TABLE IF NOT EXISTS (need try/catch logic)
|
|
35
|
+
- TEXT type (use VARCHAR2)
|
|
36
|
+
- DEFAULT before NOT NULL is required
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
SQL builder object for Oracle table creation.
|
|
40
|
+
"""
|
|
41
|
+
return (
|
|
42
|
+
sql.create_table(self.version_table)
|
|
43
|
+
.column("version_num", "VARCHAR2(32)", primary_key=True)
|
|
44
|
+
.column("description", "VARCHAR2(2000)")
|
|
45
|
+
.column("applied_at", "TIMESTAMP", default="CURRENT_TIMESTAMP")
|
|
46
|
+
.column("execution_time_ms", "INTEGER")
|
|
47
|
+
.column("checksum", "VARCHAR2(64)")
|
|
48
|
+
.column("applied_by", "VARCHAR2(255)")
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class OracleSyncMigrationTracker(OracleMigrationTrackerMixin, BaseMigrationTracker["SyncDriverAdapterBase"]):
|
|
53
|
+
"""Oracle-specific sync migration tracker."""
|
|
54
|
+
|
|
55
|
+
__slots__ = ()
|
|
56
|
+
|
|
57
|
+
def ensure_tracking_table(self, driver: "SyncDriverAdapterBase") -> None:
|
|
58
|
+
"""Create the migration tracking table if it doesn't exist.
|
|
59
|
+
|
|
60
|
+
Oracle doesn't support IF NOT EXISTS, so we check for table existence first.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
driver: The database driver to use.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
check_sql = (
|
|
67
|
+
sql.select(sql.count().as_("table_count"))
|
|
68
|
+
.from_("user_tables")
|
|
69
|
+
.where(sql.column("table_name") == self.version_table.upper())
|
|
70
|
+
)
|
|
71
|
+
result = driver.execute(check_sql)
|
|
72
|
+
|
|
73
|
+
if result.data[0]["TABLE_COUNT"] == 0:
|
|
74
|
+
driver.execute(self._get_create_table_sql())
|
|
75
|
+
self._safe_commit(driver)
|
|
76
|
+
|
|
77
|
+
def get_current_version(self, driver: "SyncDriverAdapterBase") -> "Optional[str]":
|
|
78
|
+
"""Get the latest applied migration version.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
driver: The database driver to use.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
The current migration version or None if no migrations applied.
|
|
85
|
+
"""
|
|
86
|
+
result = driver.execute(self._get_current_version_sql())
|
|
87
|
+
return result.data[0]["VERSION_NUM"] if result.data else None
|
|
88
|
+
|
|
89
|
+
def get_applied_migrations(self, driver: "SyncDriverAdapterBase") -> "list[dict[str, Any]]":
|
|
90
|
+
"""Get all applied migrations in order.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
driver: The database driver to use.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
List of migration records as dictionaries.
|
|
97
|
+
"""
|
|
98
|
+
result = driver.execute(self._get_applied_migrations_sql())
|
|
99
|
+
if not result.data:
|
|
100
|
+
return []
|
|
101
|
+
|
|
102
|
+
normalized_data = [{key.lower(): value for key, value in row.items()} for row in result.data]
|
|
103
|
+
|
|
104
|
+
return cast("list[dict[str, Any]]", normalized_data)
|
|
105
|
+
|
|
106
|
+
def record_migration(
|
|
107
|
+
self, driver: "SyncDriverAdapterBase", version: str, description: str, execution_time_ms: int, checksum: str
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Record a successfully applied migration.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
driver: The database driver to use.
|
|
113
|
+
version: Version number of the migration.
|
|
114
|
+
description: Description of the migration.
|
|
115
|
+
execution_time_ms: Execution time in milliseconds.
|
|
116
|
+
checksum: MD5 checksum of the migration content.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
applied_by = getpass.getuser()
|
|
120
|
+
|
|
121
|
+
record_sql = self._get_record_migration_sql(version, description, execution_time_ms, checksum, applied_by)
|
|
122
|
+
driver.execute(record_sql)
|
|
123
|
+
self._safe_commit(driver)
|
|
124
|
+
|
|
125
|
+
def remove_migration(self, driver: "SyncDriverAdapterBase", version: str) -> None:
|
|
126
|
+
"""Remove a migration record.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
driver: The database driver to use.
|
|
130
|
+
version: Version number to remove.
|
|
131
|
+
"""
|
|
132
|
+
remove_sql = self._get_remove_migration_sql(version)
|
|
133
|
+
driver.execute(remove_sql)
|
|
134
|
+
self._safe_commit(driver)
|
|
135
|
+
|
|
136
|
+
def _safe_commit(self, driver: "SyncDriverAdapterBase") -> None:
|
|
137
|
+
"""Safely commit a transaction only if autocommit is disabled.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
driver: The database driver to use.
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
# Check driver features first (preferred approach)
|
|
144
|
+
if driver.driver_features.get("autocommit", False):
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
# Fallback to connection-level autocommit check
|
|
148
|
+
if driver.connection and driver.connection.autocommit:
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
driver.commit()
|
|
152
|
+
except Exception:
|
|
153
|
+
logger.debug("Failed to commit transaction, likely due to autocommit being enabled")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class OracleAsyncMigrationTracker(OracleMigrationTrackerMixin, BaseMigrationTracker["AsyncDriverAdapterBase"]):
|
|
157
|
+
"""Oracle-specific async migration tracker."""
|
|
158
|
+
|
|
159
|
+
__slots__ = ()
|
|
160
|
+
|
|
161
|
+
async def ensure_tracking_table(self, driver: "AsyncDriverAdapterBase") -> None:
|
|
162
|
+
"""Create the migration tracking table if it doesn't exist.
|
|
163
|
+
|
|
164
|
+
Oracle doesn't support IF NOT EXISTS, so we check for table existence first.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
driver: The database driver to use.
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
check_sql = (
|
|
171
|
+
sql.select(sql.count().as_("table_count"))
|
|
172
|
+
.from_("user_tables")
|
|
173
|
+
.where(sql.column("table_name") == self.version_table.upper())
|
|
174
|
+
)
|
|
175
|
+
result = await driver.execute(check_sql)
|
|
176
|
+
|
|
177
|
+
if result.data[0]["TABLE_COUNT"] == 0:
|
|
178
|
+
await driver.execute(self._get_create_table_sql())
|
|
179
|
+
await self._safe_commit_async(driver)
|
|
180
|
+
|
|
181
|
+
async def get_current_version(self, driver: "AsyncDriverAdapterBase") -> "Optional[str]":
|
|
182
|
+
"""Get the latest applied migration version.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
driver: The database driver to use.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
The current migration version or None if no migrations applied.
|
|
189
|
+
"""
|
|
190
|
+
result = await driver.execute(self._get_current_version_sql())
|
|
191
|
+
return result.data[0]["VERSION_NUM"] if result.data else None
|
|
192
|
+
|
|
193
|
+
async def get_applied_migrations(self, driver: "AsyncDriverAdapterBase") -> "list[dict[str, Any]]":
|
|
194
|
+
"""Get all applied migrations in order.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
driver: The database driver to use.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
List of migration records as dictionaries.
|
|
201
|
+
"""
|
|
202
|
+
result = await driver.execute(self._get_applied_migrations_sql())
|
|
203
|
+
if not result.data:
|
|
204
|
+
return []
|
|
205
|
+
|
|
206
|
+
normalized_data = [{key.lower(): value for key, value in row.items()} for row in result.data]
|
|
207
|
+
|
|
208
|
+
return cast("list[dict[str, Any]]", normalized_data)
|
|
209
|
+
|
|
210
|
+
async def record_migration(
|
|
211
|
+
self, driver: "AsyncDriverAdapterBase", version: str, description: str, execution_time_ms: int, checksum: str
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Record a successfully applied migration.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
driver: The database driver to use.
|
|
217
|
+
version: Version number of the migration.
|
|
218
|
+
description: Description of the migration.
|
|
219
|
+
execution_time_ms: Execution time in milliseconds.
|
|
220
|
+
checksum: MD5 checksum of the migration content.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
applied_by = getpass.getuser()
|
|
224
|
+
|
|
225
|
+
record_sql = self._get_record_migration_sql(version, description, execution_time_ms, checksum, applied_by)
|
|
226
|
+
await driver.execute(record_sql)
|
|
227
|
+
await self._safe_commit_async(driver)
|
|
228
|
+
|
|
229
|
+
async def remove_migration(self, driver: "AsyncDriverAdapterBase", version: str) -> None:
|
|
230
|
+
"""Remove a migration record.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
driver: The database driver to use.
|
|
234
|
+
version: Version number to remove.
|
|
235
|
+
"""
|
|
236
|
+
remove_sql = self._get_remove_migration_sql(version)
|
|
237
|
+
await driver.execute(remove_sql)
|
|
238
|
+
await self._safe_commit_async(driver)
|
|
239
|
+
|
|
240
|
+
async def _safe_commit_async(self, driver: "AsyncDriverAdapterBase") -> None:
|
|
241
|
+
"""Safely commit a transaction only if autocommit is disabled.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
driver: The database driver to use.
|
|
245
|
+
"""
|
|
246
|
+
try:
|
|
247
|
+
# Check driver features first (preferred approach)
|
|
248
|
+
if driver.driver_features.get("autocommit", False):
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
# Fallback to connection-level autocommit check
|
|
252
|
+
if driver.connection and driver.connection.autocommit:
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
await driver.commit()
|
|
256
|
+
except Exception:
|
|
257
|
+
logger.debug("Failed to commit transaction, likely due to autocommit being enabled")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Psqlpy database configuration
|
|
1
|
+
"""Psqlpy database configuration."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from collections.abc import AsyncGenerator
|
|
@@ -77,7 +77,7 @@ __all__ = ("PsqlpyConfig", "PsqlpyConnectionParams", "PsqlpyCursor", "PsqlpyPool
|
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
class PsqlpyConfig(AsyncDatabaseConfig[PsqlpyConnection, ConnectionPool, PsqlpyDriver]):
|
|
80
|
-
"""Configuration for Psqlpy asynchronous database connections
|
|
80
|
+
"""Configuration for Psqlpy asynchronous database connections."""
|
|
81
81
|
|
|
82
82
|
driver_type: ClassVar[type[PsqlpyDriver]] = PsqlpyDriver
|
|
83
83
|
connection_type: "ClassVar[type[PsqlpyConnection]]" = PsqlpyConnection
|
|
@@ -86,17 +86,19 @@ class PsqlpyConfig(AsyncDatabaseConfig[PsqlpyConnection, ConnectionPool, PsqlpyD
|
|
|
86
86
|
self,
|
|
87
87
|
*,
|
|
88
88
|
pool_config: Optional[Union[PsqlpyPoolParams, dict[str, Any]]] = None,
|
|
89
|
-
statement_config: Optional[StatementConfig] = None,
|
|
90
89
|
pool_instance: Optional[ConnectionPool] = None,
|
|
91
90
|
migration_config: Optional[dict[str, Any]] = None,
|
|
91
|
+
statement_config: Optional[StatementConfig] = None,
|
|
92
|
+
driver_features: Optional[dict[str, Any]] = None,
|
|
92
93
|
) -> None:
|
|
93
|
-
"""Initialize Psqlpy
|
|
94
|
+
"""Initialize Psqlpy configuration.
|
|
94
95
|
|
|
95
96
|
Args:
|
|
96
|
-
pool_config: Pool configuration parameters
|
|
97
|
+
pool_config: Pool configuration parameters
|
|
97
98
|
pool_instance: Existing connection pool instance to use
|
|
98
|
-
statement_config: Default SQL statement configuration
|
|
99
99
|
migration_config: Migration configuration
|
|
100
|
+
statement_config: SQL statement configuration
|
|
101
|
+
driver_features: Driver feature configuration
|
|
100
102
|
"""
|
|
101
103
|
processed_pool_config: dict[str, Any] = dict(pool_config) if pool_config else {}
|
|
102
104
|
if "extra" in processed_pool_config:
|
|
@@ -107,6 +109,7 @@ class PsqlpyConfig(AsyncDatabaseConfig[PsqlpyConnection, ConnectionPool, PsqlpyD
|
|
|
107
109
|
pool_instance=pool_instance,
|
|
108
110
|
migration_config=migration_config,
|
|
109
111
|
statement_config=statement_config or psqlpy_statement_config,
|
|
112
|
+
driver_features=driver_features or {},
|
|
110
113
|
)
|
|
111
114
|
|
|
112
115
|
def _get_pool_config_dict(self) -> dict[str, Any]:
|
|
@@ -145,6 +148,10 @@ class PsqlpyConfig(AsyncDatabaseConfig[PsqlpyConnection, ConnectionPool, PsqlpyD
|
|
|
145
148
|
logger.exception("Failed to close psqlpy connection pool", extra={"adapter": "psqlpy", "error": str(e)})
|
|
146
149
|
raise
|
|
147
150
|
|
|
151
|
+
async def close_pool(self) -> None:
|
|
152
|
+
"""Close the connection pool."""
|
|
153
|
+
await self._close_pool()
|
|
154
|
+
|
|
148
155
|
async def create_connection(self) -> "PsqlpyConnection":
|
|
149
156
|
"""Create a single async connection (not from pool).
|
|
150
157
|
|
|
@@ -203,9 +210,6 @@ class PsqlpyConfig(AsyncDatabaseConfig[PsqlpyConnection, ConnectionPool, PsqlpyD
|
|
|
203
210
|
def get_signature_namespace(self) -> "dict[str, type[Any]]":
|
|
204
211
|
"""Get the signature namespace for Psqlpy types.
|
|
205
212
|
|
|
206
|
-
This provides all Psqlpy-specific types that Litestar needs to recognize
|
|
207
|
-
to avoid serialization attempts.
|
|
208
|
-
|
|
209
213
|
Returns:
|
|
210
214
|
Dictionary mapping type names to types.
|
|
211
215
|
"""
|