Commit 848b0942ffb6046a27d61b396dfe0ee2d1cfcb93

Authored by YunaiV
2 parents c1fddc84 569500ba

Merge remote-tracking branch 'origin/master-jdk17' into feat/mes

Showing 1 changed file with 44 additions and 17 deletions
sql/tools/convertor.py
... ... @@ -62,6 +62,10 @@ def load_and_clean(sql_file: str) -> str:
62 62 content = open(sql_file, encoding="utf-8").read()
63 63 for replace_pair in REPLACE_PAIR_LIST:
64 64 content = content.replace(*replace_pair)
  65 + # 移除所有 CHARACTER SET / COLLATE 变体 (utf8mb3、utf8 等)
  66 + content = re.sub(r" CHARACTER SET \w+ COLLATE \w+", "", content)
  67 + content = re.sub(r" CHARACTER SET \w+", "", content)
  68 + content = re.sub(r" COLLATE \w+", "", content)
65 69 # 移除索引字段的前缀长度定义,例如: `name`(32) -> `name`
66 70 # 移除索引定义上的 USING BTREE COMMENT 部分
67 71 # 相关 issue:https://t.zsxq.com/96IFc 、https://t.zsxq.com/rC3A3
... ... @@ -77,7 +81,11 @@ class Convertor(ABC):
77 81 self.src = src
78 82 self.db_type = db_type
79 83 self.content = load_and_clean(self.src)
80   - self.table_script_list = re.findall(r"CREATE TABLE [^;]*;", self.content)
  84 + # original_content 保留原始 COMMENT 信息,用于注释提取
  85 + self.original_content = open(src, encoding="utf-8").read()
  86 + # 剥离列级 COMMENT 以避免 COMMENT 值内的分号截断 CREATE TABLE 正则
  87 + content_no_comment = re.sub(r" COMMENT '(?:[^'\\]|\\.)*'", "", self.content)
  88 + self.table_script_list = re.findall(r"CREATE TABLE [^;]*;", content_no_comment)
81 89  
82 90 @abstractmethod
83 91 def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]) -> str:
... ... @@ -182,7 +190,8 @@ class Convertor(ABC):
182 190 head = head.strip().replace("`", "").lower()
183 191 tail = tail.strip().replace(r"\"", '"')
184 192 # tail = tail.replace("b'0'", "'0'").replace("b'1'", "'1'")
185   - yield f"INSERT INTO {table_name.lower()} {head} VALUES {tail}"
  193 + col_part = f" {head}" if head else ""
  194 + yield f"INSERT INTO {table_name.lower()}{col_part} VALUES {tail}"
186 195  
187 196 @staticmethod
188 197 def index(ddl: Dict) -> Generator:
... ... @@ -251,7 +260,9 @@ class Convertor(ABC):
251 260  
252 261 error_scripts = []
253 262 for table_sql in self.table_script_list:
254   - ddl = DDLParser(table_sql.replace("`", "")).run()
  263 + # 剥离 COMMENT 子句避免 DDLParser 无法处理中文等特殊字符
  264 + table_sql_for_parse = re.sub(r"\s+COMMENT\s+'[^']*'", "", table_sql)
  265 + ddl = DDLParser(table_sql_for_parse.replace("`", "")).run()
255 266  
256 267 # 如果parse失败, 需要跟进
257 268 if len(ddl) == 0:
... ... @@ -266,17 +277,21 @@ class Convertor(ABC):
266 277 continue
267 278  
268 279 # 解析注释
  280 + # 从原始 SQL 提取注释(支持中文等 DDLParser 不能处理的内容)
  281 + orig_tables = re.findall(r"CREATE TABLE [^;]*;", self.original_content)
  282 + orig_table_sql = next(
  283 + (t for t in orig_tables if f"`{table_name}`" in t or f"`{table_name.upper()}`" in t),
  284 + table_sql,
  285 + )
  286 + comments_dict = dict(Convertor.filed_comments(orig_table_sql))
269 287 for column in table_ddl["columns"]:
270   - column["comment"] = bytes(column["comment"], "utf-8").decode(
271   - r"unicode_escape"
272   - )[1:-1]
273   - table_ddl["comment"] = bytes(table_ddl["comment"], "utf-8").decode(
274   - r"unicode_escape"
275   - )[1:-1]
  288 + column["comment"] = comments_dict.get(column["name"], "")
  289 + table_ddl["comment"] = self.table_comment(orig_table_sql) or ""
276 290  
277 291 # 为每个表生成个6个基本部分
278 292 create = self.gen_create(table_ddl)
279   - pk = self.gen_pk(table_name)
  293 + has_id = any(col["name"].lower() == "id" for col in table_ddl["columns"])
  294 + pk = self.gen_pk(table_name) if has_id else ""
280 295 uk = self.gen_uk(table_ddl)
281 296 index = self.gen_index(table_ddl)
282 297 comment = self.gen_comment(table_ddl)
... ... @@ -320,25 +335,31 @@ class PostgreSQLConvertor(Convertor):
320 335  
321 336 if type == "varchar":
322 337 return f"varchar({size})"
323   - if type in ("int", "int unsigned"):
  338 + if type in ("int", "int unsigned", "int unsigned zerofill"):
324 339 return "int4"
325 340 if type in ("bigint", "bigint unsigned"):
326 341 return "int8"
327   - if type == "datetime":
  342 + if type in ("tinyint", "smallint", "tinyint unsigned"):
  343 + return "int2"
  344 + if type in ("datetime", "timestamp null"):
328 345 return "timestamp"
  346 + if type == "date":
  347 + return "date"
  348 + if type == "json":
  349 + return "jsonb"
  350 + if type == "double":
  351 + return "double precision"
329 352 if type == "timestamp":
330   - return f"timestamp({size})"
  353 + return f"timestamp({size})" if size else "timestamp"
331 354 if type == "bit":
332 355 return "bool"
333   - if type in ("tinyint", "smallint"):
334   - return "int2"
335 356 if type in ("text", "longtext"):
336 357 return "text"
337   - if type in ("blob", "mediumblob"):
  358 + if type in ("blob", "mediumblob", "longblob"):
338 359 return "bytea"
339 360 if type == "decimal":
340 361 return (
341   - f"numeric({','.join(str(s) for s in size)})" if len(size) else "numeric"
  362 + f"numeric({','.join(str(s) for s in size)})" if size and len(size) else "numeric"
342 363 )
343 364  
344 365 def gen_create(self, ddl: Dict) -> str:
... ... @@ -351,6 +372,10 @@ class PostgreSQLConvertor(Convertor):
351 372  
352 373 type = col["type"].lower()
353 374 full_type = self.translate_type(type, col["size"])
  375 + if full_type is None:
  376 + raise NotImplementedError(
  377 + f"未支持的类型: '{col['type']}' (列: {name}, 表: {ddl['table_name']})"
  378 + )
354 379 nullable = "NULL" if col["nullable"] else "NOT NULL"
355 380 default = f"DEFAULT {col['default']}" if col["default"] is not None else ""
356 381 return f"{name} {full_type} {nullable} {default}"
... ... @@ -407,6 +432,8 @@ CREATE TABLE {table_name} (
407 432 """生成 insert 语句,以及根据最后的 insert id+1 生成 Sequence"""
408 433  
409 434 inserts = list(Convertor.inserts(table_name, self.content))
  435 + # 转换 MySQL 字符串转义为 PostgreSQL 格式:\\ -> \,\' -> ''
  436 + inserts = [re.sub(r"\\\\|\\'", lambda m: "\\" if m.group() == "\\\\" else "''", s) for s in inserts]
410 437 ## 生成 insert 脚本
411 438 script = ""
412 439 last_id = 0
... ...