param( [string]$SourceDump = (Join-Path $PSScriptRoot '..\db\dump-pg-202604211927.pretrim-backup.sql'), [string]$OutputDump = (Join-Path $PSScriptRoot '..\db\dump-pg-202604211927.trimmed.sql'), [string]$ContainerName = 'regalami-dump-trim-mysql-temp', [string]$VolumeName = 'regalami-dump-trim-mysql-temp-data', [string]$DatabaseName = 'pgtrim', [string]$RootPassword = 'root' ) $ErrorActionPreference = 'Stop' function Invoke-DockerCapture { param( [Parameter(Mandatory = $true)] [string[]]$DockerArgs ) $output = & docker @DockerArgs 2>&1 if ($LASTEXITCODE -ne 0) { throw (("docker " + ($DockerArgs -join ' ')) + " failed:`n" + ($output -join "`n")) } return $output } function Invoke-DockerQuiet { param( [Parameter(Mandatory = $true)] [string[]]$DockerArgs ) & docker @DockerArgs | Out-Null if ($LASTEXITCODE -ne 0) { throw "docker $($DockerArgs -join ' ') failed" } } function Wait-ForMysqlReady { param( [Parameter(Mandatory = $true)] [string]$Name, [Parameter(Mandatory = $true)] [string]$Password ) for ($attempt = 0; $attempt -lt 180; $attempt++) { $portOutput = & docker exec $Name mysql -N -B -uroot -p$Password -e "SELECT @@port;" 2>$null if ($LASTEXITCODE -eq 0) { $reportedPort = ($portOutput | Select-Object -First 1).Trim() if ($reportedPort -eq '3306') { return } } Start-Sleep -Seconds 2 } throw "MySQL in container $Name did not become ready in time." } function Invoke-MysqlQuery { param( [Parameter(Mandatory = $true)] [string]$Query, [switch]$SkipDatabase ) $dockerArgs = @('exec', $ContainerName, 'mysql', '-N', '-B', '-uroot', "-p$RootPassword") if (-not $SkipDatabase) { $dockerArgs += @('-D', $DatabaseName) } $dockerArgs += @('-e', $Query) return Invoke-DockerCapture -DockerArgs $dockerArgs } function Get-SqlScalar { param( [Parameter(Mandatory = $true)] [string]$Query, [switch]$SkipDatabase ) $result = Invoke-MysqlQuery -Query $Query -SkipDatabase:$SkipDatabase if (-not $result) { return '' } return ($result | Select-Object -First 1).Trim() } function Quote-Identifier { param([string]$Name) return '`' + $Name.Replace('`', '``') + '`' } function Quote-SqlLiteral { param([string]$Value) return "'" + $Value.Replace("'", "''") + "'" } if (-not (Test-Path $SourceDump)) { throw "Source dump not found: $SourceDump" } $dbDirectory = Split-Path -Parent (Resolve-Path $SourceDump).Path $sourceFileName = Split-Path -Leaf $SourceDump $outputFileName = Split-Path -Leaf $OutputDump $importErrorLogFileName = [System.IO.Path]::GetFileNameWithoutExtension($sourceFileName) + '.import-errors.log' $importErrorLogPath = Join-Path $dbDirectory $importErrorLogFileName $sourceSizeBytes = (Get-Item $SourceDump).Length Write-Host "Using source dump: $SourceDump" Write-Host "Source size: $sourceSizeBytes bytes" Write-Host "Import error log: $importErrorLogPath" try { & docker rm -f $ContainerName 1>$null 2>$null & docker volume rm $VolumeName 1>$null 2>$null Invoke-DockerCapture -DockerArgs @( 'run', '-d', '--name', $ContainerName, '-e', "MYSQL_ROOT_PASSWORD=$RootPassword", '-v', "${dbDirectory}:/workspace/db", '-v', "${VolumeName}:/var/lib/mysql", 'mysql:8.4', '--max_allowed_packet=1G', '--net_read_timeout=600', '--net_write_timeout=600' ) | Out-Null Wait-ForMysqlReady -Name $ContainerName -Password $RootPassword Invoke-MysqlQuery -SkipDatabase -Query "DROP DATABASE IF EXISTS $DatabaseName; CREATE DATABASE $DatabaseName CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;" | Out-Null Write-Host "Importing source dump into temporary MySQL database..." if (Test-Path $importErrorLogPath) { Remove-Item $importErrorLogPath -Force } Invoke-DockerQuiet -DockerArgs @( 'exec', $ContainerName, 'sh', '-lc', "mysql --force -uroot -p$RootPassword $DatabaseName < /workspace/db/$sourceFileName 2> /workspace/db/$importErrorLogFileName" ) $beforeFoto = [int64](Get-SqlScalar -Query 'SELECT COUNT(*) FROM foto;') $beforeLogFoto = [int64](Get-SqlScalar -Query 'SELECT COUNT(*) FROM log_foto;') $usersExists = (Get-SqlScalar -SkipDatabase -Query "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '$DatabaseName' AND table_name = 'users';") -eq '1' $beforeUsers = 0 if ($usersExists) { $beforeUsers = [int64](Get-SqlScalar -Query 'SELECT COUNT(*) FROM users;') } $garaDateColumn = Get-SqlScalar -SkipDatabase -Query @" SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema = '$DatabaseName' AND table_name = 'gara' AND COLUMN_NAME IN ('dataGaraInizio', 'dataGaraFine', 'data', 'createTmst', 'lastUpdTmst') ORDER BY FIELD(COLUMN_NAME, 'dataGaraInizio', 'dataGaraFine', 'data', 'createTmst', 'lastUpdTmst') LIMIT 1; "@ if (-not $garaDateColumn) { throw 'Could not determine race date column from gara table.' } $usersDateColumn = '' if ($usersExists) { $usersDateColumn = Get-SqlScalar -SkipDatabase -Query @" SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema = '$DatabaseName' AND table_name = 'users' AND COLUMN_NAME IN ('dataInserimento', 'dataInizioVld', 'lastUpdTmst') ORDER BY FIELD(COLUMN_NAME, 'dataInserimento', 'dataInizioVld', 'lastUpdTmst') LIMIT 1; "@ if (-not $usersDateColumn) { $usersDateColumn = 'id_users' } } $garaDateExpr = Quote-Identifier $garaDateColumn $usersDateExpr = Quote-Identifier $usersDateColumn Invoke-MysqlQuery -Query @" DROP TABLE IF EXISTS keep_gara_ids; CREATE TABLE keep_gara_ids AS SELECT id_gara FROM gara ORDER BY $garaDateExpr DESC, id_gara DESC LIMIT 10; DROP TABLE IF EXISTS keep_foto_ids; CREATE TABLE keep_foto_ids AS SELECT id_foto FROM foto WHERE id_gara IN (SELECT id_gara FROM keep_gara_ids); "@ | Out-Null $cleanupStats = [System.Collections.Generic.List[string]]::new() Invoke-MysqlQuery -Query 'DELETE FROM log_foto;' | Out-Null $cleanupStats.Add('log_foto:deleted-all') $fotoDependentTables = Invoke-MysqlQuery -SkipDatabase -Query @" SELECT table_name FROM information_schema.columns WHERE table_schema = '$DatabaseName' AND column_name = 'id_foto' AND table_name NOT IN ('foto', 'log_foto', 'keep_foto_ids', 'keep_gara_ids') ORDER BY table_name; "@ foreach ($tableName in $fotoDependentTables) { if (-not $tableName) { continue } $trimmedTableName = $tableName.Trim() $quotedTable = Quote-Identifier $trimmedTableName $deletedRows = [int64](Get-SqlScalar -Query "SELECT COUNT(*) FROM $quotedTable WHERE id_foto IS NOT NULL AND id_foto NOT IN (SELECT id_foto FROM keep_foto_ids);") if ($deletedRows -gt 0) { Invoke-MysqlQuery -Query "DELETE FROM $quotedTable WHERE id_foto IS NOT NULL AND id_foto NOT IN (SELECT id_foto FROM keep_foto_ids);" | Out-Null $cleanupStats.Add("$($trimmedTableName):$deletedRows") } } $deletedFotoRows = [int64](Get-SqlScalar -Query 'SELECT COUNT(*) FROM foto WHERE id_foto NOT IN (SELECT id_foto FROM keep_foto_ids);') if ($deletedFotoRows -gt 0) { Invoke-MysqlQuery -Query 'DELETE FROM foto WHERE id_foto NOT IN (SELECT id_foto FROM keep_foto_ids);' | Out-Null $cleanupStats.Add("foto:$deletedFotoRows") } if ($usersExists) { Invoke-MysqlQuery -Query @" DROP TABLE IF EXISTS keep_user_ids; CREATE TABLE keep_user_ids AS SELECT id_users FROM users ORDER BY $usersDateExpr ASC, id_users ASC LIMIT 5; "@ | Out-Null $usersDependentTables = Invoke-MysqlQuery -SkipDatabase -Query @" SELECT table_name FROM information_schema.columns WHERE table_schema = '$DatabaseName' AND column_name = 'id_users' AND table_name NOT IN ('users', 'keep_user_ids') ORDER BY table_name; "@ foreach ($tableName in $usersDependentTables) { if (-not $tableName) { continue } $trimmedTableName = $tableName.Trim() $quotedTable = Quote-Identifier $trimmedTableName $deletedRows = [int64](Get-SqlScalar -Query "SELECT COUNT(*) FROM $quotedTable WHERE id_users IS NOT NULL AND id_users NOT IN (SELECT id_users FROM keep_user_ids);") if ($deletedRows -gt 0) { Invoke-MysqlQuery -Query "DELETE FROM $quotedTable WHERE id_users IS NOT NULL AND id_users NOT IN (SELECT id_users FROM keep_user_ids);" | Out-Null $cleanupStats.Add("$($trimmedTableName):$deletedRows") } } $deletedUsers = [int64](Get-SqlScalar -Query 'SELECT COUNT(*) FROM users WHERE id_users NOT IN (SELECT id_users FROM keep_user_ids);') if ($deletedUsers -gt 0) { Invoke-MysqlQuery -Query 'DELETE FROM users WHERE id_users NOT IN (SELECT id_users FROM keep_user_ids);' | Out-Null $cleanupStats.Add("users:$deletedUsers") } } $afterFoto = [int64](Get-SqlScalar -Query 'SELECT COUNT(*) FROM foto;') $afterLogFoto = [int64](Get-SqlScalar -Query 'SELECT COUNT(*) FROM log_foto;') $afterUsers = 0 if ($usersExists) { $afterUsers = [int64](Get-SqlScalar -Query 'SELECT COUNT(*) FROM users;') } $keptGaraIds = Invoke-MysqlQuery -Query "SELECT id_gara FROM keep_gara_ids ORDER BY $garaDateExpr DESC, id_gara DESC;" if (Test-Path $OutputDump) { Remove-Item $OutputDump -Force } Write-Host "Exporting trimmed dump..." Invoke-DockerQuiet -DockerArgs @( 'exec', $ContainerName, 'sh', '-lc', "mysqldump -uroot -p$RootPassword --default-character-set=utf8mb4 --single-transaction --routines --triggers --set-gtid-purged=OFF $DatabaseName > /workspace/db/$outputFileName" ) if (-not (Test-Path $OutputDump)) { throw "Trimmed dump was not created: $OutputDump" } $outputSizeBytes = (Get-Item $OutputDump).Length Write-Host "OriginalSizeBytes=$sourceSizeBytes" Write-Host "TrimmedSizeBytes=$outputSizeBytes" Write-Host "BeforeFoto=$beforeFoto" Write-Host "AfterFoto=$afterFoto" Write-Host "BeforeLogFoto=$beforeLogFoto" Write-Host "AfterLogFoto=$afterLogFoto" Write-Host "BeforeUsers=$beforeUsers" Write-Host "AfterUsers=$afterUsers" Write-Host ("KeptGaraIds=" + (($keptGaraIds | ForEach-Object { $_.Trim() }) -join ',')) Write-Host ("DependencyCleanup=" + (($cleanupStats | Sort-Object) -join ';')) if (Test-Path $importErrorLogPath) { $importErrorCount = (Get-Item $importErrorLogPath).Length Write-Host "ImportErrorLogBytes=$importErrorCount" } } finally { & docker rm -f $ContainerName 1>$null 2>$null & docker volume rm $VolumeName 1>$null 2>$null }