From a0c512273533c4ee72ae04baa34b9bc9e9650e66 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Wed, 26 Jun 2024 17:41:04 +0200 Subject: [PATCH] ensure the search database is attached to all connections --- calibre-db/src/calibre.rs | 16 ++++++++---- calibre-db/src/search.rs | 54 +++++++++++++++++++++++++++++---------- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/calibre-db/src/calibre.rs b/calibre-db/src/calibre.rs index 94bfd11..1dd1164 100644 --- a/calibre-db/src/calibre.rs +++ b/calibre-db/src/calibre.rs @@ -1,15 +1,16 @@ //! Bundle all functions together. -use std::path::Path; +use std::path::{Path, PathBuf}; use r2d2::Pool; use r2d2_sqlite::SqliteConnectionManager; +use tempfile::NamedTempFile; use crate::{ data::{ author::Author, book::Book, error::DataStoreError, pagination::SortOrder, series::Series, }, - search::{self, search}, + search::search, }; /// Top level calibre functions, bundling all sub functions in one place and providing secure access to @@ -17,6 +18,7 @@ use crate::{ #[derive(Debug, Clone)] pub struct Calibre { pool: Pool, + search_db_path: PathBuf, } impl Calibre { @@ -27,16 +29,20 @@ impl Calibre { let manager = SqliteConnectionManager::file(path); let pool = r2d2::Pool::new(manager)?; - search::attach(&pool)?; + let tmpfile = NamedTempFile::new()?; + let (_, search_db_path) = tmpfile.keep()?; - Ok(Self { pool }) + Ok(Self { + pool, + search_db_path, + }) } /// Full text search with a query. /// /// See https://www.sqlite.org/fts5.html#full_text_query_syntax for syntax. pub fn search(&self, query: &str) -> Result, DataStoreError> { - search(query, &self.pool) + search(query, &self.pool, &self.search_db_path) } /// Fetch book data from calibre, starting at `cursor`, fetching up to an amount of `limit` and diff --git a/calibre-db/src/search.rs b/calibre-db/src/search.rs index 0d61d51..8c6ae9a 100644 --- a/calibre-db/src/search.rs +++ b/calibre-db/src/search.rs @@ -5,10 +5,11 @@ //! virtual table leveraging fts5 (https://www.sqlite.org/fts5.html). Full-text search is run on //! that virtual table. +use std::path::Path; + use r2d2::{Pool, PooledConnection}; use r2d2_sqlite::SqliteConnectionManager; use rusqlite::named_params; -use tempfile::NamedTempFile; use crate::data::{book::Book, error::DataStoreError}; @@ -32,28 +33,53 @@ const SEARCH_INIT_QUERY: &str = "INSERT INTO search.fts(book_id, data) LEFT JOIN main.series AS s ON b2s.series = s.id GROUP BY b.id"; -/// Attach the fts temporary database to the read-only calibre database. -pub(crate) fn attach(pool: &Pool) -> Result<(), DataStoreError> { - let conn = pool.get()?; - let tmpfile = NamedTempFile::new()?; - let (_, path) = tmpfile.keep()?; +/// Ensure the search database is attached to the connection and +/// initializes the data if needed. +fn ensure_search_db( + conn: &PooledConnection, + db_path: &Path, +) -> Result<(), DataStoreError> { + let mut stmt = + conn.prepare("SELECT COUNT() FROM pragma_database_list WHERE name = 'search'")?; + let count: u64 = stmt.query_row([], |x| x.get(0))?; + let need_attachment = count == 0; + if need_attachment { + attach(conn, db_path)?; + init(conn)?; + } + + Ok(()) +} + +/// Attach the fts temporary database to the read-only calibre database. +fn attach( + conn: &PooledConnection, + db_path: &Path, +) -> Result<(), DataStoreError> { conn.execute( - &format!("ATTACH DATABASE '{}' AS search", path.to_string_lossy()), + &format!("ATTACH DATABASE '{}' AS search", db_path.to_string_lossy()), [], )?; - init(&conn)?; + init(conn)?; Ok(()) } /// Initialise the fts virtual table. fn init(conn: &PooledConnection) -> Result<(), DataStoreError> { - conn.execute( - "CREATE VIRTUAL TABLE search.fts USING fts5(book_id, data)", - [], - )?; - conn.execute(SEARCH_INIT_QUERY, [])?; + let mut stmt = conn + .prepare("SELECT COUNT() FROM search.sqlite_master WHERE type='table' AND name = 'fts'")?; + let count: u64 = stmt.query_row([], |x| x.get(0))?; + let need_init = count == 0; + + if need_init { + conn.execute( + "CREATE VIRTUAL TABLE search.fts USING fts5(book_id, data)", + [], + )?; + conn.execute(SEARCH_INIT_QUERY, [])?; + } Ok(()) } @@ -62,8 +88,10 @@ fn init(conn: &PooledConnection) -> Result<(), DataStor pub(crate) fn search( query: &str, pool: &Pool, + search_db_path: &Path, ) -> Result, DataStoreError> { let conn = pool.get()?; + ensure_search_db(&conn, search_db_path)?; let mut stmt = conn.prepare("SELECT book_id FROM search.fts WHERE data MATCH (:query) ORDER BY rank")?;