param( [string]$Server = $env:ONEC_SQL_SERVER, [string]$Database = $env:ONEC_SQL_DATABASE, [string]$User = $env:ONEC_SQL_USER, [string]$Password = $env:ONEC_SQL_PASSWORD, [string]$SqlPath = "reports/1c-sql/upo_test/prepare-saved-state-copy.sql", [string]$SqlPlanReport = "reports/1c-sql/upo_test/prepare-saved-state-copy-sql.json", [string]$PlanPath = "reports/1c-sql/upo_test/saved-state-copy-plan.json", [string]$BaseUrl = "http://docker-gpu.cin.su:8011", [string]$ExpectedBaseId, [ValidateSet("ConfigSave", "ConfigCASSave")] [string]$ExpectedTargetTable = "ConfigSave", [int]$TimeoutSec = 120, [string]$Report, [string]$VerifyReport, [switch]$IUnderstandThisWritesToSql ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" if (-not $IUnderstandThisWritesToSql) { throw "Refusing to execute SQL without -IUnderstandThisWritesToSql." } if (-not $Server) { throw "Server is required. Use -Server or ONEC_SQL_SERVER." } if (-not $Database) { throw "Database is required. Use -Database or ONEC_SQL_DATABASE." } if (-not $User) { throw "User is required. Use -User or ONEC_SQL_USER." } if (-not $Password) { throw "Password is required. Use -Password or ONEC_SQL_PASSWORD." } if (-not $SqlPath) { throw "SqlPath is required." } if (-not $SqlPlanReport) { throw "SqlPlanReport is required." } if (-not $PlanPath) { throw "PlanPath is required." } if (-not $ExpectedBaseId) { throw "ExpectedBaseId is required." } function Resolve-RequiredPath { param( [string]$Label, [string]$Path ) if (-not (Test-Path -LiteralPath $Path)) { throw "$Label does not exist: $Path" } return (Resolve-Path -LiteralPath $Path).ProviderPath } function Test-HasProperty { param( [object]$Object, [string]$Name ) return $null -ne $Object -and ($Object.PSObject.Properties.Name -contains $Name) } function Assert-PlanReport { param( [object]$PlanReport, [string]$ResolvedSqlPath ) if ($PlanReport.schema -ne "onec_saved_state_copy_sql_plan.v1") { throw "SqlPlanReport schema must be onec_saved_state_copy_sql_plan.v1." } if ($PlanReport.status -ne "ready") { throw "SqlPlanReport status must be ready." } if ($PlanReport.read_only -ne $true -or $PlanReport.sql_write_performed -ne $false) { throw "SqlPlanReport must describe a read-only generated artifact with sql_write_performed=false." } if ($PlanReport.base_id -ne $ExpectedBaseId) { throw "SqlPlanReport base_id must be $ExpectedBaseId." } if ($PlanReport.target_table -ne $ExpectedTargetTable) { throw "SqlPlanReport target_table must be $ExpectedTargetTable." } $expectedSource = if ($ExpectedTargetTable -eq "ConfigSave") { "Config" } else { "ConfigCAS" } if ($PlanReport.source_table -ne $expectedSource) { throw "SqlPlanReport source_table must be $expectedSource for $ExpectedTargetTable." } if (-not (Test-HasProperty $PlanReport "expected_insert_rows") -or [int]$PlanReport.expected_insert_rows -le 0) { throw "SqlPlanReport expected_insert_rows must be positive." } if ((Test-HasProperty $PlanReport "failures") -and @($PlanReport.failures).Count -gt 0) { throw "SqlPlanReport contains failures." } if ((Test-HasProperty $PlanReport "sql_path") -and $PlanReport.sql_path) { $reported = (Resolve-Path -LiteralPath ([string]$PlanReport.sql_path)).ProviderPath if ($reported -ne $ResolvedSqlPath) { throw "SqlPlanReport sql_path does not match SqlPath." } } } function Assert-GeneratedSql { param([string]$SqlText) foreach ($needle in @( "Generated by scripts/prepare_1c_saved_state_copy_sql.py", "SET XACT_ABORT ON;", "BEGIN TRANSACTION;", "INSERT INTO dbo.", "SELECT", "IF @@ROWCOUNT <>", "COMMIT TRANSACTION;" )) { if (-not $SqlText.Contains($needle)) { throw "SqlPath does not look like the reviewed saved-state copy SQL artifact; missing '$needle'." } } } $resolvedSqlPath = Resolve-RequiredPath -Label "SqlPath" -Path $SqlPath $resolvedSqlPlanReport = Resolve-RequiredPath -Label "SqlPlanReport" -Path $SqlPlanReport $resolvedPlanPath = Resolve-RequiredPath -Label "PlanPath" -Path $PlanPath $sqlText = Get-Content -Raw -LiteralPath $resolvedSqlPath -Encoding UTF8 $planReport = Get-Content -Raw -LiteralPath $resolvedSqlPlanReport -Encoding UTF8 | ConvertFrom-Json Assert-PlanReport -PlanReport $planReport -ResolvedSqlPath $resolvedSqlPath Assert-GeneratedSql -SqlText $sqlText $sqlHash = (Get-FileHash -LiteralPath $resolvedSqlPath -Algorithm SHA1).Hash $defaultReport = Join-Path (Split-Path -Parent $resolvedSqlPath) "execute-saved-state-copy-sql.json" if (-not $Report) { $Report = $defaultReport } if (-not $VerifyReport) { $VerifyReport = Join-Path (Split-Path -Parent $resolvedSqlPath) "saved-state-copy-verify.json" } $reportData = [ordered]@{ schema = "onec_saved_state_copy_sql_execution.v1" status = "error" base_url = $BaseUrl base_id = $ExpectedBaseId target_table = $ExpectedTargetTable source_table = $planReport.source_table sql_path = $resolvedSqlPath sql_sha1 = $sqlHash sql_plan_report = $resolvedSqlPlanReport plan_path = $resolvedPlanPath expected_insert_rows = [int]$planReport.expected_insert_rows sql_execution_attempted = $false sql_write_performed = $false verify_report = $VerifyReport failures = @() } $connectionString = "Server=$Server;Database=$Database;User ID=$User;Password=$Password;Encrypt=False;TrustServerCertificate=True;Application Name=Codex 1C Saved-State Copy Executor;" $connection = [System.Data.SqlClient.SqlConnection]::new($connectionString) $command = $null try { $connection.Open() $command = $connection.CreateCommand() $command.CommandTimeout = $TimeoutSec $command.CommandText = $sqlText $reportData.sql_execution_attempted = $true [void]$command.ExecuteNonQuery() $reportData.sql_write_performed = $true $verifyArgs = @( "scripts/verify_1c_saved_state_copy.py", "--base-url", $BaseUrl, "--plan", $resolvedPlanPath, "--expected-base-id", $ExpectedBaseId, "--expected-target-table", $ExpectedTargetTable, "--timeout", $TimeoutSec.ToString(), "--report", $VerifyReport, "--require-ready", "--json" ) $verifyOutput = & python @verifyArgs 2>&1 $verifyExitCode = $LASTEXITCODE $reportData.verify_exit_code = $verifyExitCode $reportData.verify_output = [string]::Join("`n", @($verifyOutput)) if ($verifyExitCode -ne 0) { $reportData.status = "blocked_verify_failed" $reportData.failures = @("SQL executed, but post-copy verification failed.") } else { $reportData.status = "verified" } } catch { $reportData.status = "error" $reportData.failures = @([string]$_) throw } finally { if ($null -ne $command) { $command.Dispose() } $connection.Close() $connection.Dispose() $reportDirectory = Split-Path -Parent $Report if ($reportDirectory) { New-Item -ItemType Directory -Force -Path $reportDirectory | Out-Null } [pscustomobject]$reportData | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $Report -Encoding UTF8 } [pscustomobject]@{ status = $reportData.status report = (Resolve-Path -LiteralPath $Report).ProviderPath verify_report = if (Test-Path -LiteralPath $VerifyReport) { (Resolve-Path -LiteralPath $VerifyReport).ProviderPath } else { $VerifyReport } sql_sha1 = $sqlHash expected_insert_rows = [int]$planReport.expected_insert_rows } | ConvertTo-Json -Depth 10