From 56abb00a2609f6b05d51b504c8936e6d6e41fde1 Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Fri, 21 Aug 2026 23:20:37 +0200 Subject: [PATCH] Set-DbaDbQueryStoreOption - Refresh the Query Store options after changing them with T-SQL MaxPlansPerQuery, WaitStatsCaptureMode and the four CustomCapturePolicy options are changed with an ALTER DATABASE statement rather than through SMO. The SMO object knows nothing about that and still holds what it read before, so the command reported the values from before its own change and left the object of the caller saying the same. This is not the staleness a reused connection brings with it: it happens on every call, because the command reads $db.QueryStoreOptions before it runs the statement and that fills the cache. The SMO path a few lines above already does Alter followed by Refresh. The T-SQL path now does the same, right after the statement, which is one round trip and only for the calls that use one of these options. Copy-DbaDbQueryStoreOption is fixed with it, because it copies the settings through this command. Fixes #10561. Tests: a Context that sets MaxPlansPerQuery and WaitStatsCaptureMode and expects the returned object and the SMO object of the caller to carry them, plus a check that the instance really has them, which passes either way and says the T-SQL was never the problem. Both new assertions fail against development with "Expected 555, but got 200". It uses the database of the Describe rather than creating one: a CREATE DATABASE at that point fails intermittently with "Could not obtain exclusive lock on database 'model'", because the tests above read Query Store on model and leave a session parked in it. That is #10584, which is not merged yet. (do Set-DbaDbQueryStoreOption, Copy-DbaDbQueryStoreOption) --- public/Set-DbaDbQueryStoreOption.ps1 | 6 +++ tests/Set-DbaDbQueryStoreOption.Tests.ps1 | 52 +++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/public/Set-DbaDbQueryStoreOption.ps1 b/public/Set-DbaDbQueryStoreOption.ps1 index 04a1de36dcf..271098a5c94 100644 --- a/public/Set-DbaDbQueryStoreOption.ps1 +++ b/public/Set-DbaDbQueryStoreOption.ps1 @@ -345,6 +345,12 @@ function Set-DbaDbQueryStoreOption { if ($query -ne "") { $null = $server.Query($query) + # These options are changed with T-SQL rather than through SMO, so the SMO + # object knows nothing about it and still holds what it read before. Without + # this the command reports the values from before its own change, and leaves + # the object of the caller saying the same. It is the treatment the SMO path + # above already gets after its Alter. See #10561. + $db.QueryStoreOptions.Refresh() } } catch { Stop-Function -Message "Could not modify configuration." -Category InvalidOperation -InnerErrorRecord $_ -Target $db -Continue diff --git a/tests/Set-DbaDbQueryStoreOption.Tests.ps1 b/tests/Set-DbaDbQueryStoreOption.Tests.ps1 index d2b4769e4f8..99896617c29 100644 --- a/tests/Set-DbaDbQueryStoreOption.Tests.ps1 +++ b/tests/Set-DbaDbQueryStoreOption.Tests.ps1 @@ -37,6 +37,14 @@ Describe $CommandName -Tag UnitTests { } Describe $CommandName -Tag IntegrationTests { + BeforeDiscovery { + # MaxPlansPerQuery and WaitStatsCaptureMode arrived with SQL Server 2017. The value decides a + # Skip, which Pester needs while it discovers the tests, so it cannot be read in BeforeAll. + $discoveryServer = Connect-DbaInstance -SqlInstance $TestConfig.InstanceMulti1 + $multi1VersionMajor = $discoveryServer.VersionMajor + $null = $discoveryServer | Disconnect-DbaInstance + } + BeforeAll { Get-DbaDatabase -SqlInstance $TestConfig.InstanceMulti1, $TestConfig.InstanceMulti2 | Where-Object Name -Match "dbatoolsci" | Remove-DbaDatabase New-DbaDatabase -SqlInstance $TestConfig.InstanceMulti1, $TestConfig.InstanceMulti2 -Name dbatoolsciqs @@ -144,4 +152,48 @@ Describe $CommandName -Tag IntegrationTests { } } } + + Context "When an option is changed that needs T-SQL" -Skip:($multi1VersionMajor -lt 14) { + BeforeAll { + $PSDefaultParameterValues["*-Dba*:EnableException"] = $true + + # The database of the Describe rather than one of its own. A CREATE DATABASE here fails + # intermittently with "Could not obtain exclusive lock on database 'model'", because the tests + # above read Query Store on model and leave a session parked in it. That is #10584, which is + # not in development yet, and this Context does not need a database of its own anyway. + $refreshDbName = "dbatoolsciqs" + + # The server object is kept, because the point of this is what the command leaves behind on it. + $refreshServer = Connect-DbaInstance -SqlInstance $TestConfig.InstanceMulti1 + $null = Set-DbaDbQueryStoreOption -SqlInstance $refreshServer -Database $refreshDbName -State ReadWrite + + # MaxPlansPerQuery and WaitStatsCaptureMode are changed with ALTER DATABASE rather than through + # SMO, so the SMO object knows nothing about it and still holds what it read before. See #10561. + $resultsRefresh = Set-DbaDbQueryStoreOption -SqlInstance $refreshServer -Database $refreshDbName -MaxPlansPerQuery 555 -WaitStatsCaptureMode Off + + $PSDefaultParameterValues.Remove("*-Dba*:EnableException") + } + + AfterAll { + $PSDefaultParameterValues["*-Dba*:EnableException"] = $true + + $null = $refreshServer | Disconnect-DbaInstance + + $PSDefaultParameterValues.Remove("*-Dba*:EnableException") + } + + It "Returns the values it has just set" { + $resultsRefresh.MaxPlansPerQuery | Should -Be 555 + $resultsRefresh.WaitStatsCaptureMode | Should -Be "Off" + } + + It "Leaves the SMO object of the caller with the new values" { + $refreshServer.Databases[$refreshDbName].QueryStoreOptions.MaxPlansPerQuery | Should -Be 555 + } + + It "Really changed them on the instance" { + $verifyQuery = "SELECT max_plans_per_query AS MaxPlansPerQuery FROM sys.database_query_store_options" + (Invoke-DbaQuery -SqlInstance $TestConfig.InstanceMulti1 -Database $refreshDbName -Query $verifyQuery).MaxPlansPerQuery | Should -Be 555 + } + } } \ No newline at end of file