diff --git a/.github/scripts/backup-remote-push-regression.sql b/.github/scripts/backup-remote-push-regression.sql new file mode 100644 index 00000000..5b04c6e0 --- /dev/null +++ b/.github/scripts/backup-remote-push-regression.sql @@ -0,0 +1,63 @@ +/* Manual integration test on two disposable SQL Server instances. + Install this branch's sp_BlitzBackups in source master. Provision an exclusive + FRKPushRemote linked server to the destination, with data access and RPC OUT, + and a login that can create/drop databases and alter dbo.backupset there. + Run with sqlcmd -b. The test toggles only this dedicated link's RPC OUT option. + No source msdb metadata is modified. */ +IF DB_ID(N'FRKPushSource') IS NOT NULL + THROW 51000,'Source fixture already exists.',1; +IF EXISTS(SELECT 1 FROM FRKPushRemote.master.sys.databases WHERE name=N'FRKPushHistory') + THROW 51000,'Remote fixture already exists.',1; +CREATE DATABASE FRKPushSource; +GO +DECLARE @File nvarchar(512)=CONVERT(nvarchar(400),SERVERPROPERTY('InstanceDefaultDataPath'))+N'FRKPushSource.bak', + @Definition nvarchar(max),@UUID uniqueidentifier,@RemoteCreated bit=0; +BEGIN TRY + BACKUP DATABASE FRKPushSource TO DISK=@File WITH INIT; + SELECT @UUID=backup_set_uuid FROM msdb.dbo.backupset WHERE database_name=N'FRKPushSource' + AND database_guid=(SELECT database_guid FROM sys.database_recovery_status WHERE database_id=DB_ID(N'FRKPushSource')); + SELECT @Definition=definition FROM sys.sql_modules WHERE object_id=OBJECT_ID(N'dbo.sp_BlitzBackups'); + SET @Definition=REPLACE(@Definition,N'ALTER PROCEDURE',N'CREATE PROCEDURE'); + EXEC FRKPushSource.sys.sp_executesql @Definition; + EXEC(N'CREATE DATABASE FRKPushHistory;') AT FRKPushRemote; + SET @RemoteCreated=1; + EXEC(N'SELECT TOP(0) * INTO FRKPushHistory.dbo.backupset FROM msdb.dbo.backupset;') AT FRKPushRemote; + /* Existing legacy table: remote DDL adds media columns, then four-part DML fills them. */ + EXEC FRKPushSource.dbo.sp_BlitzBackups @PushBackupHistoryToListener=1, + @WriteBackupsToListenerName=N'FRKPushRemote',@WriteBackupsToDatabaseName=N'FRKPushHistory',@WriteBackupsLastHours=1; + EXEC sys.sp_executesql N'IF NOT EXISTS(SELECT 1 FROM FRKPushRemote.FRKPushHistory.dbo.backupset WHERE backup_set_uuid=@UUID + AND frk_media_is_usable=1 AND frk_media_has_discard=0) + THROW 51000,''Remote schema upgrade or media mapping failed.'',1;',N'@UUID uniqueidentifier',@UUID; + EXEC sys.sp_executesql N'UPDATE FRKPushRemote.FRKPushHistory.dbo.backupset SET frk_media_is_usable=0,frk_media_has_discard=1 WHERE backup_set_uuid=@UUID;',N'@UUID uniqueidentifier',@UUID; + EXEC master.dbo.sp_serveroption N'FRKPushRemote',N'rpc out',N'false'; + /* A retained full outside a zero-hour window must refresh already populated facts without RPC. */ + EXEC FRKPushSource.dbo.sp_BlitzBackups @PushBackupHistoryToListener=1, + @WriteBackupsToListenerName=N'FRKPushRemote',@WriteBackupsToDatabaseName=N'FRKPushHistory',@WriteBackupsLastHours=0; + EXEC sys.sp_executesql N'IF NOT EXISTS(SELECT 1 FROM FRKPushRemote.FRKPushHistory.dbo.backupset WHERE backup_set_uuid=@UUID + AND frk_media_is_usable=1 AND frk_media_has_discard=0) + THROW 51000,''Remote existing media facts were not refreshed without RPC.'',1;',N'@UUID uniqueidentifier',@UUID; + EXEC master.dbo.sp_serveroption N'FRKPushRemote',N'rpc out',N'true'; + EXEC(N'USE FRKPushHistory; CREATE TABLE dbo.UpdateAudit(RowsUpdated int);') AT FRKPushRemote; + EXEC(N'USE FRKPushHistory; EXEC(N''CREATE TRIGGER dbo.TrackUpdates ON dbo.backupset AFTER UPDATE AS INSERT dbo.UpdateAudit SELECT COUNT(*) FROM inserted;'');') AT FRKPushRemote; + EXEC FRKPushSource.dbo.sp_BlitzBackups @PushBackupHistoryToListener=1, + @WriteBackupsToListenerName=N'FRKPushRemote',@WriteBackupsToDatabaseName=N'FRKPushHistory',@WriteBackupsLastHours=0; + EXEC(N'IF EXISTS(SELECT 1 FROM FRKPushHistory.dbo.UpdateAudit WHERE RowsUpdated>0) THROW 51000,''Unchanged history rows were rewritten.'',1;') AT FRKPushRemote; + EXEC(N'USE FRKPushHistory; DROP TRIGGER dbo.TrackUpdates;') AT FRKPushRemote; + EXEC(N'ALTER TABLE FRKPushHistory.dbo.backupset DROP COLUMN frk_media_is_usable,frk_media_has_discard; DELETE FRKPushHistory.dbo.backupset;') AT FRKPushRemote; + EXEC master.dbo.sp_serveroption N'FRKPushRemote',N'rpc out',N'false'; + /* Legacy destinations without RPC retain the supported history-only push. */ + EXEC FRKPushSource.dbo.sp_BlitzBackups @PushBackupHistoryToListener=1, + @WriteBackupsToListenerName=N'FRKPushRemote',@WriteBackupsToDatabaseName=N'FRKPushHistory',@WriteBackupsLastHours=1; + EXEC sys.sp_executesql N'IF NOT EXISTS(SELECT 1 FROM FRKPushRemote.FRKPushHistory.dbo.backupset WHERE backup_set_uuid=@UUID) + THROW 51000,''Legacy history push requires RPC.'',1;',N'@UUID uniqueidentifier',@UUID; +END TRY +BEGIN CATCH + EXEC master.dbo.sp_serveroption N'FRKPushRemote',N'rpc out',N'true'; + IF @RemoteCreated=1 EXEC(N'DROP DATABASE FRKPushHistory;') AT FRKPushRemote; + IF DB_ID(N'FRKPushSource') IS NOT NULL DROP DATABASE FRKPushSource; + THROW; +END CATCH; +EXEC master.dbo.sp_serveroption N'FRKPushRemote',N'rpc out',N'true'; +EXEC(N'DROP DATABASE FRKPushHistory;') AT FRKPushRemote; +DROP DATABASE FRKPushSource; +PRINT 'PASS: remote push schema upgrade, media refresh, and RPC-disabled compatibility'; diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 592eadff..ba1b9979 100755 --- a/sp_BlitzBackups.sql +++ b/sp_BlitzBackups.sql @@ -1939,9 +1939,19 @@ END IF @PushMediaFacts=1 BEGIN - /* Refresh the push window and retained full anchors with missing facts. + /* Refresh media facts for the push window and retained full anchors. + Recompute existing values so corrected source metadata is reflected; missing + source rows are left unchanged. Widen @WriteBackupsLastHours for older logs. Evaluate media on the source, never by joining unqualified central media IDs. */ SET @StringToExecute=N'UPDATE h SET + frk_media_is_usable=f.frk_media_is_usable,frk_media_has_discard=f.frk_media_has_discard, + first_family_number=b.first_family_number,last_family_number=b.last_family_number, + first_recovery_fork_guid=b.first_recovery_fork_guid,last_recovery_fork_guid=b.last_recovery_fork_guid, + fork_point_lsn=b.fork_point_lsn,differential_base_lsn=b.differential_base_lsn, + differential_base_guid=b.differential_base_guid,is_copy_only=b.is_copy_only + FROM '+QUOTENAME(@WriteBackupsToListenerName)+N'.'+QUOTENAME(@WriteBackupsToDatabaseName)+N'.dbo.backupset h + JOIN msdb.dbo.backupset b ON b.backup_set_uuid=h.backup_set_uuid + CROSS APPLY (SELECT frk_media_is_usable=CASE WHEN b.first_family_number>0 AND b.last_family_number>=b.first_family_number AND (SELECT COUNT(DISTINCT m.family_sequence_number) FROM msdb.dbo.backupmediafamily m WHERE m.media_set_id=b.media_set_id @@ -1949,15 +1959,10 @@ END AND m.physical_device_name IS NOT NULL AND UPPER(m.physical_device_name)<>N''NUL'' AND m.physical_device_name<>N''/dev/null'')=b.last_family_number-b.first_family_number+1 THEN 1 ELSE 0 END, frk_media_has_discard=CASE WHEN EXISTS(SELECT 1 FROM msdb.dbo.backupmediafamily m WHERE m.media_set_id=b.media_set_id - AND (UPPER(m.physical_device_name)=N''NUL'' OR m.physical_device_name=N''/dev/null'')) THEN 1 ELSE 0 END, - first_family_number=b.first_family_number,last_family_number=b.last_family_number, - first_recovery_fork_guid=b.first_recovery_fork_guid,last_recovery_fork_guid=b.last_recovery_fork_guid, - fork_point_lsn=b.fork_point_lsn,differential_base_lsn=b.differential_base_lsn, - differential_base_guid=b.differential_base_guid,is_copy_only=b.is_copy_only - FROM '+QUOTENAME(@WriteBackupsToListenerName)+N'.'+QUOTENAME(@WriteBackupsToDatabaseName)+N'.dbo.backupset h - JOIN msdb.dbo.backupset b ON b.backup_set_uuid=h.backup_set_uuid + AND (UPPER(m.physical_device_name)=N''NUL'' OR m.physical_device_name=N''/dev/null'')) THEN 1 ELSE 0 END) f WHERE (b.backup_start_date>=DATEADD(hour,@Hours,SYSDATETIME()) OR b.type=''D'') - AND (h.frk_media_is_usable IS NULL OR h.frk_media_has_discard IS NULL);'; + AND EXISTS(SELECT f.frk_media_is_usable,f.frk_media_has_discard,b.first_family_number,b.last_family_number,b.first_recovery_fork_guid,b.last_recovery_fork_guid,b.fork_point_lsn,b.differential_base_lsn,b.differential_base_guid,b.is_copy_only + EXCEPT SELECT h.frk_media_is_usable,h.frk_media_has_discard,h.first_family_number,h.last_family_number,h.first_recovery_fork_guid,h.last_recovery_fork_guid,h.fork_point_lsn,h.differential_base_lsn,h.differential_base_guid,h.is_copy_only);'; EXEC sys.sp_executesql @StringToExecute,N'@Hours int',@Hours=@WriteBackupsLastHours; END;