-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsp_SQLFlightRecorder.sql
More file actions
6956 lines (6390 loc) · 368 KB
/
Copy pathsp_SQLFlightRecorder.sql
File metadata and controls
6956 lines (6390 loc) · 368 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- =============================================================================
-- sp_SQLFlightRecorder
-- SQL Server DBA Flight Recorder
-- =============================================================================
-- Developed by: Ysaias Portes
-- Company: Forward Thinkers Consulting, LLC.
-- Web: https://www.forwardthinkersconsulting.com/
-- Contact: contact@forwardthinkersconsulting.com
-- Repository: https://github.com/fwdthinkers/sp_SQLFlightRecorder
-- License: MIT
--
-- A single pure-T-SQL stored procedure for capturing bounded SQL Server
-- diagnostic snapshots and producing prioritized DBA findings.
--
-- Current implementation scope:
-- * Help mode: usage and parameter documentation
-- * About mode: version and build metadata
-- * Install mode: idempotent FR_* repository schema creation
-- * Uninstall mode: clean removal with optional run-log archive
-- * Status mode: installation, configuration, rules, run, and footprint status
-- * Collect / CollectDebug / Report / Configure / Purge:
-- implemented as the procedure evolves through the v0.1 roadmap
--
-- Tool-Version: 1.1.3
-- Build-Date-Utc: 2026-08-27
-- Design: docs/design.md
-- Decisions: docs/decisions.md
--
-- Compatibility:
-- SQL Server 2012–2025 compatible where practical.
-- Single-file deployment. No preprocessor. No external runtime dependency.
--
-- Safety posture:
-- Default @Mode = 'Help' so accidental execution is non-destructive.
-- Production-oriented defaults: bounded reads, low deadlock priority,
-- lock timeout, READ UNCOMMITTED, and explicit opt-in for destructive modes.
--
-- Notes:
-- This procedure is intended to be installed in a user database by default.
-- Review documentation and test in a non-production environment before use.
-- =============================================================================
SET NOCOUNT ON;
SET ANSI_NULLS ON;
SET QUOTED_IDENTIFIER ON;
GO
IF OBJECT_ID(N'dbo.sp_SQLFlightRecorder', N'P') IS NULL
EXEC sys.sp_executesql N'CREATE PROCEDURE dbo.sp_SQLFlightRecorder AS RETURN 0;';
GO
ALTER PROCEDURE dbo.sp_SQLFlightRecorder
@Mode nvarchar(30) = N'Help'
, @DatabaseName sysname = NULL
, @StartTime datetime2(3) = NULL
, @EndTime datetime2(3) = NULL
, @MinSeverity nvarchar(20) = N'Low'
, @MaxFindings int = 200
, @TopN int = 50
, @OutputFormat nvarchar(20) = N'Default'
, @IncludeQueryPlans bit = 0
, @WhatIf bit = 0
, @PreserveRunLog bit = 0
, @Debug bit = 0
, @ConfigKey sysname = NULL
, @ConfigValue nvarchar(4000) = NULL
, @CreateAgentJob bit = 0
, @TimeZone sysname = NULL
AS
BEGIN
-- =========================================================================
-- Session safety primitives (D-132, D-133, D-134)
-- =========================================================================
SET NOCOUNT ON;
SET XACT_ABORT ON;
SET ANSI_NULLS ON;
SET ANSI_WARNINGS ON;
SET QUOTED_IDENTIFIER ON;
SET ARITHABORT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET LOCK_TIMEOUT 5000;
SET DEADLOCK_PRIORITY LOW;
-- =========================================================================
-- Constants and version info
-- =========================================================================
DECLARE @ToolVersion nvarchar(30) = N'1.1.3';
DECLARE @BuildDateUtc datetime2(3) = CONVERT(datetime2(3), '2026-08-27T00:00:00');
-- SchemaVersion 0.5.0: v1.1.0 adds retention/purge-support indexes on the
-- existing FR_* tables (D-199). Index-only DDL; no table shape changes.
-- Forward-only (D-038): Install creates the indexes on upgraded
-- repositories the first time it runs over them.
DECLARE @SchemaVersion nvarchar(20) = N'0.5.0';
-- Rule-pack version is part of the Markdown header contract (D-085).
-- It names the release that last changed rule logic or the rule catalog
-- (0.4.3 completed the FR_R0003 escalation before the v1.0 lock). 1.0.0-rc.1
-- and 1.0.0 are docs/process/version-metadata releases that change no rule
-- logic or catalog, so the rule-pack version intentionally stays 0.4.3 (not
-- tracked to ToolVersion — it names the last rule change, D-085).
DECLARE @RulePackVersion nvarchar(20) = N'0.4.3';
DECLARE @SupportedSqlServerRange nvarchar(50) = N'SQL Server 2012–2025';
DECLARE @PartNumber int = 1;
DECLARE @PartTotal int = 1;
-- =========================================================================
-- Capability probe (D-008, D-111, D-115, D-127) — closed key set
-- =========================================================================
DECLARE @EngineEditionProbe int = TRY_CONVERT(int, SERVERPROPERTY(N'EngineEdition'));
DECLARE @ProductMajorProbe int = TRY_CONVERT(int, SERVERPROPERTY(N'ProductMajorVersion'));
DECLARE @ProductLevelProbe nvarchar(20) = CONVERT(nvarchar(20), SERVERPROPERTY(N'ProductLevel'));
DECLARE @IsAzureSqlDb bit = CASE WHEN TRY_CONVERT(int, SERVERPROPERTY(N'EngineEdition')) = 5 THEN 1 ELSE 0 END;
DECLARE @IsAzureManagedInst bit = CASE WHEN TRY_CONVERT(int, SERVERPROPERTY(N'EngineEdition')) = 8 THEN 1 ELSE 0 END;
DECLARE @HasMsdb bit = CASE WHEN DB_ID(N'msdb') IS NOT NULL THEN 1 ELSE 0 END;
DECLARE @HasAgent bit = CASE WHEN DB_ID(N'msdb') IS NOT NULL AND OBJECT_ID(N'msdb.dbo.sysjobhistory', N'U') IS NOT NULL THEN 1 ELSE 0 END;
DECLARE @IsHadrEnabledProbe bit = TRY_CONVERT(bit, SERVERPROPERTY(N'IsHadrEnabled'));
DECLARE @PlatformProbe nvarchar(20) = N'Windows';
DECLARE @HasQueryStoreSupport bit = CASE WHEN ISNULL(@ProductMajorProbe, 0) >= 13
OR @EngineEditionProbe IN (5, 8)
THEN 1 ELSE 0 END;
-- v0.4 capability flags.
-- AT TIME ZONE is SQL 2016+ (ProductMajorVersion >= 13) and Azure (5/8). Display-only (D-180).
DECLARE @HasTimeZoneSupport bit = CASE WHEN ISNULL(@ProductMajorProbe, 0) >= 13
OR @EngineEditionProbe IN (5, 8)
THEN 1 ELSE 0 END;
-- Advanced HA DMVs exist on Box/MI; Azure SQL DB (edition 5) has no AG DMVs.
DECLARE @HasAdvancedHaSupport bit = CASE WHEN @EngineEditionProbe = 5 THEN 0
WHEN ISNULL(@IsHadrEnabledProbe, 0) = 1 THEN 1
ELSE 0 END;
-- Target server memory in MB (used by the buffer pool D-051 gate). Best-effort; safe on all editions.
DECLARE @TargetServerMemoryMb bigint = NULL;
-- Buffer pool descriptors DMV is unavailable on Azure SQL DB (edition 5).
DECLARE @HasBufferPoolSupport bit = CASE WHEN @EngineEditionProbe = 5 THEN 0 ELSE 1 END;
DECLARE @CapabilitySnapshot nvarchar(max);
BEGIN TRY
SELECT @TargetServerMemoryMb = CONVERT(bigint, cntr_value) / 1024
FROM sys.dm_os_performance_counters
WHERE RTRIM(counter_name) = N'Target Server Memory (KB)';
END TRY
BEGIN CATCH
SET @TargetServerMemoryMb = NULL; -- never fail the probe (D-008)
END CATCH;
-- Platform detection without @@VERSION parsing: host_platform is available
-- on SQL 2017+ (sys.dm_os_host_info). Older engines are Windows-only.
IF OBJECT_ID(N'sys.dm_os_host_info', N'V') IS NOT NULL
OR EXISTS (SELECT 1 FROM sys.all_objects WHERE name = N'dm_os_host_info')
BEGIN
BEGIN TRY
DECLARE @hostPlat nvarchar(256);
EXEC sys.sp_executesql
N'SELECT @p = host_platform FROM sys.dm_os_host_info;',
N'@p nvarchar(256) OUTPUT', @p = @hostPlat OUTPUT;
IF @hostPlat IS NOT NULL SET @PlatformProbe = CONVERT(nvarchar(20), @hostPlat);
END TRY
BEGIN CATCH
SET @PlatformProbe = N'Windows';
END CATCH;
END;
SET @CapabilitySnapshot = CONCAT(
N'EngineEdition=', ISNULL(CONVERT(nvarchar(10), @EngineEditionProbe), N''), N';',
N'ProductMajorVersion=', ISNULL(CONVERT(nvarchar(10), @ProductMajorProbe), N''), N';',
N'ProductLevel=', ISNULL(@ProductLevelProbe, N''), N';',
N'Platform=', @PlatformProbe, N';',
N'IsAzureSqlDb=', CONVERT(nvarchar(1), @IsAzureSqlDb), N';',
N'IsAzureManagedInstance=', CONVERT(nvarchar(1), @IsAzureManagedInst), N';',
N'HasMsdb=', CONVERT(nvarchar(1), @HasMsdb), N';',
N'HasAgent=', CONVERT(nvarchar(1), @HasAgent), N';',
N'IsHadrEnabled=', ISNULL(CONVERT(nvarchar(1), @IsHadrEnabledProbe), N'0'), N';',
N'HasQueryStoreSupport=', CONVERT(nvarchar(1), @HasQueryStoreSupport), N';',
N'HasAdvancedHaSupport=', CONVERT(nvarchar(1), @HasAdvancedHaSupport), N';',
N'HasBufferPoolSupport=', CONVERT(nvarchar(1), @HasBufferPoolSupport), N';',
N'HasTimeZoneSupport=', CONVERT(nvarchar(1), @HasTimeZoneSupport), N';',
N'TargetServerMemoryMb=', ISNULL(CONVERT(nvarchar(20), @TargetServerMemoryMb), N''), N';',
N'SchemaVersion=', @SchemaVersion);
-- =========================================================================
-- Input normalization and validation
-- =========================================================================
DECLARE @ModeNormalized nvarchar(30) = LTRIM(RTRIM(ISNULL(@Mode, N'Help')));
-- Alias 'Version' → 'About'
IF UPPER(@ModeNormalized) = N'VERSION'
SET @ModeNormalized = N'About';
-- @Debug routes Collect to safe CollectDebug (D-128): no collector rows,
-- a single FR_RunLog row written as Mode = 'CollectDebug'.
IF UPPER(@ModeNormalized) = N'COLLECT' AND @Debug = 1
SET @ModeNormalized = N'CollectDebug';
-- Validate closed set of modes
IF UPPER(@ModeNormalized) NOT IN (
N'HELP', N'ABOUT', N'INSTALL', N'UNINSTALL',
N'STATUS', N'COLLECT', N'REPORT', N'CONFIGURE', N'PURGE',
N'COLLECTDEBUG', N'COLLECTANDREPORT', N'INSTALLDEMODATA'
)
BEGIN
SELECT
N'Error' AS Status,
N'UnknownMode' AS ErrorCode,
CONCAT(N'@Mode must be Help, About, Install, Uninstall, Status, or other documented mode. You passed: ''', @Mode, N'''.') AS Message,
@ToolVersion AS ToolVersion;
RETURN;
END;
-- Validate parameters
IF UPPER(ISNULL(@MinSeverity, N'')) NOT IN (N'INFORMATIONAL', N'LOW', N'MEDIUM', N'HIGH', N'CRITICAL')
BEGIN
SELECT N'Error' AS Status, N'InvalidMinSeverity' AS ErrorCode,
N'@MinSeverity must be Informational, Low, Medium, High, or Critical.' AS Message,
@ToolVersion AS ToolVersion;
RETURN;
END;
IF @MaxFindings IS NULL OR @MaxFindings < 10 OR @MaxFindings > 2000
BEGIN
SELECT N'Error' AS Status, N'InvalidMaxFindings' AS ErrorCode,
N'@MaxFindings must be between 10 and 2000.' AS Message,
@ToolVersion AS ToolVersion;
RETURN;
END;
IF @TopN IS NULL OR @TopN < 1 OR @TopN > 1000
BEGIN
SELECT N'Error' AS Status, N'InvalidTopN' AS ErrorCode,
N'@TopN must be between 1 and 1000.' AS Message,
@ToolVersion AS ToolVersion;
RETURN;
END;
IF UPPER(ISNULL(@OutputFormat, N'')) NOT IN (N'DEFAULT', N'FINDINGSONLY', N'TIMELINEONLY', N'MARKDOWN')
BEGIN
SELECT N'Error' AS Status, N'InvalidOutputFormat' AS ErrorCode,
N'@OutputFormat must be Default, FindingsOnly, TimelineOnly, or Markdown.' AS Message,
@ToolVersion AS ToolVersion;
RETURN;
END;
IF @StartTime IS NOT NULL AND @EndTime IS NOT NULL AND @StartTime >= @EndTime
BEGIN
SELECT N'Error' AS Status, N'InvalidTimeWindow' AS ErrorCode,
N'@StartTime must be strictly less than @EndTime.' AS Message,
@ToolVersion AS ToolVersion;
RETURN;
END;
-- =========================================================================
-- Mode: ABOUT
-- =========================================================================
IF UPPER(@ModeNormalized) = N'ABOUT'
BEGIN
SELECT
@ToolVersion AS ToolVersion,
@BuildDateUtc AS BuildDateUtc,
@SupportedSqlServerRange AS SupportedSqlServerRange,
CONCAT(@PartNumber, N' of ', @PartTotal) AS ImplementationPart,
CONVERT(nvarchar(50), SYSUTCDATETIME(), 126) + N'Z' AS InvocationUtc;
RETURN;
END;
-- =========================================================================
-- Mode: HELP
-- =========================================================================
IF UPPER(@ModeNormalized) = N'HELP'
BEGIN
PRINT N'================================================================================';
PRINT N'sp_SQLFlightRecorder — SQL Server DBA Flight Recorder';
PRINT CONCAT(N'Version: ', @ToolVersion, N' | Part ', @PartNumber, N' of ', @PartTotal);
PRINT N'================================================================================';
PRINT N'';
PRINT N'MODES';
PRINT N'-----';
PRINT N' Help Default. Print this message.';
PRINT N' About Return version metadata (one-row result set).';
PRINT N' Install Create FR_* schema (idempotent). Part 3.';
PRINT N' Uninstall Drop FR_* schema. Part 3. @WhatIf previews; @PreserveRunLog archives.';
PRINT N' Status Six result sets: config, rules, runs, footprint, etc. Part 3.';
PRINT N' Collect Capture one diagnostic snapshot into FR_* tables.';
PRINT N' CollectDebug Validate collector readiness without writing collector rows.';
PRINT N' Report Read FR_* tables and return Findings + Timeline.';
PRINT N' Configure Read or update known FR_Config keys.';
PRINT N' Purge Batched retention cleanup. Applock-gated; logged. Supports @WhatIf.';
PRINT N' CollectAndReport Bounded Collect then Report in one call (D-024; non-recommended).';
PRINT N' InstallDemoData Insert synthetic demo rows so Report shows sample findings.';
PRINT N'';
PRINT N'v0.2 COLLECTORS (run automatically by Collect; capability-gated)';
PRINT N'-----------------------------------------------------------------';
PRINT N' Tempdb, Memory, AgentJobs, BackupHistory, AlwaysOnState, Deadlocks.';
PRINT N' Plan collection runs only when @IncludeQueryPlans = 1.';
PRINT N'';
PRINT N'v0.2 RULES';
PRINT N'----------';
PRINT N' FR_R0007 BlockingStorm, FR_R0008 TempdbVersionStoreGrowth,';
PRINT N' FR_R0009 TempdbFileImbalanceOrPressure, FR_R0010 FailedSqlAgentJobNearIncident,';
PRINT N' FR_R0011 MaintenanceJobOverlap, FR_R0012 BackupOverlapWithIncident,';
PRINT N' FR_R0013 DeadlocksObserved, FR_R0014 AlwaysOnRoleOrStateChange.';
PRINT N'';
PRINT N'v0.3 COLLECTORS (capability-gated; bounded)';
PRINT N'-------------------------------------------';
PRINT N' QueryStore (latest closed interval/DB), PlanCacheSummary, SchemaActivity.';
PRINT N' ErrorLog is OPT-IN (FR_Config.CollectErrorLog = 1); OFF by default.';
PRINT N'';
PRINT N'v0.3 RULES';
PRINT N'----------';
PRINT N' FR_R0015 QueryPlanRegression, FR_R0016 TopCpuConsumerInWindow,';
PRINT N' FR_R0017 QueryStoreDisabledOnUserDbs, FR_R0018 FailedPlanForcing,';
PRINT N' FR_R0019 QueryStoreNearingCapacity, FR_R0020 HighCompilationRate.';
PRINT N'';
PRINT N'PARAMETERS';
PRINT N'----------';
PRINT N' @Mode Which mode to run (default: Help).';
PRINT N' @MaxFindings Cap findings at 10–2000 rows (default: 200).';
PRINT N' @TopN Collector-side row cap per category (default: 50).';
PRINT N' @OutputFormat Default, FindingsOnly, TimelineOnly, or Markdown (default: Default).';
PRINT N' @IncludeQueryPlans RESERVED; no-op in this build. Plan capture and plan-XML';
PRINT N' analysis are disabled by design (D-015/D-046/D-082/D-136):';
PRINT N' this tool never reads sys.dm_exec_query_plan and never';
PRINT N' shreds plan XML in T-SQL. 1 = emit an honest coverage note.';
PRINT N' @DatabaseName Report: scope DB-bound findings/timeline to one database.';
PRINT N' Instance-level and Coverage findings are always retained.';
PRINT N' @MinSeverity Report filter applied after rules: Informational, Low, Medium,';
PRINT N' High, Critical (default: Low). Critical and Coverage are never hidden.';
PRINT N' @Debug 1 with @Mode=Collect routes to safe CollectDebug: no collector';
PRINT N' rows; dynamic SQL is printed, not executed (default: 0).';
PRINT N' @WhatIf Preview without executing (Uninstall, Purge modes).';
PRINT N' @PreserveRunLog Uninstall: 1=archive FR_RunLog with timestamped name (default: 0).';
PRINT N' @ConfigKey Configure mode: key to update. NULL returns all config.';
PRINT N' @ConfigValue Configure mode: value to write for @ConfigKey.';
PRINT N' @CreateAgentJob Install mode: explicit opt-in SQL Agent job creation.';
PRINT N'';
PRINT N'CHARTER PILLARS';
PRINT N'---------------';
PRINT N' * Boring, transparent, deterministic behavior.';
PRINT N' * Honest: every finding has Severity, Confidence, EvidenceType.';
PRINT N' * Safe on production: bounded reads, no plan shredding, READ UNCOMMITTED.';
PRINT N' * Compatible: SQL Server 2012–2025 (capability-driven branching, no string parsing).';
PRINT N' * Open source first: GitHub-native, DBA-friendly contribution model.';
PRINT N'';
PRINT N'v0.4 COLLECTORS (capability-gated; bounded)';
PRINT N'-------------------------------------------';
PRINT N' AdvancedHaState (AG queues/health; non-Azure-DB), BufferPool (opt-in; >256 GB skipped).';
PRINT N'';
PRINT N'v0.4 RULES';
PRINT N'----------';
PRINT N' FR_R0021 ConfigurationChangeInWindow, FR_R0022 LogReuseWaitElevated,';
PRINT N' FR_R0023 ThreadpoolWaitsObserved, FR_R0024 ResourceSemaphoreWaits,';
PRINT N' FR_R0025 RecentCheckDbOrBackupAge, FR_R0026 CoverageAndCapabilitySummary.';
PRINT N'';
PRINT N' @TimeZone Report: display-only IANA/Windows time zone for Markdown/Status output.';
PRINT N' UTC remains the storage and sort key. Falls back to UTC pre-SQL 2016.';
PRINT N'================================================================================';
RETURN;
END;
-- =========================================================================
-- Mode: INSTALL
-- =========================================================================
IF UPPER(@ModeNormalized) = N'INSTALL'
BEGIN
-- Validation
IF DB_NAME() IN (N'master', N'model', N'msdb', N'tempdb', N'distribution')
BEGIN
SELECT N'Error' AS Status, N'SystemDatabaseRefused' AS ErrorCode,
N'Install is allowed only in a user database (D-004).' AS Message,
@ToolVersion AS ToolVersion;
RETURN;
END;
IF ISNULL(CONVERT(nvarchar(20), DATABASEPROPERTYEX(DB_NAME(), N'Updateability')), N'') <> N'READ_WRITE'
BEGIN
SELECT N'Error' AS Status, N'ReadOnlyDatabaseRefused' AS ErrorCode,
N'Database must be READ_WRITE.' AS Message,
@ToolVersion AS ToolVersion;
RETURN;
END;
IF NOT EXISTS (
SELECT 1 FROM sys.fn_my_permissions(NULL, N'SERVER') p
WHERE p.permission_name = N'VIEW SERVER STATE'
)
BEGIN
SELECT N'Error' AS Status, N'MissingViewServerState' AS ErrorCode,
N'Requires VIEW SERVER STATE permission (D-118).' AS Message,
@ToolVersion AS ToolVersion;
RETURN;
END;
-- Check existing schema version (D-038, D-039: forward-only, no downgrade)
DECLARE @ExistingSchemaVersion nvarchar(20) = NULL;
IF OBJECT_ID(N'dbo.FR_Config', N'U') IS NOT NULL
BEGIN
SELECT @ExistingSchemaVersion = ConfigValue
FROM dbo.FR_Config
WHERE ConfigKey = N'SchemaVersion';
END;
IF @ExistingSchemaVersion IS NOT NULL AND @ExistingSchemaVersion > @SchemaVersion
BEGIN
SELECT N'Error' AS Status, N'DowngradeBlocked' AS ErrorCode,
CONCAT(N'Existing schema version ', @ExistingSchemaVersion, N' > ', @SchemaVersion, N'. Downgrade not supported (D-039).') AS Message,
@ToolVersion AS ToolVersion;
RETURN;
END;
-- Compression settings (D-034)
DECLARE @EngineEdition int = TRY_CONVERT(int, SERVERPROPERTY(N'EngineEdition'));
DECLARE @ProductMajorVersion int = TRY_CONVERT(int, SERVERPROPERTY(N'ProductMajorVersion'));
DECLARE @UsePageCompression bit = 0;
DECLARE @CreateSql nvarchar(max);
DECLARE @TableCompressionClause nvarchar(64) = N'';
DECLARE @IndexCompressionClause nvarchar(64) = N'';
DECLARE @InstallAgentSummary nvarchar(600) = N'';
IF @EngineEdition IN (3, 5, 8) OR (@EngineEdition = 2 AND ISNULL(@ProductMajorVersion, 0) >= 13)
BEGIN
SET @TableCompressionClause = N' WITH (DATA_COMPRESSION = PAGE)';
SET @IndexCompressionClause = @TableCompressionClause;
END;
BEGIN TRY
-- Create FR_Config (D-025, D-026, D-030, D-031)
IF OBJECT_ID(N'dbo.FR_Config', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_Config (
ConfigKey sysname NOT NULL PRIMARY KEY CLUSTERED,
ConfigValue nvarchar(4000) NULL,
Description nvarchar(400) NULL,
ModifiedUtc datetime2(3) NOT NULL DEFAULT (SYSUTCDATETIME())
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- Create FR_RunLog (D-030, D-031)
IF OBJECT_ID(N'dbo.FR_RunLog', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_RunLog (
RunId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
StartUtc datetime2(3) NOT NULL,
EndUtc datetime2(3) NULL,
Mode nvarchar(30) NOT NULL,
Status nvarchar(20) NULL,
Reason nvarchar(400) NULL,
InstanceFingerprint nvarchar(200) NULL,
CapabilitySnapshot nvarchar(max) NULL,
ErrorMessage nvarchar(max) NULL,
LoginName sysname NULL,
HostName sysname NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_RunLog_StartUtc_RunId ON dbo.FR_RunLog (StartUtc, RunId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- Create FR_RunLogStep
IF OBJECT_ID(N'dbo.FR_RunLogStep', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_RunLogStep (
RunStepId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
RunId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_RunLog (RunId),
StepName nvarchar(60) NOT NULL,
StartUtc datetime2(3) NOT NULL,
EndUtc datetime2(3) NULL,
Status nvarchar(20) NULL,
RowsCollected int NULL,
Reason nvarchar(400) NULL,
ErrorMessage nvarchar(max) NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_RunLogStep_StartUtc_RunStepId ON dbo.FR_RunLogStep (StartUtc, RunStepId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- Create FR_Snapshot (D-135: inserted after children)
IF OBJECT_ID(N'dbo.FR_Snapshot', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_Snapshot (
SnapshotId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotUtc datetime2(3) NOT NULL,
InstanceFingerprint nvarchar(200) NULL,
RunId bigint NULL FOREIGN KEY REFERENCES dbo.FR_RunLog (RunId)
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_Snapshot_SnapshotUtc_SnapshotId ON dbo.FR_Snapshot (SnapshotUtc, SnapshotId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- Create FR_InstanceSnapshot
IF OBJECT_ID(N'dbo.FR_InstanceSnapshot', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_InstanceSnapshot (
InstanceSnapshotId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
ServerName sysname NULL,
EngineEdition int NULL,
ProductVersion nvarchar(50) NULL,
ProductLevel nvarchar(20) NULL,
IsHadrEnabled bit NULL,
Platform nvarchar(20) NULL,
CpuCount int NULL,
PhysicalMemoryKb bigint NULL,
SqlStartTimeUtc datetime2(3) NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_InstanceSnapshot_SnapshotUtc_InstanceSnapshotId ON dbo.FR_InstanceSnapshot (SnapshotUtc, InstanceSnapshotId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- Create FR_Configuration
IF OBJECT_ID(N'dbo.FR_Configuration', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_Configuration (
ConfigurationId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
ConfigurationKind nvarchar(30) NOT NULL,
Name nvarchar(200) NOT NULL,
ValueText nvarchar(400) NULL,
IsDefault bit NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_Configuration_SnapshotUtc_ConfigurationId ON dbo.FR_Configuration (SnapshotUtc, ConfigurationId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- Create FR_Request
IF OBJECT_ID(N'dbo.FR_Request', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_Request (
RequestId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
SessionId int NOT NULL,
DatabaseId int NOT NULL,
BlockingSessionId int NULL,
WaitTypeAtCapture nvarchar(60) NULL,
WaitTimeMs int NULL,
CpuTimeMs int NULL,
LogicalReads bigint NULL,
Status nvarchar(30) NULL,
Command nvarchar(60) NULL,
OpenTransactionCount int NULL,
QueryHash binary(8) NULL,
QueryPlanHash binary(8) NULL,
RequestedMemoryKb bigint NULL,
GrantedMemoryKb bigint NULL,
MemoryGrantTimeUtc datetime2(3) NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_Request_SnapshotUtc_RequestId ON dbo.FR_Request (SnapshotUtc, RequestId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- Create FR_Wait (D-031: clustered on SnapshotUtc, SnapshotId, WaitType)
IF OBJECT_ID(N'dbo.FR_Wait', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_Wait (
WaitId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
WaitType nvarchar(60) NOT NULL,
WaitingTasksCount bigint NOT NULL,
WaitTimeMs bigint NOT NULL,
MaxWaitTimeMs bigint NOT NULL,
SignalWaitTimeMs bigint NOT NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_Wait_SnapshotUtc_SnapshotId_WaitType ON dbo.FR_Wait (SnapshotUtc, SnapshotId, WaitType)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- Create FR_FileStat
IF OBJECT_ID(N'dbo.FR_FileStat', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_FileStat (
FileStatId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
DatabaseId int NOT NULL,
FileId int NOT NULL,
NumOfReads bigint NOT NULL,
NumOfBytesRead bigint NOT NULL,
IoStallReadMs bigint NOT NULL,
NumOfWrites bigint NOT NULL,
NumOfBytesWritten bigint NOT NULL,
IoStallWriteMs bigint NOT NULL,
SizeOnDiskBytes bigint NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_FileStat_SnapshotUtc_DatabaseId_FileId ON dbo.FR_FileStat (SnapshotUtc, DatabaseId, FileId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- Create FR_PerfCounter
IF OBJECT_ID(N'dbo.FR_PerfCounter', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_PerfCounter (
PerfCounterId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
ObjectName nvarchar(128) NOT NULL,
CounterName nvarchar(128) NOT NULL,
InstanceName nvarchar(128) NULL,
CounterValue bigint NOT NULL,
CounterType int NOT NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_PerfCounter_SnapshotUtc_ObjectName_CounterName_InstanceName ON dbo.FR_PerfCounter (SnapshotUtc, ObjectName, CounterName, InstanceName)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- Create FR_QueryText (D-027)
IF OBJECT_ID(N'dbo.FR_QueryText', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_QueryText (
QueryTextId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
QueryHash binary(8) NOT NULL,
TextHash binary(32) NOT NULL,
SqlText nvarchar(max) NULL,
FirstSeenUtc datetime2(3) NOT NULL DEFAULT (SYSUTCDATETIME()),
LastSeenUtc datetime2(3) NOT NULL DEFAULT (SYSUTCDATETIME())
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE UNIQUE NONCLUSTERED INDEX UX_FR_QueryText_QueryHash_TextHash ON dbo.FR_QueryText (QueryHash, TextHash)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- ===== v0.2 tables (Historical Correlation) =====================
-- FR_Tempdb (one row per snapshot; tempdb space + file shape)
IF OBJECT_ID(N'dbo.FR_Tempdb', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_Tempdb (
TempdbId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
VersionStoreKb bigint NULL,
UserObjectKb bigint NULL,
InternalObjectKb bigint NULL,
UnallocatedExtentKb bigint NULL,
MixedExtentKb bigint NULL,
DataFileCount int NULL,
MinDataFileSizeKb bigint NULL,
MaxDataFileSizeKb bigint NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_Tempdb_SnapshotUtc_TempdbId ON dbo.FR_Tempdb (SnapshotUtc, TempdbId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- FR_Memory (one row per snapshot; memory pressure summary)
IF OBJECT_ID(N'dbo.FR_Memory', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_Memory (
MemoryId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
TotalServerMemoryKb bigint NULL,
TargetServerMemoryKb bigint NULL,
StolenServerMemoryKb bigint NULL,
MemoryGrantsPending bigint NULL,
MemoryGrantsOutstanding bigint NULL,
PageLifeExpectancy bigint NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_Memory_SnapshotUtc_MemoryId ON dbo.FR_Memory (SnapshotUtc, MemoryId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- FR_AgentJob (delta-read msdb job history; high-water in FR_Config)
IF OBJECT_ID(N'dbo.FR_AgentJob', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_AgentJob (
AgentJobRowId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
MsdbInstanceId int NOT NULL,
JobName sysname NULL,
StepId int NULL,
StepName nvarchar(200) NULL,
RunStatus int NULL,
RunOutcome nvarchar(20) NULL,
RunStartUtc datetime2(3) NULL,
RunDurationSec int NULL,
MessageText nvarchar(400) NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_AgentJob_SnapshotUtc_AgentJobRowId ON dbo.FR_AgentJob (SnapshotUtc, AgentJobRowId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- FR_BackupHistory (delta-read msdb backupset; high-water in FR_Config)
IF OBJECT_ID(N'dbo.FR_BackupHistory', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_BackupHistory (
BackupHistoryId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
MsdbBackupSetId int NOT NULL,
DatabaseName sysname NULL,
BackupType nvarchar(20) NULL,
BackupStartUtc datetime2(3) NULL,
BackupFinishUtc datetime2(3) NULL,
BackupSizeBytes bigint NULL,
IsCopyOnly bit NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_BackupHistory_SnapshotUtc_BackupHistoryId ON dbo.FR_BackupHistory (SnapshotUtc, BackupHistoryId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- FR_AlwaysOnState (one row per replica/db per snapshot)
IF OBJECT_ID(N'dbo.FR_AlwaysOnState', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_AlwaysOnState (
AlwaysOnStateId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
AgName sysname NULL,
ReplicaServer sysname NULL,
Role nvarchar(30) NULL,
OperationalState nvarchar(30) NULL,
ConnectedState nvarchar(30) NULL,
SynchronizationHealth nvarchar(30) NULL,
DatabaseName sysname NULL,
SynchronizationState nvarchar(30) NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_AlwaysOnState_SnapshotUtc_AlwaysOnStateId ON dbo.FR_AlwaysOnState (SnapshotUtc, AlwaysOnStateId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- FR_Deadlock (dedup by graph hash; bounded read of system_health)
IF OBJECT_ID(N'dbo.FR_Deadlock', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_Deadlock (
DeadlockId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
GraphHash varbinary(32) NOT NULL,
DeadlockTimeUtc datetime2(3) NULL,
ProcessCount int NULL,
DeadlockGraph nvarchar(max) NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_Deadlock_SnapshotUtc_DeadlockId ON dbo.FR_Deadlock (SnapshotUtc, DeadlockId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE NONCLUSTERED INDEX IX_FR_Deadlock_GraphHash ON dbo.FR_Deadlock (GraphHash)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- FR_QueryPlan (opt-in @IncludeQueryPlans storage; child of FR_Snapshot)
IF OBJECT_ID(N'dbo.FR_QueryPlan', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_QueryPlan (
QueryPlanId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
DatabaseId int NOT NULL,
SessionId int NULL,
QueryHash binary(8) NULL,
QueryPlanHash binary(8) NULL,
PlanXml xml NULL,
PlanXmlHash varbinary(32) NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_QueryPlan_SnapshotUtc_QueryPlanId ON dbo.FR_QueryPlan (SnapshotUtc, QueryPlanId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- ===== v0.3 tables (Query Store Integration + correlation) =======
-- FR_QueryStoreTopN (latest closed QS interval per DB; D-044/D-045/D-059)
IF OBJECT_ID(N'dbo.FR_QueryStoreTopN', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_QueryStoreTopN (
QueryStoreTopNId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
DatabaseId int NOT NULL,
DatabaseName sysname NULL,
QsQueryId bigint NOT NULL,
QsPlanId bigint NULL,
IsForcedPlan bit NULL,
ForceFailureCount bigint NULL,
LastForceFailureReason nvarchar(128) NULL,
ExecutionCount bigint NULL,
TotalDurationUs bigint NULL,
AvgDurationUs bigint NULL,
TotalCpuUs bigint NULL,
AvgCpuUs bigint NULL,
AvgLogicalReads bigint NULL,
AvgPhysicalReads bigint NULL,
AvgWrites bigint NULL,
IntervalStartUtc datetime2(3) NULL,
IntervalEndUtc datetime2(3) NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_QueryStoreTopN_SnapshotUtc_Id ON dbo.FR_QueryStoreTopN (SnapshotUtc, QueryStoreTopNId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- FR_ErrorLog (opt-in, OFF by default; D-020/D-060; high-water bounded)
IF OBJECT_ID(N'dbo.FR_ErrorLog', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_ErrorLog (
ErrorLogId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
LogDateUtc datetime2(3) NULL,
ProcessInfo nvarchar(64) NULL,
Category nvarchar(30) NULL,
LogText nvarchar(2000) NULL,
TextHash varbinary(32) NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_ErrorLog_SnapshotUtc_Id ON dbo.FR_ErrorLog (SnapshotUtc, ErrorLogId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- FR_SchemaActivity (metadata-only; capped 50 DBs; D-052)
IF OBJECT_ID(N'dbo.FR_SchemaActivity', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_SchemaActivity (
SchemaActivityId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
DatabaseId int NOT NULL,
DatabaseName sysname NULL,
ActivityKind nvarchar(30) NOT NULL,
SchemaName sysname NULL,
ObjectName sysname NULL,
StatName sysname NULL,
ModifyDateUtc datetime2(3) NULL,
RowModCount bigint NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_SchemaActivity_SnapshotUtc_Id ON dbo.FR_SchemaActivity (SnapshotUtc, SchemaActivityId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- FR_PlanCacheSummary (one bounded summary row per snapshot; D-055; no plan XML)
IF OBJECT_ID(N'dbo.FR_PlanCacheSummary', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_PlanCacheSummary (
PlanCacheSummaryId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
CachedPlanCount bigint NULL,
CachedPlanSizeKb bigint NULL,
AdHocSingleUsePlanCount bigint NULL,
AdHocSingleUsePlanSizeKb bigint NULL,
CompilationsPerSec bigint NULL,
ReCompilationsPerSec bigint NULL,
BatchRequestsPerSec bigint NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_PlanCacheSummary_SnapshotUtc_Id ON dbo.FR_PlanCacheSummary (SnapshotUtc, PlanCacheSummaryId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- ===== v0.4 tables (advanced HA + buffer pool; D-051/D-056) =======
-- FR_HaState (advanced AG context per replica/db per snapshot; queue + role-change signals)
IF OBJECT_ID(N'dbo.FR_HaState', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_HaState (
HaStateId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
AgName sysname NULL,
ReplicaServer sysname NULL,
DatabaseName sysname NULL,
IsLocalReplica bit NULL,
IsPrimaryReplica bit NULL,
RoleDesc nvarchar(60) NULL,
OperationalStateDesc nvarchar(60) NULL,
ConnectedStateDesc nvarchar(60) NULL,
SynchronizationStateDesc nvarchar(60) NULL,
SynchronizationHealthDesc nvarchar(60) NULL,
AvailabilityModeDesc nvarchar(60) NULL,
FailoverModeDesc nvarchar(60) NULL,
LogSendQueueKb bigint NULL,
LogSendRateKbPerSec bigint NULL,
RedoQueueKb bigint NULL,
RedoRateKbPerSec bigint NULL,
LastCommitUtc datetime2(3) NULL,
SecondaryLagSeconds int NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_HaState_SnapshotUtc_Id ON dbo.FR_HaState (SnapshotUtc, HaStateId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- FR_BufferPool (opt-in; bounded dm_os_buffer_descriptors summary; D-051; skipped >256 GB RAM)
-- One row per database in the buffer pool; NO per-page rows, NO user-table scans.
IF OBJECT_ID(N'dbo.FR_BufferPool', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_BufferPool (
BufferPoolId bigint IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
SnapshotId bigint NOT NULL FOREIGN KEY REFERENCES dbo.FR_Snapshot (SnapshotId),
SnapshotUtc datetime2(3) NOT NULL,
DatabaseId int NOT NULL,
DatabaseName sysname NULL,
CachedPageCount bigint NULL,
CachedSizeKb bigint NULL,
FreePageCount bigint NULL,
ModifiedPageCount bigint NULL,
TotalBufferPoolKb bigint NULL,
PercentOfPool decimal(5,2) NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
SET @CreateSql = N'CREATE CLUSTERED INDEX CIX_FR_BufferPool_SnapshotUtc_Id ON dbo.FR_BufferPool (SnapshotUtc, BufferPoolId)' + @IndexCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- Create FR_Rules (D-029: metadata only; logic in code)
IF OBJECT_ID(N'dbo.FR_Rules', N'U') IS NULL
BEGIN
SET @CreateSql = N'
CREATE TABLE dbo.FR_Rules (
RuleId nvarchar(60) NOT NULL PRIMARY KEY CLUSTERED,
Category nvarchar(30) NOT NULL,
Severity nvarchar(20) NOT NULL,
Confidence nvarchar(20) NOT NULL,
EvidenceType nvarchar(20) NOT NULL,
LifecycleState nvarchar(20) NOT NULL DEFAULT N''Active'',
ShortDescription nvarchar(400) NOT NULL,
IntroducedInVersion nvarchar(20) NOT NULL
)' + @TableCompressionClause;
EXEC sys.sp_executesql @CreateSql;
END;
-- Seed FR_Config
IF NOT EXISTS (SELECT 1 FROM dbo.FR_Config WHERE ConfigKey = N'SchemaVersion')
INSERT INTO dbo.FR_Config (ConfigKey, ConfigValue, Description)
VALUES (N'SchemaVersion', @SchemaVersion, N'Forward-only migration marker (D-038).');
IF NOT EXISTS (SELECT 1 FROM dbo.FR_Config WHERE ConfigKey = N'SnapshotIntervalSeconds')
INSERT INTO dbo.FR_Config (ConfigKey, ConfigValue, Description)
VALUES (N'SnapshotIntervalSeconds', N'60', N'Default snapshot cadence (D-042).');
IF NOT EXISTS (SELECT 1 FROM dbo.FR_Config WHERE ConfigKey = N'SnapshotRetentionDays')
INSERT INTO dbo.FR_Config (ConfigKey, ConfigValue, Description)
VALUES (N'SnapshotRetentionDays', N'7', N'Snapshot retention (7 days).');
IF NOT EXISTS (SELECT 1 FROM dbo.FR_Config WHERE ConfigKey = N'RunLogRetentionDays')
INSERT INTO dbo.FR_Config (ConfigKey, ConfigValue, Description)
VALUES (N'RunLogRetentionDays', N'28', N'Run-log retention is 4x snapshot retention (D-035).');
IF NOT EXISTS (SELECT 1 FROM dbo.FR_Config WHERE ConfigKey = N'MaxRowsPerCollector')
INSERT INTO dbo.FR_Config (ConfigKey, ConfigValue, Description)
VALUES (N'MaxRowsPerCollector', N'50', N'Default per-collector row cap (D-181).');
IF NOT EXISTS (SELECT 1 FROM dbo.FR_Config WHERE ConfigKey = N'DisabledRules')
INSERT INTO dbo.FR_Config (ConfigKey, ConfigValue, Description)
VALUES (N'DisabledRules', N'', N'Semicolon-delimited disabled rule IDs (D-099).');
-- v0.2 config keys
IF NOT EXISTS (SELECT 1 FROM dbo.FR_Config WHERE ConfigKey = N'AgentJobHighWaterInstanceId')
INSERT INTO dbo.FR_Config (ConfigKey, ConfigValue, Description)
VALUES (N'AgentJobHighWaterInstanceId', N'0', N'Last msdb sysjobhistory instance_id captured (delta read, D-050).');