Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3cc8414
Add the wide Query Store interval table's write side (#3953)
erikdarlingdata Sep 25, 2026
6102e03
Merge remote-tracking branch 'origin/dev' into feature/3953-wide-inte…
erikdarlingdata Sep 25, 2026
db881dd
Add live tests for the wide interval table, and demote V143's rung te…
erikdarlingdata Sep 25, 2026
42de316
B2: build the wide interval table's read gate and move the Queries gr…
erikdarlingdata Sep 25, 2026
24c5222
Make the table read's transaction read-only and fault-safe (#3953)
erikdarlingdata Sep 25, 2026
94d93ac
Add the #3953 grid live pass: gate, clamp, and raw/table equality (#3…
erikdarlingdata Sep 25, 2026
a9c0fae
B3b: move the Queries slicer onto the #3953 wide-interval table
erikdarlingdata Sep 25, 2026
ba7bade
Move the MCP get_query_store_top read onto the #3953 wide-interval gate
erikdarlingdata Sep 25, 2026
cb72c5d
Merge remote-tracking branch 'origin/feature/3953-b3b-slicer' into fe…
erikdarlingdata Sep 25, 2026
073488f
B4: index the slicer's legacy-row probe, add the NULL-start live test…
erikdarlingdata Sep 25, 2026
829aa06
B4: pin the wide-table min-schema-version constants to V144 (#3953)
erikdarlingdata Sep 25, 2026
49e5906
Merge origin/dev into feature/3953-wide-interval-table: renumber #395…
erikdarlingdata Sep 25, 2026
935616a
Finish the V144->V145 renumber and fix the MCP command census (#3953)
erikdarlingdata Sep 25, 2026
5aa2dd9
Fix a stacked-summary regression from the census fix (#3953)
erikdarlingdata Sep 25, 2026
01d0dc8
Merge remote-tracking branch 'origin/dev' into feature/3953-wide-inte…
erikdarlingdata Sep 25, 2026
8b0b183
Set #3953 wide-table thresholds from measured 12-hour cells (B4t)
erikdarlingdata Sep 25, 2026
4f3ab36
Merge feature/3953-b4t: set #3953 wide-table thresholds from measured…
erikdarlingdata Sep 25, 2026
f86e89d
Remove the Query Store slicer's table route (#4341, ruling issuecomme…
erikdarlingdata Sep 25, 2026
a7fdde9
Merge remote-tracking branch 'origin/dev' into feature/3953-wide-inte…
erikdarlingdata Sep 25, 2026
e4ab10a
merge: bring in dev (H1/M2/M3 review fixes, no rebase)
erikdarlingdata Sep 26, 2026
0aa324b
Check the window before the #3953 gate's round trips (review D4R H1, M3)
erikdarlingdata Sep 26, 2026
1153e84
WIP-test
erikdarlingdata Sep 26, 2026
fa5265e
Apply the fault-injection collection to QueryStoreIntervalWideWriterT…
erikdarlingdata Sep 26, 2026
ceca1c1
Clean up stale grid/MCP/slicer text and the unused literalEndUtc para…
erikdarlingdata Sep 26, 2026
7373751
Merge remote-tracking branch 'origin/dev' into feature/3953-wide-inte…
erikdarlingdata Sep 26, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2026 Erik Darling, Darling Data LLC
*
* This file is part of the SQL Server Performance Monitor.
*
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
*/

using Xunit;

namespace Darling.Tests;

/// <summary>
/// Serializes <c>QueryStoreIntervalWideWriterTests</c> against the rest of this assembly's parallel
/// <c>ScratchPostgres</c> classes (review-4341-r1, the flaky-test note on
/// <c>AWideOnlyApplyFault_RecordsAWidePendingRow_AndLeavesV143Intact</c>). That test drops the wide table's unique
/// index and then re-applies a batch under the writer's own 5 s <c>lock_timeout</c>
/// (<see cref="PerformanceMonitor.Darling.Storage.QueryStoreIntervalWide.ApplyLockTimeoutSeconds"/>). Its own
/// scratch database is never contended directly, but on a CI runner with dozens of other
/// <c>ScratchPostgres</c> classes hammering the SAME Postgres cluster at once (own-store parallelism, #1776), the
/// lock manager and WAL writer can queue an otherwise-uncontended lock request long enough to trip that 5 s bound,
/// which records a 55P03 pending row instead of the expected 42P10 and fails the test's SQLSTATE-filtered count on
/// timing alone — not a logic bug (review-4341-r1, the flaky-test note). Running this class outside the default
/// parallel pool removes that shared-cluster pressure from ITS OWN test run without touching the production
/// lock_timeout or loosening the assertion.
/// </summary>
[CollectionDefinition("query-store-interval-wide-fault-injection", DisableParallelization = true)]
public sealed class QueryStoreIntervalWideFaultInjectionCollection
{
}
102 changes: 102 additions & 0 deletions Darling/Darling.Tests/QueryStoreIntervalWideGateTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright (c) 2026 Erik Darling, Darling Data LLC
*
* This file is part of the SQL Server Performance Monitor.
*
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
*/

using System;
using PerformanceMonitor.Darling.Storage;
using Xunit;

namespace Darling.Tests;

/// <summary>
/// Unit coverage for #3953's grid/MCP top gate (ruling issuecomment-5836972848; review D4R items H3, M1):
/// <see cref="QueryStoreIntervalWide.UseTable"/>, each clause alone flipping the answer to raw. Mirrors
/// <c>PlanRegressionIntervalTableEquivalenceTests</c>' own <c>UseTable</c> theory for V143's gate. The live
/// equality, clamp and end-to-end gate tests belong beside the grid/MCP top reads that call this decision.
/// </summary>
public sealed class QueryStoreIntervalWideGateTests
{
private static readonly DateTime WindowEnd = new(2026, 9, 25, 12, 0, 0, DateTimeKind.Unspecified);
private static readonly DateTime WindowStart = WindowEnd.AddHours(-24);
private static readonly TimeSpan MinWindow = QueryStoreIntervalWide.GridWideMinWindow;

/// <summary>Every argument defaults to a value that alone passes its own clause, so a test overrides only
/// the one input the clause under test cares about.</summary>
private static bool Rule(
DateTime? filledSince = null, bool hasPending = false, DateTime? rawFloor = null,
DateTime? windowStart = null, DateTime? windowEnd = null, DateTime? literalWindowEnd = null,
DateTime? appliedThrough = null, DateTime? tableFloor = null, TimeSpan? minWindow = null) =>
QueryStoreIntervalWide.UseTable(
filledSince ?? WindowStart.AddDays(-1), hasPending, rawFloor,
windowStart ?? WindowStart, windowEnd ?? WindowEnd, literalWindowEnd,
appliedThrough ?? WindowEnd, tableFloor ?? WindowStart.AddDays(-2), minWindow ?? MinWindow);

[Fact]
public void EveryClauseHolding_ReadsTheTable() => Assert.True(Rule());

[Fact]
public void Clause1_NoCoverageRow_ReadsRaw() =>
// A real DateTime? null (no coverage row at all) — bypasses Rule's "unset means default" sentinel.
Assert.False(QueryStoreIntervalWide.UseTable(
filledSince: null, hasPending: false, rawFloor: null, windowStart: WindowStart, windowEnd: WindowEnd,
literalWindowEnd: null, appliedThrough: WindowEnd, tableFloor: WindowStart.AddDays(-2), minWindow: MinWindow));

[Fact]
public void Clause1_PendingBatch_ReadsRaw_EvenWithGoodCoverage() => Assert.False(Rule(hasPending: true));

[Fact]
public void Clause2_FilledSinceAfterMaxOfRawFloorAndWindowStart_ReadsRaw() =>
// filledSince sits after both the (absent) raw floor and the window start: the table's claim starts
// later than the window needs.
Assert.False(Rule(filledSince: WindowEnd));

[Fact]
public void Clause3_TableFloorShortOfTheOneDayMargin_ReadsRaw() =>
// table floor only reaches 23h before window start — short of the full one-day margin clause 3 needs —
// and raw has no floor (a plain store): the table cannot answer for the earliest part of the window.
Assert.False(Rule(tableFloor: WindowStart.AddHours(-23)));

[Fact]
public void Clause3_TableFloorAtOrBeyondTheOneDayMargin_ReadsTheTable() =>
Assert.True(Rule(tableFloor: WindowStart.AddDays(-1)));

[Fact]
public void Clause3_RawFloorAtOrAboveTableFloor_ReadsTheTable_EvenOutsideTheMargin() =>
Assert.True(Rule(rawFloor: WindowStart.AddDays(-3), tableFloor: WindowStart.AddDays(-3)));

[Fact]
public void Clause4_OpenEnd_SkipsTheAppliedThroughComparison() =>
// appliedThrough sits after the window end (as a viewer clock running behind the store's would produce),
// but literalWindowEnd is null (a preset): clause 4 never fires.
Assert.True(Rule(literalWindowEnd: null, appliedThrough: WindowEnd.AddMinutes(5)));

[Fact]
public void Clause4_LiteralEndBeforeAppliedThrough_ReadsRaw() =>
Assert.False(Rule(literalWindowEnd: WindowEnd, appliedThrough: WindowEnd.AddMinutes(5)));

[Fact]
public void Clause4_LiteralEndAtOrAfterAppliedThrough_ReadsTheTable() =>
Assert.True(Rule(literalWindowEnd: WindowEnd, appliedThrough: WindowEnd));

[Fact]
public void Clause5_WindowShorterThanTheMinimum_ReadsRaw() =>
Assert.False(Rule(windowStart: WindowEnd.AddHours(-1)));

[Fact]
public void Clause5_WindowAtExactlyTheMinimum_ReadsTheTable() =>
Assert.True(Rule(windowStart: WindowEnd - MinWindow));

[Fact]
public void ClampedStart_UsesTheRawFloor_OnlyWhenItIsAfterTheWindowStart()
{
Assert.Equal(WindowStart, QueryStoreIntervalWide.ClampedStart(null, WindowStart));
Assert.Equal(WindowStart, QueryStoreIntervalWide.ClampedStart(WindowStart.AddHours(-1), WindowStart));

var laterFloor = WindowStart.AddHours(1);
Assert.Equal(laterFloor, QueryStoreIntervalWide.ClampedStart(laterFloor, WindowStart));
}
}
Loading
Loading