From 0aa4595c90f7539fd571967f8abfdac34d9e2889 Mon Sep 17 00:00:00 2001 From: logan Date: Sun, 24 May 2026 15:32:51 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=9F=A5=E8=AF=A2=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E5=8D=A0=E4=BD=8D=E7=AC=A6=E6=9B=BF=E6=8D=A2=20+=20=5Fauto=5Fa?= =?UTF-8?q?dd=5Flimit=20=E8=B7=B3=E8=BF=87=E9=9D=9ESELECT=E8=AF=AD?= =?UTF-8?q?=E5=8F=A5=20+=20=E9=BB=98=E8=AE=A4=E7=94=A8=E6=88=B7=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复: - getTemplateSQL() 支持 {table}/{db}/{collection} 占位符自动替换 - _auto_add_limit() 仅对 SELECT 语句添加 LIMIT, SHOW/DESC/EXPLAIN 跳过 - seed_data 默认用户密码从 '***' 改为 'admin123' - DB 中已有用户密码已重置为 admin123 测试: 75 passed (新增2个 SHOW/DESC LIMIT 测试) --- backend/app/services/query_service.py | 12 ++++++++++-- backend/tests/test_query_service.py | 13 +++++++++++++ frontend/src/lib/queryTemplates.ts | 15 ++++++++++----- frontend/src/pages/QueriesPage.tsx | 4 ++-- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/backend/app/services/query_service.py b/backend/app/services/query_service.py index a61b3fd..220ac36 100644 --- a/backend/app/services/query_service.py +++ b/backend/app/services/query_service.py @@ -415,8 +415,11 @@ class SQLQueryHandlerBase(BaseQueryHandler): if re.search(r"\b(Exec|Execute|Sp_|Xp_)", query, re.IGNORECASE): raise QuerySecurityError("存储过程调用在查询模式中被禁止") + # 不需要添加 LIMIT 的语句前缀 + _NO_LIMIT_PREFIXES = ("SHOW", "DESC", "DESCRIBE", "EXPLAIN", "WITH") + def _auto_add_limit(self, sql: str, max_rows: int = MAX_ROWS) -> str: - """如果 SQL 没有 LIMIT 则自动添加。 + """如果 SQL 没有 LIMIT 则自动添加。仅对 SELECT 语句生效。 Args: sql: 原始 SQL @@ -426,7 +429,12 @@ class SQLQueryHandlerBase(BaseQueryHandler): 添加了 LIMIT 的 SQL """ upper_sql = sql.strip().upper() - # 更精确的 LIMIT 检测(避免匹配字符串中的 LIMIT 字样) + # SHOW / DESC / EXPLAIN 等语句不支持 LIMIT(或语义不同),跳过 + if any(upper_sql.startswith(p) for p in self._NO_LIMIT_PREFIXES): + return sql + # 仅对 SELECT 语句添加 LIMIT + if not upper_sql.startswith("SELECT"): + return sql if not self._has_limit_clause(upper_sql): sql = sql.rstrip(";") + f" LIMIT {max_rows}" return sql diff --git a/backend/tests/test_query_service.py b/backend/tests/test_query_service.py index d23ce54..ae35623 100644 --- a/backend/tests/test_query_service.py +++ b/backend/tests/test_query_service.py @@ -80,6 +80,19 @@ class TestMySQLQueryHandler: assert "10000" not in result assert "LIMIT 5" in result.upper() + def test_auto_add_limit_show_no_limit(self): + """SHOW 语句不应添加 LIMIT。""" + sql = "SHOW CREATE TABLE users" + result = self.handler._auto_add_limit(sql) + assert "LIMIT" not in result.upper() + assert result == sql + + def test_auto_add_limit_desc_no_limit(self): + """DESC 语句不应添加 LIMIT。""" + sql = "DESC users" + result = self.handler._auto_add_limit(sql) + assert "LIMIT" not in result.upper() + @pytest.mark.asyncio async def test_execute_non_readonly_raises(self): """非只读查询应抛出 ValueError。""" diff --git a/frontend/src/lib/queryTemplates.ts b/frontend/src/lib/queryTemplates.ts index 260b5b6..8657c0a 100644 --- a/frontend/src/lib/queryTemplates.ts +++ b/frontend/src/lib/queryTemplates.ts @@ -134,19 +134,24 @@ export const queryTemplates: QueryTemplate[] = [ /** * 获取模板在指定数据库类型下的查询语句 - * @param template 查询模板 - * @param dbType 数据库类型 - * @returns 对应的查询语句,如果不支持则返回提示信息 + * 自动替换占位符: {table} → 当前选中的表, {db} → 当前数据库, {collection} → 集合名 */ export function getTemplateSQL( template: QueryTemplate, - dbType: DBType + dbType: DBType, + vars?: { table?: string; db?: string; collection?: string } ): string { const sql = template.sql[dbType]; if (!sql) { return `-- 当前模板「${template.name}」不支持 ${dbType} 数据库类型`; } - return sql; + let result = sql; + const v = vars || {}; + if (v.table) result = result.replace(/\{table\}/g, v.table); + if (v.db) result = result.replace(/\{db\}/g, v.db); + if (v.collection) result = result.replace(/\{collection\}/g, v.collection); + // 未替换的占位符保留,用户手动填写 + return result; } /** diff --git a/frontend/src/pages/QueriesPage.tsx b/frontend/src/pages/QueriesPage.tsx index 0b91c3a..37c1f96 100644 --- a/frontend/src/pages/QueriesPage.tsx +++ b/frontend/src/pages/QueriesPage.tsx @@ -336,10 +336,10 @@ function NewQueryPanel({ }, [templates, templateCategory, templateSearch]); const handleApplyTemplate = useCallback((tpl: QueryTemplate) => { - const templateSQL = getTemplateSQL(tpl, dbType); + const templateSQL = getTemplateSQL(tpl, dbType, { db: database || undefined }); setSql(templateSQL); setShowTemplates(false); - }, [dbType]); + }, [dbType, database]); const handleExecute = useCallback(async () => { if (!sql || !instanceId) { setError('请选择实例并输入查询语句'); return; }