diff --git a/src/FileSearch.Core/Indexing/IndexDatabase.cs b/src/FileSearch.Core/Indexing/IndexDatabase.cs index 0d14ba2..baebec5 100644 --- a/src/FileSearch.Core/Indexing/IndexDatabase.cs +++ b/src/FileSearch.Core/Indexing/IndexDatabase.cs @@ -21,7 +21,7 @@ namespace FileSearch.Core.Indexing; /// internal sealed class IndexDatabase : IDisposable { - internal const string CurrentSchemaVersion = "25"; + internal const string CurrentSchemaVersion = "26"; private static readonly TimeSpan CompactLeaseDrainTimeout = TimeSpan.FromSeconds(5); /// @@ -715,17 +715,17 @@ private static async Task EnsureMetaTableAsync(Database db, CancellationToken ca private async Task EnsureSchemaAsync(Database db, CancellationToken cancellationToken) { - await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS index_volumes (id INTEGER PRIMARY KEY, volume_key TEXT, volume_serial TEXT, filesystem_name TEXT, is_remote INTEGER, usn_supported INTEGER, drive_kind TEXT, journal_id TEXT, last_committed_usn INTEGER, health TEXT, last_checked_utc_ticks INTEGER, last_error TEXT)", cancellationToken).ConfigureAwait(false); - await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS index_roots (id INTEGER PRIMARY KEY, root_path TEXT, indexed_utc_ticks INTEGER, options_hash TEXT, volume_id INTEGER, last_full_scan_utc_ticks INTEGER, root_file_reference_number TEXT, root_parent_file_reference_number TEXT, content_version TEXT, location_kind TEXT, update_strategy TEXT, strategy_warning TEXT, usn_catch_up_enabled INTEGER, watcher_recommended INTEGER, last_full_validation_utc_ticks INTEGER, last_validation_status TEXT, last_validation_message TEXT, last_validation_files_checked INTEGER, last_validation_missing_from_index_count INTEGER, last_validation_changed_count INTEGER, last_validation_missing_from_disk_count INTEGER, last_validation_failed_count INTEGER)", cancellationToken).ConfigureAwait(false); - await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS index_directories (id INTEGER PRIMARY KEY, root_id INTEGER, path TEXT, volume_id INTEGER, directory_reference_number TEXT, parent_file_reference_number TEXT, observed_utc_ticks INTEGER)", cancellationToken).ConfigureAwait(false); - await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY, root_id INTEGER, path TEXT, path_lower TEXT, directory_path TEXT, directory_path_lower TEXT, file_name TEXT, file_name_lower TEXT, extension TEXT, size_bytes INTEGER, created_utc_ticks INTEGER, modified_utc_ticks INTEGER, attributes INTEGER, file_type_category TEXT, indexed_utc_ticks INTEGER, status TEXT, error TEXT, volume_id INTEGER, file_reference_number TEXT, parent_file_reference_number TEXT, last_observed_usn INTEGER, content_version TEXT, open_count INTEGER, last_opened_utc_ticks INTEGER, extractor_id TEXT, extractor_version TEXT, extraction_attempt_count INTEGER, last_extraction_attempt_utc_ticks INTEGER)", cancellationToken).ConfigureAwait(false); - await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS extraction_issues (id INTEGER PRIMARY KEY, file_id INTEGER, member_path TEXT, code TEXT, message TEXT, severity TEXT, created_utc_ticks INTEGER)", cancellationToken).ConfigureAwait(false); - await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS validation_drifts (id INTEGER PRIMARY KEY, root_id INTEGER, path TEXT, kind TEXT, message TEXT, observed_utc_ticks INTEGER)", cancellationToken).ConfigureAwait(false); - await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS file_metadata_tokens (id INTEGER PRIMARY KEY, root_id INTEGER, file_id INTEGER, token TEXT)", cancellationToken).ConfigureAwait(false); + await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS index_volumes (id INTEGER PRIMARY KEY, volume_key TEXT, volume_serial TEXT, filesystem_name TEXT, is_remote INTEGER, usn_supported INTEGER, drive_kind TEXT, journal_id TEXT, last_committed_usn INTEGER, health TEXT, last_checked_utc_ticks BIGINT, last_error TEXT)", cancellationToken).ConfigureAwait(false); + await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS index_roots (id INTEGER PRIMARY KEY, root_path TEXT, indexed_utc_ticks BIGINT, options_hash TEXT, volume_id INTEGER, last_full_scan_utc_ticks BIGINT, root_file_reference_number TEXT, root_parent_file_reference_number TEXT, content_version TEXT, location_kind TEXT, update_strategy TEXT, strategy_warning TEXT, usn_catch_up_enabled INTEGER, watcher_recommended INTEGER, last_full_validation_utc_ticks BIGINT, last_validation_status TEXT, last_validation_message TEXT, last_validation_files_checked INTEGER, last_validation_missing_from_index_count INTEGER, last_validation_changed_count INTEGER, last_validation_missing_from_disk_count INTEGER, last_validation_failed_count INTEGER)", cancellationToken).ConfigureAwait(false); + await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS index_directories (id INTEGER PRIMARY KEY, root_id INTEGER, path TEXT, volume_id INTEGER, directory_reference_number TEXT, parent_file_reference_number TEXT, observed_utc_ticks BIGINT)", cancellationToken).ConfigureAwait(false); + await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY, root_id INTEGER, path TEXT, path_lower TEXT, directory_path TEXT, directory_path_lower TEXT, file_name TEXT, file_name_lower TEXT, extension TEXT, size_bytes INTEGER, created_utc_ticks BIGINT, modified_utc_ticks BIGINT, attributes INTEGER, file_type_category TEXT, indexed_utc_ticks BIGINT, status TEXT, error TEXT, volume_id INTEGER, file_reference_number TEXT, parent_file_reference_number TEXT, last_observed_usn INTEGER, content_version TEXT, open_count INTEGER, last_opened_utc_ticks BIGINT, extractor_id TEXT, extractor_version TEXT, extraction_attempt_count INTEGER, last_extraction_attempt_utc_ticks BIGINT)", cancellationToken).ConfigureAwait(false); + await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS extraction_issues (id INTEGER PRIMARY KEY, file_id INTEGER, member_path TEXT, code TEXT, message TEXT, severity TEXT, created_utc_ticks BIGINT)", cancellationToken).ConfigureAwait(false); + await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS validation_drifts (id INTEGER PRIMARY KEY, root_id INTEGER, path TEXT, kind TEXT, message TEXT, observed_utc_ticks BIGINT)", cancellationToken).ConfigureAwait(false); + await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS file_metadata_tokens (id BIGINT PRIMARY KEY, root_id INTEGER, file_id INTEGER, token TEXT)", cancellationToken).ConfigureAwait(false); await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS content_units (id INTEGER PRIMARY KEY, file_id INTEGER, kind TEXT, locator_json TEXT, unit_text TEXT, content_hash TEXT, language TEXT, extractor_id TEXT, extractor_version TEXT)", cancellationToken).ConfigureAwait(false); await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS lines (id INTEGER PRIMARY KEY, file_id INTEGER, content_unit_id INTEGER, line_number INTEGER, content TEXT, anchor_json TEXT)", cancellationToken).ConfigureAwait(false); - await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS file_trigrams (root_id INTEGER, trigram_code INTEGER, file_id INTEGER)", cancellationToken).ConfigureAwait(false); - await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS pending_changes (id INTEGER PRIMARY KEY, root_path TEXT, path TEXT, kind INTEGER, queued_utc_ticks INTEGER)", cancellationToken).ConfigureAwait(false); + await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS file_trigrams (root_id INTEGER, trigram_code BIGINT, file_id INTEGER)", cancellationToken).ConfigureAwait(false); + await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS pending_changes (id INTEGER PRIMARY KEY, root_path TEXT, path TEXT, kind INTEGER, queued_utc_ticks BIGINT)", cancellationToken).ConfigureAwait(false); await db.ExecuteAsync("CREATE TABLE IF NOT EXISTS index_sequences (name TEXT PRIMARY KEY, next_id INTEGER)", cancellationToken).ConfigureAwait(false); await TryExecuteAsync(db, "CREATE UNIQUE INDEX IF NOT EXISTS idx_index_roots_path ON index_roots(root_path)", cancellationToken).ConfigureAwait(false); diff --git a/tests/FileSearch.Core.Tests/FileIndexTests.cs b/tests/FileSearch.Core.Tests/FileIndexTests.cs index c749a39..2a947f4 100644 --- a/tests/FileSearch.Core.Tests/FileIndexTests.cs +++ b/tests/FileSearch.Core.Tests/FileIndexTests.cs @@ -1258,6 +1258,31 @@ public async Task CurrentSchemaWithMissingColumnsIsTreatedAsMissingUntilRebuilt( Assert.EndsWith("shape-rebuilt.txt", hit.Path); } + [Fact] + public async Task CurrentSchemaStoresUtcTicksAsBigInt() + { + File.WriteAllText(Path.Combine(_root, "bigint-schema.txt"), "bigint schema needle\n"); + await BuildAsync(); + + const long largeTickValue = 639219823516075047; + await using var db = await Database.OpenAsync(_dbPath, TestContext.Current.CancellationToken); + await db.ExecuteAsync($"UPDATE index_roots SET indexed_utc_ticks = {largeTickValue}", TestContext.Current.CancellationToken); + await db.ExecuteAsync($"UPDATE index_volumes SET last_checked_utc_ticks = {largeTickValue}", TestContext.Current.CancellationToken); + await db.ExecuteAsync($"UPDATE files SET created_utc_ticks = {largeTickValue}", TestContext.Current.CancellationToken); + + await AssertAnyRowMatchesAsync("index_roots", "indexed_utc_ticks"); + await AssertAnyRowMatchesAsync("files", "created_utc_ticks"); + + async Task AssertAnyRowMatchesAsync(string table, string column) + { + await using var result = await db.ExecuteAsync( + $"SELECT COUNT(*) FROM {table} WHERE {column} = {largeTickValue}", + TestContext.Current.CancellationToken); + Assert.True(await result.MoveNextAsync(TestContext.Current.CancellationToken)); + Assert.True(result.Current[0].AsInteger > 0); + } + } + [Fact] public async Task IndexedSearchToleratesWalCleanupContention() {