Skip to content

Commit fb3bcff

Browse files
andreasjordanclaude
andcommitted
Get-BulkRowsCopiedCount - Read the rows copied counter as Int64
The _rowsCopied field of SqlBulkCopy is an Int64 in current Microsoft.Data.SqlClient (the 4 byte counter of the legacy library, which Get-AdjustedTotalRowsCopied works around, was fixed upstream). The cast to [int] therefore throws above [int32]::MaxValue rows, the catch returned the -1 failure sentinel, and the callers fed that sentinel into the wrap adjustment as if it were a wrapped counter: a copy of 3 billion rows reported 4294967295 rows copied while the destination held the correct count. The callers now skip the final adjustment when the helper signals failure, so the running total from the notifications stands instead of a number that is off by billions. The wrap adjustment itself stays, it is still correct for genuinely wrapped values from an old library. Fixes #10675 (do Copy-DbaDbTableData, Import-DbaCsv, Import-DbaParquet, Get-BulkRowsCopiedCount) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 63a9dee commit fb3bcff

5 files changed

Lines changed: 79 additions & 5 deletions

File tree

private/functions/Get-BulkRowsCopiedCount.ps1

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ function Get-BulkRowsCopiedCount {
1414
- Copy-DbaDbTableData
1515
- Copy-DbaDbViewData
1616
- Import-DbaCsv
17+
- Import-DbaParquet
1718
1819
.EXAMPLE
1920
Get-BulkRowsCopied $bulkObject
@@ -27,14 +28,17 @@ function Get-BulkRowsCopiedCount {
2728
Copyright: (c) 2020 by dbatools, licensed under MIT
2829
License: MIT https://opensource.org/licenses/MIT
2930
#>
30-
[OutputType([int])]
31+
[OutputType([long])]
3132
param (
3233
[Microsoft.Data.SqlClient.SqlBulkCopy] $BulkCopy
3334
)
3435
$BindingFlags = [Reflection.BindingFlags] "NonPublic,GetField,Instance"
3536
$rowsCopiedField = [Microsoft.Data.SqlClient.SqlBulkCopy].GetField("_rowsCopied", $BindingFlags)
3637
try {
37-
return [int]$rowsCopiedField.GetValue($BulkCopy)
38+
# The field is an Int64 in current Microsoft.Data.SqlClient (an Int32 in the legacy library),
39+
# so it must not be narrowed to [int]: above [int32]::MaxValue rows that cast throws, and the
40+
# -1 then taken for the row count inflates the reported total by billions of rows (see #10675).
41+
return [long]$rowsCopiedField.GetValue($BulkCopy)
3842
} catch {
3943
return -1;
4044
}

public/Copy-DbaDbTableData.ps1

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,13 @@ function Copy-DbaDbTableData {
722722
$bulkCopy.WriteToServer($reader)
723723
$finalRowCountReported = Get-BulkRowsCopiedCount $bulkCopy
724724

725-
$script:totalRowsCopied += (Get-AdjustedTotalRowsCopied -ReportedRowsCopied $finalRowCountReported -PreviousRowsCopied $script:prevRowsCopied).NewRowCountAdded
725+
# -1 signals that the reflection lookup failed, not a wrapped counter, so it must not
726+
# reach Get-AdjustedTotalRowsCopied: fed in as a row count it inflates the total by
727+
# billions of rows (see #10675). The running total from the notifications is then the
728+
# best number available.
729+
if ($finalRowCountReported -ge 0) {
730+
$script:totalRowsCopied += (Get-AdjustedTotalRowsCopied -ReportedRowsCopied $finalRowCountReported -PreviousRowsCopied $script:prevRowsCopied).NewRowCountAdded
731+
}
726732

727733
$RowsTotal = $script:totalRowsCopied
728734
$TotalTime = [math]::Round($elapsed.Elapsed.TotalSeconds, 1)

public/Import-DbaCsv.ps1

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1493,7 +1493,13 @@ WHERE c.object_id = OBJECT_ID(@tableName)
14931493

14941494
$finalRowCountReported = Get-BulkRowsCopiedCount $bulkCopy
14951495

1496-
$script:totalRowsCopied += (Get-AdjustedTotalRowsCopied -ReportedRowsCopied $finalRowCountReported -PreviousRowsCopied $script:prevRowsCopied).NewRowCountAdded
1496+
# -1 signals that the reflection lookup failed, not a wrapped counter, so it must not
1497+
# reach Get-AdjustedTotalRowsCopied: fed in as a row count it inflates the total by
1498+
# billions of rows (see #10675). The running total from the notifications is then the
1499+
# best number available.
1500+
if ($finalRowCountReported -ge 0) {
1501+
$script:totalRowsCopied += (Get-AdjustedTotalRowsCopied -ReportedRowsCopied $finalRowCountReported -PreviousRowsCopied $script:prevRowsCopied).NewRowCountAdded
1502+
}
14971503

14981504
if ($completed) {
14991505
Write-Progress -Id 1 -Activity "Inserting $($script:totalRowsCopied) rows" -Status "Complete" -Completed

public/Import-DbaParquet.ps1

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1031,7 +1031,13 @@ WHERE c.object_id = OBJECT_ID(@tableName)
10311031

10321032
$finalRowCountReported = Get-BulkRowsCopiedCount $bulkCopy
10331033

1034-
$script:totalRowsCopied += (Get-AdjustedTotalRowsCopied -ReportedRowsCopied $finalRowCountReported -PreviousRowsCopied $script:prevRowsCopied).NewRowCountAdded
1034+
# -1 signals that the reflection lookup failed, not a wrapped counter, so it must not
1035+
# reach Get-AdjustedTotalRowsCopied: fed in as a row count it inflates the total by
1036+
# billions of rows (see #10675). The running total from the notifications is then the
1037+
# best number available.
1038+
if ($finalRowCountReported -ge 0) {
1039+
$script:totalRowsCopied += (Get-AdjustedTotalRowsCopied -ReportedRowsCopied $finalRowCountReported -PreviousRowsCopied $script:prevRowsCopied).NewRowCountAdded
1040+
}
10351041

10361042
if ($completed) {
10371043
Write-Progress -Id 1 -Activity "Inserting $($script:totalRowsCopied) rows" -Status "Complete" -Completed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0" }
2+
param(
3+
$ModuleName = "dbatools",
4+
$CommandName = "Get-BulkRowsCopiedCount",
5+
$PSDefaultParameterValues = $TestConfig.Defaults
6+
)
7+
8+
Describe $CommandName -Tag UnitTests {
9+
Context "Reading the rows copied counter" {
10+
It "Returns the counter value below Int32.MaxValue" {
11+
InModuleScope "dbatools" {
12+
$bulkCopy = New-Object -TypeName Microsoft.Data.SqlClient.SqlBulkCopy -ArgumentList "Server=dummy"
13+
$rowsCopiedField = [Microsoft.Data.SqlClient.SqlBulkCopy].GetField("_rowsCopied", [Reflection.BindingFlags]"NonPublic,GetField,Instance")
14+
$rowsCopiedField.SetValue($bulkCopy, [long]100000)
15+
16+
Get-BulkRowsCopiedCount $bulkCopy | Should -Be 100000
17+
}
18+
}
19+
20+
It "Returns the counter value above Int32.MaxValue" {
21+
# The field is an Int64 in current Microsoft.Data.SqlClient. A cast to [int] throws above
22+
# [int32]::MaxValue, the catch then returned -1, and the callers took that for a wrapped
23+
# counter and inflated the reported total by billions of rows (#10675).
24+
InModuleScope "dbatools" {
25+
$bulkCopy = New-Object -TypeName Microsoft.Data.SqlClient.SqlBulkCopy -ArgumentList "Server=dummy"
26+
$rowsCopiedField = [Microsoft.Data.SqlClient.SqlBulkCopy].GetField("_rowsCopied", [Reflection.BindingFlags]"NonPublic,GetField,Instance")
27+
$rowsCopiedField.SetValue($bulkCopy, [long]3000000000)
28+
29+
Get-BulkRowsCopiedCount $bulkCopy | Should -Be 3000000000
30+
}
31+
}
32+
33+
It "Keeps the total of the callers correct above Int32.MaxValue" {
34+
# The pattern of Copy-DbaDbTableData, Import-DbaCsv and Import-DbaParquet: the running total
35+
# from the copy notifications, plus the adjustment for the rows after the last notification.
36+
InModuleScope "dbatools" {
37+
$bulkCopy = New-Object -TypeName Microsoft.Data.SqlClient.SqlBulkCopy -ArgumentList "Server=dummy"
38+
$rowsCopiedField = [Microsoft.Data.SqlClient.SqlBulkCopy].GetField("_rowsCopied", [Reflection.BindingFlags]"NonPublic,GetField,Instance")
39+
$rowsCopiedField.SetValue($bulkCopy, [long]3000000000)
40+
41+
$totalRowsCopied = [long]2999995000
42+
$prevRowsCopied = [long]2999995000
43+
$finalRowCountReported = Get-BulkRowsCopiedCount $bulkCopy
44+
if ($finalRowCountReported -ge 0) {
45+
$totalRowsCopied += (Get-AdjustedTotalRowsCopied -ReportedRowsCopied $finalRowCountReported -PreviousRowsCopied $prevRowsCopied).NewRowCountAdded
46+
}
47+
48+
$totalRowsCopied | Should -Be 3000000000
49+
}
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)