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,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 |