mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-17 02:42:06 +08:00
- 修正完全模式下 DML 与过程调用的安全提示和限制说明 - 区分连接探针失败与可恢复 SQL 执行错误,避免数据探针被误终止 - 修复本地 execute_sql 写语句结果返回 affectedRows - 补充 AI 安全、本地工具执行与 SQL 限制回归测试
38 lines
1.4 KiB
TypeScript
38 lines
1.4 KiB
TypeScript
import { buildPaginatedSelectSQL } from './sql';
|
|
import { resolveSqlDialect } from './sqlDialect';
|
|
|
|
const AI_READONLY_SQL_KEYWORDS = new Set(['select', 'show', 'describe', 'desc', 'explain', 'with', 'pragma', 'values']);
|
|
|
|
const trimSQLStatement = (sql: string): string => String(sql || '').trim().replace(/;\s*$/, '').trim();
|
|
|
|
const isAIReadonlySQL = (sql: string): boolean => {
|
|
const firstWord = trimSQLStatement(sql).trimStart().split(/\s+/)[0]?.toLowerCase() || '';
|
|
return AI_READONLY_SQL_KEYWORDS.has(firstWord);
|
|
};
|
|
|
|
const hasExistingRowLimit = (dialect: string, sql: string): boolean => {
|
|
const text = trimSQLStatement(sql).toLowerCase();
|
|
if (!text) return false;
|
|
if (/\blimit\s+\d+\b/.test(text)) return true;
|
|
if (/\bfetch\s+(first|next)\s+\d+\s+rows?\b/.test(text)) return true;
|
|
if (/\btop\s*\(?\s*\d+\s*\)?\b/.test(text)) return true;
|
|
|
|
return (dialect === 'oracle' || dialect === 'dameng') && /\brownum\b/.test(text);
|
|
};
|
|
|
|
export const buildAIReadonlyPreviewSQL = (
|
|
dbType: string,
|
|
sql: string,
|
|
limit = 50,
|
|
driver = '',
|
|
options?: { oceanBaseProtocol?: unknown },
|
|
): string => {
|
|
const baseSQL = trimSQLStatement(sql);
|
|
const safeLimit = Math.max(0, Math.floor(Number(limit) || 0));
|
|
const dialect = resolveSqlDialect(dbType, driver, options);
|
|
if (!baseSQL || safeLimit <= 0 || !isAIReadonlySQL(baseSQL) || hasExistingRowLimit(dialect, baseSQL)) {
|
|
return baseSQL;
|
|
}
|
|
return buildPaginatedSelectSQL(dialect, baseSQL, '', safeLimit, 0);
|
|
};
|