import sys
import pymysql
import os
from PyQt5.QtWidgets import (
QWidget, QApplication, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QPushButton,
QCheckBox, QScrollArea, QFileDialog, QMessageBox, QRadioButton
)
from PyQt5.QtCore import Qt
import traceback
import re
class DatabaseExporter(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("MySQL 数据库导出为 SQLite 兼容 SQL")
self.default_host = "localhost"
self.default_user = "root"
self.default_password = "123456" # 替换为你自己的密码
self.select_all_state = False
self.resize(700, 800)
self.init_ui()
def init_ui(self):
layout = QVBoxLayout()
# 数据库选择
self.database_combobox = QComboBox()
self.database_combobox.addItem("") # 空白选项
self.database_combobox.addItem("疾病库")
self.database_combobox.addItem("智能中医系统")
self.database_combobox.addItem("实验")
self.load_tables_button = QPushButton("加载表名")
self.load_tables_button.clicked.connect(self.load_tables)
layout.addWidget(QLabel("数据库名称:"))
layout.addWidget(self.database_combobox)
layout.addWidget(self.load_tables_button)
# 表选择按钮
table_button_layout = QHBoxLayout()
self.select_all_button = QPushButton("全选")
self.select_all_button.clicked.connect(self.toggle_select_all)
table_button_layout.addWidget(self.select_all_button)
layout.addLayout(table_button_layout)
# 滚动区域显示表
self.scroll_area = QScrollArea()
self.scroll_content = QWidget()
self.scroll_layout = QVBoxLayout(self.scroll_content)
self.scroll_content.setLayout(self.scroll_layout)
self.scroll_area.setWidget(self.scroll_content)
self.scroll_area.setWidgetResizable(True)
layout.addWidget(QLabel("选择要导出的表:"))
layout.addWidget(self.scroll_area)
# 导出格式选择
export_format_layout = QHBoxLayout()
self.overwrite_sql_radio = QRadioButton("覆盖性 SQLite SQL")
self.append_sql_radio = QRadioButton("添加性 SQLite SQL")
self.overwrite_sql_radio.setChecked(True)
export_format_layout.addWidget(self.overwrite_sql_radio)
export_format_layout.addWidget(self.append_sql_radio)
layout.addLayout(export_format_layout)
# 导出按钮
self.export_button = QPushButton("导出为 SQLite SQL")
self.export_button.clicked.connect(self.export_database)
layout.addWidget(self.export_button)
self.setLayout(layout)
self.tables = []
def toggle_select_all(self):
self.select_all_state = not self.select_all_state
for i in range(self.scroll_layout.count()):
widget = self.scroll_layout.itemAt(i).widget()
if isinstance(widget, QCheckBox):
widget.setChecked(self.select_all_state)
self.select_all_button.setText("取消全选" if self.select_all_state else "全选")
def load_tables(self):
database = self.database_combobox.currentText()
if not database:
QMessageBox.warning(self, "输入错误", "请选择一个数据库")
return
try:
connection = pymysql.connect(
host=self.default_host,
user=self.default_user,
password=self.default_password,
database=database,
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
with connection.cursor() as cursor:
cursor.execute("SHOW TABLES")
self.tables = [table[f"Tables_in_{database}"] for table in cursor.fetchall()]
# 清空滚动区域
for i in reversed(range(self.scroll_layout.count())):
widget = self.scroll_layout.itemAt(i).widget()
if widget:
widget.setParent(None)
if not self.tables:
self.scroll_layout.addWidget(QLabel("该数据库中没有表"))
else:
for table in self.tables:
cb = QCheckBox(table)
self.scroll_layout.addWidget(cb)
except Exception as e:
QMessageBox.critical(self, "数据库错误", str(e))
finally:
if 'connection' in locals() and connection.open:
connection.close()
def convert_to_sqlite_create(self, create_sql, table_name):
# 替换表名
create_sql = re.sub(r'CREATE TABLE `.*?`', f'CREATE TABLE "{table_name}"', create_sql)
# 替换字段类型
create_sql = re.sub(r'varchar$[0-9]+$|char$[0-9]+$|text|longtext|mediumtext', 'TEXT', create_sql)
create_sql = re.sub(r'int unsigned|bigint unsigned|int|integer', 'INTEGER', create_sql)
create_sql = re.sub(r'datetime|date|timestamp', 'TEXT', create_sql)
create_sql = re.sub(r'decimal$[0-9,]+$|float|double|real', 'REAL', create_sql)
create_sql = re.sub(r'blob|mediumblob|longblob', 'BLOB', create_sql)
create_sql = re.sub(r'boolean|bool', 'INTEGER', create_sql)
# 主键自增替换
create_sql = re.sub(r'`id` int(?: unsigned)? NOT NULL AUTO_INCREMENT', '`id` INTEGER PRIMARY KEY', create_sql)
# 删除 KEY 定义
create_sql = re.sub(r',\s*(?:UNIQUE\s+KEY|KEY|PRIMARY KEY)[^,]*$', '', create_sql, flags=re.MULTILINE)
# 删除 ENGINE, CHARSET 等
create_sql = re.sub(r'(?:ENGINE|CHARSET|COLLATE|ROW_FORMAT|AUTO_INCREMENT)=.*?(\s|$)', '', create_sql)
return create_sql
def export_table_to_sqlite(self, connection, database, table_name, export_dir, overwrite=True):
file_path = os.path.join(export_dir, f"{table_name}_sqlite.sql")
mode = 'w' if overwrite else 'a'
try:
with open(file_path, mode, encoding='utf-8') as f:
with connection.cursor() as cursor:
# 获取表结构
try:
cursor.execute(f"SHOW CREATE TABLE `{table_name}`")
result = cursor.fetchone()
create_sql = result['Create Table'] # ✅ 正确的字段名
except Exception as e:
print(f"[{table_name}] 获取表结构失败:")
traceback.print_exc()
QMessageBox.critical(self, "SQL错误", f"获取表结构失败: {e}")
return
sqlite_create_sql = self.convert_to_sqlite_create(create_sql, table_name)
if overwrite:
f.write(f"-- 表结构: {table_name}\n")
f.write(f"DROP TABLE IF EXISTS \"{table_name}\";\n")
f.write(f"{sqlite_create_sql};\n\n")
# 获取数据
try:
cursor.execute(f"SELECT * FROM `{table_name}`")
rows = cursor.fetchall()
if not rows:
print(f"表 {table_name} 中没有数据")
return
except Exception as e:
print(f"[{table_name}] 查询表数据失败:")
traceback.print_exc()
QMessageBox.critical(self, "SQL错误", f"查询表数据失败: {e}")
return
columns = list(rows[0].keys())
# 构建插入语句
column_str = ", ".join(f'"{col}"' for col in columns)
for row in rows:
values = []
for val in row.values():
if isinstance(val, str):
escaped_val = val.replace("'", "''")
values.append(f"'{escaped_val}'")
elif isinstance(val, (bytes, bytearray)):
hex_val = "x'" + val.hex() + "'"
values.append(hex_val)
elif isinstance(val, (int, float)):
values.append(str(val))
elif val is None:
values.append("NULL")
else:
values.append(f"'{str(val)}'")
value_str = ", ".join(values)
f.write(f"INSERT INTO \"{table_name}\" ({column_str}) VALUES ({value_str});\n")
f.write("\n")
except Exception as e:
print(f"[{table_name}] 写入文件失败:")
traceback.print_exc()
QMessageBox.critical(self, "文件写入错误", f"写入 SQL 文件失败: {e}")
def export_database(self):
database = self.database_combobox.currentText()
if not database:
QMessageBox.warning(self, "输入错误", "请输入数据库名称")
return
selected_tables = []
for i in range(self.scroll_layout.count()):
widget = self.scroll_layout.itemAt(i).widget()
if isinstance(widget, QCheckBox) and widget.isChecked():
selected_tables.append(widget.text())
if not selected_tables:
QMessageBox.warning(self, "未选择表", "请选择至少一个表进行导出")
return
try:
connection = pymysql.connect(
host=self.default_host,
user=self.default_user,
password=self.default_password,
database=database,
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
print(f"已连接到数据库: {database}")
export_dir = QFileDialog.getExistingDirectory(self, "选择导出目录")
if not export_dir:
QMessageBox.warning(self, "路径无效", "未选择导出目录")
return
print(f"导出目录: {export_dir}")
overwrite = self.overwrite_sql_radio.isChecked()
for table in selected_tables:
print(f"正在导出表: {table}")
self.export_table_to_sqlite(connection, database, table, export_dir, overwrite=overwrite)
QMessageBox.information(self, "成功", "所选表已成功导出为 SQLite 兼容 SQL 文件")
except Exception as e:
print("发生异常:")
traceback.print_exc() # 打印完整堆栈
QMessageBox.critical(self, "错误", f"发生错误:{str(e)}\n\n详情请查看控制台输出")
finally:
if 'connection' in locals() and connection.open:
connection.close()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = DatabaseExporter()
window.show()
sys.exit(app.exec_())
所以我这里应该怎么修改
最新发布