WIP: corrections sérialisation API et script normalisation SQL
Backend:
- Fix Groups sur TypeMachine*Requirement: exposer typePiece/typeComposant/typeProduct
- Fix Groups sur Document, Piece, Product, Composant pour sérialisation
- Add addConstructeur/removeConstructeur sur Piece et Product
Scripts:
- Fix normalize-dump.py: gérer les schémas quotés ("public"."table")
Frontend (sous-module):
- Corrections formulaires et sérialisation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,74 +1,171 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
INSERT_RE = re.compile(
|
||||
r"(?P<prefix>INSERT\s+INTO\s+[^;]*?\()(?P<cols>[^)]*)(?P<suffix>\)\s+VALUES)",
|
||||
re.IGNORECASE | re.DOTALL,
|
||||
)
|
||||
TABLE_RE = re.compile(
|
||||
r"(?P<before>INSERT\s+INTO\s+)(?P<table>(?:\"[^\"]+\"\.|[A-Za-z_][\w$]*\.)?\"[^\"]+\"|(?:\"[^\"]+\"\.|[A-Za-z_][\w$]*\.)?[A-Za-z_][\w$]*)",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
CREATE_DB_RE = re.compile(r"^CREATE\s+DATABASE\s+.+?;$", re.IGNORECASE | re.MULTILINE)
|
||||
CONNECT_RE = re.compile(r"^\\connect\\s+.+?$", re.IGNORECASE | re.MULTILINE)
|
||||
|
||||
|
||||
TABLE_NAME_MAP = {
|
||||
"ModelType": "model_types",
|
||||
"TypeMachine": "type_machines",
|
||||
"TypeMachineComponentRequirement": "type_machine_component_requirements",
|
||||
"TypeMachinePieceRequirement": "type_machine_piece_requirements",
|
||||
"TypeMachineProductRequirement": "type_machine_product_requirements",
|
||||
"MachinePieceLink": "machine_piece_links",
|
||||
"MachineComponentLink": "machine_component_links",
|
||||
"MachineProductLink": "machine_product_links",
|
||||
"Machine": "machines",
|
||||
"Product": "products",
|
||||
"Piece": "pieces",
|
||||
"Composant": "composants",
|
||||
"Profile": "profiles",
|
||||
"CustomField": "custom_fields",
|
||||
"CustomFieldValue": "custom_field_values",
|
||||
"Document": "documents",
|
||||
"Constructeur": "constructeurs",
|
||||
"Site": "sites",
|
||||
}
|
||||
|
||||
|
||||
def normalize_identifiers(sql: str) -> str:
|
||||
SKIP_TABLES = {
|
||||
"_prisma_migrations",
|
||||
}
|
||||
|
||||
|
||||
def to_snake(name: str) -> str:
|
||||
out = []
|
||||
i = 0
|
||||
in_single = False
|
||||
length = len(sql)
|
||||
|
||||
while i < length:
|
||||
ch = sql[i]
|
||||
|
||||
if in_single:
|
||||
if ch == "'":
|
||||
if i + 1 < length and sql[i + 1] == "'":
|
||||
out.append("''")
|
||||
i += 2
|
||||
continue
|
||||
in_single = False
|
||||
length = len(name)
|
||||
for i, ch in enumerate(name):
|
||||
if ch.isupper():
|
||||
prev = name[i - 1] if i > 0 else ""
|
||||
nxt = name[i + 1] if i + 1 < length else ""
|
||||
if i > 0 and (prev.islower() or prev.isdigit() or (prev.isupper() and nxt.islower())):
|
||||
out.append("_")
|
||||
out.append(ch.lower())
|
||||
else:
|
||||
out.append(ch)
|
||||
i += 1
|
||||
continue
|
||||
return "".join(out)
|
||||
|
||||
if ch == "'":
|
||||
in_single = True
|
||||
out.append(ch)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if ch == '"':
|
||||
i += 1
|
||||
ident = []
|
||||
while i < length:
|
||||
if sql[i] == '"':
|
||||
break
|
||||
ident.append(sql[i])
|
||||
i += 1
|
||||
ident_str = ''.join(ident)
|
||||
mapped = TABLE_NAME_MAP.get(ident_str)
|
||||
if mapped is not None:
|
||||
out.append(mapped)
|
||||
else:
|
||||
out.append(ident_str.lower())
|
||||
if i < length and sql[i] == '"':
|
||||
i += 1
|
||||
continue
|
||||
def to_lower_compact(name: str) -> str:
|
||||
return name.replace("_", "").lower()
|
||||
|
||||
out.append(ch)
|
||||
i += 1
|
||||
|
||||
return ''.join(out)
|
||||
def remap_table(ident: str, mode: str) -> str:
|
||||
mapped = TABLE_NAME_MAP.get(ident)
|
||||
if mapped is not None:
|
||||
return mapped
|
||||
if mode == "snake":
|
||||
return to_snake(ident)
|
||||
if mode == "lower":
|
||||
return to_lower_compact(ident)
|
||||
raise ValueError(f"Unsupported mode: {mode}")
|
||||
|
||||
|
||||
def extract_table_ident(prefix: str) -> str | None:
|
||||
match = TABLE_RE.search(prefix)
|
||||
if not match:
|
||||
return None
|
||||
table = match.group("table")
|
||||
# Handle quoted schema like "public"."TableName"
|
||||
if '"."' in table:
|
||||
parts = table.split('"."', 1)
|
||||
ident = parts[1].strip('"')
|
||||
elif "." in table:
|
||||
_, ident = table.split(".", 1)
|
||||
ident = ident.strip('"')
|
||||
else:
|
||||
ident = table.strip('"')
|
||||
return ident
|
||||
|
||||
|
||||
def normalize_table_name(prefix: str, mode: str) -> str:
|
||||
def repl(match: re.Match[str]) -> str:
|
||||
table = match.group("table")
|
||||
schema = ""
|
||||
ident = table
|
||||
# Handle quoted schema like "public"."TableName"
|
||||
if '"."' in table:
|
||||
parts = table.split('"."', 1)
|
||||
schema_name = parts[0].strip('"')
|
||||
ident = parts[1].strip('"')
|
||||
schema = f'"{schema_name}".'
|
||||
elif "." in table:
|
||||
schema_part, ident = table.split(".", 1)
|
||||
schema_name = schema_part.strip('"')
|
||||
schema = f'"{schema_name}".'
|
||||
ident = ident.strip('"')
|
||||
else:
|
||||
ident = table.strip('"')
|
||||
mapped = remap_table(ident, mode)
|
||||
return f'{match.group("before")}{schema}"{mapped}"'
|
||||
|
||||
return TABLE_RE.sub(repl, prefix)
|
||||
|
||||
|
||||
def remap_columns(cols: str, mode: str) -> str:
|
||||
def repl(match: re.Match[str]) -> str:
|
||||
name = match.group(1)
|
||||
if mode == "snake":
|
||||
if any(ch.isupper() for ch in name):
|
||||
return f"\"{to_snake(name)}\""
|
||||
return match.group(0)
|
||||
if mode == "lower":
|
||||
return f"\"{to_lower_compact(name)}\""
|
||||
raise ValueError(f"Unsupported mode: {mode}")
|
||||
|
||||
return re.sub(r"\"([^\"]+)\"", repl, cols)
|
||||
|
||||
|
||||
def normalize_dump(sql: str, mode: str) -> str:
|
||||
sql = CREATE_DB_RE.sub("", sql)
|
||||
sql = CONNECT_RE.sub("", sql)
|
||||
|
||||
def repl(match: re.Match[str]) -> str:
|
||||
raw_prefix = match.group("prefix")
|
||||
ident = extract_table_ident(raw_prefix)
|
||||
if ident is not None:
|
||||
mapped = remap_table(ident, mode)
|
||||
if ident in SKIP_TABLES or mapped in SKIP_TABLES:
|
||||
return ""
|
||||
prefix = normalize_table_name(raw_prefix, mode)
|
||||
cols = remap_columns(match.group("cols"), mode)
|
||||
return f"{prefix}{cols}{match.group('suffix')}"
|
||||
|
||||
return INSERT_RE.sub(repl, sql)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: scripts/normalize-dump.py <input.sql> <output.sql>", file=sys.stderr)
|
||||
if len(sys.argv) not in (3, 4):
|
||||
print("Usage: scripts/normalize-dump.py <input.sql> <output.sql> [--snake|--lower]", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
src = sys.argv[1]
|
||||
dst = sys.argv[2]
|
||||
mode = "lower"
|
||||
if len(sys.argv) == 4:
|
||||
if sys.argv[3] == "--snake":
|
||||
mode = "snake"
|
||||
elif sys.argv[3] == "--lower":
|
||||
mode = "lower"
|
||||
else:
|
||||
print("Invalid mode. Use --snake or --lower.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
with open(src, "r", encoding="utf-8") as f:
|
||||
data = f.read()
|
||||
|
||||
normalized = normalize_identifiers(data)
|
||||
normalized = normalize_dump(data, mode)
|
||||
|
||||
with open(dst, "w", encoding="utf-8") as f:
|
||||
f.write(normalized)
|
||||
|
||||
Reference in New Issue
Block a user