Skip to content

Commit 5e3c3bb

Browse files
Remove-DbaDbTableData - Fix normalized table quoting edge cases (review of #10316)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent fcdca10 commit 5e3c3bb

3 files changed

Lines changed: 154 additions & 5 deletions

File tree

docs/trackers/features/commit-bug-review-TRACKER.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ Find real bugs (logic errors, null refs, incorrect behavior) and fix them. Skip
124124
| 1662d73a6 | New-DbaDatabase - Support Azure Blob Storage paths for data and log files (#10315) | DONE | Preserved rooted Data/Log paths for validation while still trimming file names; added unit regression tests. |
125125
| 241a118ce | Test-DbaDbCompression, Get-DbaDbPageInfo - Normalize table names via Get-ObjectNameParts (#10313) | DONE | Preserved schema-/database-qualified table matching and escaped SQL literals; added unit regression tests. |
126126
| 14a47a26c | Export-DbaCsv, Export-DbaDacPackage - Normalize table/schema names via Get-ObjectNameParts (#10314) | DONE | Export-DbaCsv invalid-name handling was already fixed by follow-up 74a2d1ae1; this review added Export-DbaDacPackage table-name validation and a unit regression test.
127-
| fe639f6e6 | Remove-DbaDbTableData - Normalize table name via Get-ObjectNameParts (#10316) | PENDING | |
127+
| fe639f6e6 | Remove-DbaDbTableData - Normalize table name via Get-ObjectNameParts (#10316) | DONE | Preserved database..table names, escaped ] in rebuilt identifiers, and added regression coverage. |
128128
| 070d2ee7f | Fix AppVeyor dbatools.library cache miss by installing to AllUsers scope (#10335) | PENDING | |
129129
| e3f6cc121 | Fix test for Invoke-DbaAdvancedUpdate (#10334) | PENDING | |
130130
| 6aafd24ed | Fix test for Test-DbaSpn by suppressing the warning on AppVeyor (#10331) | PENDING | |

public/Remove-DbaDbTableData.ps1

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,12 +218,26 @@ function Remove-DbaDbTableData {
218218

219219
if (Test-Bound Table) {
220220
$nameParts = Get-ObjectNameParts -ObjectName $Table
221+
if (-not $nameParts.Parsed) {
222+
Stop-Function -Message "Please check you are using proper one-, two-, or three-part names. If your table name contains special characters you must use [ ] to wrap the name. The value $Table could not be parsed as a valid table name."
223+
return
224+
}
225+
226+
$quotedTableName = "[" + $nameParts.Name.Replace("]", "]]") + "]"
221227
if ($nameParts.Database) {
222-
$bracketedTable = "[$($nameParts.Database)].[$($nameParts.Schema)].[$($nameParts.Name)]"
228+
$quotedDatabaseName = "[" + $nameParts.Database.Replace("]", "]]") + "]"
229+
230+
if ($nameParts.Schema) {
231+
$quotedSchemaName = "[" + $nameParts.Schema.Replace("]", "]]") + "]"
232+
$bracketedTable = "$quotedDatabaseName.$quotedSchemaName.$quotedTableName"
233+
} else {
234+
$bracketedTable = "$quotedDatabaseName..$quotedTableName"
235+
}
223236
} elseif ($nameParts.Schema) {
224-
$bracketedTable = "[$($nameParts.Schema)].[$($nameParts.Name)]"
237+
$quotedSchemaName = "[" + $nameParts.Schema.Replace("]", "]]") + "]"
238+
$bracketedTable = "$quotedSchemaName.$quotedTableName"
225239
} else {
226-
$bracketedTable = "[$($nameParts.Name)]"
240+
$bracketedTable = $quotedTableName
227241
}
228242
$sql += " DELETE TOP ($BatchSize) FROM $bracketedTable;"
229243
} elseif (Test-Bound DeleteSql) {

tests/Remove-DbaDbTableData.Tests.ps1

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0" }
22
param(
3-
$ModuleName = "dbatools",
3+
$ModuleName = "dbatools",
44
$CommandName = "Remove-DbaDbTableData",
55
$PSDefaultParameterValues = $TestConfig.Defaults
66
)
@@ -27,6 +27,101 @@ Describe $CommandName -Tag UnitTests {
2727
Compare-Object -ReferenceObject $expectedParameters -DifferenceObject $hasParameters | Should -BeNullOrEmpty
2828
}
2929
}
30+
31+
InModuleScope dbatools {
32+
Context "Table name normalization" {
33+
BeforeAll {
34+
function Write-Message { }
35+
function Select-DefaultView {
36+
param(
37+
[Parameter(ValueFromPipeline)]
38+
$InputObject,
39+
[Parameter(ValueFromRemainingArguments)]
40+
$RemainingArguments
41+
)
42+
43+
process {
44+
$InputObject
45+
}
46+
}
47+
}
48+
49+
BeforeEach {
50+
$script:executedSql = @()
51+
$script:deleteRowCounts = @(10, 0)
52+
$script:rowCountIndex = 0
53+
54+
$script:mockServer = [PSCustomObject]@{
55+
Name = "sql1"
56+
ComputerName = "sql1"
57+
DatabaseEngineType = [Microsoft.SqlServer.Management.Common.DatabaseEngineType]::Standalone
58+
}
59+
60+
$script:mockDatabase = [PSCustomObject]@{
61+
Name = "db1"
62+
Parent = $script:mockServer
63+
RecoveryModel = "Simple"
64+
}
65+
66+
Add-Member -InputObject $script:mockDatabase -Name Query -MemberType ScriptMethod -Value {
67+
param($sql)
68+
69+
$script:executedSql += $sql
70+
71+
if ($sql -like "SELECT COUNT(1) FROM msdb.dbo.log_shipping_monitor_primary*") {
72+
return 0
73+
}
74+
75+
if ($sql -eq "CHECKPOINT") {
76+
return [PSCustomObject]@{
77+
RowCount = 0
78+
ErrorMessage = $null
79+
}
80+
}
81+
82+
$rowCount = $script:deleteRowCounts[$script:rowCountIndex]
83+
if ($script:rowCountIndex -lt ($script:deleteRowCounts.Count - 1)) {
84+
$script:rowCountIndex += 1
85+
}
86+
87+
return [PSCustomObject]@{
88+
RowCount = $rowCount
89+
ErrorMessage = $null
90+
}
91+
} -Force
92+
93+
Mock Test-FunctionInterrupt { $false }
94+
Mock Get-DbaDatabase { $script:mockDatabase }
95+
Mock Stop-Function { throw $Message }
96+
}
97+
98+
It "Preserves the empty schema segment in three-part table names" {
99+
$result = Remove-DbaDbTableData -SqlInstance "sql1" -Database "db1" -Table "MyDb..Test" -BatchSize 10 -Confirm:$false
100+
$deleteSql = $script:executedSql | Where-Object { $PSItem -like "*DELETE TOP (10) FROM*" } | Select-Object -First 1
101+
102+
$deleteSql | Should -Not -BeNullOrEmpty
103+
$deleteSql.Contains("DELETE TOP (10) FROM [MyDb]..[Test];") | Should -Be $true
104+
$result.TotalRowsDeleted | Should -Be 10
105+
$result.TotalIterations | Should -Be 1
106+
}
107+
108+
It "Escapes closing brackets in bracketed table names" {
109+
$result = Remove-DbaDbTableData -SqlInstance "sql1" -Database "db1" -Table "[dbo].[Test]]Name]" -BatchSize 10 -Confirm:$false
110+
$deleteSql = $script:executedSql | Where-Object { $PSItem -like "*DELETE TOP (10) FROM*" } | Select-Object -First 1
111+
112+
$deleteSql | Should -Not -BeNullOrEmpty
113+
$deleteSql.Contains("DELETE TOP (10) FROM [dbo].[Test]]Name];") | Should -Be $true
114+
$result.TotalRowsDeleted | Should -Be 10
115+
$result.TotalIterations | Should -Be 1
116+
}
117+
118+
It "Stops early when the table name cannot be parsed" {
119+
{
120+
Remove-DbaDbTableData -SqlInstance "sql1" -Database "db1" -Table "one.two.three.four" -Confirm:$false
121+
} | Should -Throw "*could not be parsed as a valid table name*"
122+
}
123+
}
124+
}
30125
}
31126

32127
Describe $CommandName -Tag IntegrationTests {
@@ -175,6 +270,46 @@ Describe $CommandName -Tag IntegrationTests {
175270
$result.Database | Should -Be $dbnameSimpleModel
176271
(Invoke-DbaQuery -SqlInstance $server -Database $dbnameSimpleModel -Query "SELECT COUNT(1) AS [RowCount] FROM dbo.Test").RowCount | Should -Be 0
177272
}
273+
274+
It "Removes Data for a specified database when -Table uses database..table format" {
275+
$result = Remove-DbaDbTableData -SqlInstance $server -Database $dbnameSimpleModel -Table "$dbnameSimpleModel..Test" -BatchSize 10
276+
$result.TotalIterations | Should -Be 10
277+
$result.TotalRowsDeleted | Should -Be 100
278+
$result.LogBackups.Count | Should -Be 0
279+
$result.Timings.Count | Should -Be 10
280+
$result.Database | Should -Be $dbnameSimpleModel
281+
(Invoke-DbaQuery -SqlInstance $server -Database $dbnameSimpleModel -Query "SELECT COUNT(1) AS [RowCount] FROM dbo.Test").RowCount | Should -Be 0
282+
}
283+
}
284+
285+
Context "Functionality with escaped table names" {
286+
BeforeEach {
287+
$sqlAddRowsToEscapedTable = "
288+
IF OBJECT_ID(N'[dbo].[Test]]Name]', N'U') IS NOT NULL
289+
DROP TABLE [dbo].[Test]]Name];
290+
291+
CREATE TABLE [dbo].[Test]]Name] (Id INTEGER);
292+
293+
DECLARE
294+
@loopCounter INTEGER = 0;
295+
296+
WHILE @loopCounter < 25
297+
BEGIN
298+
INSERT INTO [dbo].[Test]]Name] VALUES (@loopCounter);
299+
SET @loopCounter = @loopCounter + 1;
300+
END;"
301+
$null = Invoke-DbaQuery -SqlInstance $server -Database $dbnameSimpleModel -Query $sqlAddRowsToEscapedTable
302+
}
303+
304+
It "Removes Data for a table name that contains a closing bracket" {
305+
$result = Remove-DbaDbTableData -SqlInstance $server -Database $dbnameSimpleModel -Table "[dbo].[Test]]Name]" -BatchSize 10
306+
$result.TotalIterations | Should -Be 3
307+
$result.TotalRowsDeleted | Should -Be 25
308+
$result.LogBackups.Count | Should -Be 0
309+
$result.Timings.Count | Should -Be 3
310+
$result.Database | Should -Be $dbnameSimpleModel
311+
(Invoke-DbaQuery -SqlInstance $server -Database $dbnameSimpleModel -Query "SELECT COUNT(1) AS [RowCount] FROM [dbo].[Test]]Name]").RowCount | Should -Be 0
312+
}
178313
}
179314

180315
Context "Functionality with bulk_logged recovery model" {

0 commit comments

Comments
 (0)