ensure the search database is attached to all connections
This commit is contained in:
parent
aa47ec7c43
commit
a0c5122735
@ -1,15 +1,16 @@
|
|||||||
//! Bundle all functions together.
|
//! Bundle all functions together.
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use r2d2::Pool;
|
use r2d2::Pool;
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
use r2d2_sqlite::SqliteConnectionManager;
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
data::{
|
data::{
|
||||||
author::Author, book::Book, error::DataStoreError, pagination::SortOrder, series::Series,
|
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
|
/// 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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Calibre {
|
pub struct Calibre {
|
||||||
pool: Pool<SqliteConnectionManager>,
|
pool: Pool<SqliteConnectionManager>,
|
||||||
|
search_db_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Calibre {
|
impl Calibre {
|
||||||
@ -27,16 +29,20 @@ impl Calibre {
|
|||||||
let manager = SqliteConnectionManager::file(path);
|
let manager = SqliteConnectionManager::file(path);
|
||||||
let pool = r2d2::Pool::new(manager)?;
|
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.
|
/// Full text search with a query.
|
||||||
///
|
///
|
||||||
/// See https://www.sqlite.org/fts5.html#full_text_query_syntax for syntax.
|
/// See https://www.sqlite.org/fts5.html#full_text_query_syntax for syntax.
|
||||||
pub fn search(&self, query: &str) -> Result<Vec<Book>, DataStoreError> {
|
pub fn search(&self, query: &str) -> Result<Vec<Book>, 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
|
/// Fetch book data from calibre, starting at `cursor`, fetching up to an amount of `limit` and
|
||||||
|
@ -5,10 +5,11 @@
|
|||||||
//! virtual table leveraging fts5 (https://www.sqlite.org/fts5.html). Full-text search is run on
|
//! virtual table leveraging fts5 (https://www.sqlite.org/fts5.html). Full-text search is run on
|
||||||
//! that virtual table.
|
//! that virtual table.
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use r2d2::{Pool, PooledConnection};
|
use r2d2::{Pool, PooledConnection};
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
use r2d2_sqlite::SqliteConnectionManager;
|
||||||
use rusqlite::named_params;
|
use rusqlite::named_params;
|
||||||
use tempfile::NamedTempFile;
|
|
||||||
|
|
||||||
use crate::data::{book::Book, error::DataStoreError};
|
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
|
LEFT JOIN main.series AS s ON b2s.series = s.id
|
||||||
GROUP BY b.id";
|
GROUP BY b.id";
|
||||||
|
|
||||||
/// Attach the fts temporary database to the read-only calibre database.
|
/// Ensure the search database is attached to the connection and
|
||||||
pub(crate) fn attach(pool: &Pool<SqliteConnectionManager>) -> Result<(), DataStoreError> {
|
/// initializes the data if needed.
|
||||||
let conn = pool.get()?;
|
fn ensure_search_db(
|
||||||
let tmpfile = NamedTempFile::new()?;
|
conn: &PooledConnection<SqliteConnectionManager>,
|
||||||
let (_, path) = tmpfile.keep()?;
|
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<SqliteConnectionManager>,
|
||||||
|
db_path: &Path,
|
||||||
|
) -> Result<(), DataStoreError> {
|
||||||
conn.execute(
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialise the fts virtual table.
|
/// Initialise the fts virtual table.
|
||||||
fn init(conn: &PooledConnection<SqliteConnectionManager>) -> Result<(), DataStoreError> {
|
fn init(conn: &PooledConnection<SqliteConnectionManager>) -> Result<(), DataStoreError> {
|
||||||
conn.execute(
|
let mut stmt = conn
|
||||||
"CREATE VIRTUAL TABLE search.fts USING fts5(book_id, data)",
|
.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;
|
||||||
conn.execute(SEARCH_INIT_QUERY, [])?;
|
|
||||||
|
if need_init {
|
||||||
|
conn.execute(
|
||||||
|
"CREATE VIRTUAL TABLE search.fts USING fts5(book_id, data)",
|
||||||
|
[],
|
||||||
|
)?;
|
||||||
|
conn.execute(SEARCH_INIT_QUERY, [])?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -62,8 +88,10 @@ fn init(conn: &PooledConnection<SqliteConnectionManager>) -> Result<(), DataStor
|
|||||||
pub(crate) fn search(
|
pub(crate) fn search(
|
||||||
query: &str,
|
query: &str,
|
||||||
pool: &Pool<SqliteConnectionManager>,
|
pool: &Pool<SqliteConnectionManager>,
|
||||||
|
search_db_path: &Path,
|
||||||
) -> Result<Vec<Book>, DataStoreError> {
|
) -> Result<Vec<Book>, DataStoreError> {
|
||||||
let conn = pool.get()?;
|
let conn = pool.get()?;
|
||||||
|
ensure_search_db(&conn, search_db_path)?;
|
||||||
|
|
||||||
let mut stmt =
|
let mut stmt =
|
||||||
conn.prepare("SELECT book_id FROM search.fts WHERE data MATCH (:query) ORDER BY rank")?;
|
conn.prepare("SELECT book_id FROM search.fts WHERE data MATCH (:query) ORDER BY rank")?;
|
||||||
|
Loading…
Reference in New Issue
Block a user