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
33 changes: 29 additions & 4 deletions Darling/Darling.Tests/QueryStoreTopLiteralEndStraddleLiveTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ public async Task LiteralEndBeforeAppliedThrough_ReadsRaw_OnBothCallers_WithZero

await using var scratch = await ScratchPostgres.CreateAsync(baseCs!, ct);
await using var connection = await OpenMigratedAsync(scratch, ct);
Assert.True(await TimescaleSupport.TryEnableAsync(connection, null, ct), "TimescaleDB must be enabled on the test cluster for the #3953 literal-end straddle pin's chunk-floor read");
await TimescaleSupport.ConvertToHypertablesAsync(connection, null, ct);
Assert.True(await ScalarBoolAsync(connection,
"SELECT EXISTS (SELECT 1 FROM timescaledb_information.hypertables WHERE hypertable_schema = 'collect' AND hypertable_name = 'query_store_stats')", ct),
"query_store_stats did not convert to a hypertable; the pin's chunk-floor diagnostic cannot be trusted");

await using var postgres = NpgsqlDataSource.Create(scratch.ConnectionString);
var runner = new DarlingCollectorRunner(postgres, new CollectorDeltaCalculator());

Expand Down Expand Up @@ -113,8 +119,12 @@ SQL ReadsTableAsync uses — instead of leaving the next reader to re-derive the
{
var pendingCount = await ScalarLongAsync(connection,
"SELECT COUNT(*) FROM collect.query_store_interval_wide_pending WHERE server_id = @server_id", ct);
var rawFloor = await ScalarNullableDateTimeAsync(connection,
"SELECT MIN(range_start) AT TIME ZONE 'UTC' FROM timescaledb_information.chunks WHERE hypertable_schema = 'collect' AND hypertable_name = 'query_store_stats'", ct);
var chunksTableExists = await ScalarBoolAsync(connection,
"SELECT to_regclass('timescaledb_information.chunks') IS NOT NULL", ct);
var rawFloor = chunksTableExists
? await ScalarNullableDateTimeAsync(connection,
"SELECT MIN(range_start) AT TIME ZONE 'UTC' FROM timescaledb_information.chunks WHERE hypertable_schema = 'collect' AND hypertable_name = 'query_store_stats'", ct)
: (DateTime?)null;
var tableFloor = await ScalarNullableDateTimeAsync(connection,
"SELECT MIN(t.first_execution_time) FROM collect.query_store_interval_wide AS t WHERE t.server_id = @server_id", ct);
Assert.Fail(
Expand Down Expand Up @@ -194,14 +204,15 @@ private static async Task SeedStraddleAsync(DarlingCollectorRunner runner, Cance
alone refuses even the open-end positive control, before clause 4 is ever reached. A second,
unrelated identity anchored at or before skewFloor pulls the server's table floor down there, so
clause 3 passes and only clause 4 (LiteralEnd vs applied_through) can still refuse the straddle read. */
var floorAnchorFirst = WindowStart.AddDays(-2);
var floorAnchor = new QueryStoreCollector.Row
{
DatabaseName = "qsStraddle",
QueryId = 6999,
PlanId = 69991,
ExecutionTypeDesc = "Regular",
FirstExecutionTime = WindowStart.AddDays(-2),
LastExecutionTime = WindowStart.AddDays(-2).AddMinutes(9),
FirstExecutionTime = floorAnchorFirst,
LastExecutionTime = floorAnchorFirst.AddMinutes(9),
QueryHash = "0x00006999",
QueryPlanHash = "0x00069991",
ExecutionCount = 10,
Expand All @@ -210,6 +221,9 @@ clause 3 passes and only clause 4 (LiteralEnd vs applied_through) can still refu
IsForcedPlan = false,
ForceFailureCount = 0,
RuntimeStatsIntervalId = 6998,
/* Tier-2 row: a legacy row (IntervalStartTimeUtc left null) makes clause 6 refuse the table, so
every seed row here must carry it to isolate clause 4 alone. */
IntervalStartTimeUtc = new DateTime(floorAnchorFirst.Year, floorAnchorFirst.Month, floorAnchorFirst.Day, floorAnchorFirst.Hour, 0, 0, DateTimeKind.Unspecified),
};
await runner.WriteBackfillBatchAsync(QueryStoreCollector.Instance, new List<QueryStoreCollector.Row> { floorAnchor }, server, WindowStart.AddDays(-2).AddMinutes(10), context, ct);

Expand All @@ -229,6 +243,9 @@ clause 3 passes and only clause 4 (LiteralEnd vs applied_through) can still refu
IsForcedPlan = false,
ForceFailureCount = 0,
RuntimeStatsIntervalId = 7000,
/* Tier-2 row: same interval (same RuntimeStatsIntervalId), so the same interval start as the
re-fetch below; a legacy row here would make clause 6 refuse the table. */
IntervalStartTimeUtc = new DateTime(day0.Year, day0.Month, day0.Day, day0.Hour, 0, 0, DateTimeKind.Unspecified),
};
await runner.WriteBackfillBatchAsync(QueryStoreCollector.Instance, new List<QueryStoreCollector.Row> { insideWindow }, server, day0.AddMinutes(10), context, ct);

Expand All @@ -249,6 +266,8 @@ clause 3 passes and only clause 4 (LiteralEnd vs applied_through) can still refu
IsForcedPlan = false,
ForceFailureCount = 0,
RuntimeStatsIntervalId = 7000,
/* Tier-2 row: same interval as insideWindow above, so the same interval start. */
IntervalStartTimeUtc = new DateTime(day0.Year, day0.Month, day0.Day, day0.Hour, 0, 0, DateTimeKind.Unspecified),
};
await runner.WriteBackfillBatchAsync(QueryStoreCollector.Instance, new List<QueryStoreCollector.Row> { reFetched }, server, afterLiteralEnd, context, ct);
}
Expand All @@ -265,6 +284,12 @@ private static async Task ForceFilledSinceAsync(NpgsqlConnection connection, Dat
await command.ExecuteNonQueryAsync(ct);
}

private static async Task<bool> ScalarBoolAsync(NpgsqlConnection connection, string sql, CancellationToken ct)
{
await using var command = new NpgsqlCommand(sql, connection);
return (bool)(await command.ExecuteScalarAsync(ct))!;
}

private static async Task<NpgsqlConnection> OpenMigratedAsync(ScratchPostgres scratch, CancellationToken ct)
{
var connection = new NpgsqlConnection(scratch.ConnectionString);
Expand Down
8 changes: 7 additions & 1 deletion Darling/Darling.Tests/SharedBaselineCacheTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,13 @@ public async Task Live_TheNextAnalysisHour_AndAnInvalidation_Recompute()
{
await using var store = await SeededStore.CreateAsync();
if (store is null) return;
var (h, ct) = (store.Hour, TestContext.Current.CancellationToken);

/* Anchor to midnight of store.Hour's own UTC day, not store.Hour itself: store.Hour is End's hour minus 6,
which wraps to hour 23 of the PREVIOUS day whenever CI's wall clock is 00:00-05:59 UTC — the exact window
this test started failing in. h.AddHours(1) below must stay inside the SAME RoundedDay key as h for the
"next hour is a cache hit" assertion to hold no matter what hour CI runs at. */
var h = PgBaselineProvider.RoundedDay(store.Hour);
var ct = TestContext.Current.CancellationToken;
var shared = new BaselineCache();
var provider = new PgTargetBaselineProvider(store.Postgres, null, shared);

Expand Down
Loading