using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using SQLInsight.Collectors.Databases; using SQLInsight.Core.Data; using SQLInsight.Core.Instances; using SQLInsight.Repository; namespace SQLInsight.IntegrationTests; /// Index usage followed over time, and statistics that were never updated. [Collection(SqlServerTestGroup.Name)] [Trait("Category", "Integration")] public class IndexUsageTests(SqlServerFixture sql) { [Fact] public async Task Usage_between_collections_is_stored_per_day_and_never_updated_statistics_are_seen() { var table = "Usage_" + Guid.NewGuid().ToString("N")[..6]; await sql.ExecuteAsSaAsync($""" USE [{SqlServerFixture.TestDatabase}]; CREATE TABLE dbo.{table} (Id int IDENTITY PRIMARY KEY, Code int NOT NULL, Name nvarchar(20)); CREATE INDEX IX_{table}_Code ON dbo.{table} (Code); CREATE STATISTICS ST_{table}_Name ON dbo.{table} (Name) WITH NORECOMPUTE; INSERT dbo.{table} (Code, Name) SELECT TOP (500) object_id % 100, N'x' FROM sys.all_objects; """); var database = "SQLInsightIdx_" + Guid.NewGuid().ToString("N")[..8]; await sql.ExecuteAsSaAsync($"CREATE DATABASE [{database}];"); try { await using var session = await AgentSession.OpenAsync(sql); var first = await session.CollectAsync(new IndexCollector()); await sql.ExecuteAsSaAsync($"USE [{SqlServerFixture.TestDatabase}]; SELECT COUNT(*) FROM dbo.{table} WITH (INDEX(IX_{table}_Code)) WHERE Code = 7;"); var second = await session.CollectAsync(new IndexCollector(), first); var index = second.Databases.Single(d => d.Name == SqlServerFixture.TestDatabase).Indexes.Single(i => i.Name == $"IX_{table}_Code"); Assert.True(index.TrackedReads >= 1); Assert.NotNull(index.TrackedSince); var delta = Assert.Single(second.UsageDeltas, d => d.Index == $"IX_{table}_Code"); Assert.True(delta.Reads >= 1); var never = second.Databases.Single(d => d.Name == SqlServerFixture.TestDatabase).Statistics.Single(s => s.Name == $"ST_{table}_Name"); Assert.Null(never.LastUpdated); Assert.Equal(500, never.Rows); // the table's rows, not the statistic's var repository = new InsightRepository(sql.DatabaseConnectionString(database)); await repository.MigrateAsync(); var id = await repository.UpsertInstanceAsync("prod-sql01", session.Instance, "SqlClient", false, DateTimeOffset.UtcNow); await repository.SaveCollectedAsync(id, "indexes", second); var usage = await repository.GetIndexUsageAsync(id, DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow); Assert.Contains(usage, u => u.IndexName == $"IX_{table}_Code" && u.Reads >= 1 && u.Days == 1); await using var factory = new WebApplicationFactory().WithWebHostBuilder(b => { b.UseEnvironment("Development"); b.UseSetting("SQLInsight:Repository:ConnectionString", sql.DatabaseConnectionString(database)); b.UseSetting("SQLInsight:Web:Authentication", "None"); }); var api = await factory.CreateClient().GetStringAsync("/api/v1/instances/prod-sql01/index-usage?days=7"); Assert.Contains($"IX_{table}_Code", api, StringComparison.Ordinal); } finally { await sql.ExecuteAsSaAsync($"ALTER DATABASE [{database}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [{database}];"); await sql.ExecuteAsSaAsync($"USE [{SqlServerFixture.TestDatabase}]; DROP TABLE dbo.{table};"); } } }