Commit 4401c703ef59dc68d97530172d4845835eb9de43

Authored by dhb52
1 parent 3140ec89

feat: 增加 UNIQUE KEY 支持

Showing 1 changed file with 142 additions and 44 deletions
sql/tools/convertor.py
... ... @@ -4,6 +4,14 @@
4 4 Author: dhb52 (https://gitee.com/dhb52)
5 5  
6 6 pip install simple-ddl-parser
  7 +
  8 +or with uv
  9 +uv run --with simple-ddl-parser convertor.py postgres > ../postgresql/ruoyi-vue-pro.sql 239ms  四 5/22 21:03:16 2025
  10 +uv run --with simple-ddl-parser convertor.py sqlserver > ../sqlserver/ruoyi-vue-pro.sql
  11 +uv run --with simple-ddl-parser convertor.py kingbase > ../kingbase/ruoyi-vue-pro.sql
  12 +uv run --with simple-ddl-parser convertor.py opengauss > ../opengauss/ruoyi-vue-pro.sql
  13 +uv run --with simple-ddl-parser convertor.py oracle > ../oracle/ruoyi-vue-pro.sql
  14 +uv run --with simple-ddl-parser convertor.py dm8 > ../dm/ruoyi-vue-pro-dm8.sql
7 15 """
8 16  
9 17 import argparse
... ... @@ -38,6 +46,7 @@ def load_and_clean(sql_file: str) -> str:
38 46 str: 清理后的sql文件内容
39 47 """
40 48 REPLACE_PAIR_LIST = (
  49 + (")\nVALUES ", ") VALUES "),
41 50 (" CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ", " "),
42 51 (" KEY `", " INDEX `"),
43 52 ("UNIQUE INDEX", "UNIQUE KEY"),
... ... @@ -45,7 +54,7 @@ def load_and_clean(sql_file: str) -> str:
45 54 ("b'1'", "'1'"),
46 55 )
47 56  
48   - content = open(sql_file).read()
  57 + content = open(sql_file, encoding="utf-8").read()
49 58 for replace_pair in REPLACE_PAIR_LIST:
50 59 content = content.replace(*replace_pair)
51 60 content = re.sub(r"ENGINE.*COMMENT", "COMMENT", content)
... ... @@ -110,12 +119,11 @@ class Convertor(ABC):
110 119 pass
111 120  
112 121 @abstractmethod
113   - def gen_comment(self, table_sql: str, table_name: str) -> str:
  122 + def gen_comment(self, table_ddl: Dict) -> str:
114 123 """生成字段/表注释
115 124  
116 125 Args:
117   - table_sql (str): 原始表SQL
118   - table_name (str): 表名
  126 + table_ddl (Dict): 表DDL
119 127  
120 128 Returns:
121 129 str: 生成脚本
... ... @@ -123,6 +131,17 @@ class Convertor(ABC):
123 131 pass
124 132  
125 133 @abstractmethod
  134 + def gen_uk(self, table_ddl: Dict) -> str:
  135 + """生成
  136 +
  137 + Args:
  138 + table_ddl (Dict): 表DDL
  139 +
  140 + Returns:
  141 + str: 生成脚本
  142 + """
  143 +
  144 + @abstractmethod
126 145 def gen_insert(self, table_name: str) -> str:
127 146 """生成 insert 语句块
128 147  
... ... @@ -179,6 +198,16 @@ class Convertor(ABC):
179 198 yield f"CREATE INDEX idx_{table_name}_{no:02d} ON {table_name} ({columns})"
180 199  
181 200 @staticmethod
  201 + def unique_index(ddl: Dict) -> Generator:
  202 + if "constraints" in ddl and "uniques" in ddl["constraints"]:
  203 + uk_list = ddl["constraints"]["uniques"]
  204 + for uk in uk_list:
  205 + table_name = ddl["table_name"]
  206 + uk_name = uk["constraint_name"]
  207 + uk_columns = uk["columns"]
  208 + yield table_name, uk_name, uk_columns
  209 +
  210 + @staticmethod
182 211 def filed_comments(table_sql: str) -> Generator:
183 212 for line in table_sql.split("\n"):
184 213 match = re.match(r"^`([^`]+)`.* COMMENT '([^']+)'", line.strip())
... ... @@ -188,7 +217,7 @@ class Convertor(ABC):
188 217 yield field, comment_string
189 218  
190 219 def table_comment(self, table_sql: str) -> str:
191   - match = re.search(r"COMMENT \= '([^']+)';", table_sql)
  220 + match = re.search(r"COMMENT \='([^']+)';", table_sql)
192 221 return match.group(1) if match else None
193 222  
194 223 def print(self):
... ... @@ -226,11 +255,21 @@ class Convertor(ABC):
226 255 if table_name.lower().startswith("qrtz"):
227 256 continue
228 257  
229   - # 为每个表生成个5个基本部分
  258 + # 解析注释
  259 + for column in table_ddl["columns"]:
  260 + column["comment"] = bytes(column["comment"], "utf-8").decode(
  261 + "unicode_escape"
  262 + )[1:-1]
  263 + table_ddl["comment"] = bytes(table_ddl["comment"], "utf-8").decode(
  264 + "unicode_escape"
  265 + )[1:-1]
  266 +
  267 + # 为每个表生成个6个基本部分
230 268 create = self.gen_create(table_ddl)
231 269 pk = self.gen_pk(table_name)
  270 + uk = self.gen_uk(table_ddl)
232 271 index = self.gen_index(table_ddl)
233   - comment = self.gen_comment(table_sql, table_name)
  272 + comment = self.gen_comment(table_ddl)
234 273 inserts = self.gen_insert(table_name)
235 274  
236 275 # 组合当前表的DDL脚本
... ... @@ -238,6 +277,8 @@ class Convertor(ABC):
238 277  
239 278 {pk}
240 279  
  280 +{uk}
  281 +
241 282 {index}
242 283  
243 284 {comment}
... ... @@ -267,17 +308,19 @@ class PostgreSQLConvertor(Convertor):
267 308  
268 309 if type == "varchar":
269 310 return f"varchar({size})"
270   - if type == "int":
  311 + if type in ("int", "int unsigned"):
271 312 return "int4"
272   - if type == "bigint" or type == "bigint unsigned":
  313 + if type in ("bigint", "bigint unsigned"):
273 314 return "int8"
274 315 if type == "datetime":
275 316 return "timestamp"
  317 + if type == "timestamp":
  318 + return f"timestamp({size})"
276 319 if type == "bit":
277 320 return "bool"
278 321 if type in ("tinyint", "smallint"):
279 322 return "int2"
280   - if type == "text":
  323 + if type in ("text", "longtext"):
281 324 return "text"
282 325 if type in ("blob", "mediumblob"):
283 326 return "bytea"
... ... @@ -316,18 +359,22 @@ CREATE TABLE {table_name} (
316 359 def gen_index(self, ddl: Dict) -> str:
317 360 return "\n".join(f"{script};" for script in self.index(ddl))
318 361  
319   - def gen_comment(self, table_sql: str, table_name: str) -> str:
  362 + def gen_comment(self, table_ddl: Dict) -> str:
320 363 """生成字段及表的注释"""
321 364  
322 365 script = ""
323   - for field, comment_string in self.filed_comments(table_sql):
  366 + for column in table_ddl["columns"]:
  367 + table_comment = column["comment"]
324 368 script += (
325   - f"COMMENT ON COLUMN {table_name}.{field} IS '{comment_string}';" + "\n"
  369 + f"COMMENT ON COLUMN {table_ddl['table_name']}.{column['name']} IS '{table_comment}';"
  370 + + "\n"
326 371 )
327 372  
328   - table_comment = self.table_comment(table_sql)
  373 + table_comment = table_ddl["comment"]
329 374 if table_comment:
330   - script += f"COMMENT ON TABLE {table_name} IS '{table_comment}';\n"
  375 + script += (
  376 + f"COMMENT ON TABLE {table_ddl['table_name']} IS '{table_comment}';\n"
  377 + )
331 378  
332 379 return script
333 380  
... ... @@ -335,6 +382,15 @@ CREATE TABLE {table_name} (
335 382 """生成主键定义"""
336 383 return f"ALTER TABLE {table_name} ADD CONSTRAINT pk_{table_name} PRIMARY KEY (id);\n"
337 384  
  385 + def gen_uk(self, table_ddl: Dict) -> str:
  386 + script = ""
  387 + uk_list = list(Convertor.unique_index(table_ddl))
  388 + for idx, (table_name, _, uk_columns) in enumerate(uk_list, 1):
  389 + uk_name = f"uk_{table_name}_{idx:02d}"
  390 + script += f"CREATE UNIQUE INDEX {uk_name} ON {table_name} ({', '.join(uk_columns)});\n"
  391 +
  392 + return script
  393 +
338 394 def gen_insert(self, table_name: str) -> str:
339 395 """生成 insert 语句,以及根据最后的 insert id+1 生成 Sequence"""
340 396  
... ... @@ -393,17 +449,19 @@ class OracleConvertor(Convertor):
393 449  
394 450 if type == "varchar":
395 451 return f"varchar2({size if size < 4000 else 4000})"
396   - if type == "int":
  452 + if type in ("int", "int unsigned"):
397 453 return "number"
398 454 if type == "bigint" or type == "bigint unsigned":
399 455 return "number"
400 456 if type == "datetime":
401 457 return "date"
  458 + if type == "timestamp":
  459 + return f"timestamp({size})"
402 460 if type == "bit":
403 461 return "number(1,0)"
404 462 if type in ("tinyint", "smallint"):
405 463 return "smallint"
406   - if type == "text":
  464 + if type in ("text", "longtext"):
407 465 return "clob"
408 466 if type in ("blob", "mediumblob"):
409 467 return "blob"
... ... @@ -423,6 +481,8 @@ class OracleConvertor(Convertor):
423 481 type = col["type"].lower()
424 482 full_type = self.translate_type(type, col["size"])
425 483 nullable = "NULL" if col["nullable"] else "NOT NULL"
  484 + # Oracle的 INSERT '' 不能通过NOT NULL校验,因此对文字类型字段覆写为 NULL
  485 + nullable = "NULL" if type in ("varchar", "text", "longtext") else nullable
426 486 default = f"DEFAULT {col['default']}" if col["default"] is not None else ""
427 487 # Oracle 中 size 不能作为字段名
428 488 field_name = '"size"' if name == "size" else name
... ... @@ -447,16 +507,20 @@ CREATE TABLE {table_name} (
447 507 def gen_index(self, ddl: Dict) -> str:
448 508 return "\n".join(f"{script};" for script in self.index(ddl))
449 509  
450   - def gen_comment(self, table_sql: str, table_name: str) -> str:
  510 + def gen_comment(self, table_ddl: Dict) -> str:
451 511 script = ""
452   - for field, comment_string in self.filed_comments(table_sql):
  512 + for column in table_ddl["columns"]:
  513 + table_comment = column["comment"]
453 514 script += (
454   - f"COMMENT ON COLUMN {table_name}.{field} IS '{comment_string}';" + "\n"
  515 + f"COMMENT ON COLUMN {table_ddl['table_name']}.{column['name']} IS '{table_comment}';"
  516 + + "\n"
455 517 )
456 518  
457   - table_comment = self.table_comment(table_sql)
  519 + table_comment = table_ddl["comment"]
458 520 if table_comment:
459   - script += f"COMMENT ON TABLE {table_name} IS '{table_comment}';\n"
  521 + script += (
  522 + f"COMMENT ON TABLE {table_ddl['table_name']} IS '{table_comment}';\n"
  523 + )
460 524  
461 525 return script
462 526  
... ... @@ -464,6 +528,15 @@ CREATE TABLE {table_name} (
464 528 """生成主键定义"""
465 529 return f"ALTER TABLE {table_name} ADD CONSTRAINT pk_{table_name} PRIMARY KEY (id);\n"
466 530  
  531 + def gen_uk(self, table_ddl: Dict) -> str:
  532 + script = ""
  533 + uk_list = list(Convertor.unique_index(table_ddl))
  534 + for idx, (table_name, _, uk_columns) in enumerate(uk_list, 1):
  535 + uk_name = f"uk_{table_name}_{idx:02d}"
  536 + script += f"CREATE UNIQUE INDEX {uk_name} ON {table_name} ({', '.join(uk_columns)});\n"
  537 +
  538 + return script
  539 +
467 540 def gen_index(self, ddl: Dict) -> str:
468 541 return "\n".join(f"{script};" for script in self.index(ddl))
469 542  
... ... @@ -521,17 +594,17 @@ class SQLServerConvertor(Convertor):
521 594  
522 595 if type == "varchar":
523 596 return f"nvarchar({size if size < 4000 else 4000})"
524   - if type == "int":
  597 + if type in ("int", "int unsigned"):
525 598 return "int"
526   - if type == "bigint" or type == "bigint unsigned":
  599 + if type in ("bigint", "bigint unsigned"):
527 600 return "bigint"
528   - if type == "datetime":
  601 + if type in ("datetime", "timestamp"):
529 602 return "datetime2"
530 603 if type == "bit":
531 604 return "varchar(1)"
532 605 if type in ("tinyint", "smallint"):
533 606 return "tinyint"
534   - if type == "text":
  607 + if type in ("text", "longtext"):
535 608 return "nvarchar(max)"
536 609 if type in ("blob", "mediumblob"):
537 610 return "varbinary(max)"
... ... @@ -571,14 +644,18 @@ GO&quot;&quot;&quot;
571 644  
572 645 return script
573 646  
574   - def gen_comment(self, table_sql: str, table_name: str) -> str:
  647 + def gen_comment(self, table_ddl: Dict) -> str:
575 648 """生成字段及表的注释"""
576 649  
577 650 script = ""
  651 + table_name = table_ddl["table_name"]
  652 +
  653 + for column in table_ddl["columns"]:
  654 + column_comment = column["comment"]
  655 + field = column["name"]
578 656  
579   - for field, comment_string in self.filed_comments(table_sql):
580 657 script += f"""EXEC sp_addextendedproperty
581   - 'MS_Description', N'{comment_string}',
  658 + 'MS_Description', N'{column_comment}',
582 659 'SCHEMA', N'dbo',
583 660 'TABLE', N'{table_name}',
584 661 'COLUMN', N'{field}'
... ... @@ -586,7 +663,7 @@ GO
586 663  
587 664 """
588 665  
589   - table_comment = self.table_comment(table_sql)
  666 + table_comment = table_ddl["comment"]
590 667 if table_comment:
591 668 script += f"""EXEC sp_addextendedproperty
592 669 'MS_Description', N'{table_comment}',
... ... @@ -601,6 +678,15 @@ GO
601 678 """生成主键定义"""
602 679 return ""
603 680  
  681 + def gen_uk(self, table_ddl: Dict) -> str:
  682 + script = ""
  683 + uk_list = list(Convertor.unique_index(table_ddl))
  684 + for idx, (table_name, _, uk_columns) in enumerate(uk_list, 1):
  685 + uk_name = f"uk_{table_name}_{idx:02d}"
  686 + script += f"CREATE UNIQUE INDEX {uk_name} ON {table_name} ({', '.join(uk_columns)})\nGO"
  687 +
  688 + return script
  689 +
604 690 def gen_index(self, ddl: Dict) -> str:
605 691 """生成 index"""
606 692 return "\n".join(f"{script}\nGO" for script in self.index(ddl))
... ... @@ -674,22 +760,22 @@ class DM8Convertor(Convertor):
674 760  
675 761 if type == "varchar":
676 762 return f"varchar({size})"
677   - if type == "int":
  763 + if type in ("int", "int unsigned"):
678 764 return "int"
679   - if type == "bigint" or type == "bigint unsigned":
  765 + if type in ("bigint", "bigint unsigned"):
680 766 return "bigint"
681 767 if type == "datetime":
682 768 return "datetime"
  769 + if type == "timestamp":
  770 + return f"timestamp({size})"
683 771 if type == "bit":
684 772 return "bit"
685 773 if type in ("tinyint", "smallint"):
686 774 return "smallint"
687   - if type == "text":
  775 + if type in ("text", "longtext"):
688 776 return "text"
689   - if type == "blob":
  777 + if type in ("blob", "mediumblob"):
690 778 return "blob"
691   - if type == "mediumblob":
692   - return "varchar(10240)"
693 779 if type == "decimal":
694 780 return (
695 781 f"decimal({','.join(str(s) for s in size)})" if len(size) else "decimal"
... ... @@ -724,19 +810,20 @@ CREATE TABLE {table_name} (
724 810  
725 811 return script
726 812  
727   - def gen_index(self, ddl: Dict) -> str:
728   - return "\n".join(f"{script};" for script in self.index(ddl))
729   -
730   - def gen_comment(self, table_sql: str, table_name: str) -> str:
  813 + def gen_comment(self, table_ddl: Dict) -> str:
731 814 script = ""
732   - for field, comment_string in self.filed_comments(table_sql):
  815 + for column in table_ddl["columns"]:
  816 + table_comment = column["comment"]
733 817 script += (
734   - f"COMMENT ON COLUMN {table_name}.{field} IS '{comment_string}';" + "\n"
  818 + f"COMMENT ON COLUMN {table_ddl['table_name']}.{column['name']} IS '{table_comment}';"
  819 + + "\n"
735 820 )
736 821  
737   - table_comment = self.table_comment(table_sql)
  822 + table_comment = table_ddl["comment"]
738 823 if table_comment:
739   - script += f"COMMENT ON TABLE {table_name} IS '{table_comment}';\n"
  824 + script += (
  825 + f"COMMENT ON TABLE {table_ddl['table_name']} IS '{table_comment}';\n"
  826 + )
740 827  
741 828 return script
742 829  
... ... @@ -744,6 +831,15 @@ CREATE TABLE {table_name} (
744 831 """生成主键定义"""
745 832 return ""
746 833  
  834 + def gen_uk(self, table_ddl: Dict) -> str:
  835 + script = ""
  836 + uk_list = list(Convertor.unique_index(table_ddl))
  837 + for idx, (table_name, _, uk_columns) in enumerate(uk_list, 1):
  838 + uk_name = f"uk_{table_name}_{idx:02d}"
  839 + script += f"CREATE UNIQUE INDEX {uk_name} ON {table_name} ({', '.join(uk_columns)});\n"
  840 +
  841 + return script
  842 +
747 843 def gen_index(self, ddl: Dict) -> str:
748 844 return "\n".join(f"{script};" for script in self.index(ddl))
749 845  
... ... @@ -784,6 +880,8 @@ class KingbaseConvertor(PostgreSQLConvertor):
784 880 type = col["type"].lower()
785 881 full_type = self.translate_type(type, col["size"])
786 882 nullable = "NULL" if col["nullable"] else "NOT NULL"
  883 + if full_type == "text":
  884 + nullable = "NULL"
787 885 default = f"DEFAULT {col['default']}" if col["default"] is not None else ""
788 886 return f"{name} {full_type} {nullable} {default}"
789 887  
... ...