Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions src/db/migrate.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import os
import re

import bcrypt
import importlib

from pyinfraboxutils import get_logger, get_env
from pyinfraboxutils.db import connect_db


# Matches C-style block comments (/* ... */), across multiple lines.
# Non-greedy so consecutive `/* a */ x /* b */` blocks are matched independently.
_BLOCK_COMMENT_RE = re.compile(r'/\*.*?\*/', re.DOTALL)

logger = get_logger("migrate")

def get_files(current_schema_version):
Expand All @@ -14,10 +21,40 @@ def get_files(current_schema_version):
files = [f for f in os.listdir(migration_path) if os.path.isfile(os.path.join(migration_path, f)) and f.startswith('0')]

files.sort(key=lambda f: int(f[:5]))
files = files[current_schema_version:]
files = [f for f in files if int(f[:5]) > current_schema_version]

return [(os.path.join(migration_path, f), int(f[:5])) for f in files]

def _has_executable_sql(sql):
"""Return True iff `sql` contains at least one non-comment, non-blank line.

`str.strip()` only removes leading/trailing whitespace; SQL comments are
kept. Passing a comment-only string to `cur.execute()` raises
`psycopg2.ProgrammingError: can't execute an empty query`, which breaks
migration runs whenever a placeholder file (e.g. an intentionally empty
migration to close a numbering gap) is applied. This helper detects such
files so the caller can skip the execute call.

Recognised as "no SQL":
- completely empty / whitespace-only files
- only `-- ...` line comments
- only `/* ... */` block comments (even across lines)
- any combination of the above

Lightweight heuristic — does not tokenise SQL, so quoted strings that
happen to contain `--` or `/* */` are still treated as executable
(which is fine, they contain real statements).
"""
# Strip C-style block comments first so any `/* ... */`-only content is
# collapsed away before the per-line `--` check.
sql_no_block = _BLOCK_COMMENT_RE.sub('', sql)
for line in sql_no_block.splitlines():
stripped = line.strip()
if stripped and not stripped.startswith('--'):
return True
return False


def apply_migration(conn, migration):
filename = migration[0]
logger.info("Starting to apply migration %s", filename)
Expand All @@ -27,8 +64,13 @@ def apply_migration(conn, migration):
sql = sql_file.read().strip()

cur = conn.cursor()
if sql:
if _has_executable_sql(sql):
cur.execute(sql)
else:
logger.info(
"Skipping execute for %s (no non-comment SQL statements)",
filename,
)

cur.close()
elif filename.endswith('.py'):
Expand Down
Loading