sql generator
This commit is contained in:
@@ -0,0 +1,352 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate client merge SQL (settings + const) from:
|
||||
- goldAli catalog template (abshode_rahimi_merge_goldali_catalog.sql UPDATE blocks)
|
||||
- client JSON: { "settings": [...], "const": [...] }
|
||||
|
||||
Usage:
|
||||
python generate_merge_sql.py abaeigold_client.json abaeigold
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
SQL_DIR = Path(__file__).resolve().parent
|
||||
CATALOG_SQL = SQL_DIR / "abshode_rahimi_merge_goldali_catalog.sql"
|
||||
|
||||
CONST_CATALOG: list[dict] = [
|
||||
{"key": "limited_price", "title_fa": "سقف قیمت محدود", "default": ""},
|
||||
{"key": "is_gateway_open", "title_fa": "باز بودن درگاه پرداخت", "default": ""},
|
||||
{"key": "liman", "title_fa": "توکن لیمان", "default": ""},
|
||||
{"key": "header_banner", "title_fa": "بنر بالای صفحه", "default": ""},
|
||||
{"key": "app_closed", "title_fa": "بسته بودن اپ", "default": "false"},
|
||||
{"key": "main_product", "title_fa": "محصول آبشده پیشفرض", "default": "13"},
|
||||
{"key": "gram", "title_fa": "گرم ثبتنام", "default": ""},
|
||||
{"key": "first_customer_wallet_value", "title_fa": "حداقل اولین خرید", "default": ""},
|
||||
{"key": "jibit_counter", "title_fa": "شمارنده جیبیت", "default": "0"},
|
||||
{"key": "price", "title_fa": "لیست قیمت TGJU (JSON)", "default": "[]"},
|
||||
]
|
||||
|
||||
# Legacy client keys → catalog key (value only)
|
||||
SETTINGS_ALIASES = {
|
||||
"liman_token": "const:liman",
|
||||
}
|
||||
|
||||
# Keys dropped from client settings (logs, legacy goldir in settings if any)
|
||||
SETTINGS_SKIP = {
|
||||
"montzar_order_response",
|
||||
"montzar_last_product_log",
|
||||
"liman_token",
|
||||
}
|
||||
|
||||
CONST_SKIP_PREFIXES = ("goldir",)
|
||||
CONST_SKIP_KEYS = {"log", "last_order_log"}
|
||||
|
||||
|
||||
def normalize_key(key: str) -> str:
|
||||
return key.strip().replace("\r", "").replace("\n", "")
|
||||
|
||||
|
||||
def sql_str(value: str | None) -> str:
|
||||
if value is None:
|
||||
return "NULL"
|
||||
return "'" + str(value).replace("\\", "\\\\").replace("'", "''") + "'"
|
||||
|
||||
|
||||
def _parse_sql_field(line: str) -> str | None:
|
||||
line = line.strip().rstrip(",")
|
||||
if line.endswith("= NULL"):
|
||||
return None
|
||||
eq = line.index("=")
|
||||
raw = line[eq + 1 :].strip()
|
||||
if raw.startswith("'") and raw.endswith("'"):
|
||||
return raw[1:-1].replace("''", "'")
|
||||
return raw
|
||||
|
||||
|
||||
def parse_catalog_updates(text: str) -> dict[str, dict]:
|
||||
catalog: dict[str, dict] = {}
|
||||
blocks = re.split(r"(?=UPDATE `settings` SET)", text)
|
||||
for block in blocks:
|
||||
if not block.startswith("UPDATE `settings` SET"):
|
||||
continue
|
||||
key_m = re.search(
|
||||
r"`updated_at` = NOW\(\)\s+WHERE `key` = '([^']+)';", block
|
||||
)
|
||||
if not key_m:
|
||||
continue
|
||||
key = key_m.group(1)
|
||||
fields: dict[str, str | None] = {}
|
||||
for line in block.splitlines():
|
||||
line = line.strip()
|
||||
for col in ("title_fa", "can_edit", "type", "description"):
|
||||
if line.startswith(f"`{col}` ="):
|
||||
fields[col] = _parse_sql_field(line)
|
||||
catalog[key] = {
|
||||
"title_fa": fields.get("title_fa") or "",
|
||||
"can_edit": int(fields.get("can_edit") or 0),
|
||||
"type": fields.get("type"),
|
||||
"description": fields.get("description"),
|
||||
}
|
||||
|
||||
# fallback: ensure full key list from DELETE block
|
||||
delete_m = re.search(
|
||||
r"DELETE FROM `settings`\s+WHERE `key` NOT IN \(\s*(.*?)\s*\);",
|
||||
text,
|
||||
re.S,
|
||||
)
|
||||
if delete_m:
|
||||
for key in re.findall(r"'([^']+)'", delete_m.group(1)):
|
||||
catalog.setdefault(
|
||||
key,
|
||||
{"title_fa": "", "can_edit": 1, "type": "text", "description": None},
|
||||
)
|
||||
catalog.pop("price_method", None)
|
||||
return catalog
|
||||
|
||||
|
||||
def load_client_rows(path: Path) -> tuple[dict[str, str | None], dict[str, str | None]]:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
settings: dict[str, str | None] = {}
|
||||
const: dict[str, str | None] = {}
|
||||
|
||||
for row in data.get("settings", []):
|
||||
key = normalize_key(row["key"])
|
||||
if key in SETTINGS_SKIP:
|
||||
continue
|
||||
val = row.get("value")
|
||||
if val is not None and isinstance(val, str):
|
||||
val = val.strip()
|
||||
settings[key] = val if val != "" else None
|
||||
|
||||
for row in data.get("const", []):
|
||||
key = normalize_key(row["key"])
|
||||
if key in CONST_SKIP_KEYS or key.startswith(CONST_SKIP_PREFIXES):
|
||||
continue
|
||||
val = row.get("value")
|
||||
if val is not None and isinstance(val, str):
|
||||
val = val.strip()
|
||||
const[key] = val if val != "" else None
|
||||
|
||||
# liman_token in settings → const.liman
|
||||
if "liman_token" in {normalize_key(r["key"]) for r in data.get("settings", [])}:
|
||||
for row in data.get("settings", []):
|
||||
if normalize_key(row["key"]) == "liman_token" and row.get("value"):
|
||||
const["liman"] = row["value"]
|
||||
|
||||
# montzar_price → price_provider
|
||||
mp = settings.get("montzar_price")
|
||||
if mp and str(mp).lower() in ("true", "1"):
|
||||
settings["price_provider"] = "montzar"
|
||||
elif settings.get("main_product_price") and str(settings.get("main_product_price")).lower() == "true":
|
||||
settings["price_provider"] = "main_product"
|
||||
elif not settings.get("price_provider"):
|
||||
settings.setdefault("price_provider", "montzar" if mp and str(mp).lower() in ("true", "1") else "")
|
||||
|
||||
# main_product from const if missing in settings
|
||||
if const.get("main_product") and not settings.get("main_product"):
|
||||
settings["main_product"] = const["main_product"]
|
||||
|
||||
return settings, const
|
||||
|
||||
|
||||
def is_active_value(value: str | None) -> int:
|
||||
if value is None or value == "":
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
def generate_settings_sql(
|
||||
client_name: str,
|
||||
client_name_fa: str,
|
||||
catalog: dict[str, dict],
|
||||
client_settings: dict[str, str | None],
|
||||
) -> str:
|
||||
lines: list[str] = [
|
||||
"-- =============================================================================",
|
||||
f"-- {client_name_fa} — ادغام settings با کاتالوگ goldAliii",
|
||||
"--",
|
||||
f"-- منبع مقادیر: {client_name}_client.json",
|
||||
f"-- قالب کلیدها: goldali ({len(catalog)} کلید)",
|
||||
"--",
|
||||
"-- قوانین:",
|
||||
"-- • کلید مشترک → value از کلاینت، metadata از کاتالوگ",
|
||||
"-- • کلید کاتالوگ بدون مقدار در کلاینت → value=''، is_active=0",
|
||||
"-- • کلیدهای اضافی کلاینت → DELETE",
|
||||
"--",
|
||||
f"-- قبل از اجرا: USE دیتابیس {client_name_fa} + بکاپ",
|
||||
"-- =============================================================================",
|
||||
"",
|
||||
"START TRANSACTION;",
|
||||
"",
|
||||
"SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;",
|
||||
"",
|
||||
"ALTER TABLE `settings`",
|
||||
" MODIFY COLUMN `description` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,",
|
||||
" MODIFY COLUMN `title_fa` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,",
|
||||
" MODIFY COLUMN `type` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL;",
|
||||
"",
|
||||
"-- 1) INSERT",
|
||||
"",
|
||||
]
|
||||
|
||||
catalog_keys = sorted(catalog.keys())
|
||||
|
||||
for key in catalog_keys:
|
||||
meta = catalog[key]
|
||||
val = client_settings.get(key)
|
||||
if val is None:
|
||||
val = ""
|
||||
active = is_active_value(val if val != "" else None)
|
||||
lines.append(
|
||||
"INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)"
|
||||
)
|
||||
lines.append(
|
||||
f"SELECT {sql_str(key)}, {sql_str(val)}, {sql_str(meta['title_fa'])}, "
|
||||
f"{meta['can_edit']}, {active}, {sql_str(meta['description'])}, {sql_str(meta['type'])}"
|
||||
)
|
||||
lines.append(f"WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = {sql_str(key)} LIMIT 1);")
|
||||
|
||||
lines.extend(["", "-- 2) UPDATE", ""])
|
||||
|
||||
for key in catalog_keys:
|
||||
meta = catalog[key]
|
||||
val = client_settings.get(key)
|
||||
if val is None:
|
||||
val = ""
|
||||
active = is_active_value(val if val != "" else None)
|
||||
lines.append("UPDATE `settings` SET")
|
||||
lines.append(f" `title_fa` = {sql_str(meta['title_fa'])},")
|
||||
lines.append(f" `can_edit` = {meta['can_edit']},")
|
||||
lines.append(f" `type` = {sql_str(meta['type'])},")
|
||||
lines.append(f" `description` = {sql_str(meta['description'])},")
|
||||
lines.append(f" `value` = {sql_str(val)},")
|
||||
lines.append(f" `is_active` = {active},")
|
||||
lines.append(" `updated_at` = NOW()")
|
||||
lines.append(f"WHERE `key` = {sql_str(key)};")
|
||||
|
||||
lines.extend([
|
||||
"",
|
||||
"-- 3) logo_login normalize",
|
||||
"",
|
||||
"DELETE FROM `settings`",
|
||||
"WHERE `key` LIKE 'logo_login%' AND `key` <> 'logo_login'",
|
||||
" AND EXISTS (SELECT 1 FROM (SELECT `id`, `key` FROM `settings`) AS `s` WHERE `s`.`key` = 'logo_login');",
|
||||
"",
|
||||
"UPDATE `settings` SET",
|
||||
" `key` = 'logo_login',",
|
||||
" `value` = TRIM(REPLACE(REPLACE(`value`, CHAR(13), ''), CHAR(10), '')),",
|
||||
" `updated_at` = NOW()",
|
||||
"WHERE `key` LIKE 'logo_login%' AND `key` <> 'logo_login';",
|
||||
"",
|
||||
"-- 4) DELETE extras",
|
||||
"",
|
||||
"DELETE FROM `settings`",
|
||||
"WHERE `key` NOT IN (",
|
||||
])
|
||||
for i, key in enumerate(catalog_keys):
|
||||
comma = "," if i < len(catalog_keys) - 1 else ""
|
||||
lines.append(f" {sql_str(key)}{comma}")
|
||||
lines.append(");")
|
||||
lines.extend([
|
||||
"",
|
||||
"DELETE FROM `settings` WHERE `key` = 'price_method';",
|
||||
"",
|
||||
"COMMIT;",
|
||||
"",
|
||||
])
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_const_sql(
|
||||
client_name: str,
|
||||
client_name_fa: str,
|
||||
client_const: dict[str, str | None],
|
||||
) -> str:
|
||||
lines: list[str] = [
|
||||
"-- =============================================================================",
|
||||
f"-- {client_name_fa} — ادغام const با کاتالوگ goldAliii",
|
||||
"--",
|
||||
f"-- منبع مقادیر: {client_name}_client.json",
|
||||
"-- قالب: 10 کلید goldali",
|
||||
"-- =============================================================================",
|
||||
"",
|
||||
"START TRANSACTION;",
|
||||
"",
|
||||
"SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;",
|
||||
"",
|
||||
"ALTER TABLE `const`",
|
||||
" MODIFY COLUMN `title_fa` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL;",
|
||||
"",
|
||||
"-- 1) INSERT",
|
||||
"",
|
||||
]
|
||||
|
||||
for item in CONST_CATALOG:
|
||||
key = item["key"]
|
||||
val = client_const.get(key)
|
||||
if val is None:
|
||||
val = item["default"]
|
||||
lines.append("INSERT INTO `const` (`key`, `value`, `title_fa`)")
|
||||
lines.append(
|
||||
f"SELECT {sql_str(key)}, {sql_str(val)}, {sql_str(item['title_fa'])}"
|
||||
)
|
||||
lines.append(f"WHERE NOT EXISTS (SELECT 1 FROM `const` WHERE `key` = {sql_str(key)} LIMIT 1);")
|
||||
|
||||
lines.extend(["", "-- 2) UPDATE", ""])
|
||||
|
||||
for item in CONST_CATALOG:
|
||||
key = item["key"]
|
||||
val = client_const.get(key)
|
||||
if val is None:
|
||||
val = item["default"]
|
||||
lines.append("UPDATE `const` SET")
|
||||
lines.append(f" `title_fa` = {sql_str(item['title_fa'])},")
|
||||
lines.append(f" `value` = {sql_str(val)},")
|
||||
lines.append(" `updated_at` = NOW()")
|
||||
lines.append(f"WHERE `key` = {sql_str(key)};")
|
||||
|
||||
allowed = [item["key"] for item in CONST_CATALOG]
|
||||
lines.extend(["", "-- 3) DELETE extras", "", "DELETE FROM `const`", "WHERE `key` NOT IN ("])
|
||||
for i, key in enumerate(allowed):
|
||||
comma = "," if i < len(allowed) - 1 else ""
|
||||
lines.append(f" {sql_str(key)}{comma}")
|
||||
lines.extend([");", "", "COMMIT;", ""])
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) < 3:
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
client_json = Path(sys.argv[1])
|
||||
client_slug = sys.argv[2]
|
||||
client_name_fa = sys.argv[3] if len(sys.argv) > 3 else client_slug
|
||||
|
||||
catalog_text = CATALOG_SQL.read_text(encoding="utf-8")
|
||||
catalog = parse_catalog_updates(catalog_text)
|
||||
if not catalog:
|
||||
print("ERROR: could not parse catalog from", CATALOG_SQL)
|
||||
sys.exit(1)
|
||||
|
||||
client_settings, client_const = load_client_rows(client_json)
|
||||
|
||||
settings_sql = generate_settings_sql(client_slug, client_name_fa, catalog, client_settings)
|
||||
const_sql = generate_const_sql(client_slug, client_name_fa, client_const)
|
||||
|
||||
out_settings = SQL_DIR / f"{client_slug}_merge_goldali_catalog.sql"
|
||||
out_const = SQL_DIR / f"{client_slug}_merge_goldali_const.sql"
|
||||
out_settings.write_text(settings_sql, encoding="utf-8")
|
||||
out_const.write_text(const_sql, encoding="utf-8")
|
||||
|
||||
print(f"Wrote {out_settings} ({len(catalog)} keys)")
|
||||
print(f"Wrote {out_const} ({len(CONST_CATALOG)} keys)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user