Commit b4a867b89c8b4dfe7c3622cb3aace1ff770ba068

Authored by 芋道源码
Committed by Gitee
2 parents d98419b0 6d19690b

!963 feat: 脚本兼容Python 3.9, 抽取更多公共代码; 更新使用文档

Merge pull request !963 from dhb52/feature/db
sql/tools/README.md
@@ -50,8 +50,16 @@ TODO 暂未支持 @@ -50,8 +50,16 @@ TODO 暂未支持
50 50
51 使用方式如下: 51 使用方式如下:
52 52
  53 +安装依赖库
  54 +
  55 +```bash
  56 +pip install simple-ddl-parser
  57 +```
  58 +
  59 +执行如下命令打印生成 postgresql 的脚本内容,其他可选参数有:oracle, sqlserver
  60 +
53 ```Bash 61 ```Bash
54 -python3 convertor.py 62 +python3 convertor.py postgres
55 ``` 63 ```
56 64
57 -然后,TODO  
58 \ No newline at end of file 65 \ No newline at end of file
  66 +程序将sql脚本打印到终端,可以重定向到临时文件tmp.sql, 确认无误后可以利用IDEA(专业版)进行格式化。
sql/tools/convertor.py
@@ -6,11 +6,12 @@ Author: dhb52 (https://gitee.com/dhb52) @@ -6,11 +6,12 @@ Author: dhb52 (https://gitee.com/dhb52)
6 pip install simple-ddl-parser 6 pip install simple-ddl-parser
7 """ 7 """
8 8
  9 +import argparse
9 import pathlib 10 import pathlib
10 import re 11 import re
11 import time 12 import time
12 from abc import ABC, abstractmethod 13 from abc import ABC, abstractmethod
13 -from typing import Dict, Tuple 14 +from typing import Dict, Generator, Optional, Tuple, Union
14 15
15 from simple_ddl_parser import DDLParser 16 from simple_ddl_parser import DDLParser
16 17
@@ -60,12 +61,12 @@ class Convertor(ABC): @@ -60,12 +61,12 @@ class Convertor(ABC):
60 self.table_script_list = re.findall(r"CREATE TABLE [^;]*;", self.content) 61 self.table_script_list = re.findall(r"CREATE TABLE [^;]*;", self.content)
61 62
62 @abstractmethod 63 @abstractmethod
63 - def translate_type(self, type: str, size: None | int | Tuple[int]) -> str: 64 + def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]) -> str:
64 """字段类型转换 65 """字段类型转换
65 66
66 Args: 67 Args:
67 type (str): 字段类型 68 type (str): 字段类型
68 - size (None | int | Tuple[int]): 字段长度描述, 如varchar(255), decimal(10,2) 69 + size (Optional[Union[int, Tuple[int]]]): 字段长度描述, 如varchar(255), decimal(10,2)
69 70
70 Returns: 71 Returns:
71 str: 类型定义 72 str: 类型定义
@@ -97,7 +98,7 @@ class Convertor(ABC): @@ -97,7 +98,7 @@ class Convertor(ABC):
97 pass 98 pass
98 99
99 @abstractmethod 100 @abstractmethod
100 - def gen_index(self, table_ddl: Dict) -> str: 101 + def gen_index(self, ddl: Dict) -> str:
101 """生成索引定义 102 """生成索引定义
102 103
103 Args: 104 Args:
@@ -133,6 +134,55 @@ class Convertor(ABC): @@ -133,6 +134,55 @@ class Convertor(ABC):
133 """ 134 """
134 pass 135 pass
135 136
  137 + @staticmethod
  138 + def inserts(table_name: str, script_content: str) -> Generator:
  139 + PREFIX = f"INSERT INTO `{table_name}`"
  140 +
  141 + # 收集 `table_name` 对应的 insert 语句
  142 + for line in script_content.split("\n"):
  143 + if line.startswith(PREFIX):
  144 + head, tail = line.replace(PREFIX, "").split(" VALUES ", maxsplit=1)
  145 + head = head.strip().replace("`", "").lower()
  146 + tail = tail.strip().replace(r"\"", '"')
  147 + # tail = tail.replace("b'0'", "'0'").replace("b'1'", "'1'")
  148 + yield f"INSERT INTO {table_name.lower()} {head} VALUES {tail}"
  149 +
  150 + @staticmethod
  151 + def index(ddl: Dict) -> Generator:
  152 + """生成索引定义
  153 +
  154 + Args:
  155 + ddl (Dict): 表DDL
  156 +
  157 + Yields:
  158 + Generator[str]: create index 语句
  159 + """
  160 +
  161 + def generate_columns(columns):
  162 + keys = [
  163 + f"{col['name'].lower()}{' ' + col['order'].lower() if col['order'] != 'ASC' else ''}"
  164 + for col in columns[0]
  165 + ]
  166 + return ", ".join(keys)
  167 +
  168 + for no, index in enumerate(ddl["index"], 1):
  169 + columns = generate_columns(index["columns"])
  170 + table_name = ddl["table_name"].lower()
  171 + yield f"CREATE INDEX idx_{table_name}_{no:02d} ON {table_name} ({columns})"
  172 +
  173 + @staticmethod
  174 + def filed_comments(table_sql: str) -> Generator:
  175 + for line in table_sql.split("\n"):
  176 + match = re.match(r"^`([^`]+)`.* COMMENT '([^']+)'", line.strip())
  177 + if match:
  178 + field = match.group(1)
  179 + comment_string = match.group(2).replace("\\n", "\n")
  180 + yield field, comment_string
  181 +
  182 + def table_comment(self, table_sql: str) -> str:
  183 + match = re.search(r"COMMENT \= '([^']+)';", table_sql)
  184 + return match.group(1) if match else None
  185 +
136 def print(self): 186 def print(self):
137 """打印转换后的sql脚本到终端""" 187 """打印转换后的sql脚本到终端"""
138 print( 188 print(
@@ -192,7 +242,7 @@ class PostgreSQLConvertor(Convertor): @@ -192,7 +242,7 @@ class PostgreSQLConvertor(Convertor):
192 def __init__(self, src): 242 def __init__(self, src):
193 super().__init__(src, "PostgreSQL") 243 super().__init__(src, "PostgreSQL")
194 244
195 - def translate_type(self, type, size): 245 + def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]):
196 """类型转换""" 246 """类型转换"""
197 247
198 type = type.lower() 248 type = type.lower()
@@ -234,27 +284,30 @@ class PostgreSQLConvertor(Convertor): @@ -234,27 +284,30 @@ class PostgreSQLConvertor(Convertor):
234 284
235 table_name = ddl["table_name"].lower() 285 table_name = ddl["table_name"].lower()
236 columns = [f"{_generate_column(col).strip()}" for col in ddl["columns"]] 286 columns = [f"{_generate_column(col).strip()}" for col in ddl["columns"]]
  287 + filed_def_list = ",\n ".join(columns)
237 script = f"""-- ---------------------------- 288 script = f"""-- ----------------------------
238 -- Table structure for {table_name} 289 -- Table structure for {table_name}
239 -- ---------------------------- 290 -- ----------------------------
240 DROP TABLE IF EXISTS {table_name}; 291 DROP TABLE IF EXISTS {table_name};
241 CREATE TABLE {table_name} ( 292 CREATE TABLE {table_name} (
242 - {',\n '.join(columns)} 293 + {filed_def_list}
243 );""" 294 );"""
244 295
245 return script 296 return script
246 297
247 - def gen_comment(self, table_sql, table_name) -> str: 298 + def gen_index(self, ddl: Dict) -> str:
  299 + return "\n".join(f"{script};" for script in self.index(ddl))
  300 +
  301 + def gen_comment(self, table_sql: str, table_name: str) -> str:
248 """生成字段及表的注释""" 302 """生成字段及表的注释"""
249 303
250 script = "" 304 script = ""
251 - for line in table_sql.split("\n"):  
252 - match = re.match(r"^`([^`]+)`.* COMMENT '([^']+)'", line.strip())  
253 - if match:  
254 - script += f"COMMENT ON COLUMN {table_name}.{match.group(1)} IS '{match.group(2).replace('\\n', '\n')}';\n" 305 + for field, comment_string in self.filed_comments(table_sql):
  306 + script += (
  307 + f"COMMENT ON COLUMN {table_name}.{field} IS '{comment_string}';" + "\n"
  308 + )
255 309
256 - match = re.search(r"COMMENT \= '([^']+)';", table_sql)  
257 - table_comment = match.group(1) if match else None 310 + table_comment = self.table_comment(table_sql)
258 if table_comment: 311 if table_comment:
259 script += f"COMMENT ON TABLE {table_name} IS '{table_comment}';\n" 312 script += f"COMMENT ON TABLE {table_name} IS '{table_comment}';\n"
260 313
@@ -264,53 +317,21 @@ CREATE TABLE {table_name} ( @@ -264,53 +317,21 @@ CREATE TABLE {table_name} (
264 """生成主键定义""" 317 """生成主键定义"""
265 return f"ALTER TABLE {table_name} ADD CONSTRAINT pk_{table_name} PRIMARY KEY (id);\n" 318 return f"ALTER TABLE {table_name} ADD CONSTRAINT pk_{table_name} PRIMARY KEY (id);\n"
266 319
267 - def gen_index(self, ddl) -> str:  
268 - """生成 index"""  
269 -  
270 - def generate_columns(columns):  
271 - keys = [  
272 - f"{col['name'].lower()}{" " + col['order'].lower() if col['order'] != 'ASC' else ''}"  
273 - for col in columns[0]  
274 - ]  
275 - return ", ".join(keys)  
276 -  
277 - script = ""  
278 - for no, index in enumerate(ddl["index"], 1):  
279 - columns = generate_columns(index["columns"])  
280 - table_name = ddl["table_name"].lower()  
281 - script += (  
282 - f"CREATE INDEX idx_{table_name}_{no:02d} ON {table_name} ({columns});\n"  
283 - )  
284 -  
285 - return script  
286 -  
287 - def gen_insert(self, table_name) -> str: 320 + def gen_insert(self, table_name: str) -> str:
288 """生成 insert 语句,以及根据最后的 insert id+1 生成 Sequence""" 321 """生成 insert 语句,以及根据最后的 insert id+1 生成 Sequence"""
289 322
290 - PREFIX = f"INSERT INTO `{table_name}`"  
291 -  
292 - # 收集 `table_name` 对应的 insert 语句  
293 - inserts = []  
294 - for line in self.content.split("\n"):  
295 - if line.startswith(PREFIX):  
296 - head, tail = line.replace(PREFIX, "").split(" VALUES ", maxsplit=1)  
297 - head = head.strip().replace("`", "").lower()  
298 - tail = tail.strip().replace(r"\"", '"')  
299 - script = f"INSERT INTO {table_name.lower()} {head} VALUES {tail}"  
300 - # bit(1)数据转换  
301 - script = script.replace("b'0'", "'0'").replace("b'1'", "'1'")  
302 - inserts.append(script)  
303 - 323 + inserts = list(Convertor.inserts(table_name, self.content))
304 ## 生成 insert 脚本 324 ## 生成 insert 脚本
305 script = "" 325 script = ""
306 last_id = 0 326 last_id = 0
307 if inserts: 327 if inserts:
  328 + inserts_lines = "\n".join(inserts)
308 script += f"""\n\n-- ---------------------------- 329 script += f"""\n\n-- ----------------------------
309 -- Records of {table_name.lower()} 330 -- Records of {table_name.lower()}
310 -- ---------------------------- 331 -- ----------------------------
311 -- @formatter:off 332 -- @formatter:off
312 BEGIN; 333 BEGIN;
313 -{'\n'.join(inserts)} 334 +{inserts_lines}
314 COMMIT; 335 COMMIT;
315 -- @formatter:on""" 336 -- @formatter:on"""
316 match = re.search(r"VALUES \((\d+),", inserts[-1]) 337 match = re.search(r"VALUES \((\d+),", inserts[-1])
@@ -332,7 +353,7 @@ class OracleConvertor(Convertor): @@ -332,7 +353,7 @@ class OracleConvertor(Convertor):
332 def __init__(self, src): 353 def __init__(self, src):
333 super().__init__(src, "Oracle") 354 super().__init__(src, "Oracle")
334 355
335 - def translate_type(self, type, size: None | int | Tuple[int]): 356 + def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]):
336 """类型转换""" 357 """类型转换"""
337 type = type.lower() 358 type = type.lower()
338 359
@@ -369,15 +390,19 @@ class OracleConvertor(Convertor): @@ -369,15 +390,19 @@ class OracleConvertor(Convertor):
369 full_type = self.translate_type(type, col["size"]) 390 full_type = self.translate_type(type, col["size"])
370 nullable = "NULL" if col["nullable"] else "NOT NULL" 391 nullable = "NULL" if col["nullable"] else "NOT NULL"
371 default = f"DEFAULT {col['default']}" if col["default"] is not None else "" 392 default = f"DEFAULT {col['default']}" if col["default"] is not None else ""
372 - return f"{'\"size\"' if name == "size" else name } {full_type} {default} {nullable}" 393 + # Oracle 中 size 不能作为字段名
  394 + field_name = '"size"' if name == "size" else name
  395 + # Oracle DEFAULT 定义在 NULLABLE 之前
  396 + return f"{field_name} {full_type} {default} {nullable}"
373 397
374 table_name = ddl["table_name"].lower() 398 table_name = ddl["table_name"].lower()
375 columns = [f"{generate_column(col).strip()}" for col in ddl["columns"]] 399 columns = [f"{generate_column(col).strip()}" for col in ddl["columns"]]
  400 + field_def_list = ",\n ".join(columns)
376 script = f"""-- ---------------------------- 401 script = f"""-- ----------------------------
377 -- Table structure for {table_name} 402 -- Table structure for {table_name}
378 -- ---------------------------- 403 -- ----------------------------
379 -CREATE TABLE {ddl['table_name'].lower()} (  
380 - {',\n '.join(columns)} 404 +CREATE TABLE {table_name} (
  405 + {field_def_list}
381 );""" 406 );"""
382 407
383 # oracle INSERT '' 不能通过 NOT NULL 校验 408 # oracle INSERT '' 不能通过 NOT NULL 校验
@@ -385,72 +410,51 @@ CREATE TABLE {ddl['table_name'].lower()} ( @@ -385,72 +410,51 @@ CREATE TABLE {ddl['table_name'].lower()} (
385 410
386 return script 411 return script
387 412
388 - def gen_comment(self, table_sql, table_name) -> str: 413 + def gen_index(self, ddl: Dict) -> str:
  414 + return "\n".join(f"{script};" for script in self.index(ddl))
  415 +
  416 + def gen_comment(self, table_sql: str, table_name: str) -> str:
389 script = "" 417 script = ""
390 - for line in table_sql.split("\n"):  
391 - match = re.search(r"`([^`]+)`.* COMMENT '([^']+)'", line)  
392 - if match:  
393 - script += f"COMMENT ON COLUMN {table_name}.{match.group(1)} IS '{match.group(2).replace('\\n', '\n')}';\n" 418 + for field, comment_string in self.filed_comments(table_sql):
  419 + script += (
  420 + f"COMMENT ON COLUMN {table_name}.{field} IS '{comment_string}';" + "\n"
  421 + )
394 422
395 - match = re.search(r"COMMENT \= '([^']+)';", table_sql)  
396 - table_comment = match.group(1) if match else None 423 + table_comment = self.table_comment(table_sql)
397 if table_comment: 424 if table_comment:
398 - script += f"COMMENT ON TABLE {table_name} IS '{table_comment}';" 425 + script += f"COMMENT ON TABLE {table_name} IS '{table_comment}';\n"
399 426
400 return script 427 return script
401 428
402 - def gen_pk(self, table_name) -> str: 429 + def gen_pk(self, table_name: str) -> str:
403 """生成主键定义""" 430 """生成主键定义"""
404 return f"ALTER TABLE {table_name} ADD CONSTRAINT pk_{table_name} PRIMARY KEY (id);\n" 431 return f"ALTER TABLE {table_name} ADD CONSTRAINT pk_{table_name} PRIMARY KEY (id);\n"
405 432
406 - def gen_index(self, table_ddl) -> str:  
407 - """生成 INDEX 定义"""  
408 -  
409 - def generate_columns(columns):  
410 - keys = [  
411 - f"{col['name'].lower()}{" " + col['order'].lower() if col['order'] != 'ASC' else ''}"  
412 - for col in columns[0]  
413 - ]  
414 - return ", ".join(keys)  
415 -  
416 - script = ""  
417 - for no, index in enumerate(table_ddl["index"], 1):  
418 - columns = generate_columns(index["columns"])  
419 - table_name = table_ddl["table_name"].lower()  
420 - script += (  
421 - f"CREATE INDEX idx_{table_name}_{no:02d} ON {table_name} ({columns});\n"  
422 - )  
423 - return script 433 + def gen_index(self, ddl: Dict) -> str:
  434 + return "\n".join(f"{script};" for script in self.index(ddl))
424 435
425 - def gen_insert(self, table_name) -> str: 436 + def gen_insert(self, table_name: str) -> str:
426 """拷贝 INSERT 语句""" 437 """拷贝 INSERT 语句"""
427 - PREFIX = f"INSERT INTO `{table_name}`"  
428 inserts = [] 438 inserts = []
429 - for line in self.content.split("\n"):  
430 - if line.startswith(PREFIX):  
431 - head, tail = line.replace(PREFIX, "").split(" VALUES ", maxsplit=1)  
432 - head = head.strip().replace("`", "").lower()  
433 - tail = tail.strip().replace(r"\"", '"')  
434 - script = f"INSERT INTO {table_name.lower()} {head} VALUES {tail}"  
435 - # bit(1)数据转换  
436 - script = script.replace("b'0'", "'0'").replace("b'1'", "'1'")  
437 - # 对日期数据添加 TO_DATE 转换  
438 - script = re.sub(  
439 - r"('\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}')",  
440 - r"to_date(\g<1>, 'SYYYY-MM-DD HH24:MI:SS')",  
441 - script,  
442 - )  
443 - inserts.append(script) 439 + for insert_script in Convertor.inserts(table_name, self.content):
  440 + # 对日期数据添加 TO_DATE 转换
  441 + insert_script = re.sub(
  442 + r"('\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}')",
  443 + r"to_date(\g<1>, 'SYYYY-MM-DD HH24:MI:SS')",
  444 + insert_script,
  445 + )
  446 + inserts.append(insert_script)
444 447
445 ## 生成 insert 脚本 448 ## 生成 insert 脚本
446 script = "" 449 script = ""
447 last_id = 0 450 last_id = 0
448 if inserts: 451 if inserts:
  452 + inserts_lines = "\n".join(inserts)
449 script += f"""\n\n-- ---------------------------- 453 script += f"""\n\n-- ----------------------------
450 -- Records of {table_name.lower()} 454 -- Records of {table_name.lower()}
451 -- ---------------------------- 455 -- ----------------------------
452 -- @formatter:off 456 -- @formatter:off
453 -{'\n'.join(inserts)} 457 +{inserts_lines}
454 COMMIT; 458 COMMIT;
455 -- @formatter:on""" 459 -- @formatter:on"""
456 match = re.search(r"VALUES \((\d+),", inserts[-1]) 460 match = re.search(r"VALUES \((\d+),", inserts[-1])
@@ -476,7 +480,7 @@ class SQLServerConvertor(Convertor): @@ -476,7 +480,7 @@ class SQLServerConvertor(Convertor):
476 def __init__(self, src): 480 def __init__(self, src):
477 super().__init__(src, "Microsoft SQL Server") 481 super().__init__(src, "Microsoft SQL Server")
478 482
479 - def translate_type(self, type, size): 483 + def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]):
480 """类型转换""" 484 """类型转换"""
481 485
482 type = type.lower() 486 type = type.lower()
@@ -507,7 +511,7 @@ class SQLServerConvertor(Convertor): @@ -507,7 +511,7 @@ class SQLServerConvertor(Convertor):
507 511
508 def _generate_column(col): 512 def _generate_column(col):
509 name = col["name"].lower() 513 name = col["name"].lower()
510 - if name == 'id': 514 + if name == "id":
511 return "id bigint NOT NULL PRIMARY KEY IDENTITY" 515 return "id bigint NOT NULL PRIMARY KEY IDENTITY"
512 if name == "deleted": 516 if name == "deleted":
513 return "deleted bit DEFAULT 0 NOT NULL" 517 return "deleted bit DEFAULT 0 NOT NULL"
@@ -520,35 +524,34 @@ class SQLServerConvertor(Convertor): @@ -520,35 +524,34 @@ class SQLServerConvertor(Convertor):
520 524
521 table_name = ddl["table_name"].lower() 525 table_name = ddl["table_name"].lower()
522 columns = [f"{_generate_column(col).strip()}" for col in ddl["columns"]] 526 columns = [f"{_generate_column(col).strip()}" for col in ddl["columns"]]
  527 + filed_def_list = ",\n ".join(columns)
523 script = f"""-- ---------------------------- 528 script = f"""-- ----------------------------
524 -- Table structure for {table_name} 529 -- Table structure for {table_name}
525 -- ---------------------------- 530 -- ----------------------------
526 DROP TABLE IF EXISTS {table_name}; 531 DROP TABLE IF EXISTS {table_name};
527 CREATE TABLE {table_name} ( 532 CREATE TABLE {table_name} (
528 - {',\n '.join(columns)} 533 + {filed_def_list}
529 ) 534 )
530 GO""" 535 GO"""
531 536
532 return script 537 return script
533 538
534 - def gen_comment(self, table_sql, table_name) -> str: 539 + def gen_comment(self, table_sql: str, table_name: str) -> str:
535 """生成字段及表的注释""" 540 """生成字段及表的注释"""
536 541
537 script = "" 542 script = ""
538 - for line in table_sql.split("\n"):  
539 - match = re.match(r"^`([^`]+)`.* COMMENT '([^']+)'", line.strip())  
540 - if match:  
541 - script += f"""EXEC sp_addextendedproperty  
542 - 'MS_Description', N'{match.group(2).replace('\\n', '\n')}', 543 +
  544 + for field, comment_string in self.filed_comments(table_sql):
  545 + script += f"""EXEC sp_addextendedproperty
  546 + 'MS_Description', N'{comment_string}',
543 'SCHEMA', N'dbo', 547 'SCHEMA', N'dbo',
544 'TABLE', N'{table_name}', 548 'TABLE', N'{table_name}',
545 - 'COLUMN', N'{match.group(1)}' 549 + 'COLUMN', N'{field}'
546 GO 550 GO
547 551
548 """ 552 """
549 553
550 - match = re.search(r"COMMENT \= '([^']+)';", table_sql)  
551 - table_comment = match.group(1) if match else None 554 + table_comment = self.table_comment(table_sql)
552 if table_comment: 555 if table_comment:
553 script += f"""EXEC sp_addextendedproperty 556 script += f"""EXEC sp_addextendedproperty
554 'MS_Description', N'{table_comment}', 557 'MS_Description', N'{table_comment}',
@@ -557,55 +560,34 @@ GO @@ -557,55 +560,34 @@ GO
557 GO 560 GO
558 561
559 """ 562 """
560 -  
561 return script 563 return script
562 564
563 - def gen_pk(self, table_name) -> str: 565 + def gen_pk(self, table_name: str) -> str:
564 """生成主键定义""" 566 """生成主键定义"""
565 return "" 567 return ""
566 568
567 - def gen_index(self, ddl) -> str: 569 + def gen_index(self, ddl: Dict) -> str:
568 """生成 index""" 570 """生成 index"""
  571 + return "\n".join(f"{script}\nGO" for script in self.index(ddl))
569 572
570 - def generate_columns(columns):  
571 - keys = [  
572 - f"{col['name'].lower()}{" " + col['order'].lower() if col['order'] != 'ASC' else ''}"  
573 - for col in columns[0]  
574 - ]  
575 - return ", ".join(keys)  
576 -  
577 - script = ""  
578 - for no, index in enumerate(ddl["index"], 1):  
579 - columns = generate_columns(index["columns"])  
580 - table_name = ddl["table_name"].lower()  
581 - script += f"CREATE INDEX idx_{table_name}_{no:02d} ON {table_name} ({columns})\nGO\n"  
582 -  
583 - return script  
584 -  
585 - def gen_insert(self, table_name) -> str: 573 + def gen_insert(self, table_name: str) -> str:
586 """生成 insert 语句,以及根据最后的 insert id+1 生成 Sequence""" 574 """生成 insert 语句,以及根据最后的 insert id+1 生成 Sequence"""
587 575
588 - PREFIX = f"INSERT INTO `{table_name}`"  
589 -  
590 # 收集 `table_name` 对应的 insert 语句 576 # 收集 `table_name` 对应的 insert 语句
591 inserts = [] 577 inserts = []
592 - for line in self.content.split("\n"):  
593 - if line.startswith(PREFIX):  
594 - head, tail = line.replace(PREFIX, "").split(" VALUES ", maxsplit=1)  
595 - head = head.strip().replace("`", "").lower()  
596 - tail = tail.strip().replace(r"\"", '"')  
597 - # SQLServer: 字符串前加N,hack,是否存在替换字符串内容的风险  
598 - tail = tail.replace(", '", ", N'").replace("VALUES ('", "VALUES (N')")  
599 - script = f"INSERT INTO {table_name.lower()} {head} VALUES {tail}"  
600 - # bit(1)数据转换  
601 - script = script.replace("b'0'", "'0'").replace("b'1'", "'1'")  
602 - # 删除 insert 的结尾分号  
603 - script = re.sub(";$", r"\nGO", script)  
604 - inserts.append(script) 578 + for insert_script in Convertor.inserts(table_name, self.content):
  579 + # SQLServer: 字符串前加N,hack,是否存在替换字符串内容的风险
  580 + insert_script = insert_script.replace(", '", ", N'").replace(
  581 + "VALUES ('", "VALUES (N')"
  582 + )
  583 + # 删除 insert 的结尾分号
  584 + insert_script = re.sub(";$", r"\nGO", insert_script)
  585 + inserts.append(insert_script)
605 586
606 ## 生成 insert 脚本 587 ## 生成 insert 脚本
607 script = "" 588 script = ""
608 if inserts: 589 if inserts:
  590 + inserts_lines = "\n".join(inserts)
609 script += f"""\n\n-- ---------------------------- 591 script += f"""\n\n-- ----------------------------
610 -- Records of {table_name.lower()} 592 -- Records of {table_name.lower()}
611 -- ---------------------------- 593 -- ----------------------------
@@ -614,7 +596,7 @@ BEGIN TRANSACTION @@ -614,7 +596,7 @@ BEGIN TRANSACTION
614 GO 596 GO
615 SET IDENTITY_INSERT {table_name.lower()} ON 597 SET IDENTITY_INSERT {table_name.lower()} ON
616 GO 598 GO
617 -{'\n'.join(inserts)} 599 +{inserts_lines}
618 SET IDENTITY_INSERT {table_name.lower()} OFF 600 SET IDENTITY_INSERT {table_name.lower()} OFF
619 GO 601 GO
620 COMMIT 602 COMMIT
@@ -625,10 +607,26 @@ GO @@ -625,10 +607,26 @@ GO
625 607
626 608
627 def main(): 609 def main():
628 - sql_file = pathlib.Path('../mysql/ruoyi-vue-pro.sql').resolve().as_posix()  
629 - # convertor = PostgreSQLConvertor(sql_file)  
630 - # convertor = OracleConvertor(sql_file)  
631 - convertor = SQLServerConvertor(sql_file) 610 + parser = argparse.ArgumentParser(description="芋道系统数据库转换工具")
  611 + parser.add_argument(
  612 + "type",
  613 + type=str,
  614 + help="目标数据库类型",
  615 + choices=["postgres", "oracle", "sqlserver"],
  616 + )
  617 + args = parser.parse_args()
  618 +
  619 + sql_file = pathlib.Path("../mysql/ruoyi-vue-pro.sql").resolve().as_posix()
  620 + convertor = None
  621 + if args.type == "postgres":
  622 + convertor = PostgreSQLConvertor(sql_file)
  623 + elif args.type == "oracle":
  624 + convertor = OracleConvertor(sql_file)
  625 + elif args.type == "sqlserver":
  626 + convertor = SQLServerConvertor(sql_file)
  627 + else:
  628 + raise NotImplementedError(f"不支持目标数据库类型: {args.type}")
  629 +
632 convertor.print() 630 convertor.print()
633 631
634 632