DBRepair development

HOW THE HELL do you kill it? It won’t stop

When it doubt .. uninstall and reboot

One other quick thing regarding Plex Media Server data: On macOS, it’s spread across a few directories:
~/Library/Application Support/Plex Media Server;
~/Library/Caches/PlexMediaServer; and,
~/Library/Logs/Plex Media Server

So, if you want to fully move your PMS data, you’ll need to account for these as well.

Thanks. I found a way to kill it (uninstall + reboot)
I will make a bunch of symlinks (UGLY HACK)

I never knew Mac was this ugly an implementation.

For what it’s worth, DBRepair.sh uses AppleScript to start and stop the server, run via the osascript utility. For example:
osascript -e 'tell app "Plex Media Server" to quit

Here are the helper functions from the script:

# Mac Helper Functions
startMacPMS() { TMPDIR=/tmp osascript -e 'tell app "Plex Media Server" to launch'; sleep 2; IsRunning && sayStarted; }
stopMacPMS() { osascript -e 'tell app "Plex Media Server" to quit'; sleep 2; IsRunning || sayStopped; }
sayStarted() { [ $HapticOkay -eq 1 ] && osascript -e 'say "started"'; }
sayStopped() { [ $HapticOkay -eq 1 ] && osascript -e 'say "Its stopped"'; }

Yes, Thanks I have all that info. They’re already integrated into the new C code.

Launching out to a shell command is the one bit of shell needed for all the hosts.

If you want to see it. (it looks a lot like the existing)

  // --- Apple Mac ---
  HaveMacPMS = false;
  setString(HomePath, "%s", getenv("HOME"));

  // Is PMS installed (two possibilities)
  if (isDir("/Applications/Plex Media Server.app") ){
    HaveMacPMS = true;
    setString(H.HostType, "Mac");
    H.PkgDir = "/Applications/Plex Media Server.app";
    H.PlexSQLite = "%s/Contents/MacOS/Plex SQLite", H.PkgDir;

  } else if (isDir("~/Applications/Plex Media Server.app")) {
    HaveMacPMS = true;
    setString(H.HostType, "Mac");
    H.PkgDir = "~/Applications/Plex Media Server.app";
    H.PlexSQLite = "%s/Contents/MacOS/Plex SQLite", H.PkgDir;
  }

  if (HaveMacPMS) {
    setString(MacDataPath, "%s/Library/Application Support/Plex Media Server", HomeDir);

    // Where the data lives
    if (HomeDir) {
      setString(H.AppSuppDir, "%s/Library/Application Support", HomeDir);
      // macOS specific: Cache is in ~/Library/Caches, not App Support
      setString(H.CacheDir, "%s/Library/Caches/PlexMediaServer/PhotoTranscoder", HomeDir);
    }

    // Build sub-paths
    setString(H.DbDir, "%s/Plex Media Server/Plug-in Support/Databases", H.AppSuppDir);
    setString(H.LogFile, "%s/DBRepair.log", H.DbDir);

    // Standard temp paths
    setString(H.TmpDir, "/tmp");
    setString(H.SysTmp, "/tmp");

    // MacOS Specifics
    setString(H.Pidof, "pgrep");
    H.RootRequired = false;

    // Service Control
    // Note: Since these are shell functions in your script, we will
    // call the internal startMacPMS / stopMacPMS C functions later.
    H.HaveStartStop = true;
    setString(H.StartCommand, "startMacPMS");
    setString(H.StopCommand, "stopMacPMS");

    return true;
  }

Looks like you’re making steady progress. I look forward to the results!

I have approx 3500 lines of C. (more comments and clearly written)

Pssss.

Don’t tell anyone, Frankie lives. :smiling_face_with_horns:

chuck@tirol bin % ./DBRepair
SYSTEM: macOS,(Mac)
ENGINE: SQLite v3.53.0
STEEL:  ICU Headers LINKED
[Smoke Test Complete]
Binary Architecture: 64-bit


Select

  1 - 'stop'      - Stop PMS.
  2 - 'automatic' - Check, Repair/Optimize, Reindex, and FTS rebuild in one step.
  3 - 'check'     - Perform integrity check of database and FTS indexes.
  4 - 'vacuum'    - Remove empty space from database without optimizing.
  5 - 'repair'    - Repair/Optimize databases.
  6 - 'reindex'   - Rebuild database indexes.
  7 - 'start'     - Start PMS

  8 - 'import'    - Import watch history from another database independent of Plex. (risky).
  9 - 'replace'   - Replace current databases with newest usable backup copy (interactive).
 10 - 'show'      - Show logfile.
 11 - 'status'    - Report status of PMS (run-state and databases).
 12 - 'undo'      - Undo last successful command.

 21 - 'prune'     - Remove old image files from PhotoTranscoder cache & all temp files left by PMS.
 23 - 'deflate'   - Deflate a bloated PMS main database.
 42 - 'ignore'    - Ignore duplicate/constraint errors.

 88 - 'update'    - Check for updates.
 98 - 'quit'      - Quit immediately.  Keep all temporary files.
 99 - 'exit'      - Exit with cleanup options.

Enter command # -or- command name (4 char min) : 10
 
Displaying Session Log: /Users/chuck/Library/Application Support/Plex Media Server/Plug-in Support/Databases/DBRepair.log
------------------------------------------------------------
2026-05-02 03.34.15 - Check   - Check  - FAIL
2026-05-02 03.34.15 - Check   - Check  - FAIL
2026-05-02 03.34.15 - Check   - PASS
2026-05-02 03.34.15 - Check   - FTS Check - No FTS4 tables
2026-05-02 03.34.15 - Check   - FTS Check - PASS
2026-05-02 03.34.42 - Exit    - Delete temp files.
2026-05-02 03.34.42 - Session end. Sat May  2 03:34:42 2026
2026-05-02 03.34.42 - ============================================================
------------------------------------------------------------

and

Enter command # -or- command name (4 char min) : 99
 
Ok to remove temporary databases/workfiles for this session? (y/N): y
Deleting all temporary work files.
chuck@tirol bin % file DBRepair 
DBRepair: Mach-O 64-bit executable arm64
chuck@tirol bin %  ll -h DBRepair 
-rwxr-xr-x  1 chuck  staff    35M May  1 19:29 DBRepair
chuck@tirol bin % 

Static compiled, no external dependencies (other than the databases)

May I have some feedback please on how this this looking and performing ?

Input DB is badly damaged and fragmented , size = 3 GB

This is what I’m proposing for the the new DBRepair to look like.
Some cosmetic work needs to be done.

It will show all work being performed

chuck@tirol Databases % ~/dbrepair/build/pc-darwin-arm64-release/bin/DBRepair stop check repair exit
SYSTEM: macOS,(MacOS)
ENGINE: SQLite v3.53.0
STEEL:  ICU Headers LINKED
[Smoke Test Complete]
Binary Architecture: 64-bit

[2026-05-05 11.28.44]  
[2026-05-05 11.28.44] PMS already stopped.
[2026-05-05 11.28.44]  
[2026-05-05 11.28.44] Checking the PMS databases...
[2026-05-05 11.29.37] Check complete. PMS main database is OK.
[2026-05-05 11.29.37] Check complete. PMS blobs database is OK.
[2026-05-05 11.29.37] 
[2026-05-05 11.29.37] Checking FTS (Full-Text Search) indexes...
[2026-05-05 11.29.37]   FTS index 'spellfix_metadata_titles' (main) - OK
[2026-05-05 11.29.37]   FTS index 'spellfix_tag_titles' (main) - OK
[2026-05-05 11.29.37]   FTS index 'fts4_metadata_titles' (main) - OK
[2026-05-05 11.29.37]   FTS index 'fts4_tag_titles' (main) - OK
[2026-05-05 11.29.37]   FTS index 'fts4_metadata_titles_icu' (main) - OK
[2026-05-05 11.29.37]   FTS index 'fts4_tag_titles_icu' (main) - OK
[2026-05-05 11.29.37]   FTS index 'spellfix_metadata_titles' (blobs) - OK
[2026-05-05 11.29.37]   FTS index 'spellfix_tag_titles' (blobs) - OK
[2026-05-05 11.29.37]   FTS index 'fts4_metadata_titles' (blobs) - OK
[2026-05-05 11.29.37]   FTS index 'fts4_tag_titles' (blobs) - OK
[2026-05-05 11.29.37]   FTS index 'fts4_metadata_titles_icu' (blobs) - OK
[2026-05-05 11.29.37]   FTS index 'fts4_tag_titles_icu' (blobs) - OK
[2026-05-05 11.29.37]  
[2026-05-05 11.29.37] Starting Logical Reconstruction (Resequenced Data Pump)...
[2026-05-05 11.29.37] Backup current databases with '-BACKUP-2026-05-05_11.29.37' timestamp.
[2026-05-05 11.29.37]   Phase 1: Recreating table schemas...
[2026-05-05 11.29.37]   Phase 2: Optimizing and copying data rows...
[2026-05-05 11.29.43]   Phase 3: Building indexes and search data...
[2026-05-05 11.30.34]   Phase 4: Verifying schema...
[2026-05-05 11.31.20] Reconstruction complete. Finalizing file swap...
[2026-05-05 11.31.20] Repair complete. Status: SALVAGED (Schema restored).
[2026-05-05 11.31.20]  
[2026-05-05 11.31.20] Deleting temporary work files.
chuck@tirol Databases % 

Current design thinking:

  1. When you call automatic, it will resequenced the DB (like the shell did) only if the DB is > 2% fragmented

  2. Reindex is redundant after repair. I do that as part of the reconstruction automatically. Does the Reindex command need stay ?

  3. Is it performing fast enough ? (I forget the shell version speed)

Please don’t hesitate if I’ve gone overboard or completely missed the mark in any way.

Does anyone have an old bloated DB I can get a copy of ?

I’m working on the Deflate code.

I think it’s right but the DB I have has other issues and can’t deflate properly

I’d like to compare against another DB

I just realized there exists a bug in DBRepair.sh for MacOS platforms.

  1. Older implementations used /Applications for Plex
  2. New implementations use $HOME/Applications

DBRepair did not understand this.

Attached please find DBRepair-1.16.00.tar to test please.

If it works on everyone’s Mac, I’ll release it. It works on mine.

DBRepair-1.16.00.tar (91 KB)

Hi ChuckPa,

I’ve been dealing with broken search on my Plex server for several months. Every backup I have going back to at least May 6th has the same issue, so the corruption predates my backup history.

Environment:

  • Plex v1.43.2.10687

  • linuxserver/plex Docker container on Unraid

  • DBRepair v1.15.00 (latest, pulled directly from GitHub master)

The error in Plex logs:

SQLITE3:0x80000001, 1, no such table: fts4_metadata_titles_icu
Got exception from request handler: sqlite3_statement_backend::prepare: no such table: fts4_metadata_titles_icu

What I’ve tried:

  • Ran DBRepair v1.13.02 (Docker image) multiple times — did not fix it

  • Ran DBRepair v1.15.00 directly from GitHub master today

  • v1.15.00 successfully rebuilt: fts4_metadata_titles, fts4_tag_titles, fts4_tag_titles_icu

  • But fts4_metadata_titles_icu is still completely absent from sqlite_master after the repair

  • All other FTS tables in blobs DB are OK

  • Database integrity checks pass (OK) on both main and blobs

  • Attempted to manually CREATE the virtual table using Plex SQLite inside the linuxserver container — fails with vtable constructor failed: fts4_metadata_titles_icu regardless of tokenizer used

Confirmed missing via:

sql

SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%fts4_metadata_titles_icu%';
-- Returns empty

Key concern: I have extensive custom collections and watch history for multiple users going back to 2011 that I cannot lose. A full fresh database is a last resort.

Any guidance appreciated. Happy to provide any additional logs or run specific commands.

Comparing to fts4_tag_titles_icu which is intact, fts4_metadata_titles_icu is missing not just the virtual table but ALL supporting infrastructure — no _segments, _segdir, _docsize, _stat tables, no insert/update/delete triggers, and no indexes. It’s as if the entire table was never created or was fully wiped.

root@Tower:~# cat “/mnt/user/appdata/plex/Library/Application Support/Plex Media Server/Logs/Plex Media Server.log” | grep -i “fts4_metadata_titles_icu|no such table” | head -20 > /tmp/plex-search-errors.txt
root@Tower:~# ^C
root@Tower:~# docker run --rm -v “/mnt/user/appdata/plex”:/config --entrypoint bash linuxserver/plex -c ‘“/usr/lib/plexmediaserver/Plex SQLite” “/config/Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db” “SELECT name, type FROM sqlite_master WHERE name LIKE “%fts4%” ORDER BY name;”’ > /tmp/plex-fts-tables.txt && cat /tmp/plex-fts-tables.txt
fts4_metadata_titles|table
fts4_metadata_titles_docsize|table
fts4_metadata_titles_segdir|table
fts4_metadata_titles_segments|table
fts4_metadata_titles_stat|table
fts4_tag_titles|table
fts4_tag_titles_after_insert_icu|trigger
fts4_tag_titles_after_update_icu|trigger
fts4_tag_titles_before_delete_icu|trigger
fts4_tag_titles_before_update_icu|trigger
fts4_tag_titles_docsize|table
fts4_tag_titles_icu|table
fts4_tag_titles_icu_docsize|table
fts4_tag_titles_icu_segdir|table
fts4_tag_titles_icu_segments|table
fts4_tag_titles_icu_stat|table
fts4_tag_titles_segdir|table
fts4_tag_titles_segments|table
fts4_tag_titles_stat|table
sqlite_autoindex_fts4_metadata_titles_segdir_1|index
sqlite_autoindex_fts4_tag_titles_icu_segdir_1|index
sqlite_autoindex_fts4_tag_titles_segdir_1|index
root@Tower:~#

@eranwolf

It sounds like you’re having the same problem many folks have been having with the recent PMS versions (FTS faults).

DBRepair (the script) has no knowledge of table schemas. That level of work is too detailed (all the schemas) to put into a script.

While I’m working on adding basic schema support to DBRepair (the executable), there’s nothing I can do at this time other than share what my working DB schema is.

The existing schema I have for the table in the blobs DB is:

[chuck@glock Databases.2004]$ /usr/lib/plexmediaserver/Plex\ SQLite com.plexapp.plugins.library.blobs.db

SQLite version 3.39.4 2022-09-29 15:55:41

Enter “.help” for usage hints.

sqlite> .schema fts4_metadata_titles_icu

CREATE VIRTUAL TABLE fts4_metadata_titles_icu USING fts4(content=‘metadata_items’, title, title_sort, original_title, tokenize=collating ‘root@colStrength=primary;colAlternate=shifted’)

/* fts4_metadata_titles_icu(title,title_sort,original_title) */;

sqlite>

Likewise, for the main DB, I have:

[chuck@glock Databases.2005]$ /usr/lib/plexmediaserver/Plex\ SQLite com.plexapp.plugins.library.db

SQLite version 3.39.4 2022-09-29 15:55:41

Enter “.help” for usage hints.

sqlite> .schema fts4_metadata_titles_icu

CREATE VIRTUAL TABLE fts4_metadata_titles_icu USING fts4(content=‘metadata_items’, title, title_sort, original_title, tokenize=collating ‘root@colStrength=primary;colAlternate=shifted’)

/* fts4_metadata_titles_icu(title,title_sort,original_title) */;

sqlite>

You might want to give that a try and then have DBRepair rebuild the contents of the table.

ChuckPa — that did it! Thank you so much.

For anyone else who hits this, here’s exactly what worked:

  1. Used the schema you provided to manually create the missing table directly against the main DB using the linuxserver/plex container and Plex SQLite:

CREATE VIRTUAL TABLE fts4_metadata_titles_icu USING fts4(content=‘metadata_items’, title, title_sort, original_title, tokenize=collating ‘root@colStrength=primary;colAlternate=shifted’)

  1. Then ran DBRepair v1.15.00 with the reindex command to populate it.

All four FTS tables rebuilt successfully and Plex search is fully working again.

For context, this had been broken for months across all my backups. The Docker image of DBRepair is still on v1.13.02 — I had to pull the latest script directly from GitHub master to get v1.15.00 with the FTS repair support.

Really appreciate you jumping in so quickly with the schema. Saved me from nuking my entire database and losing months of custom collections and watch history.

I don’t know whose image you’re using but you should reach out to the image creator.

They should not be including 1.13.02.

At very minimum, you should be able to use the UPDATE (88) command to pull the script directly into the image/container.

ALL:

I’m starting to feel a bit better and having some time to write code.
DBRepair, the program, is coming together pretty nicely. I have a lot of working pieces.

Before I start get down to heavy writing & putting pieces together , I wanted to bounce the architecture and implementation plan around for comments to make certain I’m not making any glaring mistakes or omissions.

  1. CheckDB is now intelligent. It keeps track of the errors (if any) it finds.
    – Perform fundamental integrity_check
    – Checks indexes
    – Checks triggers
    – Checks FTS tables
    – Checks for bloat.
    – Checks if the DB is fragmented

  2. DBRepair no longer exports the DB then reimports it. It does a direct binary-binary transfer from original to new DB (candidate)
    – It uses the information from the source DB (main & blobs) .. sqlite_master.
    – It reads the list of tables only. (skips indexes, fts, and other tables during this copy stage.
    – If table-by-table copy fails, it falls back to row-by-row for that specific table to salvage as much data as possible from the DB.

  3. This results in automatic optimization due to:
    – Defragment the DB while reading. (Tables are contiguous)
    – Vacuum the DB by not copying empty space. (Tables are packed)
    – Deflate the DB by filtering what it copies from statistics_bandwidth.

  4. With the data now copied, basic integrity is checked to confirm we have a viable DB. If valid, we continue with:
    – Rebuild Indexes from scratch. (New: Indexes now checked & repaired)
    – Rebuild Triggers from scratch. (New: Triggers now checked & repaired)
    – Rebuilds FTS tables from scratch. (New FTS integrated into mainstream)
    – Restores primary key ID values to prevent reusing now-deleted media IDs.
    ( Tables will retain their previous max key-id values to avoid confusing PMS )

  5. Planned changes from the original script:
    – Reindex - COPY then rebuild index. Previously reindex ran against the live DB. This preserves the original DBs as sacred.
    – FTS support - Now fully integrated into all phases of processing.
    – Triggers - Check and preserve (recreate as needed)
    – Missing Tables - DBRepair will now check all tables to confirm nothing was lost. DBRepair has name, type, & sql to recreate any of the known PMS database tables/indexes/triggers.
    – New Tables - DBRepair will also flag any new table/index/trigger it encounters so it can be added to DBRepair.

  6. Planned Menu changes:

  • Automatic & Repair become one command due to how repair works.

  • Deflate is obsolete. Deflate is integrated into all repair operations for speed at this time. It serves no obvious purpose to have it freestanding (Let me know)

  • Reindex will silently make a copy of the DB prior to reindexing it.
    Reindex is part of the repair process. It needs a DB other than the live DB.
    (This preserves keeping original sacred until done).

  • SAVE/LIST/REVERT - Due to the new implementation architecture, the “Undo” feature wasn’t enough. I am planning on supporting up to 16 steps. As you issue commands, it will silently “save” each step. At any point (interactive mode) you will be able to LIST the stack contents. (command and date/time) then REVERT to any previously saved point and undo all changes made to that point.

PS Health is coming along. More to do but getting there. :slight_smile:

All input is welcome.
Thanks,
C.

This is the most important thing - glad you are on your way to recovery.

ALL:

DBRepair v1.16.00 is now available for your consideration.

It properly supports Plex Media Server.app locations on MacOS.

I am happy you are feeling better Chuck :slightly_smiling_face:

You are an absolute legend, your script has saved me a lot of hours and frustration rebuilding a corrupt database from the most recent Plex update. I hope you are able to continue developing it

Also glad you are feeling better @ChuckPa

Something weird happening for me on GitHub. I can download what purports to be the 116 package, but script still says it is 115 from March?

Edit: Looks like code is different, maybe just forgot to change header and “Version=”??