Build lightweight AI agent admin

This commit is contained in:
Codex
2026-06-08 18:14:59 +08:00
commit e164840f43
2530 changed files with 435693 additions and 0 deletions
@@ -0,0 +1,725 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""SQL Server 异步处理器(aioodbc"""
import logging
import time
from typing import Any, Dict, List, Optional
from core.database_manager.handlers.common import (
format_connection_error,
format_size,
log_database_connect_failure,
serialize_row,
)
from core.database_manager.handlers.pools import get_aioodbc_pool
from core.database_manager.handlers.transaction_mixin import HandlerTransactionMixin
from core.database_manager.sql_utils import quote_identifier, quote_table, split_sql_statements
logger = logging.getLogger(__name__)
def _normalize_mssql_default(value: Optional[str]) -> Optional[str]:
"""Strip SQL Server INFORMATION_SCHEMA default wrappers, e.g. ((0)) -> 0."""
if not value:
return None
normalized = value.strip()
while normalized.startswith("(") and normalized.endswith(")"):
normalized = normalized[1:-1].strip()
if not normalized or normalized.upper() == "NULL":
return None
return normalized
_MSSQL_SYSTEM_SCHEMAS = (
"sys",
"INFORMATION_SCHEMA",
"guest",
"db_owner",
"db_accessadmin",
"db_securityadmin",
"db_ddladmin",
"db_backupoperator",
"db_datareader",
"db_datawriter",
"db_denydatareader",
"db_denydatawriter",
)
class SQLServerHandler(HandlerTransactionMixin):
"""SQL Server 异步处理器"""
def __init__(
self,
host: str,
port: int,
user: str,
password: str,
database: str,
extra_options: Optional[Dict[str, Any]] = None,
):
self.host = host
self.port = port
self.user = user
self.password = password
self.database = database or "master"
self.extra_options = extra_options or {}
self.conn = None
self._pool = None
self._last_connect_error: Optional[str] = None
self._in_transaction = False
async def _release_connection(self) -> None:
if self.conn and self._pool:
try:
await self._pool.release(self.conn)
except Exception as exc:
logger.warning("Error releasing SQL Server connection: %s", exc)
self.conn = None
self._pool = None
async def connect(self, database: str = None) -> bool:
db = database or self.database or "master"
try:
await self._release_connection()
pool = await get_aioodbc_pool(
self.host,
self.port,
self.user,
self.password,
db,
self.extra_options,
)
self._pool = pool
self.conn = await pool.acquire()
self._last_connect_error = None
return True
except Exception as e:
self._last_connect_error = format_connection_error(e)
log_database_connect_failure(
db_type="sqlserver",
host=self.host,
port=self.port,
user=self.user,
database=db,
detail=self._last_connect_error,
)
self.conn = None
self._pool = None
return False
async def close(self):
await self._release_connection()
async def _execute_query(self, query: str, params: tuple = None) -> List[Dict[str, Any]]:
async with self.conn.cursor() as cursor:
await cursor.execute(query, params or ())
if not cursor.description:
return []
columns = [col[0] for col in cursor.description]
rows = await cursor.fetchall()
return [dict(zip(columns, row)) for row in rows]
async def _execute_command(self, command: str, params: tuple = None) -> int:
async with self.conn.cursor() as cursor:
await cursor.execute(command, params or ())
return cursor.rowcount
async def _run_sql_query(self, sql: str) -> list:
return await self._execute_query(sql)
async def _run_sql_command(self, sql: str) -> int:
return await self._execute_command(sql)
async def _exec_tx_statement(self, sql: str) -> None:
upper = sql.strip().upper()
if upper == "BEGIN":
await self._run_sql_command("BEGIN TRANSACTION")
elif upper == "COMMIT":
await self._run_sql_command("COMMIT TRANSACTION")
elif upper == "ROLLBACK":
await self._run_sql_command("ROLLBACK TRANSACTION")
else:
await self._run_sql_command(sql)
async def get_databases(self) -> List[Dict[str, Any]]:
try:
if not await self.connect("master"):
return []
query = """
SELECT d.name,
SUSER_SNAME(d.owner_sid) AS owner,
d.collation_name AS collation,
CAST(
(SELECT SUM(CAST(mf.size AS BIGINT)) * 8192
FROM sys.master_files mf
WHERE mf.database_id = d.database_id) AS BIGINT
) AS size_bytes
FROM sys.databases d
WHERE d.state = 0
ORDER BY d.name
"""
databases = await self._execute_query(query)
for db in databases:
size_bytes = int(db.get("size_bytes") or 0)
db["size"] = format_size(size_bytes)
db["size_bytes"] = size_bytes
return databases
except Exception as e:
logger.error("Failed to get databases: %s", e)
return []
finally:
await self.close()
async def create_database(self, name: str, **kwargs) -> bool:
try:
if not await self.connect("master"):
return False
qname = quote_identifier(name, "sqlserver")
await self._execute_command(f"CREATE DATABASE {qname}")
return True
except Exception as e:
logger.error("Failed to create database %s: %s", name, e)
raise
finally:
await self.close()
async def drop_database(self, name: str) -> bool:
try:
if not await self.connect("master"):
return False
qname = quote_identifier(name, "sqlserver")
await self._execute_command(
f"ALTER DATABASE {qname} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
)
await self._execute_command(f"DROP DATABASE {qname}")
return True
except Exception as e:
logger.error("Failed to drop database %s: %s", name, e)
raise
finally:
await self.close()
async def rename_database(self, name: str, new_name: str) -> bool:
try:
if not await self.connect("master"):
return False
old_q = quote_identifier(name, "sqlserver")
new_q = quote_identifier(new_name, "sqlserver")
await self._execute_command(f"ALTER DATABASE {old_q} MODIFY NAME = {new_q}")
return True
except Exception as e:
logger.error("Failed to rename database %s to %s: %s", name, new_name, e)
raise
finally:
await self.close()
async def create_schema(self, name: str, database: str = None, owner: str = None) -> bool:
try:
if not await self.connect(database):
return False
qname = quote_identifier(name, "sqlserver")
query = f"CREATE SCHEMA {qname}"
if owner:
query += f" AUTHORIZATION {quote_identifier(owner, 'sqlserver')}"
await self._execute_command(query)
return True
except Exception as e:
logger.error("Failed to create schema %s: %s", name, e)
raise
finally:
await self.close()
async def drop_schema(self, name: str, database: str = None, cascade: bool = True) -> bool:
try:
if not await self.connect(database):
return False
qname = quote_identifier(name, "sqlserver")
await self._execute_command(f"DROP SCHEMA {qname}")
return True
except Exception as e:
logger.error("Failed to drop schema %s: %s", name, e)
raise
finally:
await self.close()
async def rename_schema(self, name: str, new_name: str, database: str = None) -> bool:
raise NotImplementedError("SQL Server 不支持直接重命名 Schema")
async def get_schemas(self, database: str = None) -> List[Dict[str, Any]]:
try:
if not await self.connect(database):
return []
placeholders = ", ".join(["?"] * len(_MSSQL_SYSTEM_SCHEMAS))
query = f"""
SELECT s.name,
USER_NAME(s.principal_id) AS owner,
(SELECT COUNT(*)
FROM sys.tables t
WHERE t.schema_id = s.schema_id) AS tables_count
FROM sys.schemas s
WHERE s.name NOT IN ({placeholders})
AND s.name NOT LIKE 'db[_]%'
ORDER BY s.name
"""
return await self._execute_query(query, _MSSQL_SYSTEM_SCHEMAS)
except Exception as e:
logger.error("Failed to get schemas: %s", e)
return []
finally:
await self.close()
async def get_tables(
self, database: str = None, schema_name: str = "dbo"
) -> List[Dict[str, Any]]:
try:
if not await self.connect(database):
return []
query = """
SELECT t.TABLE_SCHEMA AS schema_name,
t.TABLE_NAME AS table_name,
t.TABLE_TYPE AS table_type,
CAST(p.rows AS BIGINT) AS row_count
FROM INFORMATION_SCHEMA.TABLES t
LEFT JOIN sys.tables st
ON st.name = t.TABLE_NAME
AND SCHEMA_NAME(st.schema_id) = t.TABLE_SCHEMA
LEFT JOIN sys.partitions p
ON st.object_id = p.object_id
AND p.index_id IN (0, 1)
WHERE t.TABLE_SCHEMA = ?
AND t.TABLE_TYPE = 'BASE TABLE'
ORDER BY t.TABLE_NAME
"""
return await self._execute_query(query, (schema_name,))
except Exception as e:
logger.error("Failed to get tables: %s", e)
return []
finally:
await self.close()
async def get_table_columns(
self, table_name: str, schema_name: str = "dbo", database: str = None
) -> List[Dict[str, Any]]:
try:
if not await self.connect(database):
return []
query = """
SELECT c.COLUMN_NAME AS column_name,
c.DATA_TYPE AS data_type,
CASE WHEN c.IS_NULLABLE = 'YES' THEN 1 ELSE 0 END AS is_nullable,
c.COLUMN_DEFAULT AS column_default,
c.CHARACTER_MAXIMUM_LENGTH AS character_maximum_length,
c.NUMERIC_PRECISION AS numeric_precision,
c.NUMERIC_SCALE AS numeric_scale,
c.ORDINAL_POSITION AS ordinal_position,
CASE WHEN pk.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END AS is_primary_key,
CASE WHEN uq.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END AS is_unique,
ep.value AS description
FROM INFORMATION_SCHEMA.COLUMNS c
LEFT JOIN (
SELECT ku.TABLE_SCHEMA, ku.TABLE_NAME, ku.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE ku
ON tc.CONSTRAINT_NAME = ku.CONSTRAINT_NAME
AND tc.TABLE_SCHEMA = ku.TABLE_SCHEMA
AND tc.TABLE_NAME = ku.TABLE_NAME
WHERE tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
) pk ON pk.TABLE_SCHEMA = c.TABLE_SCHEMA
AND pk.TABLE_NAME = c.TABLE_NAME
AND pk.COLUMN_NAME = c.COLUMN_NAME
LEFT JOIN (
SELECT ku.TABLE_SCHEMA, ku.TABLE_NAME, ku.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE ku
ON tc.CONSTRAINT_NAME = ku.CONSTRAINT_NAME
AND tc.TABLE_SCHEMA = ku.TABLE_SCHEMA
AND tc.TABLE_NAME = ku.TABLE_NAME
WHERE tc.CONSTRAINT_TYPE = 'UNIQUE'
) uq ON uq.TABLE_SCHEMA = c.TABLE_SCHEMA
AND uq.TABLE_NAME = c.TABLE_NAME
AND uq.COLUMN_NAME = c.COLUMN_NAME
LEFT JOIN sys.schemas ss ON ss.name = c.TABLE_SCHEMA
LEFT JOIN sys.tables st ON st.schema_id = ss.schema_id AND st.name = c.TABLE_NAME
LEFT JOIN sys.columns sc ON sc.object_id = st.object_id AND sc.name = c.COLUMN_NAME
LEFT JOIN sys.extended_properties ep
ON ep.major_id = sc.object_id
AND ep.minor_id = sc.column_id
AND ep.name = 'MS_Description'
WHERE c.TABLE_SCHEMA = ? AND c.TABLE_NAME = ?
ORDER BY c.ORDINAL_POSITION
"""
columns = await self._execute_query(query, (schema_name, table_name))
for col in columns:
col["column_default"] = _normalize_mssql_default(col.get("column_default"))
return columns
except Exception as e:
logger.error("Failed to get table columns: %s", e)
return []
finally:
await self.close()
async def get_table_indexes(
self, table_name: str, schema_name: str = "dbo", database: str = None
) -> List[Dict[str, Any]]:
try:
if not await self.connect(database):
return []
query = """
SELECT i.name AS index_name,
i.type_desc AS index_type,
STUFF((
SELECT ', ' + c.name
FROM sys.index_columns ic
JOIN sys.columns c
ON ic.object_id = c.object_id
AND ic.column_id = c.column_id
WHERE ic.object_id = i.object_id
AND ic.index_id = i.index_id
ORDER BY ic.key_ordinal
FOR XML PATH('')
), 1, 2, '') AS columns,
i.is_unique,
i.is_primary_key AS is_primary,
'' AS definition
FROM sys.indexes i
JOIN sys.tables t ON i.object_id = t.object_id
JOIN sys.schemas s ON t.schema_id = s.schema_id
WHERE s.name = ? AND t.name = ? AND i.name IS NOT NULL
ORDER BY i.name
"""
indexes = await self._execute_query(query, (schema_name, table_name))
for idx in indexes:
unique = "UNIQUE " if idx.get("is_unique") else ""
idx["definition"] = f"CREATE {unique}INDEX {idx['index_name']} ON {schema_name}.{table_name} ({idx.get('columns', '')})"
return indexes
except Exception as e:
logger.error("Failed to get table indexes: %s", e)
return []
finally:
await self.close()
async def get_table_constraints(
self, table_name: str, schema_name: str = "dbo", database: str = None
) -> List[Dict[str, Any]]:
try:
if not await self.connect(database):
return []
query = """
SELECT tc.CONSTRAINT_NAME AS constraint_name,
tc.CONSTRAINT_TYPE AS constraint_type,
STUFF((
SELECT ', ' + kcu.COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
WHERE kcu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
AND kcu.TABLE_SCHEMA = tc.TABLE_SCHEMA
AND kcu.TABLE_NAME = tc.TABLE_NAME
FOR XML PATH('')
), 1, 2, '') AS columns,
kcu2.TABLE_NAME AS referenced_table,
STUFF((
SELECT ', ' + fk.COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE fk
WHERE fk.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
AND fk.TABLE_SCHEMA = tc.TABLE_SCHEMA
FOR XML PATH('')
), 1, 2, '') AS referenced_columns,
'' AS definition
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
ON tc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
AND tc.TABLE_SCHEMA = rc.CONSTRAINT_SCHEMA
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu2
ON rc.UNIQUE_CONSTRAINT_NAME = kcu2.CONSTRAINT_NAME
WHERE tc.TABLE_SCHEMA = ? AND tc.TABLE_NAME = ?
ORDER BY tc.CONSTRAINT_TYPE, tc.CONSTRAINT_NAME
"""
constraints = await self._execute_query(query, (schema_name, table_name))
for const in constraints:
const["definition"] = f"{const['constraint_type']} ({const.get('columns', '')})"
return constraints
except Exception as e:
logger.error("Failed to get table constraints: %s", e)
return []
finally:
await self.close()
async def get_table_structure(
self, table_name: str, database: str = None, schema_name: str = "dbo"
) -> Dict[str, Any]:
tables = await self.get_tables(database=database, schema_name=schema_name)
table_info = next((t for t in tables if t["table_name"] == table_name), None)
if not table_info:
raise ValueError(f"Table {schema_name}.{table_name} not found")
columns = await self.get_table_columns(table_name, schema_name, database)
indexes = await self.get_table_indexes(table_name, schema_name, database)
constraints = await self.get_table_constraints(table_name, schema_name, database)
return {
"table_info": table_info,
"columns": columns,
"indexes": indexes,
"constraints": constraints,
}
async def get_table_ddl(
self, table_name: str, schema_name: str = "dbo", database: str = None
) -> str:
try:
if not await self.connect(database):
return "-- 无法连接数据库"
full_name = quote_table(schema_name, table_name, "sqlserver")
rows = await self._execute_query(
"SELECT OBJECT_DEFINITION(OBJECT_ID(?)) AS ddl",
(f"{schema_name}.{table_name}",),
)
if rows and rows[0].get("ddl"):
return rows[0]["ddl"]
columns = await self.get_table_columns(table_name, schema_name, database)
if not columns:
return f"-- 无法获取表 {full_name} 的DDL"
ddl_lines = [f"CREATE TABLE {full_name} ("]
col_defs = []
for col in columns:
col_def = f" {quote_identifier(col['column_name'], 'sqlserver')} {col['data_type']}"
if col.get("character_maximum_length"):
col_def += f"({col['character_maximum_length']})"
if not col.get("is_nullable"):
col_def += " NOT NULL"
col_defs.append(col_def)
ddl_lines.append(",\n".join(col_defs))
ddl_lines.append(");")
return "\n".join(ddl_lines)
except Exception as e:
return f"-- 获取DDL失败: {str(e)}"
finally:
await self.close()
async def get_views(
self, database: str = None, schema_name: str = "dbo"
) -> List[Dict[str, Any]]:
try:
if not await self.connect(database):
return []
query = """
SELECT TABLE_NAME AS view_name,
TABLE_SCHEMA AS schema_name,
VIEW_DEFINITION AS view_definition,
CASE WHEN IS_UPDATABLE = 'YES' THEN 1 ELSE 0 END AS is_updatable,
CHECK_OPTION AS check_option,
'VIEW' AS view_type
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_SCHEMA = ?
ORDER BY TABLE_NAME
"""
return await self._execute_query(query, (schema_name,))
except Exception as e:
logger.error("Failed to get views: %s", e)
return []
finally:
await self.close()
async def get_view_structure(self, view_name: str, schema_name: str = "dbo") -> Dict[str, Any]:
try:
if not await self.connect():
raise ValueError("Failed to connect")
view_query = """
SELECT TABLE_NAME AS view_name, TABLE_SCHEMA AS schema_name,
VIEW_DEFINITION AS view_definition,
CASE WHEN IS_UPDATABLE = 'YES' THEN 1 ELSE 0 END AS is_updatable,
CHECK_OPTION AS check_option, 'VIEW' AS view_type
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
"""
view_info = await self._execute_query(view_query, (schema_name, view_name))
if not view_info:
raise ValueError(f"View {schema_name}.{view_name} not found")
view_info = view_info[0]
columns = await self.get_table_columns(view_name, schema_name)
definition_sql = await self.get_view_definition(view_name, schema_name)
dependencies = await self.get_view_dependencies(view_name, schema_name)
return {
"view_info": view_info,
"columns": columns,
"dependencies": dependencies,
"definition_sql": definition_sql,
}
except Exception as e:
logger.error("Failed to get view structure: %s", e)
raise
finally:
await self.close()
async def get_view_definition(self, view_name: str, schema_name: str = "dbo") -> str:
try:
if not await self.connect():
return "-- 无法连接数据库"
rows = await self._execute_query(
"""
SELECT VIEW_DEFINITION AS definition
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
""",
(schema_name, view_name),
)
if rows and rows[0].get("definition"):
full = quote_table(schema_name, view_name, "sqlserver")
return f"CREATE VIEW {full} AS\n{rows[0]['definition']}"
return f"-- 无法获取视图 {schema_name}.{view_name} 的定义"
except Exception as e:
return f"-- 获取视图定义失败: {str(e)}"
finally:
await self.close()
async def get_view_dependencies(self, view_name: str, schema_name: str = "dbo") -> List[str]:
try:
if not await self.connect():
return []
rows = await self._execute_query(
"""
SELECT DISTINCT REFERENCED_ENTITY_NAME AS table_name
FROM sys.sql_expression_dependencies d
JOIN sys.views v ON d.referencing_id = v.object_id
JOIN sys.schemas s ON v.schema_id = s.schema_id
WHERE s.name = ? AND v.name = ?
AND d.referenced_entity_name IS NOT NULL
ORDER BY REFERENCED_ENTITY_NAME
""",
(schema_name, view_name),
)
return [row["table_name"] for row in rows]
except Exception as e:
logger.error("Failed to get view dependencies: %s", e)
return []
finally:
await self.close()
async def query_data(
self,
table_name: str,
schema_name: str = None,
page: int = 1,
page_size: int = 20,
where: str = None,
order_by: str = None,
database: str = None,
) -> Dict[str, Any]:
try:
if not await self.connect(database):
return {"columns": [], "rows": [], "total": 0, "page": page, "page_size": page_size}
schema_name = schema_name or "dbo"
full_table = quote_table(schema_name, table_name, "sqlserver")
count_q = f"SELECT COUNT(*) AS total FROM {full_table}"
if where:
count_q += f" WHERE {where}"
count_result = await self._execute_query(count_q)
total = count_result[0]["total"] if count_result else 0
offset = (page - 1) * page_size
data_q = f"SELECT * FROM {full_table}"
if where:
data_q += f" WHERE {where}"
if order_by:
data_q += f" ORDER BY {order_by}"
data_q += f" OFFSET {offset} ROWS FETCH NEXT {page_size} ROWS ONLY"
rows = await self._execute_query(data_q)
rows_list = [serialize_row(row) for row in rows]
columns = list(rows_list[0].keys()) if rows_list else []
return {
"columns": columns,
"rows": rows_list,
"total": total,
"page": page,
"page_size": page_size,
}
except Exception as e:
logger.error("Failed to query data: %s", e)
return {"columns": [], "rows": [], "total": 0, "page": page, "page_size": page_size}
finally:
await self.close()
async def insert_data(
self, table_name: str, data: Dict[str, Any], schema_name: str = None
) -> Dict[str, Any]:
try:
if not await self.connect():
return {"success": False, "message": "数据库连接失败", "affected_rows": 0}
schema_name = schema_name or "dbo"
full_table = quote_table(schema_name, table_name, "sqlserver")
columns = list(data.keys())
quoted_columns = ", ".join(
quote_identifier(col, "sqlserver") for col in columns
)
placeholders = ", ".join(["?"] * len(columns))
query = f"INSERT INTO {full_table} ({quoted_columns}) VALUES ({placeholders})"
affected_rows = await self._execute_command(query, tuple(data.values()))
return {"success": True, "message": "插入成功", "affected_rows": affected_rows or 1}
except Exception as e:
return {"success": False, "message": str(e), "affected_rows": 0}
finally:
await self.close()
async def update_data(
self, table_name: str, data: Dict[str, Any], where: str, schema_name: str = None
) -> Dict[str, Any]:
try:
if not await self.connect():
return {"success": False, "message": "数据库连接失败", "affected_rows": 0}
schema_name = schema_name or "dbo"
full_table = quote_table(schema_name, table_name, "sqlserver")
set_clause = ", ".join(
f"{quote_identifier(k, 'sqlserver')} = ?" for k in data.keys()
)
query = f"UPDATE {full_table} SET {set_clause} WHERE {where}"
affected_rows = await self._execute_command(query, tuple(data.values()))
return {
"success": True,
"message": f"更新成功,影响 {affected_rows}",
"affected_rows": affected_rows,
}
except Exception as e:
return {"success": False, "message": str(e), "affected_rows": 0}
finally:
await self.close()
async def delete_data(
self, table_name: str, where: str, schema_name: str = None
) -> Dict[str, Any]:
try:
if not await self.connect():
return {"success": False, "message": "数据库连接失败", "affected_rows": 0}
schema_name = schema_name or "dbo"
full_table = quote_table(schema_name, table_name, "sqlserver")
query = f"DELETE FROM {full_table} WHERE {where}"
affected_rows = await self._execute_command(query)
return {
"success": True,
"message": f"删除成功,影响 {affected_rows}",
"affected_rows": affected_rows,
}
except Exception as e:
return {"success": False, "message": str(e), "affected_rows": 0}
finally:
await self.close()
async def execute_ddl(
self, sql: str, database: str = None, schema_name: str = None
) -> Dict[str, Any]:
try:
if not await self.connect(database):
return {"success": False, "message": "数据库连接失败", "affected_rows": 0}
statements = split_sql_statements(sql)
if not statements:
return {"success": False, "message": "无有效SQL语句", "affected_rows": 0}
for index, statement in enumerate(statements, start=1):
try:
await self._execute_command(statement)
except Exception as exc:
return {
"success": False,
"message": f"DDL执行失败 (第{index}条): {exc}",
"affected_rows": 0,
}
return {"success": True, "message": "DDL执行成功", "affected_rows": 0}
except Exception as e:
return {"success": False, "message": f"DDL执行失败: {str(e)}", "affected_rows": 0}
finally:
await self.close()