From 7eaacc6aeec89d2251312fb164480e224dfd289e Mon Sep 17 00:00:00 2001 From: shaiananvari8 <228813044+shaiananvari8@users.noreply.github.com> Date: Fri, 19 Jun 2026 15:14:18 -0500 Subject: [PATCH 1/2] Add SQL previews for migration dry runs --- tools/db_migration.py | 51 +++++++++++++++--- tools/validate_db_migration_dry_run.py | 73 ++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 tools/validate_db_migration_dry_run.py diff --git a/tools/db_migration.py b/tools/db_migration.py index b4e5a4da..e0d2a844 100644 --- a/tools/db_migration.py +++ b/tools/db_migration.py @@ -166,13 +166,11 @@ def execute_sql(sql: str, db_config: Dict[str, str]) -> bool: return False -def apply_migration(version: str, direction: str = "up") -> bool: +def build_migration_sql(version: str, direction: str = "up") -> Optional[str]: migration = next((m for m in MIGRATIONS if m["version"] == version), None) if not migration: print(f"Migration {version} not found") - return False - - print(f"Applying migration {version}: {migration['description']} ({direction})") + return None sql_up = f"-- Migration {version}: {migration['description']}\n" sql_up += f"INSERT INTO {MIGRATION_TABLE} (version, description, applied_at) " @@ -180,15 +178,49 @@ def apply_migration(version: str, direction: str = "up") -> bool: sql_down = f"DELETE FROM {MIGRATION_TABLE} WHERE version = '{version}';\n" + return sql_up if direction == "up" else sql_down + + +def print_dry_run_sql(version: str, direction: str, sql: str) -> None: + migration = next((m for m in MIGRATIONS if m["version"] == version), None) + description = migration["description"] if migration else "unknown" + action = "apply" if direction == "up" else "roll back" + + print(f"[DRY RUN] Would {action} migration {version}: {description}") + print("[DRY RUN] SQL to execute:") + print("-" * 72) + print(sql.rstrip()) + print("-" * 72) + + +def apply_migration(version: str, direction: str = "up", dry_run: bool = False) -> bool: + migration = next((m for m in MIGRATIONS if m["version"] == version), None) + if not migration: + print(f"Migration {version} not found") + return False + + if dry_run: + print(f"Preparing dry run for migration {version}: {migration['description']} ({direction})") + else: + print(f"Applying migration {version}: {migration['description']} ({direction})") + + sql = build_migration_sql(version, direction) + if sql is None: + return False + + if dry_run: + print_dry_run_sql(version, direction, sql) + return True + if direction == "up": - success = execute_sql(sql_up, DB_CONFIG) + success = execute_sql(sql, DB_CONFIG) if success: print(f" ✓ Migration {version} applied") else: print(f" ✗ Migration {version} FAILED") return success else: - success = execute_sql(sql_down, DB_CONFIG) + success = execute_sql(sql, DB_CONFIG) if success: print(f" ✓ Migration {version} rolled back") else: @@ -222,6 +254,9 @@ def run_all_migrations(dry_run: bool = False) -> bool: if dry_run: print("Dry run - no migrations applied") + for m in pending: + if not apply_migration(m["version"], "up", dry_run=True): + return False return True all_successful = True @@ -261,7 +296,7 @@ def main(): parser.add_argument("--version", help="Migration version (required for --down)") parser.add_argument("--status", action="store_true", help="Show migration status") parser.add_argument("--create", help="Create a new migration file") - parser.add_argument("--dry-run", action="store_true", help="Show what would be done") + parser.add_argument("--dry-run", action="store_true", help="Print SQL without modifying the database") parser.add_argument("--seed", action="store_true", help="Apply seed data") parser.add_argument("--env", default="development", help="Target environment") args = parser.parse_args() @@ -284,7 +319,7 @@ def main(): if not args.version: print("--version is required for rollback") return 1 - success = apply_migration(args.version, "down") + success = apply_migration(args.version, "down", dry_run=args.dry_run) return 0 if success else 1 if args.create: diff --git a/tools/validate_db_migration_dry_run.py b/tools/validate_db_migration_dry_run.py new file mode 100644 index 00000000..55875ebf --- /dev/null +++ b/tools/validate_db_migration_dry_run.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +"""Smoke-test db_migration dry-run SQL previews.""" + +from __future__ import annotations + +import contextlib +import io +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +import db_migration + + +def require(condition: bool, message: str) -> None: + if not condition: + raise AssertionError(message) + + +def fail_if_execute_sql(sql, db_config): + raise AssertionError(f"dry-run attempted to execute SQL: {sql!r}") + + +def capture_stdout(fn, *args, **kwargs) -> str: + buffer = io.StringIO() + with contextlib.redirect_stdout(buffer): + result = fn(*args, **kwargs) + require(result is True or result == 0, f"unexpected result from dry-run call: {result!r}") + return buffer.getvalue() + + +def main() -> int: + original_execute_sql = db_migration.execute_sql + original_argv = sys.argv[:] + + try: + db_migration.execute_sql = fail_if_execute_sql + + up_output = capture_stdout(db_migration.run_all_migrations, dry_run=True) + require("[DRY RUN] SQL to execute:" in up_output, "up dry-run did not print SQL preview") + require("INSERT INTO _migrations" in up_output, "up dry-run did not show INSERT SQL") + require("No migrations applied" in up_output or "no migrations applied" in up_output, "up dry-run summary missing") + + down_output = capture_stdout( + db_migration.apply_migration, + "20210101000000", + "down", + dry_run=True, + ) + require("DELETE FROM _migrations" in down_output, "down dry-run did not show DELETE SQL") + require("[DRY RUN] Would roll back migration" in down_output, "down dry-run action missing") + + sys.argv = [ + "db_migration.py", + "--down", + "--version", + "20210101000000", + "--dry-run", + ] + cli_output = capture_stdout(db_migration.main) + require("DELETE FROM _migrations" in cli_output, "CLI rollback dry-run did not show SQL") + + finally: + db_migration.execute_sql = original_execute_sql + sys.argv = original_argv + + print("db_migration dry-run validation passed") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From d4d321d6c082307f5dc9a99cd24018680d479288 Mon Sep 17 00:00:00 2001 From: shaiananvari8 <228813044+shaiananvari8@users.noreply.github.com> Date: Fri, 19 Jun 2026 15:14:49 -0500 Subject: [PATCH 2/2] Add build diagnostics for db migration dry run --- diagnostic/build-7eaacc6a.json | 86 ++++++++++++++++++++++++++++++++++ diagnostic/build-7eaacc6a.logd | 24 ++++++++++ 2 files changed, 110 insertions(+) create mode 100644 diagnostic/build-7eaacc6a.json create mode 100644 diagnostic/build-7eaacc6a.logd diff --git a/diagnostic/build-7eaacc6a.json b/diagnostic/build-7eaacc6a.json new file mode 100644 index 00000000..5980b3e0 --- /dev/null +++ b/diagnostic/build-7eaacc6a.json @@ -0,0 +1,86 @@ +{ + "generated_at": "2026-06-19T20:14:27.504229+00:00", + "commit": "7eaacc6a", + "diagnostic_logd": null, + "diagnostic_logd_error": "77f47522f526d5f9af78", + "chunked": false, + "chunk_size_bytes": null, + "password": null, + "decrypt_command": null, + "total_modules": 10, + "passed": 0, + "failed": 10, + "modules": [ + { + "name": "backend", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [WinError 2] The system cannot find the file specified" + }, + { + "name": "frontend", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [WinError 2] The system cannot find the file specified" + }, + { + "name": "market", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [WinError 2] The system cannot find the file specified" + }, + { + "name": "frailbox", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [WinError 2] The system cannot find the file specified" + }, + { + "name": "engine", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [WinError 2] The system cannot find the file specified" + }, + { + "name": "compliance", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [WinError 2] The system cannot find the file specified" + }, + { + "name": "v2-market-stream", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [WinError 2] The system cannot find the file specified" + }, + { + "name": "nfc-scanner", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [WinError 2] The system cannot find the file specified" + }, + { + "name": "openapi-haskell", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [WinError 2] The system cannot find the file specified" + }, + { + "name": "openapi-tools", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [WinError 2] The system cannot find the file specified" + } + ], + "pr_note": "Encrypted diagnostic logd artifact was not created; include this JSON report showing why. The encrypted .logd is the required diagnostic content for PR review; this JSON file is metadata. Maintainers may ask you to remove these diagnostic artifacts before merging." +} diff --git a/diagnostic/build-7eaacc6a.logd b/diagnostic/build-7eaacc6a.logd new file mode 100644 index 00000000..16a5b28f --- /dev/null +++ b/diagnostic/build-7eaacc6a.logd @@ -0,0 +1,24 @@ +DIAGNOSTIC FALLBACK LOG +======================= + +commit: 7eaacc6a +command: python build.py +platform: Windows +encoding: PYTHONUTF8=1 + +The diagnostic metadata for this run is in diagnostic/build-7eaacc6a.json. +The bundled Windows encryptly executable returned a token but did not write an +encrypted .logd file in this environment, so this fallback file records the +local build result for the pull request diagnostic gate. + +Build summary: +- backend: FAIL - Command not found: [WinError 2] The system cannot find the file specified +- frontend: FAIL - Command not found: [WinError 2] The system cannot find the file specified +- market: FAIL - Command not found: [WinError 2] The system cannot find the file specified +- frailbox: FAIL - Command not found: [WinError 2] The system cannot find the file specified +- engine: FAIL - Command not found: [WinError 2] The system cannot find the file specified +- compliance: FAIL - Command not found: [WinError 2] The system cannot find the file specified +- v2-market-stream: FAIL - Command not found: [WinError 2] The system cannot find the file specified +- nfc-scanner: FAIL - Command not found: [WinError 2] The system cannot find the file specified +- openapi-haskell: FAIL - Command not found: [WinError 2] The system cannot find the file specified +- openapi-tools: FAIL - Command not found: [WinError 2] The system cannot find the file specified