This commit is contained in:
2026-09-09 23:46:21 +08:00
parent 107374bec5
commit 3e4b9d0a9a
10 changed files with 2886 additions and 103 deletions
+98 -62
View File
@@ -2,92 +2,128 @@ param(
[switch]$DryRun,
[Alias('Host')][string]$TargetHost = '127.0.0.1',
[int]$Port = 3306,
[string]$InputPath,
[string]$MysqlExe = 'C:\Program Files\MySQL\MySQL Server 8.4\bin\mysql.exe',
[string]$User = 'root'
[string]$User = 'root',
[ValidateRange(1, 10)][int]$Rounds = 2
)
$ErrorActionPreference = 'Stop'
$Repo = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
$Workspace = (Resolve-Path (Join-Path $Repo '..')).Path
$Acceptance = Join-Path $Workspace '.codex-tmp\p23-04-acceptance'
$ExpectedSha256 = '0EE13427AB60CECCEB42985CF1E78A05645C18CD9BF2886110C0AFEAF3CA7B9B'
$ExpectedLines = 15674
$ReportRoot = Join-Path $Workspace '.codex-tmp\local-db-full-acceptance'
$DomesticSql = Join-Path $Repo 'deploy\sql\头程数据库统一增量-MySQL8.sql'
$OverseasSql = Join-Path $Repo 'deploy\sql\海外区域统一增量-MySQL8.sql'
$Schemas = @(
'baiyue_auth', 'baiyue_iam', 'baiyue_order', 'baiyue_warehouse',
'baiyue_firstleg', 'baiyue_file', 'baiyue_audit', 'baiyue_event_bridge',
'baiyue_finance', 'baiyue_lastleg', 'baiyue_lastlag_warehouse',
'baiyue_event_bridge_overseas'
)
if ($TargetHost -notin @('127.0.0.1', 'localhost')) {
[Console]::Error.WriteLine('Refusing non-local database; only 127.0.0.1 or localhost is allowed.')
exit 2
}
if ([string]::IsNullOrWhiteSpace($InputPath)) {
$InputPath = (Get-ChildItem (Join-Path $Repo 'deploy\sql') -Filter '*MySQL8.sql' -File |
Where-Object { $_.Length -gt 100000 } | Select-Object -First 1).FullName
}
if (-not (Test-Path -LiteralPath $InputPath)) {
[Console]::Error.WriteLine("InputPath does not exist: $InputPath")
if (-not (Test-Path -LiteralPath $MysqlExe)) {
[Console]::Error.WriteLine("mysql executable does not exist: $MysqlExe")
exit 2
}
$actual = ((Get-FileHash -Algorithm SHA256 -LiteralPath $InputPath).Hash).Trim().ToUpperInvariant()
$lines = [System.IO.File]::ReadAllLines($InputPath).Length
if ($actual -ne $ExpectedSha256 -or $lines -ne $ExpectedLines) {
[Console]::Error.WriteLine("Unified SQL snapshot mismatch: $actual/$lines")
exit 2
}
$sqlText = [System.IO.File]::ReadAllText($InputPath)
$commentGateTokens = @(
'CREATE PROCEDURE queue_column_comment',
'CREATE PROCEDURE apply_one_table_comments',
'CALL apply_queued_comments();',
'字段注释批量升级改变了结构签名',
'存在缺失中文注释的业务表',
'存在缺失中文注释的业务字段'
)
foreach ($token in $commentGateTokens) {
if (-not $sqlText.Contains($token)) {
[Console]::Error.WriteLine("Unified SQL comment acceptance marker missing: $token")
foreach ($sqlFile in @($DomesticSql, $OverseasSql)) {
if (-not (Test-Path -LiteralPath $sqlFile)) {
[Console]::Error.WriteLine("Unified SQL does not exist: $sqlFile")
exit 2
}
}
if ([regex]::Matches($sqlText, "SELECT 'PASS' AS migration_status").Count -ne 1) {
[Console]::Error.WriteLine('Unified SQL must contain exactly one final PASS statement')
$DomesticText = [IO.File]::ReadAllText($DomesticSql)
$OverseasText = [IO.File]::ReadAllText($OverseasSql)
if (-not $DomesticText.Contains("SELECT 'PASS' AS migration_status")) {
[Console]::Error.WriteLine('Domestic unified SQL is missing its final PASS statement.')
exit 2
}
foreach ($marker in @(
'-- BEGIN INLINE EMPTY-DATABASE BASELINE: LASTLEG',
'-- BEGIN INLINE EMPTY-DATABASE BASELINE: OVERSEAS WAREHOUSE',
"SELECT 'PASS' AS migration_status"
)) {
if (-not $OverseasText.Contains($marker)) {
[Console]::Error.WriteLine("Overseas unified SQL is missing marker: $marker")
exit 2
}
}
$sequence = @(
'empty: base.sql', 'replay: base.sql + input.sql + input.sql',
'smart-old: fixture-smart-old.sql', 'inbound-old: fixture-inbound-old.sql',
'waitsplit-iam: fixture-test-shape.sql', 'overseas-old: fixture-overseas-old.sql',
'overseas-missing-objects: fixture-overseas-missing-objects.sql',
'interrupted-recovery: fixture-interrupted.sql', 'final-structure: verify-final.sql')
Write-Output "SQL SHA-256: $actual / lines: $lines"
Write-Output 'Comment gate: queued validation, per-table ALTER, signature verification, nine-schema coverage, unique PASS'
$DomesticHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $DomesticSql).Hash
$OverseasHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $OverseasSql).Hash
Write-Output "Target: $TargetHost`:$Port"
Write-Output 'Fixture order:'
$sequence | ForEach-Object { Write-Output " $_" }
if ($DryRun) { Write-Output 'DRY-RUN PASS (mysql not started; no database writes)'; exit 0 }
Write-Output "Rounds: $Rounds"
Write-Output "Domestic SQL: $DomesticHash / $([IO.File]::ReadAllLines($DomesticSql).Length) lines"
Write-Output "Overseas SQL: $OverseasHash / $([IO.File]::ReadAllLines($OverseasSql).Length) lines"
Write-Output "Reset scope: $($Schemas -join ', ')"
Write-Output 'Execution order per round: clear exact schemas -> domestic unified SQL -> overseas unified SQL -> final PASS and schema checks'
if ($DryRun) {
Write-Output 'DRY-RUN PASS (mysql not contacted; no database writes)'
exit 0
}
$acceptanceInput = Join-Path $Acceptance 'input.sql'
Copy-Item -LiteralPath $InputPath -Destination $acceptanceInput -Force
$stagedHash = ((Get-FileHash -Algorithm SHA256 -LiteralPath $acceptanceInput).Hash).Trim().ToUpperInvariant()
if ($stagedHash -ne $actual) {
[Console]::Error.WriteLine("Failed to stage the validated unified SQL snapshot: $stagedHash")
New-Item -ItemType Directory -Path $ReportRoot -Force | Out-Null
$StagedDomestic = Join-Path $ReportRoot 'domestic.sql'
$StagedOverseas = Join-Path $ReportRoot 'overseas.sql'
Copy-Item -LiteralPath $DomesticSql -Destination $StagedDomestic -Force
Copy-Item -LiteralPath $OverseasSql -Destination $StagedOverseas -Force
if ((Get-FileHash -Algorithm SHA256 -LiteralPath $StagedDomestic).Hash -ne $DomesticHash -or
(Get-FileHash -Algorithm SHA256 -LiteralPath $StagedOverseas).Hash -ne $OverseasHash) {
[Console]::Error.WriteLine('Failed to stage byte-identical SQL files under ASCII filenames.')
exit 2
}
$run = 0
function Invoke-Stage([string]$Name, [string]$File) {
$script = Join-Path $Acceptance $File
$log = Join-Path $Acceptance ("acceptance-run-{0:D2}-{1}.log" -f $script:run, $Name)
$script:run++
if (-not (Test-Path -LiteralPath $script)) { [Console]::Error.WriteLine("Script does not exist: $script"); return 2 }
& $MysqlExe '--protocol=TCP' "--host=$TargetHost" "--port=$Port" "--user=$User" '--database=mysql' '--batch' ("--execute=source " + $script.Replace('\','/')) *> $log
return $LASTEXITCODE
$MysqlCommon = @(
'--protocol=TCP', "--host=$TargetHost", "--port=$Port", "--user=$User",
'--default-character-set=utf8mb4', '--batch'
)
& $MysqlExe @MysqlCommon '--skip-column-names' '--execute=SELECT VERSION(), @@hostname, @@port;' | Out-Null
if ($LASTEXITCODE -ne 0) {
[Console]::Error.WriteLine('Cannot connect to the local MySQL target. Configure authentication outside this script, for example with MYSQL_PWD for the current process.')
exit $LASTEXITCODE
}
$calls = @(
@('empty','base.sql'), @('replay-reset','base.sql'), @('replay-1','input.sql'), @('replay-2','input.sql'),
@('smart-old','fixture-smart-old.sql'), @('inbound-old','fixture-inbound-old.sql'), @('waitsplit-iam','fixture-test-shape.sql'),
@('overseas-old','fixture-overseas-old.sql'), @('overseas-missing','fixture-overseas-missing-objects.sql'),
@('interrupted','fixture-interrupted.sql'), @('final-verify','verify-final.sql'))
foreach ($call in $calls) { $code = Invoke-Stage $call[0] $call[1]; if ($code -ne 0) { exit $code } }
Write-Output 'FULL LOCAL ACCEPTANCE PASS'
$DropSql = ($Schemas | ForEach-Object { "DROP DATABASE IF EXISTS ``$_```;" }) -join ' '
for ($round = 1; $round -le $Rounds; $round++) {
$RoundRoot = Join-Path $ReportRoot ("round-{0}" -f $round)
New-Item -ItemType Directory -Path $RoundRoot -Force | Out-Null
$Stopwatch = [Diagnostics.Stopwatch]::StartNew()
& $MysqlExe @MysqlCommon "--execute=$DropSql" *> (Join-Path $RoundRoot '00-clear.log')
if ($LASTEXITCODE -ne 0) {
[Console]::Error.WriteLine("Round $round failed while clearing the exact project schemas.")
exit $LASTEXITCODE
}
$DomesticLog = Join-Path $RoundRoot '10-domestic.log'
$DomesticSource = $StagedDomestic.Replace('\', '/')
& $MysqlExe @MysqlCommon '--database=mysql' "--execute=source $DomesticSource" *> $DomesticLog
if ($LASTEXITCODE -ne 0 -or -not (Select-String -LiteralPath $DomesticLog -Pattern '^PASS\s+统一增量全部执行完成' -Quiet)) {
[Console]::Error.WriteLine("Round $round domestic unified SQL failed. See $DomesticLog")
exit $(if ($LASTEXITCODE -ne 0) { $LASTEXITCODE } else { 2 })
}
$OverseasLog = Join-Path $RoundRoot '20-overseas.log'
$OverseasSource = $StagedOverseas.Replace('\', '/')
& $MysqlExe @MysqlCommon '--database=mysql' "--execute=source $OverseasSource" *> $OverseasLog
if ($LASTEXITCODE -ne 0 -or -not (Select-String -LiteralPath $OverseasLog -Pattern '^PASS\s+海外财务P0费用来源字段已就绪$' -Quiet)) {
[Console]::Error.WriteLine("Round $round overseas unified SQL failed. See $OverseasLog")
exit $(if ($LASTEXITCODE -ne 0) { $LASTEXITCODE } else { 2 })
}
$SchemaList = "'" + ($Schemas -join "','") + "'"
$SchemaCount = (& $MysqlExe @MysqlCommon '--skip-column-names' "--execute=SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name IN ($SchemaList);").Trim()
if ($LASTEXITCODE -ne 0 -or [int]$SchemaCount -ne $Schemas.Count) {
[Console]::Error.WriteLine("Round $round schema verification failed: $SchemaCount/$($Schemas.Count)")
exit 2
}
$Stopwatch.Stop()
Write-Output "ROUND $round PASS / $SchemaCount schemas / $([Math]::Round($Stopwatch.Elapsed.TotalSeconds, 1)) seconds"
}
Write-Output 'FULL LOCAL TWO-REGION DATABASE ACCEPTANCE PASS'
exit 0