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
76 changes: 76 additions & 0 deletions .github/sql/ci_execute_report_views.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
Copyright 2026 Darling Data, LLC
https://www.erikdarling.com/

CI: execute every report.* view against the seeded rows (#1669).

Existence checks (OBJECT_ID) let two view bugs ship: #1635's bit -> nvarchar conversion (Msg 245)
and #1666's binary(8) boxed raw into sql_variant. Both compile fine and only fail (or corrupt)
when rows flow through the projection. This sweep runs AFTER ci_seed_report_sources.sql so rows
exist, and materializes each view with SELECT * INTO - a plain COUNT(*) would let the optimizer
prune the projected columns and skip exactly the per-row conversions this exists to catch.

Enumerates sys.views dynamically: a new report view is covered the day it ships, and views that
only exist post-collection (the dynamically-built report.query_snapshots pair) are simply absent
on a fresh install rather than hardcoded failures. Views whose WHERE excludes every seed row still
execute their plan shape; they just prove less - the seeds aim rows at the windows the views read.
*/

SET ANSI_NULLS ON;
SET QUOTED_IDENTIFIER ON;
SET NOCOUNT ON;
GO

USE PerformanceMonitor;
GO

DECLARE
@view_name sysname,
@sql nvarchar(max),
@failed integer = 0,
@executed integer = 0,
@failures nvarchar(max) = N'';

DECLARE view_sweep CURSOR LOCAL FAST_FORWARD FOR
SELECT
v.name
FROM sys.views AS v
WHERE SCHEMA_NAME(v.schema_id) = N'report'
ORDER BY v.name;

OPEN view_sweep;
FETCH NEXT FROM view_sweep INTO @view_name;

WHILE @@FETCH_STATUS = 0
BEGIN
/* SELECT * INTO forces every projected column to be evaluated for every row -
the temp table is scoped to the EXEC batch and vanishes with it. */
SET @sql = N'SELECT * INTO #ci_sink FROM report.' + QUOTENAME(@view_name) + N';';

BEGIN TRY
EXECUTE sys.sp_executesql @sql;
SET @executed += 1;
END TRY
BEGIN CATCH
SET @failed += 1;
SET @failures +=
NCHAR(10) + N' report.' + @view_name +
N' -> Msg ' + CAST(ERROR_NUMBER() AS nvarchar(10)) +
N': ' + ERROR_MESSAGE();
PRINT N'FAIL: report.' + @view_name + N' -> Msg ' + CAST(ERROR_NUMBER() AS nvarchar(10)) + N': ' + ERROR_MESSAGE();
END CATCH;

FETCH NEXT FROM view_sweep INTO @view_name;
END;

CLOSE view_sweep;
DEALLOCATE view_sweep;

PRINT N'Report view execution sweep: ' + CAST(@executed AS nvarchar(10)) + N' executed clean, ' + CAST(@failed AS nvarchar(10)) + N' failed.';

IF @failed > 0
BEGIN
DECLARE @error_message nvarchar(2048) = N'Report view execution sweep failed for ' + CAST(@failed AS nvarchar(10)) + N' view(s):' + LEFT(@failures, 1900);
THROW 50069, @error_message, 1;
END;
GO
58 changes: 58 additions & 0 deletions .github/sql/ci_generate_seed_rows.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
Copyright 2026 Darling Data, LLC
https://www.erikdarling.com/

DEV TOOL - not run by CI. Regenerates the generated section of ci_seed_report_sources.sql from a
database carrying the current install schema: run it there, paste the PRINTed INSERTs between the
"generated" markers, and keep the hand-authored history pairs at the bottom of that file (#1669).
*/

SET NOCOUNT ON;
/* Generate one INSERT per collect/config base table: NOT NULL, non-identity, non-computed columns.
Values are type-driven; time-ish columns land 5 minutes ago so "today"/"last hour" views see them. */
DECLARE @sql nvarchar(max);
DECLARE t CURSOR LOCAL FAST_FORWARD FOR
SELECT s.name, tb.name
FROM sys.tables AS tb
JOIN sys.schemas AS s ON s.schema_id = tb.schema_id
WHERE s.name IN (N'collect', N'config')
AND tb.is_ms_shipped = 0
ORDER BY s.name, tb.name;
DECLARE @s sysname, @tb sysname;
OPEN t;
FETCH NEXT FROM t INTO @s, @tb;
WHILE @@FETCH_STATUS = 0
BEGIN
DECLARE @cols nvarchar(max) = N'', @vals nvarchar(max) = N'';
SELECT
@cols += CASE WHEN @cols = N'' THEN N'' ELSE N', ' END + QUOTENAME(c.name),
@vals += CASE WHEN @vals = N'' THEN N'' ELSE N', ' END +
CASE
WHEN c.name = N'severity' THEN N'N''CRITICAL'''
WHEN tp.name = N'datetimeoffset' THEN N'SYSDATETIMEOFFSET()'
WHEN c.name IN (N'query_text', N'query_plan_text', N'query_sql_text', N'statement_text', N'plan_xml_compressed') AND tp.name IN (N'varbinary')
THEN N'COMPRESS(N''SELECT 1 AS ci_seed;'')'
WHEN tp.name IN (N'nvarchar', N'varchar', N'sysname', N'nchar', N'char') THEN N'N''ci'''
WHEN tp.name IN (N'datetime2', N'datetime', N'smalldatetime') THEN N'DATEADD(MINUTE, -5, SYSDATETIME())'
WHEN tp.name = N'date' THEN N'CONVERT(date, SYSDATETIME())'
WHEN tp.name = N'time' THEN N'CONVERT(time, SYSDATETIME())'
WHEN tp.name IN (N'bit') THEN N'1'
WHEN tp.name IN (N'int', N'bigint', N'smallint', N'tinyint', N'decimal', N'numeric', N'float', N'real', N'money') THEN N'1'
WHEN tp.name IN (N'varbinary', N'binary') THEN N'0x00'
WHEN tp.name = N'uniqueidentifier' THEN N'NEWID()'
WHEN tp.name = N'xml' THEN N'CONVERT(xml, N''<ci/>'')'
ELSE N'NULL /* unhandled: ' + tp.name + N' */'
END
FROM sys.columns AS c
JOIN sys.types AS tp ON tp.user_type_id = c.user_type_id
WHERE c.object_id = OBJECT_ID(QUOTENAME(@s) + N'.' + QUOTENAME(@tb))
AND c.is_identity = 0
AND c.is_computed = 0
AND c.is_nullable = 0;
IF @cols <> N''
PRINT N'IF NOT EXISTS (SELECT 1/0 FROM ' + QUOTENAME(@s) + N'.' + QUOTENAME(@tb) + N' WHERE 1 = 1) INSERT INTO ' + QUOTENAME(@s) + N'.' + QUOTENAME(@tb) + N' (' + @cols + N') VALUES (' + @vals + N');';
ELSE
PRINT N'/* ' + QUOTENAME(@s) + N'.' + QUOTENAME(@tb) + N': all columns nullable/identity - DEFAULT VALUES */';
FETCH NEXT FROM t INTO @s, @tb;
END;
CLOSE t; DEALLOCATE t;
Loading
Loading