functions/generation/Invoke-SldgDataGeneration.ps1
|
function Invoke-SldgDataGeneration { <# .SYNOPSIS Executes data generation according to a generation plan. .DESCRIPTION Generates synthetic data for all tables in the plan, respecting FK dependencies, unique constraints, and custom rules. Data is generated in topological order so that parent tables are populated before child tables. Within each row, columns with -CrossColumnDependency rules (set via Set-SldgGenerationRule) are automatically reordered so that dependency columns are generated first. This enables context-dependent AI generation — e.g., a JSON column can vary its structure based on the value of a report-type column in the same row. .PARAMETER Plan The generation plan from New-SldgGenerationPlan. .PARAMETER ConnectionInfo Target database connection. If not specified, uses the active connection. .PARAMETER WhatIf Shows what would be generated without actually inserting data. .PARAMETER NoInsert Generates data in memory but does not write to the database. Use this with -PassThru to get the generated DataTables. .PARAMETER PassThru Returns the generated data as part of the result object. .PARAMETER UseTransaction Wraps all inserts in a single database transaction. If any table fails, all previously inserted data is rolled back. .PARAMETER Parallel Generates independent tables in parallel (Synthetic and Scenario modes; masking stays sequential). .PARAMETER ThrottleLimit Maximum number of tables to generate in parallel when using -Parallel. .PARAMETER ValidateAfterGeneration Runs Test-SldgGeneratedData after inserts complete and attaches the validation results to the generation result. .PARAMETER FailOnValidationError Turns validation errors from -ValidateAfterGeneration into a terminating error. .PARAMETER FailOnSkippedRows Turns any skipped/ignored inserted rows into a terminating error. This is useful for strict data quality gates. .PARAMETER Confirm Prompts for confirmation before inserting data into each table. .EXAMPLE PS C:\> $result = Invoke-SldgDataGeneration -Plan $plan Generates and inserts data for all tables in the plan. .EXAMPLE PS C:\> $result = Invoke-SldgDataGeneration -Plan $plan -NoInsert -PassThru Generates data in memory without inserting. .EXAMPLE PS C:\> $result = Invoke-SldgDataGeneration -Plan $plan -UseTransaction Generates and inserts data within a single transaction. If any table fails, all previously inserted data is rolled back. .EXAMPLE PS C:\> $result = Invoke-SldgDataGeneration -Plan $plan -Parallel -ThrottleLimit 4 Independent tables are generated in parallel, up to 4 at a time. #> [OutputType([SqlLabDataGenerator.GenerationResult])] [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory, ValueFromPipeline)] [SqlLabDataGenerator.GenerationPlan]$Plan, [SqlLabDataGenerator.Connection]$ConnectionInfo, [switch]$NoInsert, [switch]$PassThru, [switch]$UseTransaction, [switch]$Parallel, [int]$ThrottleLimit, [switch]$ValidateAfterGeneration, [switch]$FailOnValidationError, [switch]$FailOnSkippedRows ) process { if (-not $ConnectionInfo) { $ConnectionInfo = $script:SldgState.ActiveConnection } if (-not $ConnectionInfo -and -not $NoInsert) { Stop-PSFFunction -String 'Connect.NoActiveConnectionOrNoInsert' -EnableException $true } if ($ConnectionInfo) { Assert-SldgConnectionOpen -ConnectionInfo $ConnectionInfo } if ($ValidateAfterGeneration -and ($NoInsert -or -not $ConnectionInfo)) { Stop-PSFFunction -String 'Validation.RequiresInsertedData' -EnableException $true } $provider = if ($ConnectionInfo) { Get-SldgProviderInternal -ConnectionInfo $ConnectionInfo } else { $null } # Resolve all configuration + per-run identity in one place (refactor extract). $ctx = Initialize-SldgGenerationContext -ThrottleLimitOverride $ThrottleLimit $batchSize = $ctx.BatchSize $streamingThreshold = $ctx.StreamingThreshold $streamingChunkSize = $ctx.StreamingChunkSize $fkQueryLimit = $ctx.FkQueryLimit $uniqueQueryLimit = $ctx.UniqueQueryLimit $dbCommandTimeout = $ctx.DbCommandTimeout $ThrottleLimit = $ctx.ThrottleLimit $correlationId = $ctx.CorrelationId $executingUser = $ctx.ExecutingUser $generationStartTime = $ctx.GenerationStartTime Write-PSFMessage -Level Host -Message ($script:strings.'Generation.Starting' -f $Plan.TableCount, $Plan.Mode) # Seed for reproducibility if ($ctx.Seed -gt 0) { $null = Get-Random -SetSeed $ctx.Seed } $fkValues = @{} $script:SldgFkWarningIssued.Clear() $tableResults = [System.Collections.Generic.List[object]]::new() $fkFallbackStats = [System.Collections.Generic.List[object]]::new() $totalInserted = 0 $transaction = $null Write-PSFMessage -Level Verbose -Message ('Generation correlation id: {0}' -f $correlationId) Write-PSFMessage -Level Verbose -Message ($script:strings.'Generation.AuditStart' -f $executingUser, $Plan.Database, $Plan.TableCount, $Plan.Mode) # Start a transaction if requested if ($UseTransaction -and -not $NoInsert -and $ConnectionInfo) { $transaction = $ConnectionInfo.DbConnection.BeginTransaction() Write-PSFMessage -Level Verbose -Message ($script:strings.'Generation.TransactionStarted' -f $ConnectionInfo.Provider) } # Parallel config $useParallel = $Parallel -and -not ($Plan.Mode -eq 'Masking') $generationFailed = $false $validationResults = @() $isMaskingMode = $Plan.Mode -eq 'Masking' $failedTables = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) # S4: Auto-enable transaction for masking mode to prevent data loss from DELETE+INSERT if ($isMaskingMode -and -not $NoInsert -and $ConnectionInfo -and -not $transaction) { $transaction = $ConnectionInfo.DbConnection.BeginTransaction() Write-PSFMessage -Level Verbose -Message $script:strings.'Generation.MaskingTransactionStarted' } $tableIndex = 0 $tableTotal = $Plan.Tables.Count # Auto-enable AI row-level generation when the plan was created with -UseAI $aiGenOverrideApplied = $false if ($Plan.UseAIGeneration) { $currentAIGen = Get-PSFConfigValue -FullName 'SqlLabDataGenerator.Generation.AIGeneration' if (-not $currentAIGen) { Set-PSFConfig -FullName 'SqlLabDataGenerator.Generation.AIGeneration' -Value $true $aiGenOverrideApplied = $true Write-PSFMessage -Level Verbose -Message $script:strings.'Generation.AIGenOverride' } } # A2: Pre-scan and disable FK constraints for circular dependency tables before insertion $circularTables = @($Plan.Tables | Where-Object { $_.HasCircularDependency }) $disabledFKInfo = $null if ($circularTables.Count -gt 0 -and -not $NoInsert -and $ConnectionInfo -and $ConnectionInfo.DbConnection) { $disableParams = @{ CircularTables = $circularTables ConnectionInfo = $ConnectionInfo } if ($transaction) { $disableParams['Transaction'] = $transaction } $disabledFKInfo = Disable-SldgCircularFKConstraint @disableParams } # ── Parallel generation path (Synthetic/Scenario only) ── if ($useParallel -and $Plan.Tables.Count -gt 0) { $parallelResult = Invoke-SldgParallelTableGeneration -Plan $Plan -FkValues $fkValues ` -ConnectionInfo $ConnectionInfo -Provider $provider -Transaction $transaction ` -BatchSize $batchSize -ThrottleLimit $ThrottleLimit ` -StreamingThreshold $streamingThreshold -StreamingChunkSize $streamingChunkSize ` -NoInsert:$NoInsert -PassThru:$PassThru $tableResults.AddRange($parallelResult.TableResults) $totalInserted = $parallelResult.TotalInserted $generationFailed = $parallelResult.GenerationFailed foreach ($fallback in @($parallelResult.FKFallbackStats)) { $fkFallbackStats.Add($fallback) } if ($generationFailed -and $transaction) { try { $transaction.Rollback() } catch { Write-PSFMessage -Level Warning -Message ($script:strings.'Generation.ParallelRollbackFailed' -f $_) } $transaction = $null } } # ── Sequential generation path (original) ── else { foreach ($tablePlan in $Plan.Tables) { $tableIndex++ $pct = [int](($tableIndex - 1) / [Math]::Max($tableTotal, 1) * 100) Write-Progress -Activity 'Generating data' -Status "Table $tableIndex of ${tableTotal}: $($tablePlan.FullName)" -PercentComplete $pct if (-not $PSCmdlet.ShouldProcess("$($tablePlan.FullName) ($($tablePlan.RowCount) rows)", "Generate data")) { continue } # Per-table stopwatch for audit DurationMs (correlation traceability) $tableStopwatch = [System.Diagnostics.Stopwatch]::StartNew() $tableResultCountBefore = $tableResults.Count # Masking mode: read existing data, mask PII columns, write back if ($isMaskingMode) { if (-not $ConnectionInfo -or -not $provider) { Stop-PSFFunction -Message $script:strings.'Generation.MaskingNotSupported' -EnableException $true } Invoke-PSFProtectedCommand -ActionString 'Generation.MaskingTable' -ActionStringValues $tablePlan.RowCount, $tablePlan.SchemaName, $tablePlan.TableName -Target $tablePlan.FullName -ScriptBlock { $maskParams = @{ TablePlan = $tablePlan ConnectionInfo = $ConnectionInfo Provider = $provider Plan = $Plan BatchSize = $batchSize NoInsert = $NoInsert PassThru = $PassThru } if ($transaction) { $maskParams['Transaction'] = $transaction } $maskResult = Invoke-SldgMaskingTable @maskParams $totalInserted += $maskResult.RowCount $tableResults.Add($maskResult) } -PSCmdlet $PSCmdlet -EnableException $false if (Test-PSFFunctionInterrupt) { $tableResults.Add([SqlLabDataGenerator.TableResult]@{ TableName = $tablePlan.FullName RowCount = 0 Success = $false Error = $Error[0].Exception.Message }) if ($transaction) { $generationFailed = $true Write-PSFMessage -Level Warning -Message ($script:strings.'Generation.MaskingRollingBack' -f $tablePlan.FullName) try { $transaction.Rollback() } catch { Write-PSFMessage -Level Error -Message ($script:strings.'Generation.MaskingRollbackCritical' -f $_) } $transaction = $null $totalInserted = 0 Stop-PSFFunction -String 'Generation.MaskingRolledBack' -StringValues $tablePlan.FullName -EnableException $true } } continue } # Skip tables whose FK parent tables have failed — child will inevitably fail without FK values if ($failedTables.Count -gt 0 -and $tablePlan.ForeignKeys -and $tablePlan.ForeignKeys.Count -gt 0) { $failedParents = @($tablePlan.ForeignKeys | ForEach-Object { "$($_.ReferencedSchema).$($_.ReferencedTable)" } | Where-Object { $failedTables.Contains($_) } | Select-Object -Unique) if ($failedParents.Count -gt 0) { Write-PSFMessage -Level Warning -Message ($script:strings.'Generation.SkippedDueToParent' -f $tablePlan.FullName, ($failedParents -join ', ')) [void]$failedTables.Add($tablePlan.FullName) $tableResults.Add([SqlLabDataGenerator.TableResult]@{ TableName = $tablePlan.FullName RowCount = 0 Success = $false Error = "Skipped: parent table(s) failed: $($failedParents -join ', ')" }) continue } } Write-PSFMessage -Level Host -Message ($script:strings.'Generation.Table' -f $tablePlan.RowCount, $tablePlan.SchemaName, $tablePlan.TableName) # FK DB fallback: batch-load missing FK parent values grouped by parent table if ($tablePlan.ForeignKeys -and $tablePlan.ForeignKeys.Count -gt 0 -and $ConnectionInfo -and $provider) { $fkFallbackParams = @{ TablePlan = $tablePlan FkValues = $fkValues ConnectionInfo = $ConnectionInfo FkQueryLimit = $fkQueryLimit CommandTimeout = $dbCommandTimeout } if ($transaction) { $fkFallbackParams['Transaction'] = $transaction } $loadedFallbacks = Resolve-SldgForeignKeyFallback @fkFallbackParams foreach ($fallback in @($loadedFallbacks)) { $fkFallbackStats.Add($fallback) } } # Get table info from schema (need full column info) $tableRules = if ($Plan.GenerationRules.ContainsKey($tablePlan.FullName)) { $Plan.GenerationRules[$tablePlan.FullName] } else { $null } # Build a table info object with semantic types $tableInfo = ConvertTo-SldgTableInfo -TablePlan $tablePlan # For non-identity integer PK columns, query MAX(PK) so we can auto-generate sequential values if ($ConnectionInfo) { Set-SldgPrimaryKeyStartValue -TableInfo $tableInfo -TablePlan $tablePlan ` -ConnectionInfo $ConnectionInfo -Transaction $transaction -CommandTimeout $dbCommandTimeout } Invoke-PSFProtectedCommand -ActionString 'Generation.InsertingTable' -ActionStringValues $tablePlan.RowCount, $tablePlan.SchemaName, $tablePlan.TableName -Target $tablePlan.FullName -ScriptBlock { # Streaming mode: large tables generate and write in chunks to keep memory bounded if ($streamingThreshold -gt 0 -and $tablePlan.RowCount -gt $streamingThreshold) { Write-PSFMessage -Level Host -Message ($script:strings.'Generation.StreamingStarting' -f $tablePlan.FullName, $tablePlan.RowCount, $streamingChunkSize) $streamParams = @{ TableInfo = $tableInfo TotalRowCount = $tablePlan.RowCount ChunkSize = $streamingChunkSize GeneratorMap = $Plan.GeneratorMap ForeignKeyValues = $fkValues TableRules = $tableRules BatchSize = $batchSize NoInsert = $NoInsert PassThru = $PassThru } if ($ConnectionInfo) { $streamParams['ConnectionInfo'] = $ConnectionInfo } if ($transaction) { $streamParams['Transaction'] = $transaction } if ($provider) { $streamParams['WriteFunction'] = $provider.FunctionMap.WriteData } # Query existing unique values for streaming mode (same as non-streaming path) if ($ConnectionInfo -and $provider) { $uqParams = @{ TableInfo = $tableInfo TablePlan = $tablePlan ConnectionInfo = $ConnectionInfo UniqueQueryLimit = $uniqueQueryLimit CommandTimeout = $dbCommandTimeout } if ($transaction) { $uqParams['Transaction'] = $transaction } $streamExistingUnique = Get-SldgExistingUniqueValue @uqParams if ($streamExistingUnique) { $streamParams['ExistingUniqueValues'] = $streamExistingUnique } } # Two-tier AI: pass per-table generation notes to streaming mode if ($Plan.AIAdvice -and $Plan.AIAdvice.TableGenerationNotes -and $Plan.AIAdvice.TableGenerationNotes.ContainsKey($tablePlan.FullName)) { $streamParams['TableNotes'] = $Plan.AIAdvice.TableGenerationNotes[$tablePlan.FullName] } $streamResult = Invoke-SldgStreamingGeneration @streamParams foreach ($key in $streamResult.GeneratedValues.Keys) { $fkValues[$key] = $streamResult.GeneratedValues[$key] } $insertedCount = $streamResult.InsertedCount } else { # Query existing unique values from the DB in a single batched query $existingUnique = $null if ($ConnectionInfo -and $provider) { $uqParams = @{ TableInfo = $tableInfo TablePlan = $tablePlan ConnectionInfo = $ConnectionInfo UniqueQueryLimit = $uniqueQueryLimit CommandTimeout = $dbCommandTimeout } if ($transaction) { $uqParams['Transaction'] = $transaction } $existingUnique = Get-SldgExistingUniqueValue @uqParams } $rowSetParams = @{ TableInfo = $tableInfo RowCount = $tablePlan.RowCount GeneratorMap = $Plan.GeneratorMap ForeignKeyValues = $fkValues TableRules = $tableRules ExistingUniqueValues = $existingUnique } # Two-tier AI: pass per-table generation notes from schema analysis if ($Plan.AIAdvice -and $Plan.AIAdvice.TableGenerationNotes -and $Plan.AIAdvice.TableGenerationNotes.ContainsKey($tablePlan.FullName)) { $rowSetParams['TableNotes'] = $Plan.AIAdvice.TableGenerationNotes[$tablePlan.FullName] } $rowSet = New-SldgRowSet @rowSetParams # Merge generated FK values for child tables foreach ($key in $rowSet.GeneratedValues.Keys) { $fkValues[$key] = $rowSet.GeneratedValues[$key] } $insertedCount = 0 if (-not $NoInsert -and $ConnectionInfo) { $writeParams = @{ ConnectionInfo = $ConnectionInfo SchemaName = $tablePlan.SchemaName TableName = $tablePlan.TableName Data = $rowSet.DataTable BatchSize = $batchSize } if ($transaction) { $writeParams['Transaction'] = $transaction } $insertedCount = & $provider.FunctionMap.WriteData @writeParams # Post-insert: collect actual PK values from DB for identity/auto-increment columns # that are NOT in the in-memory DataTable. Child tables need these FK references. $collectedPK = Get-SldgPostInsertPrimaryKeyValue -TablePlan $tablePlan -ExistingFkValues $fkValues ` -ConnectionInfo $ConnectionInfo -Transaction $transaction ` -FkQueryLimit $fkQueryLimit -CommandTimeout $dbCommandTimeout foreach ($pkKey in $collectedPK.Keys) { $fkValues[$pkKey] = $collectedPK[$pkKey] } } else { $insertedCount = $rowSet.RowCount } } $totalInserted += $insertedCount Write-PSFMessage -Level Host -Message ($script:strings.'Generation.TableComplete' -f $tablePlan.FullName, $insertedCount) $tableResult = [SqlLabDataGenerator.TableResult]@{ TableName = $tablePlan.FullName RowCount = $insertedCount Success = $true Error = $null } $skippedRows = [Math]::Max(0, [int]$tablePlan.RowCount - [int]$insertedCount) $tableResult.RequestedRows = [int]$tablePlan.RowCount $tableResult.SkippedRows = $skippedRows if ($PassThru -and $rowSet) { $tableResult.DataTable = $rowSet.DataTable } elseif ($PassThru -and $streamResult -and $streamResult.DataTable) { $tableResult.DataTable = $streamResult.DataTable } elseif ($rowSet -and $rowSet.DataTable) { # Release DataTable memory when not returning to caller $rowSet.DataTable.Dispose() } $tableResults.Add($tableResult) } -PSCmdlet $PSCmdlet -EnableException $false if (Test-PSFFunctionInterrupt) { [void]$failedTables.Add($tablePlan.FullName) $tableResults.Add([SqlLabDataGenerator.TableResult]@{ TableName = $tablePlan.FullName RowCount = 0 Success = $false Error = $Error[0].Exception.Message }) $failedResult = $tableResults[$tableResults.Count - 1] $failedResult.RequestedRows = [int]$tablePlan.RowCount $failedResult.SkippedRows = [int]$tablePlan.RowCount if ($transaction) { $generationFailed = $true Write-PSFMessage -Level Warning -Message ($script:strings.'Generation.RollingBack' -f $tablePlan.FullName) try { $transaction.Rollback() } catch { Write-PSFMessage -Level Error -Message ($script:strings.'Generation.RollbackCritical' -f $_) } $transaction = $null $totalInserted = 0 foreach ($tr in $tableResults) { if ($tr.Success) { $tr.RolledBack = $true } } Stop-PSFFunction -String 'Generation.DataRolledBack' -StringValues $tablePlan.FullName -EnableException $true } } # Stamp per-table duration on any TableResult(s) added during this iteration $tableStopwatch.Stop() $tableElapsedMs = [int]$tableStopwatch.Elapsed.TotalMilliseconds for ($i = $tableResultCountBefore; $i -lt $tableResults.Count; $i++) { $tableResults[$i].DurationMs = $tableElapsedMs } } } # end: sequential/parallel branch # Re-enable FK constraints for all circular dependency tables after insertion $fkReenableFailures = [System.Collections.Generic.List[string]]::new() if ($disabledFKInfo -and $disabledFKInfo.DisabledTables.Count -gt 0 -and $ConnectionInfo -and $ConnectionInfo.DbConnection) { $reenableParams = @{ DisabledInfo = $disabledFKInfo ConnectionInfo = $ConnectionInfo } if ($transaction) { $reenableParams['Transaction'] = $transaction } $fkReenableFailures = Enable-SldgCircularFKConstraint @reenableParams } if ($fkReenableFailures.Count -gt 0) { $generationFailed = $true if ($transaction) { try { $transaction.Rollback() } catch { Write-PSFMessage -Level Error -Message ($script:strings.'Generation.FKReenableRollbackFailed' -f $_) } $transaction = $null } Stop-PSFFunction -String 'Generation.FKReenableCritical' -StringValues ($fkReenableFailures -join ', ') -EnableException $true } # Normalize per-table quality metrics. Parallel generation and skipped-parent paths may # create TableResult objects outside the sequential table block. foreach ($tableResult in $tableResults) { $matchingPlanTable = $Plan.Tables | Where-Object { $_.FullName -eq $tableResult.TableName } | Select-Object -First 1 $requestedRowsForTable = if ($matchingPlanTable) { [int]$matchingPlanTable.RowCount } else { [int]$tableResult.RowCount } $skippedRowsForTable = if ($tableResult.Success) { [Math]::Max(0, $requestedRowsForTable - [int]$tableResult.RowCount) } else { 0 } $tableResult.RequestedRows = $requestedRowsForTable $tableResult.SkippedRows = $skippedRowsForTable } # Strict row-quality gate: detect provider fallbacks such as SQLite INSERT OR IGNORE. # Run before commit so -UseTransaction can still roll back strict failures. $skippedRowCount = ($tableResults | ForEach-Object { [int]$_.SkippedRows } | Measure-Object -Sum).Sum if ($null -eq $skippedRowCount) { $skippedRowCount = 0 } if ($FailOnSkippedRows -and $skippedRowCount -gt 0) { $generationFailed = $true if ($transaction) { try { $transaction.Rollback() } catch { Write-PSFMessage -Level Error -Message ($script:strings.'Generation.RollbackCritical' -f $_) } $transaction = $null $totalInserted = 0 } Stop-PSFFunction -String 'Generation.SkippedRowsStrictFailure' -StringValues $skippedRowCount -EnableException $true } # Commit transaction if all succeeded if ($transaction -and -not $generationFailed) { try { $transaction.Commit() Write-PSFMessage -Level Verbose -Message $script:strings.'Generation.TransactionCommitted' } catch { Write-PSFMessage -Level Warning -Message ($script:strings.'Generation.CommitFailed' -f $_) try { $transaction.Rollback() } catch { Write-PSFMessage -Level Error -Message ($script:strings.'Generation.CommitRollbackCritical' -f $_) } $totalInserted = 0 $generationFailed = $true } } # Optional post-generation database validation if ($ValidateAfterGeneration) { # The plan carries everything the validators need; rebuild a SchemaModel from it so # validation runs against the same tables and columns that were generated. $validationSchema = [SqlLabDataGenerator.SchemaModel]@{ Database = $Plan.Database Tables = @($Plan.Tables | ForEach-Object { ConvertTo-SldgTableInfo -TablePlan $_ }) TableCount = $Plan.TableCount DiscoveredAt = Get-Date } $validationResults = @(Test-SldgGeneratedData -Schema $validationSchema -ConnectionInfo $ConnectionInfo) $validationErrors = @($validationResults | Where-Object { -not $_.Passed -and $_.Severity -eq 'Error' }) if ($FailOnValidationError -and $validationErrors.Count -gt 0) { Stop-PSFFunction -String 'Validation.StrictFailure' -StringValues $validationErrors.Count -EnableException $true } } Write-Progress -Activity 'Generating data' -Completed $generationDuration = (Get-Date) - $generationStartTime Write-PSFMessage -Level Host -Message ($script:strings.'Generation.Complete' -f $Plan.TableCount, $totalInserted) Write-PSFMessage -Level Verbose -Message ($script:strings.'Generation.AuditComplete' -f $executingUser, $totalInserted, $generationDuration.TotalSeconds.ToString('F1'), $generationFailed) # Restore AI generation config if we overrode it if ($aiGenOverrideApplied) { Set-PSFConfig -FullName 'SqlLabDataGenerator.Generation.AIGeneration' -Value $false } # Persistent audit log — append a JSON record for compliance/traceability Write-SldgAuditRecord -Plan $Plan -TotalInserted $totalInserted -StartTime $generationStartTime ` -User $executingUser -GenerationFailed $generationFailed -TableResults $tableResults ` -CorrelationId $correlationId # Store generated data reference $script:SldgState.GeneratedData[$Plan.Database] = $tableResults $qualityReport = Get-SldgGenerationQualityReport -Plan $Plan -TableResults $tableResults ` -TotalInserted $totalInserted -FkFallbackStats $fkFallbackStats ` -ValidationRun:$ValidateAfterGeneration -ValidationResults $validationResults $result = [SqlLabDataGenerator.GenerationResult]@{ Database = $Plan.Database Mode = $Plan.Mode TableCount = $Plan.TableCount TotalRows = $totalInserted Tables = $tableResults.ToArray() SuccessCount = ($tableResults | Where-Object Success).Count FailureCount = ($tableResults | Where-Object { -not $_.Success }).Count StartedAt = $generationStartTime CompletedAt = Get-Date Duration = $generationDuration User = $executingUser } $result.QualityReport = $qualityReport $result.ValidationResults = @($validationResults | Where-Object { $null -ne $_ }) $result } } |