Add PostgresSubprocessException and refactor database initialization logic
- Introduced PostgresSubprocessException to handle errors during PostgreSQL subprocess execution. - Refactored PostgresDatabaseProvider to improve schema initialization script discovery. - Enhanced error handling for psql command execution, including logging of output and errors. - Implemented methods to find the schema initialization script and the psql executable path.
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
-- 10_create_supplementary_indexes.sql
|
||||
-- Creates additional performance indexes not included in the base schema dump
|
||||
-- These are safe to run multiple times (uses IF NOT EXISTS checks)
|
||||
|
||||
\echo 'Creating supplementary performance indexes...';
|
||||
|
||||
-- ============================================================================
|
||||
-- BaseItems Supplementary Indexes
|
||||
-- (Schema dump has 19 indexes, adding 3 more for specific query patterns)
|
||||
-- ============================================================================
|
||||
|
||||
-- Composite index for filtered library browsing (type + virtual + parent)
|
||||
-- Use case: Browse items by type in a specific library, excluding virtual items
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_indexes
|
||||
WHERE schemaname = 'library'
|
||||
AND tablename = 'BaseItems'
|
||||
AND indexname = 'idx_baseitems_type_isvirtualitem_topparentid'
|
||||
) THEN
|
||||
RAISE NOTICE 'Creating idx_baseitems_type_isvirtualitem_topparentid...';
|
||||
CREATE INDEX CONCURRENTLY idx_baseitems_type_isvirtualitem_topparentid
|
||||
ON library."BaseItems" ("Type", "IsVirtualItem", "TopParentId")
|
||||
WHERE "IsVirtualItem" = false;
|
||||
RAISE NOTICE '✓ Created idx_baseitems_type_isvirtualitem_topparentid';
|
||||
ELSE
|
||||
RAISE NOTICE '✓ Index idx_baseitems_type_isvirtualitem_topparentid already exists';
|
||||
END IF;
|
||||
EXCEPTION
|
||||
WHEN duplicate_table THEN
|
||||
RAISE NOTICE '✓ Index idx_baseitems_type_isvirtualitem_topparentid already exists (caught duplicate)';
|
||||
END $$;
|
||||
|
||||
-- Index for folder hierarchy queries
|
||||
-- Use case: Navigate folder structures efficiently
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_indexes
|
||||
WHERE schemaname = 'library'
|
||||
AND tablename = 'BaseItems'
|
||||
AND indexname = 'idx_baseitems_topparentid_isfolder'
|
||||
) THEN
|
||||
RAISE NOTICE 'Creating idx_baseitems_topparentid_isfolder...';
|
||||
CREATE INDEX CONCURRENTLY idx_baseitems_topparentid_isfolder
|
||||
ON library."BaseItems" ("TopParentId", "IsFolder", "IsVirtualItem")
|
||||
WHERE "TopParentId" IS NOT NULL;
|
||||
RAISE NOTICE '✓ Created idx_baseitems_topparentid_isfolder';
|
||||
ELSE
|
||||
RAISE NOTICE '✓ Index idx_baseitems_topparentid_isfolder already exists';
|
||||
END IF;
|
||||
EXCEPTION
|
||||
WHEN duplicate_table THEN
|
||||
RAISE NOTICE '✓ Index idx_baseitems_topparentid_isfolder already exists (caught duplicate)';
|
||||
END $$;
|
||||
|
||||
-- Index for recently added content with filters
|
||||
-- Use case: "Recently Added" view across all libraries
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_indexes
|
||||
WHERE schemaname = 'library'
|
||||
AND tablename = 'BaseItems'
|
||||
AND indexname = 'idx_baseitems_datecreated_filtered'
|
||||
) THEN
|
||||
RAISE NOTICE 'Creating idx_baseitems_datecreated_filtered...';
|
||||
CREATE INDEX CONCURRENTLY idx_baseitems_datecreated_filtered
|
||||
ON library."BaseItems" ("DateCreated" DESC, "Type", "IsVirtualItem")
|
||||
WHERE "DateCreated" IS NOT NULL AND "IsVirtualItem" = false;
|
||||
RAISE NOTICE '✓ Created idx_baseitems_datecreated_filtered';
|
||||
ELSE
|
||||
RAISE NOTICE '✓ Index idx_baseitems_datecreated_filtered already exists';
|
||||
END IF;
|
||||
EXCEPTION
|
||||
WHEN duplicate_table THEN
|
||||
RAISE NOTICE '✓ Index idx_baseitems_datecreated_filtered already exists (caught duplicate)';
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- ItemValuesMap Supplementary Index
|
||||
-- (Schema dump has IX_ItemValuesMap_ItemId, adding reverse lookup)
|
||||
-- ============================================================================
|
||||
|
||||
-- Index for reverse lookup (find items by genre/tag)
|
||||
-- Use case: "Show all items with genre 'Action'"
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_indexes
|
||||
WHERE schemaname = 'library'
|
||||
AND tablename = 'ItemValuesMap'
|
||||
AND indexname = 'idx_itemvaluesmap_itemvalueid_itemid'
|
||||
) THEN
|
||||
RAISE NOTICE 'Creating idx_itemvaluesmap_itemvalueid_itemid...';
|
||||
CREATE INDEX CONCURRENTLY idx_itemvaluesmap_itemvalueid_itemid
|
||||
ON library."ItemValuesMap" ("ItemValueId", "ItemId");
|
||||
RAISE NOTICE '✓ Created idx_itemvaluesmap_itemvalueid_itemid';
|
||||
ELSE
|
||||
RAISE NOTICE '✓ Index idx_itemvaluesmap_itemvalueid_itemid already exists';
|
||||
END IF;
|
||||
EXCEPTION
|
||||
WHEN duplicate_table THEN
|
||||
RAISE NOTICE '✓ Index idx_itemvaluesmap_itemvalueid_itemid already exists (caught duplicate)';
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- ActivityLogs Supplementary Index
|
||||
-- (Schema dump has IX_ActivityLogs_DateCreated, adding user-specific index)
|
||||
-- ============================================================================
|
||||
|
||||
-- Index for user-specific activity queries
|
||||
-- Use case: "Show activity for user X"
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Check if ActivityLogs table exists
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'activitylog'
|
||||
AND table_name = 'ActivityLogs'
|
||||
) THEN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_indexes
|
||||
WHERE schemaname = 'activitylog'
|
||||
AND tablename = 'ActivityLogs'
|
||||
AND indexname = 'idx_activitylogs_userid_datecreated'
|
||||
) THEN
|
||||
RAISE NOTICE 'Creating idx_activitylogs_userid_datecreated...';
|
||||
CREATE INDEX CONCURRENTLY idx_activitylogs_userid_datecreated
|
||||
ON activitylog."ActivityLogs" ("UserId", "DateCreated" DESC)
|
||||
WHERE "UserId" IS NOT NULL;
|
||||
RAISE NOTICE '✓ Created idx_activitylogs_userid_datecreated';
|
||||
ELSE
|
||||
RAISE NOTICE '✓ Index idx_activitylogs_userid_datecreated already exists';
|
||||
END IF;
|
||||
ELSE
|
||||
RAISE NOTICE '⚠ ActivityLogs table not found, skipping user-specific index';
|
||||
END IF;
|
||||
EXCEPTION
|
||||
WHEN duplicate_table THEN
|
||||
RAISE NOTICE '✓ Index idx_activitylogs_userid_datecreated already exists (caught duplicate)';
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- Analyze Tables to Update Statistics
|
||||
-- ============================================================================
|
||||
|
||||
\echo 'Updating table statistics...';
|
||||
|
||||
ANALYZE library."BaseItems";
|
||||
ANALYZE library."ItemValuesMap";
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'activitylog'
|
||||
AND table_name = 'ActivityLogs'
|
||||
) THEN
|
||||
ANALYZE activitylog."ActivityLogs";
|
||||
RAISE NOTICE '✓ ActivityLogs statistics updated';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
\echo '✓ Supplementary indexes created successfully!';
|
||||
\echo '';
|
||||
\echo 'Summary:';
|
||||
\echo ' - Added 3 composite indexes on BaseItems';
|
||||
\echo ' - Added 1 reverse lookup index on ItemValuesMap';
|
||||
\echo ' - Added 1 user-specific index on ActivityLogs';
|
||||
\echo '';
|
||||
\echo 'These indexes complement the 50+ existing indexes from your schema dump.';
|
||||
@@ -0,0 +1,227 @@
|
||||
# Jellyfin PostgreSQL Database Setup (.NET 11 Preview)
|
||||
|
||||
## Overview
|
||||
|
||||
Due to .NET 11 being a preview release, EF Core migrations cannot run automatically at startup. Database schema is created using SQL scripts.
|
||||
|
||||
**✨ NEW: Automatic Schema Initialization**
|
||||
If the database is **empty** (no `library` schema found), Jellyfin will **automatically execute** `sql/schema_init/create_database_schema.sql` at startup!
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Automatic Setup (Recommended)
|
||||
|
||||
### For Fresh Database - Zero Configuration!
|
||||
|
||||
1. **Create an empty PostgreSQL database**:
|
||||
```sh
|
||||
psql -U postgres -c "CREATE DATABASE jellyfin OWNER jellyfin;"
|
||||
```
|
||||
|
||||
2. **Just start Jellyfin**:
|
||||
```sh
|
||||
dotnet run --project Jellyfin.Server
|
||||
```
|
||||
|
||||
**That's it!** Jellyfin will:
|
||||
- ✅ Detect the database is empty (no `library` schema)
|
||||
- ✅ Automatically load `sql/schema_init/create_database_schema.sql`
|
||||
- ✅ Execute the full schema creation script
|
||||
- ✅ Initialize all tables, schemas, and base indexes
|
||||
- ✅ Continue with normal startup
|
||||
|
||||
**What you'll see in logs**:
|
||||
```
|
||||
[INF] Database is empty (library schema not found). Attempting to initialize from SQL script...
|
||||
[INF] Found schema script at: D:\Jellyfin\sql\schema_init\create_database_schema.sql
|
||||
[INF] Executing create_database_schema.sql to initialize database...
|
||||
[INF] Successfully initialized database from SQL script
|
||||
[INF] Database schema verification complete.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Manual Setup (Alternative)
|
||||
|
||||
If you prefer manual control or the automatic setup fails:
|
||||
|
||||
## 🔧 Manual Setup (Alternative)
|
||||
|
||||
If you prefer manual control or the automatic setup fails:
|
||||
|
||||
### Step 1: Create Database and Schema
|
||||
|
||||
Run the main schema creation script:
|
||||
|
||||
```sh
|
||||
psql -U jellyfin -d postgres -f sql/schema_init/create_database_schema.sql
|
||||
```
|
||||
|
||||
This creates:
|
||||
- All database schemas (library, activitylog, authentication, etc.)
|
||||
- All tables with proper columns and data types
|
||||
- Primary keys and foreign keys
|
||||
- Base indexes
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Apply Supplementary Performance Indexes
|
||||
|
||||
After the main schema is created, apply the performance indexes:
|
||||
|
||||
```sh
|
||||
psql -U jellyfin -d jellyfin -f sql/indexes/apply-supplementary-indexes-migration.sql
|
||||
```
|
||||
|
||||
This creates 5 additional performance indexes for:
|
||||
- Library browsing
|
||||
- Folder hierarchy
|
||||
- Recently added content
|
||||
- Genre/tag filtering
|
||||
- Episode deduplication
|
||||
|
||||
---
|
||||
|
||||
### Step 3: Apply ActivityLog Index Fix
|
||||
|
||||
If the ActivityLogs table exists, apply the final index:
|
||||
|
||||
```sh
|
||||
psql -U jellyfin -d jellyfin -f sql/fix-activitylog-index.sql
|
||||
```
|
||||
|
||||
This creates the 6th performance index for user activity queries.
|
||||
|
||||
---
|
||||
|
||||
## 📋 What Changed for .NET 11 Preview
|
||||
|
||||
### Automatic SQL Script Execution ✨
|
||||
|
||||
**New Feature**: Empty database auto-initialization!
|
||||
|
||||
When Jellyfin starts:
|
||||
1. ✅ Connects to PostgreSQL database
|
||||
2. ✅ Checks if `library` schema exists
|
||||
3. ✅ **If empty**: Automatically runs `sql/schema_init/create_database_schema.sql`
|
||||
4. ✅ **If not empty**: Skips initialization and proceeds normally
|
||||
5. ⚠️ Logs warning about any pending migrations (doesn't apply them)
|
||||
6. ✅ Continues with normal startup
|
||||
|
||||
### In Code (`PostgresDatabaseProvider.cs`)
|
||||
|
||||
**Disabled**:
|
||||
- ❌ `context.Database.EnsureCreatedAsync()` - Bypasses migration tracking
|
||||
- ❌ `context.Database.MigrateAsync()` - Not compatible with .NET 11 preview
|
||||
|
||||
**Added**:
|
||||
- ✅ Empty database detection (checks for `library` schema)
|
||||
- ✅ Automatic SQL script execution from `sql/schema_init/create_database_schema.sql`
|
||||
- ✅ Proper error handling and logging
|
||||
- ✅ 10-minute timeout for large schema creation
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Upgrading Existing Database
|
||||
|
||||
If you already have a Jellyfin database and are upgrading:
|
||||
|
||||
### Option 1: Apply Only Missing Indexes (Recommended)
|
||||
|
||||
If your database structure is already correct, just add the performance indexes:
|
||||
|
||||
```sh
|
||||
# Apply supplementary indexes
|
||||
psql -U jellyfin -d jellyfin -f sql/indexes/apply-supplementary-indexes-migration.sql
|
||||
|
||||
# Apply ActivityLog index fix
|
||||
psql -U jellyfin -d jellyfin -f sql/fix-activitylog-index.sql
|
||||
```
|
||||
|
||||
### Option 2: Full Schema Recreation (Data Loss!)
|
||||
|
||||
**⚠️ WARNING: This will delete all data!**
|
||||
|
||||
Only do this for a fresh start:
|
||||
|
||||
```sh
|
||||
# Drop existing database
|
||||
psql -U jellyfin -d postgres -c "DROP DATABASE IF EXISTS jellyfin;"
|
||||
|
||||
# Recreate from schema
|
||||
psql -U jellyfin -d postgres -f sql/schema_init/create_database_schema.sql
|
||||
psql -U jellyfin -d jellyfin -f sql/indexes/apply-supplementary-indexes-migration.sql
|
||||
psql -U jellyfin -d jellyfin -f sql/fix-activitylog-index.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 SQL Script Locations
|
||||
|
||||
```
|
||||
D:\Projects\pgsql-jellyfin\
|
||||
├── sql/
|
||||
│ ├── schema_init/
|
||||
│ │ ├── create_database_schema.sql ← Main database schema
|
||||
│ │ └── 10_create_supplementary_indexes.sql ← (EF migration file)
|
||||
│ ├── indexes/
|
||||
│ │ └── apply-supplementary-indexes-migration.sql ← Performance indexes
|
||||
│ ├── diagnostics/
|
||||
│ │ ├── diagnostics.sql
|
||||
│ │ ├── monitor-query-performance.sql
|
||||
│ │ └── query-analysis.sql
|
||||
│ ├── fix-activitylog-index.sql ← ActivityLog index fix
|
||||
│ └── README-DATABASE-SETUP.md ← This file
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Verification
|
||||
|
||||
After running the scripts, verify everything is set up correctly:
|
||||
|
||||
```sql
|
||||
-- Check schemas exist
|
||||
SELECT schema_name FROM information_schema.schemata
|
||||
WHERE schema_name IN ('library', 'activitylog', 'authentication', 'displaypreferences');
|
||||
|
||||
-- Check migration history
|
||||
SELECT * FROM library."__EFMigrationsHistory" ORDER BY "MigrationId";
|
||||
|
||||
-- Check indexes
|
||||
SELECT schemaname, tablename, indexname
|
||||
FROM pg_indexes
|
||||
WHERE indexname LIKE 'idx_%'
|
||||
ORDER BY tablename, indexname;
|
||||
```
|
||||
|
||||
Expected:
|
||||
- ✅ 4+ schemas
|
||||
- ✅ 3+ migrations in history
|
||||
- ✅ 6+ custom performance indexes
|
||||
|
||||
---
|
||||
|
||||
## 🎯 When .NET 11 Goes GA
|
||||
|
||||
When .NET 11 is officially released (no longer preview):
|
||||
|
||||
1. Uncomment the migration code in `PostgresDatabaseProvider.cs`
|
||||
2. Remove the warning log statements
|
||||
3. EF Core migrations will work automatically again
|
||||
|
||||
---
|
||||
|
||||
## 📞 Need Help?
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
1. **Check logs**: Look for database connection errors
|
||||
2. **Verify credentials**: Ensure PostgreSQL user has proper permissions
|
||||
3. **Check schema**: Run verification queries above
|
||||
4. **Manual fixes**: All scripts use `IF NOT EXISTS` - safe to re-run
|
||||
|
||||
---
|
||||
|
||||
**Created**: 2026-03-07
|
||||
**Commit**: `0911146` - EF migrations disabled for .NET 11 preview
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user