using Deal.Infrastructure.Persistence; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace Deal.Infrastructure.Services; /// /// Обслуживание FTS-индексов схемы тенанта — POST /api/admin/fts/rebuild (Ruling 6, план Task 10; /// fts.py rebuild L48–67). /// /// /// SearchTsv — генерируемые STORED-колонки Cards/RejectedItems (миграция TenantPipeline, Ruling 6): tsvector /// авто-актуален при записи строк, «пересборка индекса раз в сутки» как в DuckDB-прототипе не нужна. /// — реальная идемпотентная обслуживающая операция: CREATE INDEX IF NOT EXISTS /// обоих GIN-индексов (самовосстановление, если индекс отсутствует) + ANALYZE обеих таблиц (актуальная /// статистика для планировщика Postgres). Имена индексов/таблиц — внутренние константы (не пользовательский /// ввод), поэтому SQL без параметров (как fts.py L51–52). Успех — true ({ok:true, ready:true} ответа); /// сбой (нет DDL-прав и т.п.) логируется и возвращает false — {ok:false, ready:false}, как rebuild() python /// (frontend rebuildFts store.js L1883–1889 показывает ошибку по ready). /// public sealed class FtsMaintenance(TenantDbContext dbContext, ILogger logger) { // Самовосстановление GIN-индекса карточек (имя — из миграции TenantPipeline, Ruling 6). private const string CardsIndexSql = "CREATE INDEX IF NOT EXISTS \"IX_Cards_SearchTsv\" ON \"Cards\" USING gin (\"SearchTsv\")"; // Самовосстановление GIN-индекса отсева (имя — из миграции TenantPipeline, Ruling 6). private const string RejectedIndexSql = "CREATE INDEX IF NOT EXISTS \"IX_RejectedItems_SearchTsv\" ON \"RejectedItems\" USING gin (\"SearchTsv\")"; // Актуализация статистики таблицы карточек для планировщика (fts.py rebuild L58–60). private const string AnalyzeCardsSql = "ANALYZE \"Cards\""; // Актуализация статистики таблицы отсева для планировщика (fts.py rebuild L58–60). private const string AnalyzeRejectedSql = "ANALYZE \"RejectedItems\""; /// /// Пересобирает FTS-индексы текущей схемы тенанта: CREATE INDEX IF NOT EXISTS + ANALYZE. /// /// Токен отмены. /// true — индексы на месте и статистика собрана; false — сбой (эндпоинт отвечает {ok:false, ready:false}). public async Task RebuildAsync(CancellationToken ct) { try { await dbContext.Database.ExecuteSqlRawAsync(CardsIndexSql, ct); await dbContext.Database.ExecuteSqlRawAsync(RejectedIndexSql, ct); await dbContext.Database.ExecuteSqlRawAsync(AnalyzeCardsSql, ct); await dbContext.Database.ExecuteSqlRawAsync(AnalyzeRejectedSql, ct); return true; } catch (Exception exception) { logger.LogWarning(exception, "Пересборка FTS-индексов не удалась"); return false; } } }