feat: Add FTS4 index integrity check and rebuild functionality

Add detection and automatic repair for FTS4 (Full-Text Search) index
corruption, which can occur even when PRAGMA integrity_check passes.

New functions:
- CheckFTS(): Validates FTS4 index integrity using SQLite's integrity-check command
- DoFTSRebuild(): Rebuilds corrupted FTS4 indexes using SQLite's rebuild command

Enhanced existing commands:
- 'check' (option 3): Now includes FTS integrity checking
- 'reindex' (option 6): Now checks FTS indexes and rebuilds if corruption detected
- 'automatic' (option 2): Checks FTS after repair/reindex and automatically rebuilds if damaged

Refactored FTS table query into reusable FTS_TABLE_QUERY constant for maintainability.

Fixes: https://github.com/ChuckPa/DBRepair/issues/269
This commit is contained in:
Max Allison
2026-01-22 07:43:52 -06:00
parent b62b606dc7
commit 8e998598fd
2 changed files with 318 additions and 13 deletions

View File

@@ -62,6 +62,17 @@ STATPERMS="%a"
# On all hosts except QNAP
DFFLAGS="-m"
# FTS4 virtual table query - excludes shadow tables (_content, _segments, etc.)
FTS_TABLE_QUERY="SELECT name FROM sqlite_master"
FTS_TABLE_QUERY="$FTS_TABLE_QUERY WHERE type='table'"
FTS_TABLE_QUERY="$FTS_TABLE_QUERY AND sql LIKE '%fts4%'"
FTS_TABLE_QUERY="$FTS_TABLE_QUERY AND name NOT LIKE '%_content'"
FTS_TABLE_QUERY="$FTS_TABLE_QUERY AND name NOT LIKE '%_segments'"
FTS_TABLE_QUERY="$FTS_TABLE_QUERY AND name NOT LIKE '%_segdir'"
FTS_TABLE_QUERY="$FTS_TABLE_QUERY AND name NOT LIKE '%_stat'"
FTS_TABLE_QUERY="$FTS_TABLE_QUERY AND name NOT LIKE '%_docsize'"
FTS_TABLE_QUERY="$FTS_TABLE_QUERY ORDER BY name;"
# If LC_ALL is null, default to C
[ "$LC_ALL" = "" ] && export LC_ALL=C
@@ -158,6 +169,83 @@ CheckDatabases() {
return $Damaged
}
# Global flags for FTS status (separate from main DB)
FTSDamaged=0
CheckedFTS=0
# Check FTS (Full-Text Search) index integrity
CheckFTS() {
# Arg1 = calling function name for logging
FTSDamaged=0
local Caller="${1:-Check}"
local FTSFail=0
Output "Checking FTS (Full-Text Search) indexes"
# Get list of FTS4 virtual tables (exclude shadow tables)
FTSTables="$("$PLEX_SQLITE" $CPPL.db "$FTS_TABLE_QUERY" 2>&1)"
if [ -z "$FTSTables" ]; then
Output "No FTS4 tables found in main database."
WriteLog "$Caller - FTS Check - No FTS4 tables"
return 0
fi
# Check each FTS table
for Table in $FTSTables
do
Result="$("$PLEX_SQLITE" $CPPL.db "INSERT INTO $Table($Table) VALUES('integrity-check');" 2>&1)"
ExitCode=$?
if [ $ExitCode -eq 0 ] && [ -z "$Result" ]; then
Output " FTS index '$Table' - OK"
WriteLog "$Caller - FTS Check: $Table - PASS"
else
Output " FTS index '$Table' - DAMAGED"
Output " Error: $Result"
WriteLog "$Caller - FTS Check: $Table - FAIL ($Result)"
FTSFail=1
fi
done
# Check blobs database FTS tables
FTSTablesBlobs="$("$PLEX_SQLITE" $CPPL.blobs.db "$FTS_TABLE_QUERY" 2>&1)"
if [ -n "$FTSTablesBlobs" ]; then
for Table in $FTSTablesBlobs
do
Result="$("$PLEX_SQLITE" $CPPL.blobs.db "INSERT INTO $Table($Table) VALUES('integrity-check');" 2>&1)"
ExitCode=$?
if [ $ExitCode -eq 0 ] && [ -z "$Result" ]; then
Output " FTS index '$Table' (blobs) - OK"
WriteLog "$Caller - FTS Check (blobs): $Table - PASS"
else
Output " FTS index '$Table' (blobs) - DAMAGED"
Output " Error: $Result"
WriteLog "$Caller - FTS Check (blobs): $Table - FAIL ($Result)"
FTSFail=1
fi
done
fi
CheckedFTS=1
if [ $FTSFail -eq 0 ]; then
Output "FTS integrity check complete. All FTS indexes OK."
WriteLog "$Caller - FTS Check - PASS"
FTSDamaged=0
else
Output "FTS integrity check complete. One or more FTS indexes are DAMAGED."
Output "Use 'reindex' command (option 6) or 'automatic' (option 2) to rebuild."
WriteLog "$Caller - FTS Check - FAIL"
FTSDamaged=1
fi
return $FTSFail
}
# Return list of database backup dates for consideration in replace action
GetDates(){
@@ -932,6 +1020,151 @@ DoIndex() {
}
##### FTS REBUILD
DoFTSRebuild() {
# Clear flags
Damaged=0
Fail=0
# Check databases before rebuilding FTS if not previously checked
if ! CheckDatabases "FTSRbld" ; then
Damaged=1
CheckedDB=1
Fail=1
[ $IgnoreErrors -eq 1 ] && Fail=0
fi
# If damaged, warn but allow continue (FTS corruption often passes integrity_check)
if [ $Damaged -eq 1 ] && [ $IgnoreErrors -eq 0 ]; then
Output "WARNING: Database integrity check failed."
Output "FTS rebuild may still help if the corruption is isolated to FTS indexes."
if ! ConfirmYesNo "Continue with FTS rebuild anyway? "; then
Output "FTS rebuild cancelled."
return 1
fi
fi
# Make backup
Output "Backing up databases"
MakeBackups "FTSRbld"
Result=$?
[ $IgnoreErrors -eq 1 ] && Result=0
if [ $Result -eq 0 ]; then
WriteLog "FTSRbld - MakeBackup - PASS"
else
Output "Error making backups. Cannot continue."
WriteLog "FTSRbld - MakeBackup - FAIL ($Result)"
Fail=1
return 1
fi
# Get list of FTS4 tables (exclude shadow tables)
Output "Scanning for FTS4 tables in main database..."
FTSTables="$("$PLEX_SQLITE" $CPPL.db "$FTS_TABLE_QUERY" 2>&1)"
Result=$?
if ! SQLiteOK $Result; then
Output "Error scanning for FTS tables. Error code $Result"
WriteLog "FTSRbld - Scan FTS tables - FAIL ($Result)"
Fail=1
RestoreSaved "$TimeStamp"
return 1
fi
# If no FTS tables found, nothing to do
if [ -z "$FTSTables" ]; then
Output "No FTS4 tables found in database."
WriteLog "FTSRbld - No FTS4 tables found"
return 0
fi
Output "Found FTS4 tables:"
for Table in $FTSTables
do
Output " - $Table"
done
Output ""
# Rebuild each FTS table
Output "Rebuilding FTS4 indexes in main database..."
for Table in $FTSTables
do
Output " Rebuilding $Table..."
"$PLEX_SQLITE" $CPPL.db "INSERT INTO $Table($Table) VALUES('rebuild');" 2>&1
Result=$?
[ $IgnoreErrors -eq 1 ] && Result=0
if SQLiteOK $Result; then
Output " $Table rebuilt successfully."
WriteLog "FTSRbld - Rebuild: $Table - PASS"
else
Output " $Table rebuild failed. Error code $Result"
WriteLog "FTSRbld - Rebuild: $Table - FAIL ($Result)"
Fail=1
fi
done
# Check blobs database for FTS tables
Output ""
Output "Scanning for FTS4 tables in blobs database..."
FTSTablesBlobs="$("$PLEX_SQLITE" $CPPL.blobs.db "$FTS_TABLE_QUERY" 2>&1)"
Result=$?
if ! SQLiteOK $Result; then
Output "Error scanning blobs database for FTS tables. Error code $Result"
WriteLog "FTSRbld - Scan FTS tables (blobs) - FAIL ($Result)"
# Don't fail entirely if blobs scan fails - main DB may be OK
elif [ -z "$FTSTablesBlobs" ]; then
Output "No FTS4 tables found in blobs database."
WriteLog "FTSRbld - No FTS4 tables in blobs database"
else
Output "Found FTS4 tables in blobs database:"
for Table in $FTSTablesBlobs
do
Output " - $Table"
done
Output ""
Output "Rebuilding FTS4 indexes in blobs database..."
for Table in $FTSTablesBlobs
do
Output " Rebuilding $Table..."
"$PLEX_SQLITE" $CPPL.blobs.db "INSERT INTO $Table($Table) VALUES('rebuild');" 2>&1
Result=$?
[ $IgnoreErrors -eq 1 ] && Result=0
if SQLiteOK $Result; then
Output " $Table rebuilt successfully."
WriteLog "FTSRbld - Rebuild (blobs): $Table - PASS"
else
Output " $Table rebuild failed. Error code $Result"
WriteLog "FTSRbld - Rebuild (blobs): $Table - FAIL ($Result)"
Fail=1
fi
done
fi
Output ""
Output "FTS rebuild complete."
if [ $Fail -eq 0 ]; then
SetLast "FTSRbld" "$TimeStamp"
WriteLog "FTSRbld - PASS"
else
Output "Some FTS tables failed to rebuild. Restoring backup."
RestoreSaved "$TimeStamp"
WriteLog "FTSRbld - FAIL"
fi
return $Fail
}
##### UNDO
DoUndo(){
# Confirm there is something to undo
@@ -2188,8 +2421,8 @@ do
echo ""
[ $HaveStartStop -gt 0 ] && echo " 1 - 'stop' - Stop PMS."
[ $HaveStartStop -eq 0 ] && echo " 1 - 'stop' - (Not available. Stop manually.)"
echo " 2 - 'automatic' - Check, Repair/Optimize, and Reindex Database in one step."
echo " 3 - 'check' - Perform integrity check of database."
echo " 2 - 'automatic' - Check, Repair/Optimize, Reindex, and FTS rebuild in one step."
echo " 3 - 'check' - Perform integrity check of database and FTS indexes."
echo " 4 - 'vacuum' - Remove empty space from database without optimizing."
echo " 5 - 'repair' - Repair/Optimize databases."
echo " 6 - 'reindex' - Rebuild database indexes."
@@ -2326,9 +2559,29 @@ do
WriteLog "Reindex - PASS"
fi
# Now check FTS indexes and repair if damaged
DoUpdateTimestamp
Output ""
if ! CheckFTS "Auto "; then
Output ""
Output "FTS indexes are damaged. Attempting automatic FTS rebuild..."
WriteLog "Auto - FTS damaged, attempting rebuild"
if DoFTSRebuild; then
WriteLog "Auto - FTS Rebuild - PASS"
Output "FTS rebuild successful."
else
WriteLog "Auto - FTS Rebuild - FAIL"
Output "FTS rebuild failed. You may need to run 'reindex' command manually."
# Don't fail auto entirely - main DB repair succeeded
fi
else
WriteLog "Auto - FTS Check - PASS"
fi
# All good to here
WriteLog "Auto - COMPLETED"
Output "Automatic Check, Repair/optimize, & Index successful."
Output "Automatic Check, Repair/optimize, Index, & FTS check successful."
;;
@@ -2350,6 +2603,14 @@ do
WriteLog "Check - FAIL"
CheckedDB=0
fi
# Also check FTS indexes (these can be damaged even when integrity_check passes)
Output ""
if ! CheckFTS "Check "; then
Output ""
Output "NOTE: FTS indexes are damaged but main database structure is OK."
Output " Use 'reindex' (option 6) or 'automatic' (option 2) to rebuild."
fi
;;
# Vacuum
@@ -2413,6 +2674,24 @@ do
# Now index
if DoIndex ; then
WriteLog "Reindex - PASS"
# Check FTS indexes and repair if damaged
Output ""
if ! CheckFTS "Reindex"; then
Output ""
Output "FTS indexes are damaged. Rebuilding..."
WriteLog "Reindex - FTS damaged, attempting rebuild"
if DoFTSRebuild; then
WriteLog "Reindex - FTS Rebuild - PASS"
Output "FTS rebuild successful."
else
WriteLog "Reindex - FTS Rebuild - FAIL"
Output "FTS rebuild failed."
fi
else
WriteLog "Reindex - FTS Check - PASS"
fi
else
WriteLog "Reindex - FAIL"
fi
@@ -2494,6 +2773,9 @@ do
[ $CheckedDB -eq 0 ] && Output " Databases are not checked, Status unknown."
[ $CheckedDB -eq 1 ] && [ $Damaged -eq 0 ] && Output " Databases are OK."
[ $CheckedDB -eq 1 ] && [ $Damaged -eq 1 ] && Output " Databases were checked and are damaged."
[ $CheckedFTS -eq 0 ] && Output " FTS indexes are not checked, Status unknown."
[ $CheckedFTS -eq 1 ] && [ $FTSDamaged -eq 0 ] && Output " FTS indexes are OK."
[ $CheckedFTS -eq 1 ] && [ $FTSDamaged -eq 1 ] && Output " FTS indexes are damaged."
Output ""
;;
@@ -2713,4 +2995,4 @@ do
esac
done
done
exit 0
exit 0