""" MySQL 数据库连接组件 提供 MySQL 数据库连接和查询接口,实现与 DatabaseConnection 相同的接口。 包含自动 SQL 转换功能,将 SQL Server SQL 转换为 MySQL 兼容格式。 """ import mysql.connector from typing import List, Dict, Any, Optional import sys import os import re # 添加项目根目录到 sys.path project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if project_root not in sys.path: sys.path.insert(0, project_root) from config.defaults import DEFAULT_APP_CONFIG class MySQLConnection: """MySQL 数据库连接类,实现与 DatabaseConnection 相同的接口""" def __init__(self, config: Optional[Dict[str, Any]] = None): """ 初始化 MySQL 数据库连接 Args: config: 数据库配置字典,默认从 DEFAULT_APP_CONFIG 读取 """ if config is None: # 从默认配置获取 MySQL 配置 config = { "host": DEFAULT_APP_CONFIG.database.host, "port": DEFAULT_APP_CONFIG.database.port, "database": DEFAULT_APP_CONFIG.database.database, "user": DEFAULT_APP_CONFIG.database.username, "password": DEFAULT_APP_CONFIG.database.password, } self.config = config self.connection = None def connect(self) -> mysql.connector.MySQLConnection: """ 建立 MySQL 数据库连接 Returns: mysql.connector.MySQLConnection: 数据库连接对象 """ if self.connection is not None: return self.connection try: self.connection = mysql.connector.connect( host=self.config['host'], port=self.config['port'], database=self.config['database'], user=self.config['user'], password=self.config['password'], charset='utf8mb4', autocommit=False ) print( f"成功连接到 MySQL 数据库: {self.config['host']}:{self.config['port']}/{self.config['database']}" ) return self.connection except mysql.connector.Error as e: print(f"MySQL 数据库连接失败: {e}") raise def disconnect(self): """关闭数据库连接""" if self.connection: self.connection.close() self.connection = None print("MySQL 数据库连接已关闭") def _translate_table_name(self, sql: str) -> str: """ 将 SQL Server 表名格式转换为 MySQL 格式 转换规则: - [schema].[table] -> schema_table - 表名中的空格替换为下划线 Args: sql: SQL 语句 Returns: 转换后的 SQL 语句 """ # 匹配 [schema].[table] 格式 pattern = r'\[([a-zA-Z_][a-zA-Z0-9_]*)\]\.\[([^\]]+)\]' def replace_table_name(match): schema = match.group(1) table = match.group(2) # 将表名中的空格替换为下划线 table = table.replace(' ', '_') return f"{schema}_{table}" result = re.sub(pattern, replace_table_name, sql) return result def _translate_placeholder(self, sql: str) -> str: """ 将 SQL Server 占位符转换为 MySQL 格式 转换规则: - ? -> %s Args: sql: SQL 语句 Returns: 转换后的 SQL 语句 """ return sql.replace('?', '%s') def _translate_merge(self, sql: str) -> str: """ 将 T-SQL MERGE 语句转换为 MySQL INSERT ... ON DUPLICATE KEY UPDATE 示例输入: MERGE [dbo].[MaterialsToBeDeleted] AS target USING (SELECT ? AS MaterialCode, ? AS ManagerName) AS source ON (target.MaterialCode = source.MaterialCode) WHEN MATCHED THEN UPDATE SET ManagerName = source.ManagerName WHEN NOT MATCHED THEN INSERT (MaterialCode, ManagerName) VALUES (...); 示例输出: INSERT INTO dbo_MaterialsToBeDeleted (MaterialCode, ManagerName) VALUES (%s, %s) ON DUPLICATE KEY UPDATE ManagerName = VALUES(ManagerName); Args: sql: SQL 语句 Returns: 转换后的 SQL 语句 """ # 检测是否为 MERGE 语句 if not re.match(r'\s*MERGE', sql, re.IGNORECASE): return sql # 解析 MERGE 语句的各个部分 # 这是一个简化的实现,假设 MERGE 语句遵循标准格式 # 先转换表名,再解析 sql_with_translated_table = self._translate_table_name(sql) # 提取目标表 target_match = re.search(r'MERGE\s+(\S+)\s+AS\s+target', sql_with_translated_table, re.IGNORECASE) if not target_match: return sql target_table = target_match.group(1) # 提取 INSERT 的列 insert_match = re.search(r'INSERT\s*\(([^)]+)\)\s*VALUES\s*\(([^)]+)\)', sql, re.IGNORECASE) if not insert_match: return sql columns = insert_match.group(1).strip() values_part = insert_match.group(2).strip() # 提取 UPDATE 部分 update_match = re.search(r'UPDATE\s+SET\s+([^\s]+)\s*=\s*source\.([^\s]+)', sql, re.IGNORECASE) if not update_match: return sql update_column = update_match.group(1) # 从 USING 子句中统计占位符数量 # 匹配: USING (SELECT ? AS MaterialCode, ? AS ManagerName) AS source using_match = re.search(r'USING\s*\((.+)\)\s+AS\s+source', sql, re.IGNORECASE | re.DOTALL) if not using_match: return sql using_clause = using_match.group(1) # 从 SELECT 部分提取 select_match = re.search(r'SELECT\s+(.+)', using_clause, re.IGNORECASE) if not select_match: return sql using_select = select_match.group(1) # 计算占位符(?)的数量 placeholder_count = using_select.count('?') # 生成相应数量的 %s 占位符 mysql_placeholders = ', '.join(['%s'] * placeholder_count) # 构建 MySQL INSERT ... ON DUPLICATE KEY UPDATE 语句 mysql_sql = f""" INSERT INTO {target_table} ({columns}) VALUES ({mysql_placeholders}) ON DUPLICATE KEY UPDATE {update_column} = VALUES({update_column}) """.strip() return mysql_sql def _translate_sql(self, sql: str) -> str: """ 将 SQL Server SQL 转换为 MySQL 兼容格式 转换顺序: 1. 表名转换 ([schema].[table] -> schema_table) 2. 占位符转换 (? -> %s) 3. MERGE 语句转换 Args: sql: SQL 语句 Returns: 转换后的 SQL 语句 """ result = sql # 1. 表名转换 result = self._translate_table_name(result) # 2. 占位符转换 result = self._translate_placeholder(result) # 3. MERGE 语句转换 if re.match(r'\s*MERGE', sql, re.IGNORECASE): result = self._translate_merge(sql) # MERGE 转换已经处理了表名和占位符,所以需要重新处理 result = self._translate_table_name(result) result = self._translate_placeholder(result) return result def execute_query( self, sql: str, params: Optional[tuple] = None ) -> List[Dict[str, Any]]: """ 执行查询语句并返回结果 Args: sql: SQL 查询语句 params: 查询参数(可选) Returns: List[Dict[str, Any]]: 查询结果列表,每个元素为一行数据的字典 """ if not self.connection: self.connect() # 转换 SQL translated_sql = self._translate_sql(sql) cursor = self.connection.cursor(dictionary=True) try: if params: cursor.execute(translated_sql, params) else: cursor.execute(translated_sql) # 将结果转换为字典列表 results = cursor.fetchall() return results except mysql.connector.Error as e: print(f"查询执行失败: {e}") print(f"原始 SQL: {sql}") print(f"转换后 SQL: {translated_sql}") if params: print(f"参数: {params}") raise finally: cursor.close() def execute_update(self, sql: str, params: Optional[tuple] = None) -> int: """ 执行更新/插入/删除语句 Args: sql: SQL 语句 params: 参数(可选) Returns: int: 受影响的行数 """ if not self.connection: self.connect() # 转换 SQL translated_sql = self._translate_sql(sql) cursor = self.connection.cursor() try: if params: cursor.execute(translated_sql, params) else: cursor.execute(translated_sql) self.connection.commit() return cursor.rowcount except mysql.connector.Error as e: self.connection.rollback() print(f"执行失败,已回滚: {e}") print(f"原始 SQL: {sql}") print(f"转换后 SQL: {translated_sql}") if params: print(f"参数: {params}") raise finally: cursor.close() def __enter__(self): """支持 with 语句的上下文管理器入口""" self.connect() return self def __exit__(self, exc_type, exc_val, exc_tb): """支持 with 语句的上下文管理器出口""" self.disconnect()