Files
llm/scripts/execute_1c_saved_state_copy_sql.ps1
T
2026-08-14 09:40:51 +03:00

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.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