Spaces:
Running
Running
| """Patch setup_db.py to import SQL via Python instead of mariadb CLI. | |
| The `mariadb` CLI restore fails with some external MySQL hosts. This patch | |
| replaces the CLI-based restore with direct `frappe.db.sql_ddl()` calls, | |
| reading and executing the SQL file through the existing Python connection. | |
| """ | |
| import os, re | |
| SETUP_DB = "apps/frappe/frappe/database/mariadb/setup_db.py" | |
| if not os.path.exists(SETUP_DB): | |
| print(f"ERROR: {SETUP_DB} not found") | |
| exit(1) | |
| with open(SETUP_DB) as f: | |
| content = f.read() | |
| OLD_CALL = '''\tDbManager(frappe.local.db).restore_database( | |
| \t\tverbose, db_name, source_sql, db_name, frappe.conf.db_password | |
| \t)''' | |
| NEW_CALL = '''\t# PATCHED: Import SQL via Python connection (avoids mariadb CLI issues) | |
| \tprint(f" [patch] Reading SQL from {source_sql}") | |
| \twith open(source_sql, "r", encoding="utf-8") as _f: | |
| \t\t_raw = _f.read() | |
| \t# Split on ";\\n" to isolate individual SQL statements. | |
| \t# Each fragment may contain leading comment lines ("-- ...") — strip them | |
| \t# so that CREATE / DROP / INSERT statements are not silently skipped. | |
| \t_clean_statements = [] | |
| \tfor _frag in _raw.replace("\\r\\n", "\\n").split(";\\n"): | |
| \t\t_lines = [] | |
| \t\tfor _line in _frag.split("\\n"): | |
| \t\t\t_stripped = _line.strip() | |
| \t\t\tif _stripped.startswith("--") or _stripped == "": | |
| \t\t\t\tcontinue | |
| \t\t\t_lines.append(_line) | |
| \t\t_cleaned = "\\n".join(_lines).strip() | |
| \t\tif _cleaned and _cleaned != ";": | |
| \t\t\t_clean_statements.append(_cleaned) | |
| \tprint(f" [patch] Found {len(_clean_statements)} SQL statements, importing via db.sql_ddl...") | |
| \t_ok = _fail = 0 | |
| \tfor _idx, _stmt in enumerate(_clean_statements): | |
| \t\ttry: | |
| \t\t\tfrappe.db.sql_ddl(_stmt) | |
| \t\t\t_ok += 1 | |
| \t\texcept Exception as _e: | |
| \t\t\t_fail += 1 | |
| \t\t\tprint(f" [patch] STMT {_idx} FAIL: {_stmt[:160]}... ({_e})") | |
| \t\t\tpass | |
| \tprint(f" [patch] SQL import complete: {_ok} OK, {_fail} failed") | |
| \tprint(f" [patch] Tables after import: {frappe.db.get_tables(cached=False)}")''' | |
| PATCHES = 0 | |
| if OLD_CALL in content: | |
| content = content.replace(OLD_CALL, NEW_CALL) | |
| PATCHES += 1 | |
| print(f"SUCCESS: Patched restore_database call -> Python SQL import") | |
| else: | |
| print(f"WARNING: Could not find restore_database call in {SETUP_DB}") | |
| if 'import_db_from_sql' in content: | |
| idx = content.index('import_db_from_sql') | |
| print("--- Around import_db_from_sql ---") | |
| print(content[idx:idx+600]) | |
| # Also patch the tabDefaultValue table check to be case-insensitive | |
| # External MySQL may have lower_case_table_names=1, returning lowercase table names | |
| OLD_CHECK = '''\tif "tabDefaultValue" not in frappe.db.get_tables(cached=False):''' | |
| NEW_CHECK = '''\t_tables_lower = [t.lower() for t in frappe.db.get_tables(cached=False)] | |
| \tif "tabdefaultvalue" not in _tables_lower:''' | |
| if OLD_CHECK in content: | |
| content = content.replace(OLD_CHECK, NEW_CHECK) | |
| PATCHES += 1 | |
| print(f"SUCCESS: Patched tabDefaultValue check -> case-insensitive") | |
| else: | |
| print(f"WARNING: Could not find tabDefaultValue check in {SETUP_DB}") | |
| if 'tabDefaultValue' in content: | |
| idx = content.index('tabDefaultValue') | |
| print(f"--- Around tabDefaultValue (offset {idx}) ---") | |
| print(content[max(0,idx-60):idx+200]) | |
| if PATCHES: | |
| with open(SETUP_DB, 'w') as f: | |
| f.write(content) | |
| print(f"DONE: Applied {PATCHES} patch(es) to {SETUP_DB}") | |
| else: | |
| print(f"FAILED: No patches applied") | |