Commit 569500baa79e94a505afe5051d1e0230ccd50743

Authored by 芋道源码
Committed by Gitee
2 parents a695eb5d c0d991f8

!1528 fix(sql):修复convertor中报表、工作流mysql转换pgsql相关问题

Merge pull request !1528 from 祺/master-jdk17
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,6 +62,10 @@ def load_and_clean(sql_file: str) -> str:
62 content = open(sql_file, encoding="utf-8").read() 62 content = open(sql_file, encoding="utf-8").read()
63 for replace_pair in REPLACE_PAIR_LIST: 63 for replace_pair in REPLACE_PAIR_LIST:
64 content = content.replace(*replace_pair) 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 # 移除索引字段的前缀长度定义,例如: `name`(32) -> `name` 69 # 移除索引字段的前缀长度定义,例如: `name`(32) -> `name`
66 # 移除索引定义上的 USING BTREE COMMENT 部分 70 # 移除索引定义上的 USING BTREE COMMENT 部分
67 # 相关 issue:https://t.zsxq.com/96IFc 、https://t.zsxq.com/rC3A3 71 # 相关 issue:https://t.zsxq.com/96IFc 、https://t.zsxq.com/rC3A3
@@ -77,7 +81,11 @@ class Convertor(ABC): @@ -77,7 +81,11 @@ class Convertor(ABC):
77 self.src = src 81 self.src = src
78 self.db_type = db_type 82 self.db_type = db_type
79 self.content = load_and_clean(self.src) 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 @abstractmethod 90 @abstractmethod
83 def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]) -> str: 91 def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]) -> str:
@@ -182,7 +190,8 @@ class Convertor(ABC): @@ -182,7 +190,8 @@ class Convertor(ABC):
182 head = head.strip().replace("`", "").lower() 190 head = head.strip().replace("`", "").lower()
183 tail = tail.strip().replace(r"\"", '"') 191 tail = tail.strip().replace(r"\"", '"')
184 # tail = tail.replace("b'0'", "'0'").replace("b'1'", "'1'") 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 @staticmethod 196 @staticmethod
188 def index(ddl: Dict) -> Generator: 197 def index(ddl: Dict) -> Generator:
@@ -251,7 +260,9 @@ class Convertor(ABC): @@ -251,7 +260,9 @@ class Convertor(ABC):
251 260
252 error_scripts = [] 261 error_scripts = []
253 for table_sql in self.table_script_list: 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 # 如果parse失败, 需要跟进 267 # 如果parse失败, 需要跟进
257 if len(ddl) == 0: 268 if len(ddl) == 0:
@@ -266,17 +277,21 @@ class Convertor(ABC): @@ -266,17 +277,21 @@ class Convertor(ABC):
266 continue 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 for column in table_ddl["columns"]: 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 # 为每个表生成个6个基本部分 291 # 为每个表生成个6个基本部分
278 create = self.gen_create(table_ddl) 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 uk = self.gen_uk(table_ddl) 295 uk = self.gen_uk(table_ddl)
281 index = self.gen_index(table_ddl) 296 index = self.gen_index(table_ddl)
282 comment = self.gen_comment(table_ddl) 297 comment = self.gen_comment(table_ddl)
@@ -320,25 +335,31 @@ class PostgreSQLConvertor(Convertor): @@ -320,25 +335,31 @@ class PostgreSQLConvertor(Convertor):
320 335
321 if type == "varchar": 336 if type == "varchar":
322 return f"varchar({size})" 337 return f"varchar({size})"
323 - if type in ("int", "int unsigned"): 338 + if type in ("int", "int unsigned", "int unsigned zerofill"):
324 return "int4" 339 return "int4"
325 if type in ("bigint", "bigint unsigned"): 340 if type in ("bigint", "bigint unsigned"):
326 return "int8" 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 return "timestamp" 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 if type == "timestamp": 352 if type == "timestamp":
330 - return f"timestamp({size})" 353 + return f"timestamp({size})" if size else "timestamp"
331 if type == "bit": 354 if type == "bit":
332 return "bool" 355 return "bool"
333 - if type in ("tinyint", "smallint"):  
334 - return "int2"  
335 if type in ("text", "longtext"): 356 if type in ("text", "longtext"):
336 return "text" 357 return "text"
337 - if type in ("blob", "mediumblob"): 358 + if type in ("blob", "mediumblob", "longblob"):
338 return "bytea" 359 return "bytea"
339 if type == "decimal": 360 if type == "decimal":
340 return ( 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 def gen_create(self, ddl: Dict) -> str: 365 def gen_create(self, ddl: Dict) -> str:
@@ -351,6 +372,10 @@ class PostgreSQLConvertor(Convertor): @@ -351,6 +372,10 @@ class PostgreSQLConvertor(Convertor):
351 372
352 type = col["type"].lower() 373 type = col["type"].lower()
353 full_type = self.translate_type(type, col["size"]) 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 nullable = "NULL" if col["nullable"] else "NOT NULL" 379 nullable = "NULL" if col["nullable"] else "NOT NULL"
355 default = f"DEFAULT {col['default']}" if col["default"] is not None else "" 380 default = f"DEFAULT {col['default']}" if col["default"] is not None else ""
356 return f"{name} {full_type} {nullable} {default}" 381 return f"{name} {full_type} {nullable} {default}"
@@ -407,6 +432,8 @@ CREATE TABLE {table_name} ( @@ -407,6 +432,8 @@ CREATE TABLE {table_name} (
407 """生成 insert 语句,以及根据最后的 insert id+1 生成 Sequence""" 432 """生成 insert 语句,以及根据最后的 insert id+1 生成 Sequence"""
408 433
409 inserts = list(Convertor.inserts(table_name, self.content)) 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 ## 生成 insert 脚本 437 ## 生成 insert 脚本
411 script = "" 438 script = ""
412 last_id = 0 439 last_id = 0