From 8544702bb204260265f29374298178f58af894bb Mon Sep 17 00:00:00 2001 From: alcibiadesc Date: Sat, 1 Nov 2025 20:05:53 +0100 Subject: [PATCH] Add security fixes and improvements to search optimization - Add FTS5 table existence check to avoid log spam on non-FTS databases - Escape FTS5 special characters (quotes) to prevent query errors - Wrap FTS5 search terms in quotes for phrase matching accuracy - Improve logging: change author ordering debug to warning for visibility - Add comment explaining author_sort data issues These changes improve robustness and security without affecting performance. --- cps/db.py | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/cps/db.py b/cps/db.py index a92b6f0b5..db88c2652 100644 --- a/cps/db.py +++ b/cps/db.py @@ -924,8 +924,9 @@ class CalibreDB: authors_ordered.append(author) ids_remaining.discard(author.id) else: + # This can happen if author_sort has stale data or formatting issues book_id = entry.id if isinstance(entry, Books) else (entry.Books.id if combined else entry.id) - log.debug("Author '{}' of book {} not found in author list".format(auth, book_id)) + log.warning("Author '{}' of book {} not found in author list, skipping in sort order".format(auth, book_id)) # Add any remaining authors not in sort order for author_id in ids_remaining: @@ -967,16 +968,30 @@ class CalibreDB: # Try FTS5 search first for better performance fts_ids = None - try: - fts_results = self.session.execute( - text("SELECT DISTINCT rowid FROM books_fts WHERE books_fts MATCH :term"), - {"term": term} - ).fetchall() - if fts_results: - fts_ids = [r[0] for r in fts_results] - except Exception as ex: - # FTS5 not available or query failed, fall back to traditional search - log.debug("FTS5 search failed, using traditional search: {}".format(ex)) + # Check if FTS5 table exists before attempting search + if not hasattr(self, '_fts_available'): + try: + result = self.session.execute( + text("SELECT name FROM sqlite_master WHERE type='table' AND name='books_fts'") + ).fetchone() + self._fts_available = result is not None + except Exception: + self._fts_available = False + + if self._fts_available: + try: + # Escape FTS5 special characters to prevent query errors + term_fts = term.replace('"', '""') + # Wrap in quotes for phrase matching and better accuracy + fts_results = self.session.execute( + text("SELECT DISTINCT rowid FROM books_fts WHERE books_fts MATCH :term"), + {"term": f'"{term_fts}"'} + ).fetchall() + if fts_results: + fts_ids = [r[0] for r in fts_results] + except Exception as ex: + # FTS5 query failed, fall back to traditional search + log.debug("FTS5 search failed for term '{}', using fallback: {}".format(term, ex)) # Build base query with optimized joins base_query = self.session.query(Books).filter(self.common_filters(True))