Skip to content

feat: PostgreSQL Database Provider v2.0.0.0 — health check, timezone fix, JellyTrend integration, release workflow - #1

Merged
BORNIOS merged 14 commits into
mainfrom
release/2.0.0.0
Aug 29, 2026
Merged

BORNIOS merged 14 commits into
mainfrom
release/2.0.0.0

Conversation

@BORNIOS

@BORNIOS BORNIOS commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Resumen

Versión 2.0.0.0 — reescritura mayor del plugin. Salto de v1.0 a v2.0 por la magnitud de los cambios.


🔧 Correcciones y mejoras core

Fix: timezone del servidor PostgreSQL

  • Bug: el switch global Npgsql.EnableLegacyTimestampBehavior = true forzaba UTC en todas las lecturas, ignorando la timezone configurada en el servidor.
  • Fix: reemplazado por DateTimeKindNormalizingInterceptor — interceptor EF Core que solo normaliza parámetros de escritura con Kind=Unspecified. Las lecturas respetan la timezone del servidor.

Fix: crash por duplicate PK en BaseItemProviders (23505)

  • Interceptor UpsertConflictInterceptor previene el crash fatal cuando Jellyfin intenta insertar una clave duplicada.

Nuevo: Health Check automático

  • Se ejecuta 10 segundos después del arranque y puede lanzarse manualmente.
  • Diagnostica: conexión, extensiones, índices inválidos, bloat de tablas, secuencias desfasadas, estadísticas obsoletas y queries lentas.
  • Auto-reparación en un clic para problemas recuperables.

Nuevo: DbErrorLoggingInterceptor

  • Interceptor EF Core que registra todos los errores de base de datos en el log del plugin.

Integración opcional con JellyTrend

  • PostgresRecommendationQueryProvider: implementación de IRecommendationQueryProvider con SQL nativo y operador && de arrays + índices GIN (4-10× más rápido en recomendaciones).
  • Registro condicional en DI — silencioso si JellyTrend no está instalado.
  • JellyTrendContract/: mirror de compilación de la interfaz (sin referencia al DLL de JellyTrend).

📦 Release workflow

  • .github/workflows/release.yml: nuevo pipeline workflow_dispatch basado en el patrón de JellyTrend.
  • ZIP incluye solo los 3 DLLs que Jellyfin no provee en runtime: Jellyfin.Database.Providers.Postgres.dll + Npgsql.dll + Npgsql.EntityFrameworkCore.PostgreSQL.dll.
  • Calcula checksum MD5, actualiza manifest.json y crea el GitHub Release automáticamente.

📚 Documentación

  • README.md (ES) y README.en.md (EN): reescritos completos siguiendo el estilo de JellyTrend.
  • Capturas de las 4 pestañas del plugin añadidas en Screenshots/.
  • manifest.json: corregido imageUrl (apuntaba a tag v1.1.0.0 inexistente), reescritos description y overview para v2.0.

⚠️ Notas

  • LegacyDB/ eliminado — eran respaldos locales que se colaron accidentalmente en commits anteriores.
  • La rama Version1.1.0.0 remota fue eliminada — esa versión nunca se publicó.

Checklist

  • Build limpio 0 warnings / 0 errors
  • Fix timezone verificado en producción
  • LegacyDB no incluido en ningún commit de esta rama
  • manifest.json y Directory.Build.props listos para el workflow de release

Jorge Chávez Bornios added 14 commits August 28, 2026 22:28
…ization UI

- InstantSearchService: sub-15ms search via pg_trgm GIN indexes, ILIKE fallback
- ExportToSqliteService: export PG data directly to .db binary (no external tools)
  Auto-detects native jellyfin.db path; creates tables from PG schema if absent;
  DELETE+INSERT in 10k-row WAL transactions if .db already exists
- OptimizeIndexesTask: weekly scheduled task; inject ILogger via constructor
- PostgresDatabaseProvider: extract RunOptimizationsAsync as static method
  Fixes 503 on ApplyOptimizations - no longer requires provider instance (Current)
- PostgresController: ApplyOptimizations uses RunOptimizationsAsync static;
  new endpoints: ExportToSqliteDefaultPath, ExportToSqliteProgress,
  StartExportToSqlite, InstantSearch, OptimizationStatus, ApplyOptimizations
- PostgresServiceRegistrator: registers ExportToSqliteService + InstantSearchService
- configurationPage.html: 3 tabs; Migration tab adds PG->SQLite export card;
  Maintenance tab adds Optimization card with Apply button + GIN index status list;
  fix JS: check both camelCase/PascalCase for trgmAvailable and isValid;
  poll status every 5s (12 attempts) after applying optimizations
- PluginConfiguration: MinPoolSize, MaxAutoPrepare fields
- build.yaml + csproj: version 1.0.0.1
- README.md + README.en.md: accurate docs for all new features
- IX_BaseItems_ParentId_DateCreated_Partial: covers Latest-per-library queries
- IX_BaseItems_TopParentId_DateCreated_Partial: covers home section rows
- IX_BaseItems_Type_TopParentId_DateCreated_Partial: covers type-filtered library views
- IX_UserData_UserId_LastPlayedDate_Partial: covers Resume watching (was full-scan)
- IX_UserData_UserId_IsFavorite_Partial: covers Favorites per user

All indexes are partial (WHERE IsVirtualItem=false / WHERE PlaybackPositionTicks>0)
reducing index size and keeping planner statistics clean.
Applied via ApplyNavigationIndexesAsync — idempotent, CONCURRENTLY, no lock.
Benefits all clients simultaneously (web, mobile, Roku, Xbox, Samsung, iOS).
…) rewrite

- ApplyServerMemoryTuningAsync: work_mem 16MB, effective_cache_size 1GB,
  random_page_cost 1.1 (SSD-friendly). Applied via ALTER DATABASE SET — idempotent,
  survives restarts. Falls back gracefully if superuser/owner privilege missing.

- HomeQueryCacheInterceptor: EF Core DbCommandInterceptor that caches SELECT
  results on BaseItems/UserData/ItemValues in a MemoryCache (30s TTL, 200-entry cap).
  15-query home-page fan-out drops to ~0 DB round-trips on cache hits.
  Benefits all clients simultaneously (web, mobile, Roku, Xbox, Samsung, iOS).

- ORDER BY RANDOM() rewrite: detects RANDOM() in intercepted queries,
  rewrites to TABLESAMPLE BERNOULLI(30) on the first FROM clause.
  O(1) page-level sampling vs O(n log n) per-row random sort.
  Random queries are never cached (each request gets fresh randomness).

All three are best-effort — any failure falls back to the original query path
without breaking Jellyfin.
… error logging

- TryExecuteIndexWithRetryAsync: 3 retries (30s, 60s, 120s) for CREATE INDEX
  CONCURRENTLY when deadlocked (40P01). Shared by both GIN trigram and navigation
  index creation. Solves the 9-index cascade failure on noisy startups.

- IncludeErrorDetail=true on maintenance connections: without this flag,
  PostgresException.Detail/Hint are null, making deadlock and corruption
  errors completely opaque.

- Improved error logging: all PostgresException catch blocks now include
  Detail/Hint fields for actionable diagnostics.

- Autovacuum tuning catch: now logs PG Detail instead of just the exception type.
…rovements

BREAKING CHANGES
- Plugin version bumped to 2.0.0.0
- Target: Jellyfin 10.11.10 / net9.0
- PostgresDatabaseProvider is now public (required by Jellyfin loader)

ARCHITECTURE REFACTOR
- MigrationService (44KB) split into focused helpers:
  MigrationEngine, MigrationDiscovery, MigrationSchemaPreparer,
  MigrationFkManager, MigrationTableCopier, MigrationCodeMigrations
- MaintenanceService split into MaintenanceService + MaintenanceBackupService
- ExportToSqliteService + ExportHelpers separated
- PostgresDatabaseProvider: reads connection string from database.xml at startup

EXPORT PG → SQLITE (major feature)
- Full schema creation via Jellyfin SQLite provider (Assembly.LoadFromAssemblyPath)
- Data export: 30 tables, FK-safe order, PRAGMA foreign_keys OFF during import
- Post-export cleanup: removes orphaned Devices/ApiKeys/CustomDisplayPrefs
- Post-export: pre-marks all Jellyfin code migrations in __EFMigrationsHistory
  so legacy routines (RemoveDuplicateExtras → TypedBaseItems) are skipped on restart
- Creates valid library.db stub so migration backup step succeeds
- CheckExportTarget endpoint: UI checks file existence before showing dialog
- Overwrite dialog: backup (.bkp) / overwrite / cancel options

PLUGIN LOG (PostgresLog)
- Dedicated daily log file: Postgres-yyyy-MM-dd.log in Jellyfin log directory
- Logs: provider init, export start/progress/complete, backup start/complete
- Warn-level only — no info spam

UI (configurationPage.html)
- Export progress: fixed PascalCase→camelCase normalization (poll was infinite)
- Export tab: recovers state when re-opened mid-export
- Export dialog: only shown when target file exists (CheckExportTarget API)
- Export console: table-by-table progress visible in real time

CODE QUALITY (jellyfin.ruleset fully enforced)
- SA1116/SA1117/SA1025/SA1513/SA1611/SA1615: all corrected with real code
- CA2100/CA3003/CA3006: SuppressMessage with justified reasons (no #pragma)
- CA1823: removed unused fields (SkipTables, MigrationIdFormats)
- 0 build errors, 0 warnings in Debug and Release
## Health Check (nuevo)
- HealthCheckService con 6 checks paralelos: índices inválidos, bloat, secuencias,
  StaleAnalyze, índices sin uso y SlowQueries (pg_stat_statements)
- Detección multi-schema (public + pg_toast) con mensajes diferenciados para superusuario
- Separación de _ccnew huérfanos (require DROP INDEX manual) de índices reparables
- Auto-repair: REINDEX CONCURRENTLY, VACUUM ANALYZE, setval — skip explícito de _ccnew
- Logging compacto en PostgresLog: resumen + sólo Warn/Error listados individualmente
- Auto-ejecución al arrancar (via PostgresDatabaseProvider.Initialise, flag una sola vez)
- Endpoints: GET /HealthCheck, POST /HealthCheck/Repair
- Tab 'Health' dedicado en la UI con semáforo 🟢🟡🔴 y botón Auto-repair contextual

## Dialog overlay (fix)
- Reemplazado el dialog inline roto por <dialog> HTML5 nativo con showModal()
- Backdrop y estilos completamente inline — funciona aunque Jellyfin descarte el <head>
- Paleta semántica de botones: cancel/confirm/safe/warning/danger
- Icono + título por acción (Activar=🚀verde, TRUNCATE=🗑️rojo, Restore=⚠️naranja, etc.)
- Cierre con clic en backdrop y Escape nativo del browser

## Performance
- Quitar Pooling=false en InstantSearchService (causaba ~4-5s por apertura TCP/TLS)
- 3 índices parciales nuevos: OfficialRating, SeriesId/SeasonId/IndexNumber, UserData PlaybackPosition
- pg_stat_statements activado automáticamente en ApplyOptimizations
- MaxAutoPrepare=0 en InstantSearch para no contaminar el pool compartido

## Correcciones de producción (esta sesión)
- Revertir DISCARD ALL en FindInvalidIndexesAsync (causaba error 26000 en producción)
- DefaultSchema constante en PostgresController (eliminó 4 literales 'public' dispersos)
- Null-forgiving operators eliminados del controller
- MapRowAsync extraído de ExecuteSearchAsync (reduce complejidad cognitiva)
- pgEx pasado a LogWarning en catch de deadlock (CA2100)
- SA1402/SA1649: HealthSeverity, HealthFinding y HealthCheckResult en archivos separados
Npgsql 9.x rejects DateTime values with Kind=Unspecified when writing to
'timestamp with time zone' columns. Jellyfin's external metadata providers
(TVDB, TMDB, etc.) generate air dates and premiere dates without explicitly
setting DateTimeKind.Utc, causing DbUpdateException during library scans.

Enable the Npgsql legacy timestamp behavior switch so Kind=Unspecified is
treated as UTC, matching the behavior of Npgsql <=6.x and preventing
errors from TvdbMissingEpisodeProvider.AddVirtualEpisode and similar paths.

Fixes: Cannot write DateTime with Kind=Unspecified to PostgreSQL type
       'timestamp with time zone' (DbUpdateException during library scan)
Add UpsertConflictInterceptor — an EF Core DbCommandInterceptor that
rewrites plain INSERT statements on junction tables into
INSERT ... ON CONFLICT DO NOTHING before they reach PostgreSQL.

Root cause: Jellyfin's BaseItemRepository.UpdateOrInsertItems generates
INSERT without checking for existing rows first. SQLite silently accepted
this (implicit REPLACE semantics), but PostgreSQL raises error 23505
(unique constraint violation) which propagated as an unhandled exception
killing the server when triggered by the TMDbBoxSets plugin timer.

Tables covered: BaseItemProviders, BaseItemImageInfos, AncestorIds,
ItemValuesMap, PeopleBaseItemMap, BaseItemTrailerTypes, BaseItemMetadataFields.

Also downgrade CA3001 to Info in jellyfin.ruleset — the interceptor
rewrites EF Core-generated SQL only, never user-supplied HTTP input.
v1.1.0.0 was never published — removing all traces from manifest.json.
v1.0.0.0 remains the latest stable release.
- README.md (ES) and README.en.md (EN): full rewrite following JellyTrend
  style (centered header only, badges, emoji sections, details/summary FAQ)
- Document all 4 plugin tabs with screenshots: Configuration, Health Check,
  Migration, Maintenance
- Cover v2.0 features: health check, DateTimeKindNormalizingInterceptor,
  JellyTrend integration, bidirectional migration, InstantSearch, pool tuning
- Rename Screenshots/Tab-Mantenance.png -> Tab-Maintenance.png (typo fix)
- Remove duplicated plugin tab section in README.md (was rendered twice)
- Rename 'busqueda instantanea/InstantSearch' to 'busqueda casi instantanea/
  near-instant search' with clarifying note (depends on hardware and client)
- Update related FAQ entries in both ES and EN
…ow, docs

Core changes:
- DateTimeKindNormalizingInterceptor: replace Npgsql legacy timestamp switch
  with targeted interceptor that normalizes DateTime.Kind=Unspecified on
  write parameters only, preserving server-side timezone on reads
- PostgresRecommendationQueryProvider: IRecommendationQueryProvider impl
  using Npgsql direct SQL with array && operator + GIN indexes (4-10x faster
  recommendations when JellyTrend is also installed)
- JellyTrendContract/IRecommendationQueryProvider.cs: compile-time mirror of
  JellyTrend public contract (no DLL reference needed at build time)
- PostgresServiceRegistrator: conditional DI registration of recommendation
  provider (silent no-op when JellyTrend is not installed)
- DbErrorLoggingInterceptor: new EF Core interceptor logging all DB errors

Project:
- .csproj: remove HintPath reference to JellyTrend DLL
- Remove LegacyDB/ folder (accidental backup files, never needed)

Release workflow:
- .github/workflows/release.yml: new workflow_dispatch pipeline based on
  JellyTrend pattern; ZIP includes only the 3 runtime DLLs Jellyfin does not
  provide (plugin + Npgsql + Npgsql.EFCore.PostgreSQL)

Docs:
- README.md (ES) and README.en.md (EN): full rewrite for v2.0
- Screenshots/: add all 4 plugin tab screenshots
- manifest.json: fix imageUrl (v1.1.0.0 tag → main branch), rewrite
  description and overview for v2.0 feature set
- build.yaml: update to JellyTrend style (restore/build/publish/artifact),
  add release/* branches trigger, remove legacy Version* branch trigger
- scan-codeql.yaml: new CodeQL analysis workflow (csharp, monthly schedule)
- renovate.json: move from workflows/ to .github/ (correct location)
- test.yaml: remove — not part of JellyTrend workflow set
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@BORNIOS
BORNIOS merged commit b860c20 into main Aug 29, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants