Complete storage and search foundation

This commit is contained in:
2026-07-15 16:53:34 -05:00
parent 05ff4bcf9f
commit 2ece7bc36a
12 changed files with 288 additions and 27 deletions
+1 -1
View File
@@ -77,7 +77,7 @@ checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[package]]
name = "app"
version = "0.1.26"
version = "0.1.32"
dependencies = [
"log",
"rusqlite",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "app"
version = "0.1.26"
version = "0.1.32"
description = "A Tauri App"
authors = ["you"]
license = ""
+50 -3
View File
@@ -226,6 +226,7 @@ fn open_db(app: &AppHandle) -> Result<Connection, String> {
let mut conn = Connection::open(path).map_err(|err| err.to_string())?;
migrate(&conn)?;
seed(&mut conn)?;
sync_search_indexes(&conn)?;
Ok(conn)
}
@@ -297,6 +298,20 @@ fn migrate(conn: &Connection) -> Result<(), String> {
FOREIGN KEY (verse_id) REFERENCES verses(id),
FOREIGN KEY (tag_id) REFERENCES tags(id)
);
CREATE VIRTUAL TABLE IF NOT EXISTS verses_fts USING fts5(
text,
reference,
content='verses',
content_rowid='id'
);
CREATE INDEX IF NOT EXISTS idx_verses_reference ON verses(book, chapter, verse);
CREATE INDEX IF NOT EXISTS idx_verse_strongs_strong ON verse_strongs(strong);
CREATE INDEX IF NOT EXISTS idx_verse_strongs_verse ON verse_strongs(verse_id);
CREATE INDEX IF NOT EXISTS idx_verse_footnotes_verse ON verse_footnotes(verse_id);
CREATE INDEX IF NOT EXISTS idx_verse_tags_tag ON verse_tags(tag_id);
CREATE INDEX IF NOT EXISTS idx_tags_label ON tags(label);
",
)
.map_err(|err| err.to_string())?;
@@ -778,20 +793,28 @@ fn load_verses(conn: &Connection, query: Option<&str>) -> Result<Vec<Verse>, Str
match query {
Some(query) => {
let fts_query = search_query_for_fts(query);
if fts_query.is_empty() {
return Ok(Vec::new());
}
let pattern = format!("%{}%", query);
let mut stmt = conn
.prepare(
"
SELECT v.id, v.book, v.chapter, v.verse, v.text
FROM verses v
JOIN verses_fts fts ON fts.rowid = v.id
WHERE verses_fts MATCH ?1
UNION
SELECT id, book, chapter, verse, text
FROM verses
WHERE text LIKE ?1
OR book || ' ' || chapter || ':' || verse LIKE ?1
WHERE book || ' ' || chapter || ':' || verse LIKE ?2
ORDER BY id
",
)
.map_err(|err| err.to_string())?;
let rows = stmt
.query_map(params![pattern], row_to_verse_shell)
.query_map(params![fts_query, pattern], row_to_verse_shell)
.map_err(|err| err.to_string())?;
for row in rows {
@@ -1110,6 +1133,30 @@ fn load_all_tags(conn: &Connection) -> Result<Vec<Tag>, String> {
Ok(tags)
}
fn sync_search_indexes(conn: &Connection) -> Result<(), String> {
let verse_count: i64 = conn
.query_row("SELECT COUNT(*) FROM verses", [], |row| row.get(0))
.map_err(|err| err.to_string())?;
let fts_count: i64 = conn
.query_row("SELECT COUNT(*) FROM verses_fts", [], |row| row.get(0))
.map_err(|err| err.to_string())?;
if verse_count != fts_count {
conn.execute("INSERT INTO verses_fts(verses_fts) VALUES('rebuild')", [])
.map_err(|err| err.to_string())?;
}
Ok(())
}
fn search_query_for_fts(query: &str) -> String {
query
.split(|char: char| !char.is_ascii_alphanumeric())
.filter(|token| !token.is_empty())
.collect::<Vec<_>>()
.join(" ")
}
fn looks_like_strongs(query: &str) -> bool {
let mut chars = query.chars();
matches!(chars.next(), Some('G' | 'g' | 'H' | 'h')) && chars.all(|char| char.is_ascii_digit())
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Libre Study",
"version": "0.1.26",
"version": "0.1.32",
"identifier": "org.librestudy.desktop",
"build": {
"frontendDist": "../dist",