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]$SummaryPath, [string]$OutputPath ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" 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 $SummaryPath) { throw "SummaryPath is required." } if (-not $OutputPath) { throw "OutputPath is required." } $metadataFields = @( "attributes", "tabular_sections", "dimensions", "resources", "forms", "templates", "commands", "addressing_attributes", "accounting_flags", "columns", "enum_values", "integration_service_channels", "operations", "url_templates" "tabular_section_attributes" ) $summary = Get-Content -LiteralPath $SummaryPath -Raw -Encoding UTF8 | ConvertFrom-Json $checks = New-Object System.Collections.Generic.List[object] $tableChecks = New-Object System.Collections.Generic.List[object] foreach ($row in $summary.outputs) { $report = Get-Content -LiteralPath $row.output -Raw -Encoding UTF8 | ConvertFrom-Json $tables = @($report.object_storage_routes | Where-Object { $_.route_kind -eq "table" -and $_.physical_name_candidate } | ForEach-Object { $_.physical_name_candidate }) foreach ($field in $metadataFields) { if (-not ($report.PSObject.Properties.Name -contains $field)) { continue } foreach ($item in @($report.$field)) { $itemCandidateTables = $tables if ($item.PSObject.Properties.Name -contains "parent_physical_tables") { $parentTables = @($item.parent_physical_tables | Where-Object { $_.PSObject.Properties.Name -contains "table" -and $_.table } | ForEach-Object { $_.table }) if ($parentTables.Count -gt 0) { $itemCandidateTables = $parentTables } } foreach ($column in @($item.physical_columns | Where-Object { $_.PSObject.Properties.Name -contains "column" -and $_.column })) { $checks.Add([pscustomobject]@{ kind = $report.kind object_name = $report.identity.name object_guid = $report.identity.guid metadata_field = $field item_name = $item.name item_guid = $item.uuid value_type = if ($column.PSObject.Properties.Name -contains "value_type") { $column.value_type } elseif ($column.PSObject.Properties.Name -contains "value_types") { ($column.value_types -join ",") } else { $null } predicted_column = $column.column candidate_tables = $itemCandidateTables }) } $physicalTables = @() if ($item.PSObject.Properties.Name -contains "physical_tables") { $physicalTables = @($item.physical_tables) } foreach ($tableCandidate in @($physicalTables | Where-Object { $_.PSObject.Properties.Name -contains "table" -and $_.table })) { $tableChecks.Add([pscustomobject]@{ kind = $report.kind object_name = $report.identity.name object_guid = $report.identity.guid metadata_field = $field item_name = $item.name item_guid = $item.uuid predicted_table = $tableCandidate.table reason = $tableCandidate.reason }) } } } } $connectionString = "Server=$Server;Database=$Database;User ID=$User;Password=$Password;Encrypt=False;TrustServerCertificate=True;Application Name=Codex 1C Column Prediction Validation;" $connection = [System.Data.SqlClient.SqlConnection]::new($connectionString) $results = New-Object System.Collections.Generic.List[object] $foundCount = 0 try { $connection.Open() $allTables = @( @($checks | ForEach-Object { $_.candidate_tables } | Where-Object { $_ }) @($tableChecks | ForEach-Object { $_.predicted_table } | Where-Object { $_ }) ) | Select-Object -Unique $schemaLookup = @{} if ($allTables.Count -gt 0) { $command = $connection.CreateCommand() $command.CommandTimeout = 0 $tableParams = @() for ($i = 0; $i -lt $allTables.Count; $i++) { $name = "@t$i" $tableParams += $name $null = $command.Parameters.Add($name, [System.Data.SqlDbType]::NVarChar, 128) $command.Parameters[$name].Value = $allTables[$i] } $command.CommandText = @" SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE, IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME IN ($($tableParams -join ",")) "@ $reader = $command.ExecuteReader() try { while ($reader.Read()) { $item = [pscustomobject]@{ table = [string]$reader.GetValue(0) column = [string]$reader.GetValue(1) data_type = [string]$reader.GetValue(2) max_length = if ($reader.IsDBNull(3)) { $null } else { $reader.GetValue(3) } precision = if ($reader.IsDBNull(4)) { $null } else { $reader.GetValue(4) } scale = if ($reader.IsDBNull(5)) { $null } else { $reader.GetValue(5) } nullable = [string]$reader.GetValue(6) } $key = "$($item.table)|$($item.column)" if (-not $schemaLookup.ContainsKey($key)) { $schemaLookup[$key] = New-Object System.Collections.Generic.List[object] } $schemaLookup[$key].Add($item) } } finally { if ($reader -ne $null) { $reader.Close() $reader.Dispose() } if ($command -ne $null) { $command.Dispose() } } } foreach ($check in $checks) { $matches = New-Object System.Collections.Generic.List[object] foreach ($table in @($check.candidate_tables)) { $key = "$table|$($check.predicted_column)" if ($schemaLookup.ContainsKey($key)) { foreach ($match in $schemaLookup[$key]) { $matches.Add($match) } } } if ($matches.Count -gt 0) { $foundCount++ } $results.Add([pscustomobject]@{ kind = $check.kind object_name = $check.object_name object_guid = $check.object_guid metadata_field = $check.metadata_field item_name = $check.item_name item_guid = $check.item_guid value_type = $check.value_type predicted_column = $check.predicted_column candidate_tables = $check.candidate_tables found = $matches.Count -gt 0 matches = $matches }) } } finally { if ($connection -ne $null) { $connection.Dispose() } } $byKind = $results | Group-Object kind | ForEach-Object { [pscustomobject]@{ kind = $_.Name predicted_count = $_.Count found_count = @($_.Group | Where-Object found).Count } } $tableResults = New-Object System.Collections.Generic.List[object] $tableFoundCount = 0 foreach ($check in $tableChecks) { $exists = $false foreach ($key in $schemaLookup.Keys) { if ($key.StartsWith("$($check.predicted_table)|")) { $exists = $true break } } if ($exists) { $tableFoundCount++ } $tableResults.Add([pscustomobject]@{ kind = $check.kind object_name = $check.object_name object_guid = $check.object_guid metadata_field = $check.metadata_field item_name = $check.item_name item_guid = $check.item_guid predicted_table = $check.predicted_table reason = $check.reason found = $exists }) } $tableByKind = $tableResults | Group-Object kind | ForEach-Object { [pscustomobject]@{ kind = $_.Name predicted_count = $_.Count found_count = @($_.Group | Where-Object found).Count } } $report = [pscustomobject]@{ schema = "onec_predicted_column_validation.v1" summary = $SummaryPath database = $Database predicted_count = $checks.Count found_count = $foundCount predicted_table_count = $tableChecks.Count found_table_count = $tableFoundCount by_kind = $byKind table_by_kind = $tableByKind results = $results table_results = $tableResults } $resolvedOutput = [System.IO.Path]::GetFullPath($OutputPath) New-Item -ItemType Directory -Force -Path ([System.IO.Path]::GetDirectoryName($resolvedOutput)) | Out-Null $report | ConvertTo-Json -Depth 12 | Out-File -LiteralPath $resolvedOutput -Encoding UTF8 $report | Select-Object schema, predicted_count, found_count, predicted_table_count, found_table_count | ConvertTo-Json -Compress