|
8 | 8 | from sqlmesh.core.console import Console |
9 | 9 | from sqlmesh.core.dialect import schema_ |
10 | 10 | from sqlmesh.core.environment import Environment |
11 | | -from sqlmesh.core.snapshot import SnapshotEvaluator |
| 11 | +from sqlmesh.core.snapshot import SnapshotEvaluator, SnapshotId |
12 | 12 | from sqlmesh.core.state_sync import StateSync |
13 | 13 | from sqlmesh.core.state_sync.common import ( |
14 | 14 | logger, |
@@ -193,3 +193,76 @@ def delete_expired_snapshots( |
193 | 193 | failures.append(message) |
194 | 194 | logger.info("Cleaned up %s expired snapshots", num_expired_snapshots) |
195 | 195 | return failures |
| 196 | + |
| 197 | + |
| 198 | +def delete_snapshots_for_environment( |
| 199 | + state_sync: StateSync, |
| 200 | + snapshot_evaluator: SnapshotEvaluator, |
| 201 | + target_snapshot_ids: t.Collection[SnapshotId], |
| 202 | + *, |
| 203 | + force_delete: bool = False, |
| 204 | + console: t.Optional[Console] = None, |
| 205 | +) -> t.List[str]: |
| 206 | + """Delete snapshots that are exclusively owned by a specific (now-deleted) environment. |
| 207 | +
|
| 208 | + This performs a scoped cleanup: only the provided snapshot IDs are considered for deletion, |
| 209 | + and only those that are not referenced by any remaining active environment will be removed. |
| 210 | +
|
| 211 | + Args: |
| 212 | + state_sync: StateSync instance to query and delete snapshot state from. |
| 213 | + snapshot_evaluator: SnapshotEvaluator instance to clean up physical tables. |
| 214 | + target_snapshot_ids: The snapshot IDs to consider for deletion (typically from the |
| 215 | + environment that was just invalidated/deleted). |
| 216 | + force_delete: If True, delete snapshot state records even when physical table cleanup fails. |
| 217 | + console: Optional console for reporting progress. |
| 218 | +
|
| 219 | + Returns: |
| 220 | + List of failure messages encountered during cleanup. |
| 221 | + """ |
| 222 | + if not target_snapshot_ids: |
| 223 | + return [] |
| 224 | + |
| 225 | + failures: t.List[str] = [] |
| 226 | + batch = state_sync.get_expired_snapshots( |
| 227 | + ignore_ttl=True, |
| 228 | + batch_range=ExpiredBatchRange.all_batch_range(), |
| 229 | + target_snapshot_ids=target_snapshot_ids, |
| 230 | + ) |
| 231 | + if batch is None: |
| 232 | + return failures |
| 233 | + |
| 234 | + logger.info( |
| 235 | + "Cleaning up %s snapshots exclusively owned by invalidated environment", |
| 236 | + len(batch.expired_snapshot_ids), |
| 237 | + ) |
| 238 | + |
| 239 | + cleanup_succeeded = True |
| 240 | + if batch.cleanup_tasks: |
| 241 | + try: |
| 242 | + snapshot_evaluator.cleanup( |
| 243 | + target_snapshots=batch.cleanup_tasks, |
| 244 | + on_complete=console.update_cleanup_progress if console else None, |
| 245 | + ) |
| 246 | + except Exception as failed_drops: |
| 247 | + message = f"Failed to clean up: {failed_drops}" |
| 248 | + logger.warning(message) |
| 249 | + failures.append(message) |
| 250 | + cleanup_succeeded = False |
| 251 | + |
| 252 | + if cleanup_succeeded or force_delete: |
| 253 | + try: |
| 254 | + state_sync.delete_expired_snapshots( |
| 255 | + batch_range=ExpiredBatchRange.all_batch_range(), |
| 256 | + ignore_ttl=True, |
| 257 | + target_snapshot_ids=target_snapshot_ids, |
| 258 | + ) |
| 259 | + logger.info( |
| 260 | + "Cleaned up %s snapshots from invalidated environment", |
| 261 | + len(batch.expired_snapshot_ids), |
| 262 | + ) |
| 263 | + except Exception as e: |
| 264 | + message = f"Failed to delete snapshot state records: {e}" |
| 265 | + logger.warning(message) |
| 266 | + failures.append(message) |
| 267 | + |
| 268 | + return failures |
0 commit comments