perf(directories): SQL-фильтрация/пагинация справочников, N+1, лимиты column-values
Шаги 1.1 и 1.2 плана production-готовности: - getDataTableRowsPaged: фильтры/поиск/сортировка/пагинация в SQL (values->>N) - getDataTablesWithAccess и list_directories без N+1 - /column-values: limit (дефолт 20, кап 1000), убраны debug-логи с горячего пути - tree-режим и legacy-путь TableEditor сохранены 1:1 - 10 новых тестов (88/88)
This commit is contained in:
@@ -3331,18 +3331,15 @@ To block task creation from task.before_create, set: ctx.result = { allow: false
|
||||
const all = await storage.getDataTablesByOrganization(organizationId);
|
||||
// Фильтруем выдачу по scopes.tableIds (null = все справочники)
|
||||
const allowed = all.filter((t) => isTableAllowed(t.id));
|
||||
const result = await Promise.all(
|
||||
allowed.map(async (t) => {
|
||||
const rows = await storage.getDataTableRows(t.id, organizationId);
|
||||
return {
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
columns: t.columns,
|
||||
rowCount: rows.length,
|
||||
};
|
||||
})
|
||||
);
|
||||
// Количество строк по всем справочникам — одним GROUP BY запросом (без N+1)
|
||||
const rowCounts = await storage.getDataTableRowCounts(organizationId);
|
||||
const result = allowed.map((t) => ({
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
columns: t.columns,
|
||||
rowCount: rowCounts.get(t.id) ?? 0,
|
||||
}));
|
||||
return {
|
||||
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
|
||||
};
|
||||
@@ -3421,55 +3418,40 @@ To block task creation from task.before_create, set: ctx.result = { allow: false
|
||||
const table = await storage.getDataTable(tableId, organizationId);
|
||||
if (!table) return directoryError(`Справочник ${tableId} не найден`);
|
||||
|
||||
let rows = await storage.getDataTableRows(tableId, organizationId);
|
||||
|
||||
// tree-режим: дерево строится из всех строк (иерархия parent_id / treeColumns),
|
||||
// поэтому здесь осознанно полная выборка — пагинация в дереве неприменима.
|
||||
if (mode === "tree") {
|
||||
const rows = await storage.getDataTableRows(tableId, organizationId);
|
||||
const tree = buildDataTableTree(table, rows, { filters, search });
|
||||
return {
|
||||
content: [{ type: "text" as const, text: JSON.stringify(tree, null, 2) }],
|
||||
};
|
||||
}
|
||||
|
||||
// flat: фильтры по подстроке (includes), без учёта регистра
|
||||
// flat-режим: фильтры/поиск/сортировка/пагинация на уровне SQL
|
||||
let numericFilters: Record<number, string> | undefined;
|
||||
if (filters) {
|
||||
Object.entries(filters).forEach(([col, val]) => {
|
||||
numericFilters = {};
|
||||
for (const [col, val] of Object.entries(filters)) {
|
||||
const ci = parseInt(col);
|
||||
if (!isNaN(ci) && val) {
|
||||
rows = rows.filter((r) => {
|
||||
const v = Array.isArray(r.values) ? r.values : [];
|
||||
return String(v[ci] || '').toLowerCase().includes(val.toLowerCase());
|
||||
});
|
||||
}
|
||||
});
|
||||
if (!isNaN(ci) && val) numericFilters[ci] = val;
|
||||
}
|
||||
}
|
||||
|
||||
// Поиск по всем значениям строки
|
||||
if (search) {
|
||||
const q = search.toLowerCase();
|
||||
rows = rows.filter((r) => {
|
||||
const v = Array.isArray(r.values) ? r.values : [];
|
||||
return v.some((val) => String(val ?? '').toLowerCase().includes(q));
|
||||
});
|
||||
}
|
||||
const { rows, total } = await storage.getDataTableRowsPaged(tableId, organizationId, {
|
||||
filters: numericFilters,
|
||||
search,
|
||||
sortColumn,
|
||||
sortDirection,
|
||||
limit: limit ?? 100,
|
||||
offset: offset ?? 0,
|
||||
});
|
||||
|
||||
// Сортировка по колонке
|
||||
if (sortColumn !== undefined && (sortDirection === 'asc' || sortDirection === 'desc')) {
|
||||
const ci = sortColumn;
|
||||
rows = [...rows].sort((a, b) => {
|
||||
const av = Array.isArray(a.values) ? a.values : [];
|
||||
const bv = Array.isArray(b.values) ? b.values : [];
|
||||
const cmp = String(av[ci] || '').toLowerCase().localeCompare(String(bv[ci] || '').toLowerCase(), 'ru');
|
||||
return sortDirection === 'asc' ? cmp : -cmp;
|
||||
});
|
||||
}
|
||||
|
||||
const total = rows.length;
|
||||
const paged = rows.slice(offset ?? 0, (offset ?? 0) + (limit ?? 100));
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: JSON.stringify({
|
||||
rows: paged.map((r) => ({ id: r.id, parentId: r.parentId, values: r.values, position: r.position })),
|
||||
rows: rows.map((r) => ({ id: r.id, parentId: r.parentId, values: r.values, position: r.position })),
|
||||
total,
|
||||
}, null, 2),
|
||||
}],
|
||||
|
||||
Reference in New Issue
Block a user