Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions TESTING_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,7 @@ Candidate-ordered parallel-index recovery tests must prove that the fatal result
YAML `outline` integration coverage must assert nested sequence paths, mapping-parent depth, unchanged source ranges and symbol counts, deterministic sorting, and cursor replay across a page boundary.
Search-core orchestration coverage must keep validation ahead of database execution, preserve list-recipes > named-query > recipe > plain route precedence and the recipe-field-before-conflict / plain-conflict-before-field aggregation error order, keep count-mode zero/nonzero payloads aligned, retain strict-not-found exit semantics, and order profiled NDJSON as result records, profile metadata, then the terminal record.
Plain search strict-count coverage (#5406) extends the shared #5393 direct/batch fixture and `OriginContinuationIssue5348Tests`. On net8/net9, preserve zero/nonzero numeric and JSON counts, `--format count`, serial/parallel batch classification, snapshot guards, and byte-budget precedence. Filtered unknown origins must remain non-authoritative even with `--allow-partial`; completed classification may establish a strict zero. Keep grouped/named/recipe and MCP count regressions as controls.
The same cancellation fixture covers named-query and selected recipe routes (#5427): rows, counts, compact/summary/NDJSON, aggregations and plain/recipe issue drafts. Keep successful controls, pre-cancellation, cancellation inside SQLite and at the first/last managed row or group, CLI/batch interruption classification, and nested cancelled queries followed by successful retries on one inherited batch connection. Restore every async-local seam; do not use sleeps to trigger cancellation or classify it as an invalid child query.
`QueryCommandRunnerSearchCancellationTests` (#5421) covers pre-cancelled plain rows/counts/aggregations and cancellation inside real SQLite search statements through direct, CLI and serial/parallel batch entry points. Keep FTS hits, exact misses, numeric/JSON output, strict/allow-partial precedence, native interruption and successful retry on net8/net9. Also verify that C#/Python managed origin classification retains the effective scoped token, reader reuse restores its lifetime token, and cancellation at the first/last prepared aggregation group emits no success. Direct CLI cancellation writes a human error; batch children report `E012_INTERRUPTED`. The console-sensitive fixture restores its async-local connection factory and aggregation seam; callbacks trigger cancellation without sleeps or global pool resets.
CLI parsing, command execution, and installer behavior. Index command coverage is split by run mode or feature area, and query command coverage is split by command family with partial test classes so shared console and fixture helpers stay centralized. Keep repeated query-result fixtures, such as overlapping chunk content used by multiple search deduplication tests, in narrow class-level helpers instead of duplicating local builders. `ProgramCliTests.cs` covers top-level entrypoint behavior that must be exercised through a subprocess, while `InstallScriptTests.cs` runs focused bash snippets against `install.sh` in library mode to lock in release-installer regressions without performing real network installs. Installer bundle-generation tests must also verify that `install.sh` is marked generated while every canonical `install_modules/` source remains unmarked.
Suggestions usage-hint coverage for #5196 must keep unknown-option human diagnostics and missing-id / invalid-combination JSON errors aligned on `cdidx suggestions --help`, preserve the existing error-code, category, usage, and exit-code contracts, and assert that the literal `<cmd>` placeholder is never emitted. `CommandErrorWriterTests.cs` must keep catalog-backed known-command hints and the global fallback for missing, unknown, nested, or control-bearing command identities.
Expand Down Expand Up @@ -2397,6 +2398,7 @@ results-only・array・envelope・compact の形状、終端と改行の厳密
YAML `outline` の integration coverage では、入れ子 sequence path、mapping parent の depth、不変な source range と symbol count、決定的な sort、page 境界を越える cursor replay を検証してください。
search core の orchestration coverage では、validation が database execution より先に行われること、list-recipes > named-query > recipe > plain の route precedence と recipe では field error が conflict より先・plain では conflict が field error より先になる aggregation error 順、count mode の zero/nonzero payload、strict-not-found の exit semantics、profile 付き NDJSON が result record、profile metadata、terminal record の順になることを維持してください。
通常検索の strict 件数テスト (#5406) は、#5393 の直接実行/batch 共有 fixture と `OriginContinuationIssue5348Tests` を拡張します。net8/net9 でゼロ件/非ゼロ件の数値・JSON、`--format count`、直列/並列 batch の分類、スナップショットの保護、応答サイズエラーの優先順位を維持してください。フィルターで除外した分類不明の候補は `--allow-partial` 指定時も不在の根拠にせず、分類完了時だけ strict の確定したゼロ件として扱います。grouped/named/recipe と MCP の既存件数テストも対照として実行します。
同じ取消フィクスチャで、名前付き検索と選択したレシピの各経路 (#5427) を検証します。行・件数・compact/summary/NDJSON・集計・通常/レシピの Issue draft を対象に、正常実行、事前取消、SQLite 内および管理コードの最初/最後の行・集計グループでの取消、CLI/バッチの中断分類を維持してください。継承した1つのバッチ接続で入れ子の検索を取り消した後、再実行が成功することも確認します。非同期コンテキスト単位のテストフックをすべて復元し、待ち時間に依存した取消や無効な子クエリへの分類を避けてください。
`QueryCommandRunnerSearchCancellationTests` (#5421) は、通常検索の行・件数・集計の事前取消と、直接実行・CLI・直列/並列バッチでの SQLite 検索文の実行中の取消を検証します。FTS の一致、完全一致検索の未検出、数値/JSON 出力、strict/allow-partial より取消が優先すること、SQLite の中断と再実行の成功を net8/net9 で維持してください。C#/Python の管理コードによる検索元分類がスコープの有効なトークンを保持すること、reader の再利用時に元の寿命トークンを復元すること、最初/最後の集計グループ準備時の取消で成功出力を行わないことも検証します。直接 CLI は人間向けエラーを出し、バッチ内では `E012_INTERRUPTED` を報告します。console-sensitive なフィクスチャは非同期コンテキスト単位の接続ファクトリと集計用テストフックを復元し、待ち時間や全体の接続プール初期化に依存せずコールバックで取消を発生させます。
CLI の引数解析、コマンド実行、installer 挙動のテスト。Index command coverage は run mode または機能領域ごとの partial suite に分割し、Query command coverage は command family ごとの partial test class に分割して、共有 console / fixture helper は一箇所に保ちます。`ProgramCliTests.cs` はグローバル引数の解釈や完全な CLI 起動フローのように subprocess 経由で確認すべき Program エントリポイント挙動を扱い、`InstallScriptTests.cs` は `install.sh` を library mode で source した bash snippet を実行して、実ネットワーク install を行わずに release installer の回帰を固定する。installer bundle 生成テストでは、`install.sh` が generated と判定される一方、canonical な `install_modules/` source はすべて unmarked のままであることも検証してください。
#5196 の suggestions usage-hint coverage では、unknown option の human 診断と missing id / invalid combination の JSON error がともに `cdidx suggestions --help` を案内し、既存の error code、category、usage、exit code 契約を維持し、literal `<cmd>` placeholder を出力しないことを固定してください。`CommandErrorWriterTests.cs` では catalog に基づく既知 command の hint と、command identity が欠落・未知・nested・control character を含む場合の global fallback を維持してください。
Expand Down
12 changes: 8 additions & 4 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,8 +441,10 @@ find counts, incomplete search-origin classification, and potentially stale
snapshots cannot establish absence.
`--allow-partial` accepts partial output without making its zero authoritative;
cancellation, timeout, cursor, and output errors retain their precedence.
Plain search propagates cancellation through database setup and query execution for
rows, counts, and aggregations, including serial and parallel batch children.
Plain, named-query and recipe search propagate cancellation through database setup,
SQLite queries and managed result processing for rows, counts and aggregations,
including issue-draft exports and serial/parallel batch children. Cancellation stops
the whole invocation; it is never recorded as an invalid child query or a zero count.
Cancelling the CLI returns exit `130`; cancelled batch children also report
`E012_INTERRUPTED`. `--strict-not-found` and `--allow-partial` cannot turn that
cancellation into a miss or success.
Expand Down Expand Up @@ -4714,8 +4716,10 @@ command、query、filter、ordering、page limit を変えずに再利用して
検索元の分類が未完了の場合、古い可能性があるスナップショットでは不在を断定しません。
`--allow-partial` は部分結果を受け入れるだけで 0 件の確実性を高めず、
取消・タイムアウト・カーソル・出力エラーを優先します。
通常検索は、行・件数・集計の各出力で、DB の準備から検索の実行まで取消を伝播します。
直列・並列バッチ内の検索も対象です。CLI の取消は終了コード `130` を返し、
通常検索・名前付き検索・レシピ検索は、行・件数・集計の各出力で、DB の準備、SQLite 検索、
管理コードによる結果処理まで取消を伝播します。Issue draft の出力と直列・並列バッチ内の
検索も対象です。取消は呼出し全体を中断し、無効な子クエリやゼロ件として記録しません。
CLI の取消は終了コード `130` を返し、
バッチ内の取消では `E012_INTERRUPTED` も報告します。`--strict-not-found` や
`--allow-partial` を指定しても、取消を未検出や成功には変換しません。
最終 page と 0 件 page は cursor を公開しません。byte cap により terminal
Expand Down
19 changes: 19 additions & 0 deletions changelog.d/unreleased/5427.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
category: fixed
issues:
- 5427
affected:
- src/CodeIndex/Cli/QueryCommandRunner.SearchRouting.cs
- src/CodeIndex/Cli/QueryCommandRunner.SearchExecution.cs
- src/CodeIndex/Cli/QueryCommandRunner.SearchResults.cs
- src/CodeIndex/Cli/QueryCommandRunner.SearchRecipes.cs
- src/CodeIndex/Cli/QueryCommandRunner.SearchRecipeExecution.cs
---

## English

- **Named-query and recipe search honor caller cancellation (#5427)** — propagate cancellation through database setup, SQLite execution and managed row/count/aggregation processing, including issue-draft exports and serial/parallel batch execution. Cancelled child queries interrupt the invocation instead of producing a degraded success or zero count, and reused readers restore their previous cancellation scope.

## 日本語

- **名前付き検索とレシピ検索で呼出元の取消を反映 (#5427)** — Issue draft の出力と直列・並列バッチを含め、DB の準備、SQLite 実行、管理コードでの行・件数・集計処理に取消を伝播します。子クエリの取消は不完全な成功やゼロ件に変換せず呼出し全体を中断し、再利用する reader の取消スコープを元に戻します。
27 changes: 21 additions & 6 deletions src/CodeIndex/Cli/QueryCommandRunner.SearchExecution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,33 @@ private static SearchExecutionPlan CreateSearchExecutionPlan(

private static int ExecutePlainSearch(SearchExecutionPlan plan)
{
plan.CancellationToken.ThrowIfCancellationRequested();
var outcome = new SearchExecutionOutcome();
return WithDb(
return WithSearchDb(
plan.Options,
plan.JsonOptions,
plan.CancellationToken,
reader => ExecutePlainSearch(reader, plan, outcome),
_ => WritePlainSearchTerminal(plan, outcome));
}

private static int WithSearchDb(
QueryCommandOptions options,
JsonSerializerOptions jsonOptions,
CancellationToken cancellationToken,
Func<DbReader, int> action,
Action<int>? afterProfile = null)
{
cancellationToken.ThrowIfCancellationRequested();
return WithDb(
options,
jsonOptions,
reader =>
{
using var cancellationScope = reader.BeginCancellationScope(plan.CancellationToken);
return reader.RunWithCancellationInterrupt(() => ExecutePlainSearch(reader, plan, outcome));
using var cancellationScope = reader.BeginCancellationScope(cancellationToken);
return reader.RunWithCancellationInterrupt(() => action(reader));
},
_ => WritePlainSearchTerminal(plan, outcome),
cancellationToken: plan.CancellationToken);
afterProfile,
cancellationToken: cancellationToken);
}

private static int ExecutePlainSearch(
Expand Down
15 changes: 13 additions & 2 deletions src/CodeIndex/Cli/QueryCommandRunner.SearchRecipeExecution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ private readonly record struct SearchRecipeQueryMaterializationResult(
private static SearchRecipeQueryMaterializationResult MaterializeSearchRecipeQuery(
in SearchRecipeQueryMaterializationRequest request)
{
request.Reader.ThrowIfCancellationRequested();
EnsureSearchRecipeCoverage(request.Reader, request.Scope, request.Options);
var queryScope = BuildSearchRecipeQueryScope(request.Scope, request.RecipeQuery);
var guardFilters = BuildSearchRecipeGuardFilters(request.Options, request.RecipeQuery);
Expand Down Expand Up @@ -92,7 +93,8 @@ private static SearchRecipeQueryMaterializationResult MaterializeSearchRecipeQue
request.RecipeQuery.Query,
rawFtsOverride: request.RawFtsOverride,
recipeQuery: request.RecipeQuery,
originCoverageObserver: complete => originCoverageComplete &= complete);
originCoverageObserver: complete => originCoverageComplete &= complete,
cancellationToken: request.Reader.Cancellation);
if (!originCoverageComplete) coverageRestriction = "origin_classification_incomplete";
var sourceTotalAuthoritative = request.ResultLimit.HasValue && coverageRestriction == null;
rows = ApplySearchRecipeSemanticFilter(
Expand All @@ -101,6 +103,7 @@ private static SearchRecipeQueryMaterializationResult MaterializeSearchRecipeQue
request.RecipeQuery,
rows);
ApplyXmlSettingsAuditClassifications(request.Reader, request.RecipeQuery, rows);
request.Reader.ThrowIfCancellationRequested();
MarkSearchRecipeQueryExecuted(request.Scope, request.RecipeQuery.Name);
return new SearchRecipeQueryMaterializationResult(rows, sourceTotalAuthoritative, candidateWindowExhausted, coverageRestriction);
}
Expand Down Expand Up @@ -130,6 +133,7 @@ private static List<SearchRecipeQueryResultJsonResult> CollectSearchRecipeQueryR
hasFailures = false;
foreach (var recipeQuery in recipeQueries)
{
reader.ThrowIfCancellationRequested();
try
{
var exact = ResolveSearchRecipeMatchMode(recipeQuery, options).Exact;
Expand Down Expand Up @@ -260,6 +264,7 @@ private static List<SearchRecipeQueryResultJsonResult> CollectSearchRecipeQueryR
}
}

reader.ThrowIfCancellationRequested();
return queryResults;
}

Expand All @@ -280,6 +285,7 @@ private static List<SearchRecipeCompactQueryResultJsonResult> CollectSearchRecip
hasFailures = false;
foreach (var recipeQuery in recipeQueries)
{
reader.ThrowIfCancellationRequested();
try
{
var exact = ResolveSearchRecipeMatchMode(recipeQuery, options).Exact;
Expand Down Expand Up @@ -372,6 +378,7 @@ private static List<SearchRecipeCompactQueryResultJsonResult> CollectSearchRecip
}
}

reader.ThrowIfCancellationRequested();
return queryResults;
}

Expand Down Expand Up @@ -416,6 +423,7 @@ private static List<SearchRecipeCountQueryJsonResult> CountSearchRecipeQueryResu
hasFailures = false;
foreach (var recipeQuery in recipeQueries)
{
reader.ThrowIfCancellationRequested();
try
{
var exact = ResolveSearchRecipeMatchMode(recipeQuery, options).Exact;
Expand Down Expand Up @@ -472,6 +480,7 @@ private static List<SearchRecipeCountQueryJsonResult> CountSearchRecipeQueryResu
}

fileCount = paths.Count;
reader.ThrowIfCancellationRequested();
return queryCounts;
}

Expand All @@ -490,6 +499,7 @@ private static List<SearchRecipeAggregationQueryJsonResult> CollectSearchRecipeA
total = 0;
foreach (var recipeQuery in recipeQueries)
{
reader.ThrowIfCancellationRequested();
var exact = ResolveSearchRecipeMatchMode(recipeQuery, options).Exact;
var materializationRequest = new SearchRecipeQueryMaterializationRequest(
reader,
Expand All @@ -504,7 +514,7 @@ private static List<SearchRecipeAggregationQueryJsonResult> CollectSearchRecipeA
foreach (var path in rows.Select(row => row.Result.Path))
paths.Add(path);

var groups = BuildSearchGroupedCounts(groupBy, rows);
var groups = BuildSearchGroupedCounts(groupBy, rows, reader.Cancellation);
var selection = ApplySearchGroupOutputSelection(groups, options);
total += rows.Count;
queryResults.Add(new SearchRecipeAggregationQueryJsonResult(
Expand All @@ -522,6 +532,7 @@ private static List<SearchRecipeAggregationQueryJsonResult> CollectSearchRecipeA
}

fileCount = paths.Count;
reader.ThrowIfCancellationRequested();
return queryResults;
}

Expand Down
Loading
Loading