WIP
This commit is contained in:
parent
1c95f4391f
commit
b4a0aadef9
73 changed files with 2993 additions and 1632 deletions
|
@ -10,8 +10,9 @@ use std::path::Path;
|
|||
use r2d2::{Pool, PooledConnection};
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use rusqlite::named_params;
|
||||
use snafu::{ResultExt, Snafu};
|
||||
|
||||
use crate::data::{book::Book, error::DataStoreError};
|
||||
use crate::data::book::Book;
|
||||
|
||||
/// A lot of joins but only run once at startup.
|
||||
const SEARCH_INIT_QUERY: &str = "INSERT INTO search.fts(book_id, data)
|
||||
|
@ -33,20 +34,61 @@ 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";
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum EnsureSearchDbError {
|
||||
#[snafu(display("Failed to prepare statement."))]
|
||||
PrepareEnsureSearch { source: rusqlite::Error },
|
||||
#[snafu(display("Failed to execute statement."))]
|
||||
ExecuteEnsureSearch { source: rusqlite::Error },
|
||||
#[snafu(display("Failed to attach database."))]
|
||||
Attach { source: AttachError },
|
||||
#[snafu(display("Failed to initialize database."))]
|
||||
Init { source: InitError },
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum AttachError {
|
||||
#[snafu(display("Failed to execute statement."))]
|
||||
ExecuteAttach { source: rusqlite::Error },
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum InitError {
|
||||
#[snafu(display("Failed to prepare statement."))]
|
||||
PrepareInit { source: rusqlite::Error },
|
||||
#[snafu(display("Failed to execute statement."))]
|
||||
ExecuteInit { source: rusqlite::Error },
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum SearchError {
|
||||
#[snafu(display("Failed ensure the search db is initialized."))]
|
||||
EnsureDb { source: EnsureSearchDbError },
|
||||
#[snafu(display("Failed to get connection from pool."))]
|
||||
Connection { source: r2d2::Error },
|
||||
#[snafu(display("Failed to prepare statement."))]
|
||||
PrepareSearch { source: rusqlite::Error },
|
||||
#[snafu(display("Failed to execute statement."))]
|
||||
ExecuteSearch { source: rusqlite::Error },
|
||||
}
|
||||
|
||||
/// Ensure the search database is attached to the connection and
|
||||
/// initializes the data if needed.
|
||||
fn ensure_search_db(
|
||||
conn: &PooledConnection<SqliteConnectionManager>,
|
||||
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))?;
|
||||
) -> Result<(), EnsureSearchDbError> {
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT COUNT() FROM pragma_database_list WHERE name = 'search'")
|
||||
.context(PrepareEnsureSearchSnafu)?;
|
||||
let count: u64 = stmt
|
||||
.query_row([], |x| x.get(0))
|
||||
.context(ExecuteEnsureSearchSnafu)?;
|
||||
let need_attachment = count == 0;
|
||||
|
||||
if need_attachment {
|
||||
attach(conn, db_path)?;
|
||||
init(conn)?;
|
||||
attach(conn, db_path).context(AttachSnafu)?;
|
||||
init(conn).context(InitSnafu)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -56,29 +98,32 @@ fn ensure_search_db(
|
|||
fn attach(
|
||||
conn: &PooledConnection<SqliteConnectionManager>,
|
||||
db_path: &Path,
|
||||
) -> Result<(), DataStoreError> {
|
||||
) -> Result<(), AttachError> {
|
||||
conn.execute(
|
||||
&format!("ATTACH DATABASE '{}' AS search", db_path.to_string_lossy()),
|
||||
[],
|
||||
)?;
|
||||
init(conn)?;
|
||||
)
|
||||
.context(ExecuteAttachSnafu)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Initialise the fts virtual table.
|
||||
fn init(conn: &PooledConnection<SqliteConnectionManager>) -> Result<(), DataStoreError> {
|
||||
fn init(conn: &PooledConnection<SqliteConnectionManager>) -> Result<(), InitError> {
|
||||
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))?;
|
||||
.prepare("SELECT COUNT() FROM search.sqlite_master WHERE type='table' AND name = 'fts'")
|
||||
.context(PrepareInitSnafu)?;
|
||||
let count: u64 = stmt.query_row([], |x| x.get(0)).context(ExecuteInitSnafu)?;
|
||||
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, [])?;
|
||||
)
|
||||
.context(ExecuteInitSnafu)?;
|
||||
conn.execute(SEARCH_INIT_QUERY, [])
|
||||
.context(ExecuteInitSnafu)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -89,15 +134,17 @@ pub(crate) fn search(
|
|||
query: &str,
|
||||
pool: &Pool<SqliteConnectionManager>,
|
||||
search_db_path: &Path,
|
||||
) -> Result<Vec<Book>, DataStoreError> {
|
||||
let conn = pool.get()?;
|
||||
ensure_search_db(&conn, search_db_path)?;
|
||||
) -> Result<Vec<Book>, SearchError> {
|
||||
let conn = pool.get().context(ConnectionSnafu)?;
|
||||
ensure_search_db(&conn, search_db_path).context(EnsureDbSnafu)?;
|
||||
|
||||
let mut stmt =
|
||||
conn.prepare("SELECT book_id FROM search.fts WHERE data MATCH (:query) ORDER BY rank")?;
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT book_id FROM search.fts WHERE data MATCH (:query) ORDER BY rank")
|
||||
.context(PrepareSearchSnafu)?;
|
||||
let params = named_params! { ":query": query };
|
||||
let books = stmt
|
||||
.query_map(params, |r| -> Result<u64, rusqlite::Error> { r.get(0) })?
|
||||
.query_map(params, |r| -> Result<u64, rusqlite::Error> { r.get(0) })
|
||||
.context(ExecuteSearchSnafu)?
|
||||
.filter_map(Result::ok)
|
||||
.filter_map(|id| Book::scalar_book(&conn, id).ok())
|
||||
.collect();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue