Skip to content

Commit 8dbac3f

Browse files
BrentOzarclaude
andcommitted
sp_ineachdb: add Azure SQL DB support as sp_MSforeachdb drop-in
Detect EngineEdition 5 and branch the two constructs Azure forbids: - Skip the 3-part `[dbname].sys.sp_executesql` call; run `@cmd` in the current DB via `EXEC sys.sp_executesql` instead. - Seed `#ineachdb` with only the current database; downstream filter DELETEs still apply. Also rewrite incoming `@command` on Azure so sp_MSforeachdb-style inputs work unchanged: strip `USE [?]`/`USE ?` variants and collapse `[?].schema.object` / `?.schema.object` to 2-part names. Box SQL, Managed Instance (EngineEdition 8), and other editions keep the existing code path byte-for-byte. Fixes BrentOzarULTD#3943 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ed5762e commit 8dbac3f

1 file changed

Lines changed: 42 additions & 4 deletions

File tree

sp_ineachdb.sql

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,27 @@ BEGIN
105105
@cr char(2) = CHAR(13) + CHAR(10),
106106
@SQLVersion AS tinyint = (@@microsoftversion / 0x1000000) & 0xff, -- Stores the SQL Server Version Number(8(2000),9(2005),10(2008 & 2008R2),11(2012),12(2014),13(2016),14(2017),15(2019)
107107
@ServerName AS sysname = CONVERT(sysname, SERVERPROPERTY('ServerName')), -- Stores the SQL Server Instance name.
108-
@NoSpaces nvarchar(20) = N'%[^' + CHAR(9) + CHAR(32) + CHAR(10) + CHAR(13) + N']%'; --Pattern for PATINDEX
108+
@NoSpaces nvarchar(20) = N'%[^' + CHAR(9) + CHAR(32) + CHAR(10) + CHAR(13) + N']%', --Pattern for PATINDEX
109+
@IsAzureSqlDb bit = CASE WHEN CONVERT(int, SERVERPROPERTY('EngineEdition')) = 5 THEN 1 ELSE 0 END;
110+
111+
/* Azure SQL DB forbids cross-database calls and 3-part names. Rewrite the
112+
incoming @command so sp_MSforeachdb-style inputs work unchanged:
113+
- strip USE [?]; / USE ?; variants (can't change database context)
114+
- collapse [?].schema.object / ?.schema.object to schema.object
115+
Force CI collation so USE/Use/use all match regardless of server collation. */
116+
IF @IsAzureSqlDb = 1 AND @command IS NOT NULL
117+
BEGIN
118+
DECLARE @rc nvarchar(10) = @replace_character;
119+
120+
SET @command = REPLACE(@command COLLATE SQL_Latin1_General_CP1_CI_AS, N'USE [' + @rc + N'];', N'');
121+
SET @command = REPLACE(@command COLLATE SQL_Latin1_General_CP1_CI_AS, N'USE [' + @rc + N']', N'');
122+
SET @command = REPLACE(@command COLLATE SQL_Latin1_General_CP1_CI_AS, N'USE ' + @rc + N';', N'');
123+
SET @command = REPLACE(@command COLLATE SQL_Latin1_General_CP1_CI_AS, N'USE ' + @rc, N'');
124+
125+
/* Bracketed form first — otherwise '?.' would turn '[?].' into '[]'. */
126+
SET @command = REPLACE(@command COLLATE SQL_Latin1_General_CP1_CI_AS, N'[' + @rc + N'].', N'');
127+
SET @command = REPLACE(@command COLLATE SQL_Latin1_General_CP1_CI_AS, @rc + N'.', N'');
128+
END
109129

110130

111131
CREATE TABLE #ineachdb(id int, name nvarchar(512), is_distributor bit);
@@ -162,7 +182,16 @@ BEGIN
162182
3)If we find a [, we begin to accumulate the result until we reach closing ], (jumping over escaped ]]).
163183
4)Finally, tabs, line breaks and spaces are removed from unquoted names
164184
*/
165-
WITH C
185+
IF @IsAzureSqlDb = 1
186+
BEGIN
187+
/* Azure SQL DB: the session is bound to one user database. Seed with it and
188+
let the downstream filter DELETEs decide whether it survives. */
189+
INSERT #ineachdb(id, name, is_distributor)
190+
SELECT DB_ID(), DB_NAME(), 0;
191+
END
192+
ELSE
193+
BEGIN
194+
;WITH C
166195
AS (SELECT V.SrcList
167196
, CAST('' AS nvarchar(MAX)) AS Name
168197
, V.DBList
@@ -213,6 +242,7 @@ WHERE ( EXISTS (SELECT NULL FROM F WHERE F.name = d.name AND F.SrcList = 'In')
213242
OR @database_list IS NULL)
214243
AND NOT EXISTS (SELECT NULL FROM F WHERE F.name = d.name AND F.SrcList = 'Out')
215244
OPTION (MAXRECURSION 0);
245+
END
216246
;
217247
-- next, let's delete any that *don't* match various criteria passed in
218248
DELETE dbs FROM #ineachdb AS dbs
@@ -356,8 +386,16 @@ OPTION (MAXRECURSION 0);
356386

357387
IF COALESCE(@print_command_only,0) = 0
358388
BEGIN
359-
SET @exec = @dbq + @sx;
360-
EXEC @exec @cmd;
389+
IF @IsAzureSqlDb = 1
390+
BEGIN
391+
/* Azure SQL DB: no 3-part names allowed; just run in current DB. */
392+
EXEC sys.sp_executesql @cmd;
393+
END
394+
ELSE
395+
BEGIN
396+
SET @exec = @dbq + @sx;
397+
EXEC @exec @cmd;
398+
END
361399
END
362400
END TRY
363401

0 commit comments

Comments
 (0)