86883cd5c6
- Refactor migrations and provider to use multiple PostgreSQL schemas, each matching a legacy SQLite database (activitylog, authentication, displaypreferences, library, users). - All tables, foreign keys, and indexes are now schema-qualified; Down migration drops tables by schema. - Provider ensures schemas exist before migrations; entities are mapped to correct schemas in OnModelCreating. - Add support for max-pool-size, min-pool-size, and multiplexing connection options; update logging accordingly. - VACUUM ANALYZE now runs per schema during scheduled optimization. - TruncateAllTablesAsync now truncates tables with schema qualification. - README updated with schema structure, new options, and multiplexing warnings. - CacheDecorator now calls async repository methods using .GetAwaiter().GetResult(), with documentation. - Lays groundwork for full async/await and multiplexing support in the database layer.
300 lines
9.9 KiB
PowerShell
300 lines
9.9 KiB
PowerShell
# Async Migration Analysis Script
|
|
# Scans the Jellyfin codebase for synchronous database operations that need conversion
|
|
|
|
param(
|
|
[string]$Path = ".",
|
|
[switch]$DetailedReport,
|
|
[switch]$ExportCsv
|
|
)
|
|
|
|
Write-Host "🔍 Scanning for synchronous database operations..." -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
# Define patterns to search for
|
|
$patterns = @{
|
|
"SaveChanges" = @{
|
|
Pattern = "\.SaveChanges\s*\(\s*\)"
|
|
Replacement = "await .SaveChangesAsync(cancellationToken)"
|
|
Severity = "HIGH"
|
|
Description = "Synchronous database save operation"
|
|
}
|
|
"ToList" = @{
|
|
Pattern = "\.ToList\s*\(\s*\)"
|
|
Replacement = "await .ToListAsync(cancellationToken)"
|
|
Severity = "HIGH"
|
|
Description = "Synchronous list materialization"
|
|
}
|
|
"ToArray" = @{
|
|
Pattern = "\.ToArray\s*\(\s*\)"
|
|
Replacement = "await .ToArrayAsync(cancellationToken)"
|
|
Severity = "HIGH"
|
|
Description = "Synchronous array materialization"
|
|
}
|
|
"FirstOrDefault" = @{
|
|
Pattern = "\.FirstOrDefault\s*\("
|
|
Replacement = 'await .FirstOrDefaultAsync('
|
|
Severity = "HIGH"
|
|
Description = "Synchronous first element query"
|
|
}
|
|
"First" = @{
|
|
Pattern = "\.First\s*\("
|
|
Replacement = 'await .FirstAsync('
|
|
Severity = "HIGH"
|
|
Description = "Synchronous first element query"
|
|
}
|
|
"SingleOrDefault" = @{
|
|
Pattern = "\.SingleOrDefault\s*\("
|
|
Replacement = 'await .SingleOrDefaultAsync('
|
|
Severity = "MEDIUM"
|
|
Description = "Synchronous single element query"
|
|
}
|
|
"Single" = @{
|
|
Pattern = "\.Single\s*\("
|
|
Replacement = 'await .SingleAsync('
|
|
Severity = "MEDIUM"
|
|
Description = "Synchronous single element query"
|
|
}
|
|
"Any" = @{
|
|
Pattern = "\.Any\s*\("
|
|
Replacement = 'await .AnyAsync('
|
|
Severity = "MEDIUM"
|
|
Description = "Synchronous existence check"
|
|
}
|
|
"Count" = @{
|
|
Pattern = "\.Count\s*\(\s*\)"
|
|
Replacement = "await .CountAsync(cancellationToken)"
|
|
Severity = "MEDIUM"
|
|
Description = "Synchronous count operation"
|
|
}
|
|
"LongCount" = @{
|
|
Pattern = "\.LongCount\s*\("
|
|
Replacement = "await .LongCountAsync("
|
|
Severity = "MEDIUM"
|
|
Description = "Synchronous long count operation"
|
|
}
|
|
"ExecuteDelete" = @{
|
|
Pattern = "\.ExecuteDelete\s*\(\s*\)"
|
|
Replacement = "await .ExecuteDeleteAsync(cancellationToken)"
|
|
Severity = "HIGH"
|
|
Description = "Synchronous bulk delete"
|
|
}
|
|
"ExecuteUpdate" = @{
|
|
Pattern = "\.ExecuteUpdate\s*\("
|
|
Replacement = "await .ExecuteUpdateAsync("
|
|
Severity = "HIGH"
|
|
Description = "Synchronous bulk update"
|
|
}
|
|
"BeginTransaction" = @{
|
|
Pattern = "\.BeginTransaction\s*\(\s*\)"
|
|
Replacement = "await .BeginTransactionAsync(cancellationToken)"
|
|
Severity = "HIGH"
|
|
Description = "Synchronous transaction start"
|
|
}
|
|
"Commit" = @{
|
|
Pattern = "transaction\.Commit\s*\(\s*\)"
|
|
Replacement = "await transaction.CommitAsync(cancellationToken)"
|
|
Severity = "HIGH"
|
|
Description = "Synchronous transaction commit"
|
|
}
|
|
"Rollback" = @{
|
|
Pattern = "transaction\.Rollback\s*\(\s*\)"
|
|
Replacement = "await transaction.RollbackAsync(cancellationToken)"
|
|
Severity = "MEDIUM"
|
|
Description = "Synchronous transaction rollback"
|
|
}
|
|
"Sum" = @{
|
|
Pattern = "\.Sum\s*\("
|
|
Replacement = 'await .SumAsync('
|
|
Severity = "LOW"
|
|
Description = "Synchronous sum aggregation"
|
|
}
|
|
"Average" = @{
|
|
Pattern = "\.Average\s*\("
|
|
Replacement = 'await .AverageAsync('
|
|
Severity = "LOW"
|
|
Description = "Synchronous average aggregation"
|
|
}
|
|
"Min" = @{
|
|
Pattern = "\.Min\s*\("
|
|
Replacement = 'await .MinAsync('
|
|
Severity = "LOW"
|
|
Description = "Synchronous min aggregation"
|
|
}
|
|
"Max" = @{
|
|
Pattern = "\.Max\s*\("
|
|
Replacement = 'await .MaxAsync('
|
|
Severity = "LOW"
|
|
Description = "Synchronous max aggregation"
|
|
}
|
|
"Load" = @{
|
|
Pattern = "\.Load\s*\(\s*\)"
|
|
Replacement = 'await .LoadAsync(cancellationToken)'
|
|
Severity = "MEDIUM"
|
|
Description = "Synchronous navigation property load"
|
|
}
|
|
}
|
|
|
|
# Exclude patterns (already async or intentionally sync)
|
|
$excludePatterns = @(
|
|
"Async\s*\(",
|
|
"// ASYNC",
|
|
"\.ConfigureAwait",
|
|
"AsyncEnumerable"
|
|
)
|
|
|
|
# Get all .cs files, excluding test files and migrations initially
|
|
$files = Get-ChildItem -Path $Path -Recurse -Filter "*.cs" |
|
|
Where-Object {
|
|
$_.FullName -notmatch "\\bin\\" -and
|
|
$_.FullName -notmatch "\\obj\\" -and
|
|
$_.FullName -notmatch "\\Migrations\\" -and
|
|
$_.FullName -notmatch "\.Designer\.cs$"
|
|
}
|
|
|
|
$results = @()
|
|
$summary = @{}
|
|
|
|
foreach ($file in $files) {
|
|
$content = Get-Content -Path $file.FullName -Raw
|
|
|
|
foreach ($patternName in $patterns.Keys) {
|
|
$patternInfo = $patterns[$patternName]
|
|
$pattern = $patternInfo.Pattern
|
|
|
|
# Find matches
|
|
$matches = [regex]::Matches($content, $pattern)
|
|
|
|
foreach ($match in $matches) {
|
|
# Check if this line is already async or should be excluded
|
|
$lineStart = $content.Substring(0, $match.Index).LastIndexOf("`n")
|
|
if ($lineStart -lt 0) { $lineStart = 0 }
|
|
$lineEnd = $content.IndexOf("`n", $match.Index)
|
|
if ($lineEnd -lt 0) { $lineEnd = $content.Length }
|
|
|
|
$line = $content.Substring($lineStart, $lineEnd - $lineStart).Trim()
|
|
|
|
# Skip if already async or excluded
|
|
$shouldExclude = $false
|
|
foreach ($excludePattern in $excludePatterns) {
|
|
if ($line -match $excludePattern) {
|
|
$shouldExclude = $true
|
|
break
|
|
}
|
|
}
|
|
|
|
if (-not $shouldExclude) {
|
|
# Calculate line number
|
|
$lineNumber = ($content.Substring(0, $match.Index) -split "`n").Count
|
|
|
|
$result = [PSCustomObject]@{
|
|
File = $file.FullName.Replace($Path, "").TrimStart('\')
|
|
LineNumber = $lineNumber
|
|
Pattern = $patternName
|
|
Severity = $patternInfo.Severity
|
|
Description = $patternInfo.Description
|
|
MatchedText = $match.Value
|
|
Line = $line
|
|
Replacement = $patternInfo.Replacement
|
|
}
|
|
|
|
$results += $result
|
|
|
|
# Update summary
|
|
if (-not $summary.ContainsKey($patternName)) {
|
|
$summary[$patternName] = 0
|
|
}
|
|
$summary[$patternName]++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Display results
|
|
Write-Host "📊 Analysis Complete!" -ForegroundColor Green
|
|
Write-Host ""
|
|
Write-Host "=" * 80 -ForegroundColor DarkGray
|
|
Write-Host "SUMMARY" -ForegroundColor Yellow
|
|
Write-Host "=" * 80 -ForegroundColor DarkGray
|
|
Write-Host ""
|
|
|
|
$totalIssues = $results.Count
|
|
$highSeverity = ($results | Where-Object { $_.Severity -eq "HIGH" }).Count
|
|
$mediumSeverity = ($results | Where-Object { $_.Severity -eq "MEDIUM" }).Count
|
|
$lowSeverity = ($results | Where-Object { $_.Severity -eq "LOW" }).Count
|
|
|
|
Write-Host "Total synchronous operations found: " -NoNewline
|
|
Write-Host $totalIssues -ForegroundColor Red
|
|
Write-Host ""
|
|
Write-Host " 🔴 HIGH severity: " -NoNewline
|
|
Write-Host $highSeverity -ForegroundColor Red
|
|
Write-Host " 🟡 MEDIUM severity: " -NoNewline
|
|
Write-Host $mediumSeverity -ForegroundColor Yellow
|
|
Write-Host " 🟢 LOW severity: " -NoNewline
|
|
Write-Host $lowSeverity -ForegroundColor Green
|
|
Write-Host ""
|
|
|
|
Write-Host "Breakdown by operation type:" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
$summary.GetEnumerator() | Sort-Object Value -Descending | ForEach-Object {
|
|
$severity = $patterns[$_.Key].Severity
|
|
$color = switch ($severity) {
|
|
"HIGH" { "Red" }
|
|
"MEDIUM" { "Yellow" }
|
|
"LOW" { "Green" }
|
|
}
|
|
Write-Host " $($_.Key): " -NoNewline
|
|
Write-Host $_.Value -ForegroundColor $color -NoNewline
|
|
Write-Host " ($severity)"
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "=" * 80 -ForegroundColor DarkGray
|
|
|
|
if ($DetailedReport) {
|
|
Write-Host ""
|
|
Write-Host "DETAILED FINDINGS" -ForegroundColor Yellow
|
|
Write-Host "=" * 80 -ForegroundColor DarkGray
|
|
Write-Host ""
|
|
|
|
# Group by file
|
|
$resultsByFile = $results | Group-Object -Property File
|
|
|
|
foreach ($fileGroup in $resultsByFile | Sort-Object Name) {
|
|
Write-Host ""
|
|
Write-Host "📄 $($fileGroup.Name)" -ForegroundColor Cyan
|
|
Write-Host "-" * 80 -ForegroundColor DarkGray
|
|
|
|
foreach ($item in $fileGroup.Group | Sort-Object LineNumber) {
|
|
$severityColor = switch ($item.Severity) {
|
|
"HIGH" { "Red" }
|
|
"MEDIUM" { "Yellow" }
|
|
"LOW" { "Green" }
|
|
}
|
|
|
|
Write-Host " Line $($item.LineNumber): " -NoNewline
|
|
Write-Host "$($item.Pattern) " -NoNewline -ForegroundColor $severityColor
|
|
Write-Host "- $($item.Description)"
|
|
Write-Host " Current: " -NoNewline
|
|
Write-Host $item.MatchedText -ForegroundColor Gray
|
|
Write-Host " Replacement: " -NoNewline
|
|
Write-Host $item.Replacement -ForegroundColor Green
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($ExportCsv) {
|
|
$csvPath = Join-Path $Path "async_migration_report.csv"
|
|
$results | Export-Csv -Path $csvPath -NoTypeInformation
|
|
Write-Host ""
|
|
Write-Host "📁 Report exported to: " -NoNewline
|
|
Write-Host $csvPath -ForegroundColor Cyan
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "💡 TIP: Run with -DetailedReport for line-by-line analysis" -ForegroundColor DarkGray
|
|
Write-Host "💡 TIP: Run with -ExportCsv to export findings to CSV" -ForegroundColor DarkGray
|
|
Write-Host ""
|
|
|
|
# Return results for further processing
|
|
return $results
|