From cbe37f16f3ca5aca3dc2e112fdbddeee73f8abfc Mon Sep 17 00:00:00 2001 From: Widthdom Date: Thu, 24 Sep 2026 23:58:56 +0900 Subject: [PATCH 1/2] Fix SQL system-variable persistence and repeated seed statement scans --- DEVELOPER_GUIDE.md | 29 +++++ TESTING_GUIDE.md | 11 ++ .../+sql-reference-indexing.fixed.md | 17 +++ ...lReferenceExtractor.StatementBoundaries.cs | 84 +++++++++++++++ .../Languages/SqlReferenceExtractor.cs | 7 +- src/CodeIndex/Models/SymbolKindCatalog.cs | 1 + .../DbSchemaConstraintTests.cs | 53 +++++++++ .../IndexCommandRunnerSqlTests.cs | 61 +++++++++++ .../ReferenceExtractorTests.cs | 1 + .../SqlReferenceStatementBoundaryTests.cs | 102 ++++++++++++++++++ 10 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 changelog.d/unreleased/+sql-reference-indexing.fixed.md create mode 100644 src/CodeIndex/Indexer/References/Languages/SqlReferenceExtractor.StatementBoundaries.cs create mode 100644 tests/CodeIndex.Tests/IndexCommandRunnerSqlTests.cs create mode 100644 tests/CodeIndex.Tests/SqlReferenceStatementBoundaryTests.cs diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index d3cbb57ec1..8eac37614a 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -2349,6 +2349,22 @@ TypeScript decorators emit `annotation` rows for the decorator name and must not C# named-argument labels such as `overwrite:` are syntax, not type positions, and must not emit `type_reference` rows. The declaration-type scanner skips a leading single-colon label inside an argument fragment, including comma-terminated multiline argument lines, while preserving expression references, named `out` declaration types, explicitly typed lambda and anonymous-method parameters, and typed LINQ range variables in the argument value. Multiline property subpatterns likewise keep the type after their property label. Alias-qualified names (`Alias::Type`), statement and `case` labels, nullable types, and ternary expressions remain distinct colon-bearing constructs. +### SQL reference persistence and statement carry + +SQL `@@` references use the registered `system_variable` kind in both writer +validation and SQLite CHECK constraints. Opening an existing database for indexing +upgrades older kind constraints while preserving its reference rows. After an older +writer fails on these references, rerun normal `cdidx index ` with the +fixed binary; deleting the database or forcing `--rebuild` is unnecessary. + +SQL reference extraction clears statement carry at standalone `GO` batch separators +(including positive repeat counts and trailing comments). It also clears a preceding +INSERT, UPDATE, DELETE, or SET prefix before an unambiguous independent data statement +when parentheses are balanced. This prevents repeated seed statements without +semicolons from rescanning all preceding statements. Compound WITH/MERGE/CREATE +prefixes, nested expressions, and upsert continuation clauses retain their context; +temporary-object names survive batch boundaries. + ### GPU and shader reference extraction CUDA, GLSL, HLSL, Metal, and WGSL use a request-scoped, stateless reference @@ -6941,6 +6957,19 @@ TypeScript decorator は decorator 名を `annotation` 行として出力し、d C# の `overwrite:` のような named-argument label は構文であり、型位置ではないため `type_reference` 行を出力してはならない。declaration-type scanner は argument fragment の先頭にある単一 colon の label を、comma で終わる複数行 argument も含めて読み飛ばし、argument value 内の式参照、named `out` declaration の型、明示型 lambda / anonymous method の parameter、および型付き LINQ range variable を維持する。複数行 property subpattern でも property label 後の型を維持する。alias-qualified name(`Alias::Type`)、statement / `case` label、nullable type、ternary expression は別の colon 構文として扱う。 +### SQL 参照の保存と文の持ち越し + +SQL の `@@` 参照は、writer の検証と SQLite CHECK 制約の両方に登録した +`system_variable` 種別で保存します。既存DBをインデックス用に開く際、古い種別制約は +参照行を保持して更新します。旧版がこの参照の保存に失敗した場合は、修正版で通常の +`cdidx index ` を再実行してください。DBの削除や `--rebuild` は不要です。 + +SQL 参照抽出は、独立した `GO` バッチ区切り(正の繰り返し回数、末尾コメントも含む) +で文の持ち越しを解消します。先行文が INSERT/UPDATE/DELETE/SET で括弧が閉じている +場合も、明確に独立したデータ操作文の前で解消します。これにより、セミコロンのない +初期データ文が過去の全文を繰り返し解析するのを防ぎます。WITH/MERGE/CREATE、 +入れ子の式、upsert の継続句は文脈を維持し、一時オブジェクト名もバッチをまたいで保持します。 + ### GPU / shader の参照抽出 CUDA、GLSL、HLSL、Metal、WGSL は request ごとの stateless な参照 extractor を使う。 diff --git a/TESTING_GUIDE.md b/TESTING_GUIDE.md index 6a33c3b721..e71294ae75 100644 --- a/TESTING_GUIDE.md +++ b/TESTING_GUIDE.md @@ -1,5 +1,16 @@ # Testing Guide +SQL indexing regressions in `IndexCommandRunnerSqlTests`, `DbSchemaConstraintTests` +and `ReferenceExtractorTests.Extract_SQL_SystemVariablesEmitsSystemVariableReferences` +cover `@@` kind validation, fresh/full/scoped persistence, coordinates and existing +CHECK-constraint migration without losing reference rows. `SqlReferenceStatementBoundaryTests` +checks bounded carry and exact target counts for repeated DML with semicolons, GO, +or neither; retain multiline, CTE, MERGE, upsert, literal and temp-object controls. +Run these with SQL symbol/graph and raw/fresh reference persistence tests on net8/net9. +SQL の `@@` 種別・新規/全体/限定更新・座標・既存DBの制約更新を両runtimeで検証します。 +繰り返しDMLの性能回帰は時間の閾値ではなく持ち越し量と参照件数で検出し、複数行SQLの +文脈と一時オブジェクト、文字列内の GO を維持してください。 + Run `python3 -m unittest discover -s .agent_harness/tests` for shared command-guard changes. The existing policy-contract matrix checks the core, Codex PreToolUse and PermissionRequest, and Claude PreToolUse without executing the tested commands. diff --git a/changelog.d/unreleased/+sql-reference-indexing.fixed.md b/changelog.d/unreleased/+sql-reference-indexing.fixed.md new file mode 100644 index 0000000000..6d5198691a --- /dev/null +++ b/changelog.d/unreleased/+sql-reference-indexing.fixed.md @@ -0,0 +1,17 @@ +--- +category: fixed +affected: + - src/CodeIndex/Models/SymbolKindCatalog.cs + - src/CodeIndex/Indexer/References/Languages/SqlReferenceExtractor.cs + - src/CodeIndex/Indexer/References/Languages/SqlReferenceExtractor.StatementBoundaries.cs +--- + +## English + +- Fixed SQL indexing failures when saving `@@ROWCOUNT`, `@@IDENTITY`, and other `system_variable` references. Existing database kind constraints upgrade automatically on the next index run while preserving stored rows. +- Avoid repeatedly rescanning preceding seed statements at `GO` boundaries and between independent INSERT/UPDATE/DELETE statements without semicolons. Multiline SQL and compound-statement context remain intact. + +## 日本語 + +- `@@ROWCOUNT`、`@@IDENTITY` などの `system_variable` 参照を保存する際にSQLインデックスが失敗する問題を修正しました。既存DBの種別制約は次回のインデックス実行時に保存済みの行を維持して自動更新します。 +- `GO` 区切りやセミコロンのない独立した INSERT/UPDATE/DELETE 文で、過去の初期データ文を繰り返し再解析する問題を修正しました。複数行SQLと複合文の文脈は維持します。 diff --git a/src/CodeIndex/Indexer/References/Languages/SqlReferenceExtractor.StatementBoundaries.cs b/src/CodeIndex/Indexer/References/Languages/SqlReferenceExtractor.StatementBoundaries.cs new file mode 100644 index 0000000000..fa2c81a17c --- /dev/null +++ b/src/CodeIndex/Indexer/References/Languages/SqlReferenceExtractor.StatementBoundaries.cs @@ -0,0 +1,84 @@ +using System.Globalization; +using System.Text.RegularExpressions; +using Regex = CodeIndex.Indexer.BoundedRegex; + +namespace CodeIndex.Indexer; + +internal static partial class SqlReferenceExtractor +{ + // Only unambiguous data-statement starts qualify: UPDATE SET (MERGE / ON CONFLICT) + // and UPDATE column = value (ON DUPLICATE KEY) are continuations, not boundaries. + // MERGE / ON CONFLICT の UPDATE SET や ON DUPLICATE KEY の UPDATE column = value は + // 継続句なので、独立した DML 文の開始だけを境界にする。 + private static readonly Regex IndependentDataStatementStartRegex = new( + $@"^\s*(?:UPDATE\s+{QualifiedIdentifierNoCapturePattern}\s+SET\b|INSERT\s+(?:INTO\s+)?{QualifiedIdentifierNoCapturePattern}|DELETE\s+FROM\s+{QualifiedIdentifierNoCapturePattern})", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + private static bool IsBatchSeparator(string line) + { + var text = line.AsSpan().Trim(); + if (text.Length < 2 || !text[..2].Equals("GO", StringComparison.OrdinalIgnoreCase)) + return false; + if (text.Length == 2) + return true; + return char.IsWhiteSpace(text[2]) + && int.TryParse(text[2..].Trim(), NumberStyles.None, CultureInfo.InvariantCulture, out var count) + && count > 0; + } + + private static bool ShouldFlushDataStatementPrefixAtLineBoundary(string prefix, string nextLine) + { + var start = SkipWhitespaceAhead(prefix, 0); + // Keep compound statements (WITH, MERGE, CREATE, etc.) intact. Their later + // lines can depend on earlier clauses even outside parentheses. + // WITH / MERGE / CREATE などは前の句が後続行の解析に必要なので維持する。 + if (!IsKeywordAt(prefix, start, "INSERT") + && !IsKeywordAt(prefix, start, "UPDATE") + && !IsKeywordAt(prefix, start, "DELETE") + && !IsKeywordAt(prefix, start, "SET")) + { + return false; + } + + if (!IndependentDataStatementStartRegex.IsMatch(nextLine)) + return false; + + // The input is already comment/string masked. Quoted identifiers must still + // be skipped so parentheses in names cannot create a false statement boundary. + // 入力の comment / string はマスク済み。引用識別子内の括弧も境界に数えない。 + var depth = 0; + for (var index = start; index < prefix.Length; index++) + { + var value = prefix[index]; + if (value is '[' or '"' or '`') + { + var closing = value == '[' ? ']' : value; + var closed = false; + while (++index < prefix.Length) + { + if (prefix[index] != closing) + continue; + if (index + 1 < prefix.Length && prefix[index + 1] == closing) + { + index++; + continue; + } + closed = true; + break; + } + if (!closed) + return false; + } + else if (value == '(') + { + depth++; + } + else if (value == ')' && --depth < 0) + { + return false; + } + } + + return depth == 0; + } +} diff --git a/src/CodeIndex/Indexer/References/Languages/SqlReferenceExtractor.cs b/src/CodeIndex/Indexer/References/Languages/SqlReferenceExtractor.cs index ff777f0c71..f1ad8bb53f 100644 --- a/src/CodeIndex/Indexer/References/Languages/SqlReferenceExtractor.cs +++ b/src/CodeIndex/Indexer/References/Languages/SqlReferenceExtractor.cs @@ -142,11 +142,16 @@ public static HashSet Emit( if (string.IsNullOrWhiteSpace(lineFragment)) return suppressedCallIndices; - if (ShouldFlushTempObjectPrefixAtLineBoundary(state.StatementPrefix, lineFragment)) + var isBatchSeparator = IsBatchSeparator(lineFragment); + if (isBatchSeparator + || ShouldFlushDataStatementPrefixAtLineBoundary(state.StatementPrefix, lineFragment) + || ShouldFlushTempObjectPrefixAtLineBoundary(state.StatementPrefix, lineFragment)) { CollectTempObjectNamesFromStatement(state.StatementPrefix, state.EstablishedTempObjectNames); state.StatementPrefix = string.Empty; } + if (isBatchSeparator) + return suppressedCallIndices; var combinedLine = CombineStatementPrefix(state.StatementPrefix, lineFragment, out var lineOffset); int statementStart = 0; diff --git a/src/CodeIndex/Models/SymbolKindCatalog.cs b/src/CodeIndex/Models/SymbolKindCatalog.cs index 15769d7bc4..397b409e22 100644 --- a/src/CodeIndex/Models/SymbolKindCatalog.cs +++ b/src/CodeIndex/Models/SymbolKindCatalog.cs @@ -138,6 +138,7 @@ public static class SymbolKindCatalog "stage", "razor_event_binding", "subscribe", + "system_variable", "type_reference", "type_tag", "unsubscribe", diff --git a/tests/CodeIndex.Tests/DbSchemaConstraintTests.cs b/tests/CodeIndex.Tests/DbSchemaConstraintTests.cs index b512db2f00..94961fd3a7 100644 --- a/tests/CodeIndex.Tests/DbSchemaConstraintTests.cs +++ b/tests/CodeIndex.Tests/DbSchemaConstraintTests.cs @@ -93,6 +93,59 @@ public void InitializeSchema_KindCheckConstraintsMatchCatalog_Issue4178() } } + [Fact] + public void InitializeSchema_AddsSqlSystemVariableKindToExistingReferenceChecks() + { + var dbDir = TestProjectHelper.CreateTempProject("codeindex_sql_kind_migration"); + var dbPath = Path.Combine(dbDir, "codeindex.db"); + try + { + using (var db = new DbContext(DbOpenIntent.WriteIndex, dbPath)) + db.InitializeSchema(); + + using (var conn = new SqliteConnection(new SqliteConnectionStringBuilder + { DataSource = dbPath, Pooling = false }.ConnectionString)) + { + conn.Open(); + var legacySql = ReadCreateSql(conn, "symbol_references") + .Replace("'system_variable', ", "", StringComparison.Ordinal); + Exec(conn, "PRAGMA foreign_keys=OFF; DROP TABLE symbol_references;"); + Exec(conn, legacySql); + Exec(conn, """ + PRAGMA foreign_keys=ON; + INSERT INTO files (id, path, lang) VALUES (1, 'SampleData.sql', 'sql'); + INSERT INTO reference_lines (id, file_id, line, context) VALUES (9, 1, 1, 'EXEC SaveOrder;'); + INSERT INTO symbol_references (id, file_id, symbol_name, reference_kind, line, reference_line_id) + VALUES (7, 1, 'SaveOrder', 'call', 1, 9); + """); + Assert.Throws(() => Exec(conn, """ + INSERT INTO symbol_references (file_id, symbol_name, reference_kind, line) + VALUES (1, '@@ROWCOUNT', 'system_variable', 2) + """)); + } + + using var migrated = new DbContext(DbOpenIntent.WriteIndex, dbPath); + migrated.InitializeSchema(); + var writer = new DbWriter(migrated.Connection); + writer.InsertReferences([ + new ReferenceRecord { FileId = 1, SymbolName = "@@ROWCOUNT", ReferenceKind = "system_variable", Line = 2 } + ]); + Assert.Equal(1L, CountRows(migrated.Connection, "symbol_references", "id = 7 AND reference_line_id = 9 AND reference_kind = 'call'")); + Assert.Equal(1L, CountRows(migrated.Connection, "symbol_references", "symbol_name = '@@ROWCOUNT' AND reference_kind = 'system_variable'")); + AssertSameSet(SymbolKindCatalog.ReferenceKinds, + ExtractCheckValues(ReadCreateSql(migrated.Connection, "symbol_references"), "reference_kind")); + using var check = migrated.Connection.CreateCommand(); + check.CommandText = "PRAGMA foreign_key_check"; + Assert.Null(check.ExecuteScalar()); + check.CommandText = "PRAGMA integrity_check"; + Assert.Equal("ok", check.ExecuteScalar()); + } + finally + { + TestProjectHelper.DeleteDirectory(dbDir); + } + } + [Fact] public void InitializeSchema_PublicTaxonomyMutationCannotSplitCanonicalContracts() { diff --git a/tests/CodeIndex.Tests/IndexCommandRunnerSqlTests.cs b/tests/CodeIndex.Tests/IndexCommandRunnerSqlTests.cs new file mode 100644 index 0000000000..7a27908e74 --- /dev/null +++ b/tests/CodeIndex.Tests/IndexCommandRunnerSqlTests.cs @@ -0,0 +1,61 @@ +using CodeIndex.Cli; +using CodeIndex.Database; + +namespace CodeIndex.Tests; + +public partial class IndexCommandRunnerTests +{ + [Fact] + public void Run_SqlSystemVariablesPersistAcrossFreshFullAndScopedUpdates() + { + var projectRoot = CreateTempProject(); + var dbPath = Path.Combine(projectRoot, ".cdidx", "codeindex.db"); + const string content = """ + CREATE PROCEDURE dbo.SaveOrder + AS + BEGIN + SELECT @@IDENTITY; + IF @@ROWCOUNT = 0 SELECT @@ERROR; + SELECT @@session.sql_mode, @@global.max_connections; + END; + """; + try + { + var path = Path.Combine(projectRoot, "SampleData.sql"); + for (var pass = 0; pass < 3; pass++) + { + File.WriteAllText(path, content + new string('\n', pass)); + string[] arguments = pass == 2 + ? [projectRoot, "--files", "SampleData.sql", "--db", dbPath, "--json", "--quiet"] + : [projectRoot, "--db", dbPath, "--json", "--quiet"]; + var (exitCode, json) = RunAndCaptureJson(arguments); + Assert.Equal(CommandExitCodes.Success, exitCode); + Assert.Equal("success", json.GetProperty("status").GetString()); + Assert.True(json.GetProperty("index_complete").GetBoolean()); + Assert.True(json.GetProperty("reference_graph_complete").GetBoolean()); + + using var connection = OpenNonPoolingConnection(dbPath); + connection.Open(); + using var command = connection.CreateCommand(); + command.CommandText = """ + SELECT symbol_name, line, column_number + FROM symbol_references + WHERE reference_kind = 'system_variable' + ORDER BY line, column_number + """; + using var reader = command.ExecuteReader(); + var actual = new List<(string Name, int Line, int Column)>(); + while (reader.Read()) + actual.Add((reader.GetString(0), reader.GetInt32(1), reader.GetInt32(2))); + Assert.Equal( + [("@@IDENTITY", 4, 12), ("@@ROWCOUNT", 5, 8), ("@@ERROR", 5, 30), + ("@@session.sql_mode", 6, 12), ("@@global.max_connections", 6, 32)], + actual); + } + } + finally + { + DeleteDirectory(projectRoot); + } + } +} diff --git a/tests/CodeIndex.Tests/ReferenceExtractorTests.cs b/tests/CodeIndex.Tests/ReferenceExtractorTests.cs index 8fae1968af..e3c87aaa53 100644 --- a/tests/CodeIndex.Tests/ReferenceExtractorTests.cs +++ b/tests/CodeIndex.Tests/ReferenceExtractorTests.cs @@ -9741,6 +9741,7 @@ CREATE PROCEDURE dbo.SaveOrder Assert.Contains(references, r => r.SymbolName == "@@ERROR" && r.ReferenceKind == "system_variable"); Assert.Contains(references, r => r.SymbolName == "@@session.sql_mode" && r.ReferenceKind == "system_variable"); Assert.Contains(references, r => r.SymbolName == "@@global.max_connections" && r.ReferenceKind == "system_variable"); + Assert.All(references, r => Assert.True(SymbolKindCatalog.IsValidReferenceKind(r.ReferenceKind), r.ReferenceKind)); } [Fact] diff --git a/tests/CodeIndex.Tests/SqlReferenceStatementBoundaryTests.cs b/tests/CodeIndex.Tests/SqlReferenceStatementBoundaryTests.cs new file mode 100644 index 0000000000..52e18370dc --- /dev/null +++ b/tests/CodeIndex.Tests/SqlReferenceStatementBoundaryTests.cs @@ -0,0 +1,102 @@ +using CodeIndex.Indexer; +using CodeIndex.Models; + +namespace CodeIndex.Tests; + +public sealed class SqlReferenceStatementBoundaryTests +{ + [Fact] + public void SeedStatements_KeepCarryBoundedAndPreserveReferences() + { + const int count = 128; + foreach (var separator in new[] { "\n", "\nGO\n", "\n go 2 -- batch\n", ";\n" }) + { + var content = "SET NOCOUNT ON\n" + string.Concat(Enumerable.Range(0, count).Select(index => + ((index % 3) switch + { + 0 => "UPDATE dbo.Translation SET Value = N'updated' WHERE Id = 1", + 1 => "INSERT INTO dbo.Translation (Id, Value) VALUES (1, N'inserted')", + _ => "DELETE FROM dbo.Translation WHERE Id = 1" + }) + separator)); + var state = SqlReferenceExtractor.CreateState(); + var references = new List(); + var seen = new ReferenceDedupeSet(); + var lines = content.Split('\n'); + for (var index = 0; index < lines.Length; index++) + { + SqlReferenceExtractor.Emit(lines[index], lines[index], index + 1, references, seen, 1, state, + _ => null, _ => false, (_, _) => false); + Assert.True(state.StatementPrefix.Length < 256, $"Unbounded statement carry at line {index + 1}"); + } + + var actual = ReferenceExtractor.Extract(1, "sql", content, SymbolExtractor.Extract(1, "sql", content)); + var targets = actual.Where(reference => reference.SymbolName == "Translation").ToArray(); + Assert.Equal(count, targets.Length); + Assert.All(targets, reference => + { + Assert.Equal("reference", reference.ReferenceKind); + Assert.Equal("Translation", lines[reference.Line - 1].Substring(reference.Column - 1, "Translation".Length)); + }); + } + } + + [Fact] + public void MultilineStatements_PreserveContinuationAndTempObjectsAcrossBatches() + { + foreach (var (prefix, continuation) in new[] + { + ("INSERT INTO dbo.Target SELECT (", "UPDATE dbo.Source SET Id = 2"), + ("INSERT INTO dbo.Target (Id) VALUES (1) ON CONFLICT (Id) DO", "UPDATE SET Id = excluded.Id"), + ("INSERT INTO dbo.Target (Id) VALUES (1) ON DUPLICATE KEY", "UPDATE Id = 2"), + ("WITH changed AS (SELECT Id FROM dbo.Source)", "UPDATE dbo.Target SET Id = 2"), + ("MERGE dbo.Target USING dbo.Source ON 1=1 WHEN MATCHED THEN", "UPDATE SET Id = 2"), + ("SELECT * FROM", "[GO]"), + ("SELECT * FROM", "GO_Source"), + ("SELECT * FROM", "GO.Id"), + }) + { + var state = SqlReferenceExtractor.CreateState(); + state.StatementPrefix = prefix; + SqlReferenceExtractor.Emit(continuation, continuation, 2, [], new ReferenceDedupeSet(), 1, state, + _ => null, _ => false, (_, _) => false); + Assert.StartsWith(prefix + "\n", state.StatementPrefix, StringComparison.Ordinal); + } + + const string content = """ + CREATE TABLE #Scratch (Id int) + GO + INSERT INTO #Scratch (Id) + SELECT Id FROM dbo.Source + GO + DELETE FROM dbo.Target + WHERE Id IN ( + SELECT Id FROM #Scratch + ) + GO + INSERT INTO dbo.Target (Id) VALUES (1) + ON CONFLICT (Id) DO + UPDATE SET Id = excluded.Id + RETURNING Id; + MERGE dbo.Target AS t + USING dbo.Source AS s ON t.Id = s.Id + WHEN MATCHED THEN + UPDATE SET t.Id = s.Id + WHEN NOT MATCHED THEN + INSERT (Id) VALUES (s.Id); + SELECT 'first line + GO + last line', $$first line + GO + last line$$ FROM dbo.LiteralSource; + """; + var references = ReferenceExtractor.Extract(1, "sql", content, SymbolExtractor.Extract(1, "sql", content)); + Assert.Contains(references, reference => reference.SymbolName == "Source" && reference.Line == 4); + Assert.Contains(references, reference => reference.SymbolName == "#Scratch" && reference.Line == 8); + Assert.Contains(references, reference => reference.SymbolName == "Source" && reference.Line == 16); + Assert.Contains(references, reference => reference.SymbolName == "Id" && reference.ReferenceKind == "join_condition_reference"); + Assert.Contains(references, reference => reference.SymbolName == "Id" && reference.Line == 18 && reference.ReferenceKind == "column_reference"); + Assert.Contains(references, reference => reference.SymbolName == "Id" && reference.Line == 20 && reference.ReferenceKind == "column_reference"); + Assert.Contains(references, reference => reference.SymbolName == "LiteralSource" && reference.Line == 25); + Assert.DoesNotContain(references, reference => reference.SymbolName == "GO"); + } +} From b9e51102b7834ca13fc62893644bd56057a78c94 Mon Sep 17 00:00:00 2001 From: Widthdom Date: Fri, 25 Sep 2026 01:53:25 +0900 Subject: [PATCH 2/2] Fix SQL schema migration test formatting for CI --- tests/CodeIndex.Tests/DbSchemaConstraintTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CodeIndex.Tests/DbSchemaConstraintTests.cs b/tests/CodeIndex.Tests/DbSchemaConstraintTests.cs index 94961fd3a7..2a5b2a5d67 100644 --- a/tests/CodeIndex.Tests/DbSchemaConstraintTests.cs +++ b/tests/CodeIndex.Tests/DbSchemaConstraintTests.cs @@ -104,7 +104,7 @@ public void InitializeSchema_AddsSqlSystemVariableKindToExistingReferenceChecks( db.InitializeSchema(); using (var conn = new SqliteConnection(new SqliteConnectionStringBuilder - { DataSource = dbPath, Pooling = false }.ConnectionString)) + { DataSource = dbPath, Pooling = false }.ConnectionString)) { conn.Open(); var legacySql = ReadCreateSql(conn, "symbol_references")