mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-10-06 15:16:56 +01:00
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0d8b7764-f15a-49ea-8d50-d9147334f8af
110 lines
3.8 KiB
PowerShell
110 lines
3.8 KiB
PowerShell
# Shared deterministic paging. Callers build the complete immutable result first.
|
|
#requires -Version 7.2
|
|
Set-StrictMode -Version Latest
|
|
|
|
function Get-ResultSnapshot {
|
|
param([Parameter(Mandatory)] $Value)
|
|
|
|
$json = ConvertTo-Json -InputObject $Value -Depth 30 -Compress
|
|
return [Convert]::ToHexString(
|
|
[Security.Cryptography.SHA256]::HashData([Text.Encoding]::UTF8.GetBytes($json))
|
|
).ToLowerInvariant()
|
|
}
|
|
|
|
function Get-SerializedByteCount {
|
|
param([Parameter(Mandatory)] [string] $Json)
|
|
|
|
# PowerShell writes one platform newline after the returned JSON string.
|
|
return [Text.Encoding]::UTF8.GetByteCount($Json) +
|
|
[Text.Encoding]::UTF8.GetByteCount([Environment]::NewLine)
|
|
}
|
|
|
|
function ConvertTo-BoundedPage {
|
|
param(
|
|
[Parameter(Mandatory)] [Collections.IDictionary] $Header,
|
|
[Parameter(Mandatory)] [Collections.IDictionary] $Groups,
|
|
[ValidateRange(0, 2147483647)] [int] $Offset = 0,
|
|
[string] $Snapshot,
|
|
[ValidateRange(1024, 16000)] [int] $MaxBytes = 16000
|
|
)
|
|
|
|
$total = 0
|
|
foreach ($name in $Groups.Keys) {
|
|
$total += $Groups[$name].Count
|
|
}
|
|
if (($total -eq 0 -and $Offset -ne 0) -or ($total -gt 0 -and $Offset -ge $total)) {
|
|
throw "Invalid Offset=$Offset for totalCount=$total; no rows were returned."
|
|
}
|
|
if ($Offset -gt 0 -and -not $Snapshot) {
|
|
throw 'Continuation requires Snapshot from the preceding page.'
|
|
}
|
|
if ($Snapshot -and $Snapshot -cne $Header.snapshot) {
|
|
throw 'Snapshot changed or continuation belongs to another request. Discard partial results and restart at Offset=0.'
|
|
}
|
|
|
|
$page = [ordered]@{}
|
|
foreach ($key in $Header.Keys) {
|
|
$page[$key] = $Header[$key]
|
|
}
|
|
$page.offset = $Offset
|
|
$page.returnedCount = 0
|
|
$page.totalCount = $total
|
|
$page.remainingCount = $total - $Offset
|
|
$page.complete = ($total -eq 0)
|
|
$page.continuation = if ($total) {
|
|
[ordered]@{ offset = $Offset; snapshot = $Header.snapshot }
|
|
}
|
|
else {
|
|
$null
|
|
}
|
|
foreach ($name in $Groups.Keys) {
|
|
$page[$name] = [Collections.Generic.List[object]]::new()
|
|
}
|
|
|
|
$json = ConvertTo-Json -InputObject $page -Depth 30 -Compress
|
|
if ((Get-SerializedByteCount -Json $json) -gt $MaxBytes) {
|
|
throw "Page envelope exceeds MaxBytes=$MaxBytes. Use READ's path-discovery fallback; never truncate."
|
|
}
|
|
|
|
$position = 0
|
|
foreach ($name in $Groups.Keys) {
|
|
foreach ($row in $Groups[$name]) {
|
|
if ($position++ -lt $Offset) {
|
|
continue
|
|
}
|
|
|
|
$page[$name].Add($row)
|
|
$page.returnedCount++
|
|
$page.remainingCount--
|
|
$page.complete = ($page.remainingCount -eq 0)
|
|
$page.continuation = if ($page.complete) {
|
|
$null
|
|
}
|
|
else {
|
|
[ordered]@{
|
|
offset = $Offset + $page.returnedCount
|
|
snapshot = $Header.snapshot
|
|
}
|
|
}
|
|
|
|
$next = ConvertTo-Json -InputObject $page -Depth 30 -Compress
|
|
if ((Get-SerializedByteCount -Json $next) -gt $MaxBytes) {
|
|
$page[$name].RemoveAt($page[$name].Count - 1)
|
|
$page.returnedCount--
|
|
$page.remainingCount++
|
|
$page.complete = $false
|
|
$page.continuation = [ordered]@{
|
|
offset = $Offset + $page.returnedCount
|
|
snapshot = $Header.snapshot
|
|
}
|
|
if ($page.returnedCount -eq 0) {
|
|
$identity = if ($row.path) { " at $($row.path)" } else { " at Offset=$Offset" }
|
|
throw "One complete $name row plus envelope exceeds MaxBytes=$MaxBytes$identity. No row was clipped."
|
|
}
|
|
return $json
|
|
}
|
|
$json = $next
|
|
}
|
|
}
|
|
return $json
|
|
}
|