205 lines
7.7 KiB
PowerShell
205 lines
7.7 KiB
PowerShell
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
|