🐛 fix(query-editor): 修复多数据源大查询限流失效

- SQL限流:抽取查询自动限流工具,修复 SELECT 判断大小写不一致导致限制未生效
- 方言适配:按 Oracle/Dameng、SQL Server、MySQL/PostgreSQL 等方言分别注入行数限制
- 自定义驱动:支持 custom 连接根据 driver 解析 Oracle、PostgreSQL、SQL Server 等方言
- MongoDB修复:修正 db.collection.find() 解析边界,并对 find/只读 aggregate 下推 limit
- Oracle优化:DSN 增加 PREFETCH_ROWS 和 LOB FETCH 参数,减少大结果集拉取开销
- 测试覆盖:补充 SQL 方言矩阵、MongoDB 限流和 Oracle DSN 参数测试
Refs #424
This commit is contained in:
Syngnat
2026-04-29 10:29:19 +08:00
parent f51dbcfb2c
commit 05a913ccb2
7 changed files with 640 additions and 360 deletions

View File

@@ -321,7 +321,7 @@ const parseCollectionAndMethod = (raw: string): {
pos = nextPos;
} else {
let end = pos;
while (end < input.length && /[A-Za-z0-9_$.-]/.test(input[end])) end++;
while (end < input.length && /[A-Za-z0-9_$-]/.test(input[end])) end++;
collection = input.slice(pos, end).trim();
pos = end;
}
@@ -662,7 +662,7 @@ export const buildMongoFindCommand = (params: {
if (params.sort && Object.keys(params.sort).length > 0) {
command.sort = params.sort;
}
if (Number.isFinite(params.limit) && Number(params.limit) > 0) {
if (Number.isFinite(params.limit) && Number(params.limit) >= 0) {
command.limit = Math.floor(Number(params.limit));
}
if (Number.isFinite(params.skip) && Number(params.skip) > 0) {
@@ -678,6 +678,45 @@ export const buildMongoCountCommand = (collection: string, filter: Record<string
});
};
const hasOwn = (obj: Record<string, unknown>, key: string) => Object.prototype.hasOwnProperty.call(obj, key);
const isMongoCommandObject = (value: unknown): value is Record<string, unknown> => (
!!value && typeof value === 'object' && !Array.isArray(value)
);
export const applyMongoQueryAutoLimit = (
command: string,
maxRows: number,
): { command: string; applied: boolean; maxRows: number } => {
if (!Number.isFinite(maxRows) || maxRows <= 0) return { command, applied: false, maxRows };
let parsed: unknown;
try {
parsed = JSON.parse(String(command || '').trim());
} catch {
return { command, applied: false, maxRows };
}
if (!isMongoCommandObject(parsed)) return { command, applied: false, maxRows };
const nextMaxRows = Math.floor(Number(maxRows));
if (hasOwn(parsed, 'find')) {
if (hasOwn(parsed, 'limit')) return { command, applied: false, maxRows };
parsed.limit = nextMaxRows;
return { command: JSON.stringify(parsed), applied: true, maxRows };
}
if (hasOwn(parsed, 'aggregate') && Array.isArray(parsed.pipeline)) {
const pipeline = parsed.pipeline as unknown[];
const hasExplicitLimit = pipeline.some((stage) => isMongoCommandObject(stage) && hasOwn(stage, '$limit'));
const hasWriteStage = pipeline.some((stage) => isMongoCommandObject(stage) && (hasOwn(stage, '$out') || hasOwn(stage, '$merge')));
if (hasExplicitLimit || hasWriteStage) return { command, applied: false, maxRows };
pipeline.push({ $limit: nextMaxRows });
return { command: JSON.stringify(parsed), applied: true, maxRows };
}
return { command, applied: false, maxRows };
};
const buildMongoInsertCommand = (
collection: string,
documents: Record<string, unknown>[],