Commit 569500baa79e94a505afe5051d1e0230ccd50743
Committed by
Gitee
!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 | 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 | ... | ... |