diff --git a/rslib/src/card/mod.rs b/rslib/src/card/mod.rs index c3945fd5a..41c11ec0d 100644 --- a/rslib/src/card/mod.rs +++ b/rslib/src/card/mod.rs @@ -274,12 +274,11 @@ impl Collection { ))?; let config = self.get_deck_config(config_id, true)?.unwrap(); let mut steps_adjuster = RemainingStepsAdjuster::new(&config); - self.storage.set_search_table_to_card_ids(cards, false)?; let sched = self.scheduler_version(); let usn = self.usn()?; self.transact(Op::SetCardDeck, |col| { let mut count = 0; - for mut card in col.storage.all_searched_cards()? { + for mut card in col.all_cards_for_ids(cards, false)? { if card.deck_id == deck_id { continue; } @@ -299,11 +298,10 @@ impl Collection { } let flag = flag as u8; - self.storage.set_search_table_to_card_ids(cards, false)?; let usn = self.usn()?; self.transact(Op::SetFlag, |col| { let mut count = 0; - for mut card in col.storage.all_searched_cards()? { + for mut card in col.all_cards_for_ids(cards, false)? { let original = card.clone(); if card.set_flag(flag) { // To avoid having to rebuild the study queues, we mark the card as requiring diff --git a/rslib/src/import_export/gather.rs b/rslib/src/import_export/gather.rs index d615ee40f..7b7cb76e1 100644 --- a/rslib/src/import_export/gather.rs +++ b/rslib/src/import_export/gather.rs @@ -12,6 +12,7 @@ use crate::{ latex::extract_latex, prelude::*, revlog::RevlogEntry, + search::{CardTableGuard, NoteTableGuard}, text::{extract_media_refs, extract_underscored_css_imports, extract_underscored_references}, }; @@ -37,21 +38,22 @@ impl ExchangeData { ) -> Result<()> { self.days_elapsed = col.timing_today()?.days_elapsed; self.creation_utc_offset = col.get_creation_utc_offset(); - self.notes = col.gather_notes(search)?; - self.cards = col.gather_cards()?; - self.decks = col.gather_decks()?; - self.notetypes = col.gather_notetypes()?; + let (notes, guard) = col.gather_notes(search)?; + self.notes = notes; + let (cards, guard) = guard.col.gather_cards()?; + self.cards = cards; + self.decks = guard.col.gather_decks()?; + self.notetypes = guard.col.gather_notetypes()?; self.check_ids()?; if with_scheduling { - self.revlog = col.gather_revlog()?; - self.deck_configs = col.gather_deck_configs(&self.decks)?; + self.revlog = guard.col.gather_revlog()?; + self.deck_configs = guard.col.gather_deck_configs(&self.decks)?; } else { - self.remove_scheduling_information(col); + self.remove_scheduling_information(guard.col); }; - col.storage.clear_searched_notes_table()?; - col.storage.clear_searched_cards_table() + Ok(()) } pub(super) fn gather_media_names( @@ -171,14 +173,22 @@ fn svg_getter(notetypes: &[Notetype]) -> impl Fn(NotetypeId) -> bool { } impl Collection { - fn gather_notes(&mut self, search: impl TryIntoSearch) -> Result> { - self.search_notes_into_table(search)?; - self.storage.all_searched_notes() + fn gather_notes(&mut self, search: impl TryIntoSearch) -> Result<(Vec, NoteTableGuard)> { + let guard = self.search_notes_into_table(search)?; + guard + .col + .storage + .all_searched_notes() + .map(|notes| (notes, guard)) } - fn gather_cards(&mut self) -> Result> { - self.storage.search_cards_of_notes_into_table()?; - self.storage.all_searched_cards() + fn gather_cards(&mut self) -> Result<(Vec, CardTableGuard)> { + let guard = self.search_cards_of_notes_into_table()?; + guard + .col + .storage + .all_searched_cards() + .map(|cards| (cards, guard)) } fn gather_decks(&mut self) -> Result> { diff --git a/rslib/src/import_export/text/csv/export.rs b/rslib/src/import_export/text/csv/export.rs index dc44e96ce..3003b768f 100644 --- a/rslib/src/import_export/text/csv/export.rs +++ b/rslib/src/import_export/text/csv/export.rs @@ -53,16 +53,15 @@ impl Collection { progress.call(ExportProgress::File)?; let mut incrementor = progress.incrementor(ExportProgress::Notes); - self.search_notes_into_table(request.search_node())?; - let ctx = NoteContext::new(&request, self)?; + let guard = self.search_notes_into_table(request.search_node())?; + let ctx = NoteContext::new(&request, guard.col)?; let mut writer = note_file_writer_with_header(&request.out_path, &ctx)?; - self.storage.for_each_note_in_search(|note| { + guard.col.storage.for_each_note_in_search(|note| { incrementor.increment()?; writer.write_record(ctx.record(¬e))?; Ok(()) })?; writer.flush()?; - self.storage.clear_searched_notes_table()?; Ok(incrementor.count()) } diff --git a/rslib/src/notetype/notetypechange.rs b/rslib/src/notetype/notetypechange.rs index 5d845a35a..1a97a8dab 100644 --- a/rslib/src/notetype/notetypechange.rs +++ b/rslib/src/notetype/notetypechange.rs @@ -8,7 +8,7 @@ use std::collections::{HashMap, HashSet}; use super::{CardGenContext, Notetype, NotetypeKind}; use crate::{ prelude::*, - search::{JoinSearches, Node, SearchNode, SortMode, TemplateKind}, + search::{JoinSearches, Node, SearchNode, TemplateKind}, storage::comma_separated_ids, }; @@ -294,11 +294,9 @@ impl Collection { if !map.removed.is_empty() { let ords = SearchBuilder::any(map.removed.iter().map(|o| TemplateKind::Ordinal(*o as u16))); - self.search_cards_into_table(nids.and(ords), SortMode::NoOrder)?; - for card in self.storage.all_searched_cards()? { + for card in self.all_cards_for_search(nids.and(ords))? { self.remove_card_and_add_grave_undoable(card, usn)?; } - self.storage.clear_searched_cards_table()?; } Ok(()) @@ -316,14 +314,12 @@ impl Collection { .keys() .map(|o| TemplateKind::Ordinal(*o as u16)), ); - self.search_cards_into_table(nids.and(ords), SortMode::NoOrder)?; - for mut card in self.storage.all_searched_cards()? { + for mut card in self.all_cards_for_search(nids.and(ords))? { let original = card.clone(); card.template_idx = *map.remapped.get(&(card.template_idx as usize)).unwrap() as u16; self.update_card_inner(&mut card, original, usn)?; } - self.storage.clear_searched_cards_table()?; } Ok(()) @@ -349,7 +345,6 @@ impl Collection { { self.remove_card_and_add_grave_undoable(card, usn)?; } - self.storage.clear_searched_notes_table()?; } Ok(()) diff --git a/rslib/src/notetype/schemachange.rs b/rslib/src/notetype/schemachange.rs index d5168407f..f4916b8f4 100644 --- a/rslib/src/notetype/schemachange.rs +++ b/rslib/src/notetype/schemachange.rs @@ -8,7 +8,7 @@ use std::collections::HashMap; use super::{CardGenContext, CardTemplate, Notetype}; use crate::{ prelude::*, - search::{JoinSearches, SortMode, TemplateKind}, + search::{JoinSearches, TemplateKind}, }; /// True if any ordinals added, removed or reordered. @@ -148,22 +148,18 @@ impl Collection { if !changes.removed.is_empty() { let ords = SearchBuilder::any(changes.removed.iter().cloned().map(TemplateKind::Ordinal)); - self.search_cards_into_table(nt.id.and(ords), SortMode::NoOrder)?; - for card in self.storage.all_searched_cards()? { + for card in self.all_cards_for_search(nt.id.and(ords))? { self.remove_card_and_add_grave_undoable(card, usn)?; } - self.storage.clear_searched_cards_table()?; } // update ordinals for cards with a repositioned template if !changes.moved.is_empty() { let ords = SearchBuilder::any(changes.moved.keys().cloned().map(TemplateKind::Ordinal)); - self.search_cards_into_table(nt.id.and(ords), SortMode::NoOrder)?; - for mut card in self.storage.all_searched_cards()? { + for mut card in self.all_cards_for_search(nt.id.and(ords))? { let original = card.clone(); card.template_idx = *changes.moved.get(&card.template_idx).unwrap(); self.update_card_inner(&mut card, original, usn)?; } - self.storage.clear_searched_cards_table()?; } if should_generate_cards(&changes, nt, old_templates) { diff --git a/rslib/src/scheduler/bury_and_suspend.rs b/rslib/src/scheduler/bury_and_suspend.rs index 5e746b1fe..5a235ae7d 100644 --- a/rslib/src/scheduler/bury_and_suspend.rs +++ b/rslib/src/scheduler/bury_and_suspend.rs @@ -10,7 +10,7 @@ use crate::{ unbury_deck_request::Mode as UnburyDeckMode, }, prelude::*, - search::{JoinSearches, SearchNode, SortMode, StateKind}, + search::{JoinSearches, SearchNode, StateKind}, }; impl Card { @@ -42,32 +42,29 @@ impl Collection { /// Unbury cards from the previous day. /// Done automatically, and does not mark the cards as modified. pub(crate) fn unbury_on_day_rollover(&mut self, today: u32) -> Result<()> { - self.search_cards_into_table("is:buried", SortMode::NoOrder)?; - self.storage.for_each_card_in_search(|mut card| { + self.for_each_card_in_search(StateKind::Buried, |col, mut card| { card.restore_queue_after_bury_or_suspend(); - self.storage.update_card(&card) + col.storage.update_card(&card) })?; - self.storage.clear_searched_cards_table()?; self.set_last_unburied_day(today) } - /// Unsuspend/unbury cards in search table, and clear it. - /// Marks the cards as modified. - fn unsuspend_or_unbury_searched_cards(&mut self) -> Result<()> { + /// Unsuspend/unbury cards. Marks the cards as modified. + fn unsuspend_or_unbury_searched_cards(&mut self, cards: Vec) -> Result<()> { let usn = self.usn()?; - for original in self.storage.all_searched_cards()? { + for original in cards { let mut card = original.clone(); if card.restore_queue_after_bury_or_suspend() { self.update_card_inner(&mut card, original, usn)?; } } - self.storage.clear_searched_cards_table() + Ok(()) } pub fn unbury_or_unsuspend_cards(&mut self, cids: &[CardId]) -> Result> { self.transact(Op::UnburyUnsuspend, |col| { - col.storage.set_search_table_to_card_ids(cids, false)?; - col.unsuspend_or_unbury_searched_cards() + let cards = col.all_cards_for_ids(cids, false)?; + col.unsuspend_or_unbury_searched_cards(cards) }) } @@ -78,17 +75,19 @@ impl Collection { UnburyDeckMode::SchedOnly => StateKind::SchedBuried, }; self.transact(Op::UnburyUnsuspend, |col| { - col.search_cards_into_table( - SearchNode::DeckIdWithChildren(deck_id).and(state), - SortMode::NoOrder, - )?; - col.unsuspend_or_unbury_searched_cards() + let cards = + col.all_cards_for_search(SearchNode::DeckIdWithChildren(deck_id).and(state))?; + col.unsuspend_or_unbury_searched_cards(cards) }) } /// Bury/suspend cards in search table, and clear it. /// Marks the cards as modified. - fn bury_or_suspend_searched_cards(&mut self, mode: BuryOrSuspendMode) -> Result { + fn bury_or_suspend_cards_inner( + &mut self, + cards: Vec, + mode: BuryOrSuspendMode, + ) -> Result { let mut count = 0; let usn = self.usn()?; let sched = self.scheduler_version(); @@ -105,7 +104,7 @@ impl Collection { } }; - for original in self.storage.all_searched_cards()? { + for original in cards { let mut card = original.clone(); if card.queue != desired_queue { // do not bury suspended cards as that would unsuspend them @@ -121,8 +120,6 @@ impl Collection { } } - self.storage.clear_searched_cards_table()?; - Ok(count) } @@ -136,8 +133,8 @@ impl Collection { BuryOrSuspendMode::BurySched | BuryOrSuspendMode::BuryUser => Op::Bury, }; self.transact(op, |col| { - col.storage.set_search_table_to_card_ids(cids, false)?; - col.bury_or_suspend_searched_cards(mode) + let cards = col.all_cards_for_ids(cids, false)?; + col.bury_or_suspend_cards_inner(cards, mode) }) } @@ -149,14 +146,14 @@ impl Collection { include_reviews: bool, include_day_learn: bool, ) -> Result { - self.storage.search_siblings_for_bury( + let cards = self.storage.all_siblings_for_bury( cid, nid, include_new, include_reviews, include_day_learn, )?; - self.bury_or_suspend_searched_cards(BuryOrSuspendMode::BurySched) + self.bury_or_suspend_cards_inner(cards, BuryOrSuspendMode::BurySched) } } diff --git a/rslib/src/scheduler/filtered/mod.rs b/rslib/src/scheduler/filtered/mod.rs index 5c715567a..a9d3bfe84 100644 --- a/rslib/src/scheduler/filtered/mod.rs +++ b/rslib/src/scheduler/filtered/mod.rs @@ -135,8 +135,7 @@ impl Collection { ); let order = order_and_limit_for_search(term, ctx.today); - self.search_cards_into_table(&search, SortMode::Custom(order))?; - for mut card in self.storage.all_searched_cards_in_search_order()? { + for mut card in self.all_cards_for_search_in_order(&search, SortMode::Custom(order))? { let original = card.clone(); card.move_into_filtered_deck(ctx, position); self.update_card_inner(&mut card, original, ctx.usn)?; diff --git a/rslib/src/scheduler/new.rs b/rslib/src/scheduler/new.rs index 6eacff19c..961debdc2 100644 --- a/rslib/src/scheduler/new.rs +++ b/rslib/src/scheduler/new.rs @@ -155,8 +155,7 @@ impl Collection { let usn = self.usn()?; let mut position = self.get_next_card_position(); self.transact(Op::ScheduleAsNew, |col| { - col.storage.set_search_table_to_card_ids(cids, true)?; - let cards = col.storage.all_searched_cards_in_search_order()?; + let cards = col.all_cards_for_ids(cids, true)?; for mut card in cards { let original = card.clone(); if card.schedule_as_new(position, reset_counts, restore_position) { @@ -168,7 +167,6 @@ impl Collection { col.update_card_inner(&mut card, original, usn)?; } col.set_next_card_position(position)?; - col.storage.clear_searched_cards_table()?; match context { Some(ScheduleAsNewContext::Browser) => { @@ -234,8 +232,7 @@ impl Collection { if shift { self.shift_existing_cards(starting_from, step * cids.len() as u32, usn, v2)?; } - self.storage.set_search_table_to_card_ids(cids, true)?; - let cards = self.storage.all_searched_cards_in_search_order()?; + let cards = self.all_cards_for_ids(cids, true)?; let sorter = NewCardSorter::new(&cards, starting_from, step, order); let mut count = 0; for mut card in cards { @@ -245,7 +242,6 @@ impl Collection { self.update_card_inner(&mut card, original, usn)?; } } - self.storage.clear_searched_cards_table()?; Ok(count) } @@ -286,13 +282,11 @@ impl Collection { } fn shift_existing_cards(&mut self, start: u32, by: u32, usn: Usn, v2: bool) -> Result<()> { - self.storage.search_cards_at_or_above_position(start)?; - for mut card in self.storage.all_searched_cards()? { + for mut card in self.storage.all_cards_at_or_above_position(start)? { let original = card.clone(); card.set_new_position(card.due as u32 + by, v2); self.update_card_inner(&mut card, original, usn)?; } - self.storage.clear_searched_cards_table()?; Ok(()) } } diff --git a/rslib/src/scheduler/reviews.rs b/rslib/src/scheduler/reviews.rs index 943d620cd..40b7f268e 100644 --- a/rslib/src/scheduler/reviews.rs +++ b/rslib/src/scheduler/reviews.rs @@ -112,8 +112,7 @@ impl Collection { let distribution = Uniform::from(spec.min..=spec.max); let mut decks_initial_ease: HashMap = HashMap::new(); self.transact(Op::SetDueDate, |col| { - col.storage.set_search_table_to_card_ids(cids, false)?; - for mut card in col.storage.all_searched_cards()? { + for mut card in col.all_cards_for_ids(cids, false)? { let deck_id = card.original_deck_id.or(card.deck_id); let ease_factor = match decks_initial_ease.get(&deck_id) { Some(ease) => *ease, @@ -139,7 +138,6 @@ impl Collection { col.log_manually_scheduled_review(&card, &original, usn)?; col.update_card_inner(&mut card, original, usn)?; } - col.storage.clear_searched_cards_table()?; if let Some(key) = context { col.set_config_string_inner(key, days)?; } diff --git a/rslib/src/scheduler/upgrade.rs b/rslib/src/scheduler/upgrade.rs index 79a2f472b..6ba42d8ab 100644 --- a/rslib/src/scheduler/upgrade.rs +++ b/rslib/src/scheduler/upgrade.rs @@ -130,21 +130,21 @@ impl Collection { } fn upgrade_cards_to_v2(&mut self) -> Result<()> { - let count = self.search_cards_into_table( + let guard = self.search_cards_into_table( // can't add 'is:learn' here, as it matches on card type, not card queue "deck:filtered OR is:review", SortMode::NoOrder, )?; - if count > 0 { - let decks = self.storage.get_decks_map()?; - let configs = self.storage.get_deck_config_map()?; - self.storage.for_each_card_in_search(|mut card| { + if guard.cards > 0 { + let decks = guard.col.storage.get_decks_map()?; + let configs = guard.col.storage.get_deck_config_map()?; + guard.col.storage.for_each_card_in_search(|mut card| { let filtered_info = get_filter_info_for_card(&card, &decks, &configs); card.upgrade_to_v2(filtered_info); - self.storage.update_card(&card) + guard.col.storage.update_card(&card) })?; } - self.storage.clear_searched_cards_table() + Ok(()) } } #[cfg(test)] diff --git a/rslib/src/search/mod.rs b/rslib/src/search/mod.rs index dd17cb01d..f7212c8ed 100644 --- a/rslib/src/search/mod.rs +++ b/rslib/src/search/mod.rs @@ -122,6 +122,32 @@ where } } +pub struct CardTableGuard<'a> { + pub col: &'a mut Collection, + pub cards: usize, +} + +impl Drop for CardTableGuard<'_> { + fn drop(&mut self) { + if let Err(err) = self.col.storage.clear_searched_cards_table() { + println!("{err:?}"); + } + } +} + +pub struct NoteTableGuard<'a> { + pub col: &'a mut Collection, + pub notes: usize, +} + +impl Drop for NoteTableGuard<'_> { + fn drop(&mut self) { + if let Err(err) = self.col.storage.clear_searched_notes_table() { + println!("{err:?}"); + } + } +} + impl Collection { pub fn search_cards(&mut self, search: N, mode: SortMode) -> Result> where @@ -188,12 +214,14 @@ impl Collection { } /// Place the matched card ids into a temporary 'search_cids' table - /// instead of returning them. Use clear_searched_cards() to remove it. - /// Returns number of added cards. - pub(crate) fn search_cards_into_table(&mut self, search: N, mode: SortMode) -> Result - where - N: TryIntoSearch, - { + /// instead of returning them. Returns a guard with a collection reference + /// and the number of added cards. When the guard is dropped, the temporary + /// table is cleaned up. + pub(crate) fn search_cards_into_table( + &mut self, + search: impl TryIntoSearch, + mode: SortMode, + ) -> Result { let top_node = search.try_into_search()?; let writer = SqlWriter::new(self, ReturnItemType::Cards); let want_order = mode != SortMode::NoOrder; @@ -209,30 +237,64 @@ impl Collection { } let sql = format!("insert into search_cids {}", sql); - self.storage + let cards = self + .storage .db .prepare(&sql)? - .execute(params_from_iter(args)) - .map_err(Into::into) + .execute(params_from_iter(args))?; + + Ok(CardTableGuard { cards, col: self }) } - pub(crate) fn all_cards_for_search(&mut self, search: N) -> Result> - where - N: TryIntoSearch, - { - self.search_cards_into_table(search, SortMode::NoOrder)?; - let cards = self.storage.all_searched_cards(); - self.storage.clear_searched_cards_table()?; - cards + pub(crate) fn all_cards_for_search(&mut self, search: impl TryIntoSearch) -> Result> { + let guard = self.search_cards_into_table(search, SortMode::NoOrder)?; + guard.col.storage.all_searched_cards() } - /// Place the matched note ids into a temporary 'search_nids' table - /// instead of returning them. Use clear_searched_notes() to remove it. - /// Returns number of added notes. - pub(crate) fn search_notes_into_table(&mut self, search: N) -> Result - where - N: TryIntoSearch, - { + pub(crate) fn all_cards_for_search_in_order( + &mut self, + search: impl TryIntoSearch, + mode: SortMode, + ) -> Result> { + let guard = self.search_cards_into_table(search, mode)?; + guard.col.storage.all_searched_cards_in_search_order() + } + + pub(crate) fn all_cards_for_ids( + &self, + cards: &[CardId], + preserve_order: bool, + ) -> Result> { + self.storage.with_searched_cards_table(preserve_order, || { + self.storage.set_search_table_to_card_ids(cards)?; + if preserve_order { + self.storage.all_searched_cards_in_search_order() + } else { + self.storage.all_searched_cards() + } + }) + } + + pub(crate) fn for_each_card_in_search( + &mut self, + search: impl TryIntoSearch, + mut func: impl FnMut(&Collection, Card) -> Result<()>, + ) -> Result<()> { + let guard = self.search_cards_into_table(search, SortMode::NoOrder)?; + guard + .col + .storage + .for_each_card_in_search(|card| func(guard.col, card)) + } + + /// Place the matched card ids into a temporary 'search_nids' table + /// instead of returning them. Returns a guard with a collection reference + /// and the number of added notes. When the guard is dropped, the temporary + /// table is cleaned up. + pub(crate) fn search_notes_into_table( + &mut self, + search: impl TryIntoSearch, + ) -> Result { let top_node = search.try_into_search()?; let writer = SqlWriter::new(self, ReturnItemType::Notes); let mode = SortMode::NoOrder; @@ -242,11 +304,21 @@ impl Collection { self.storage.setup_searched_notes_table()?; let sql = format!("insert into search_nids {}", sql); - self.storage + let notes = self + .storage .db .prepare(&sql)? - .execute(params_from_iter(args)) - .map_err(Into::into) + .execute(params_from_iter(args))?; + + Ok(NoteTableGuard { notes, col: self }) + } + + /// Place the ids of cards with notes in 'search_nids' into 'search_cids'. + /// Returns number of added cards. + pub(crate) fn search_cards_of_notes_into_table(&mut self) -> Result { + self.storage.setup_searched_cards_table()?; + let cards = self.storage.search_cards_of_notes_into_table()?; + Ok(CardTableGuard { cards, col: self }) } } diff --git a/rslib/src/stats/graphs.rs b/rslib/src/stats/graphs.rs index 0fb7d7a86..8eeb311eb 100644 --- a/rslib/src/stats/graphs.rs +++ b/rslib/src/stats/graphs.rs @@ -15,9 +15,9 @@ impl Collection { search: &str, days: u32, ) -> Result { - self.search_cards_into_table(search, SortMode::NoOrder)?; + let guard = self.search_cards_into_table(search, SortMode::NoOrder)?; let all = search.trim().is_empty(); - self.graph_data(all, days) + guard.col.graph_data(all, days) } fn graph_data(&mut self, all: bool, days: u32) -> Result { @@ -41,8 +41,6 @@ impl Collection { .get_pb_revlog_entries_for_searched_cards(revlog_start)? }; - self.storage.clear_searched_cards_table()?; - Ok(pb::GraphsResponse { cards: cards.into_iter().map(Into::into).collect(), revlog, diff --git a/rslib/src/storage/card/mod.rs b/rslib/src/storage/card/mod.rs index 0b5c364e2..2e0549d06 100644 --- a/rslib/src/storage/card/mod.rs +++ b/rslib/src/storage/card/mod.rs @@ -408,14 +408,15 @@ impl super::SqliteStorage { note_ids: &[NoteId], ordinal: usize, ) -> Result> { - self.set_search_table_to_note_ids(note_ids)?; - self.db - .prepare_cached(concat!( - include_str!("get_card.sql"), - " where nid in (select nid from search_nids) and ord > ?" - ))? - .query_and_then([ordinal as i64], |r| row_to_card(r).map_err(Into::into))? - .collect() + self.with_ids_in_searched_notes_table(note_ids, || { + self.db + .prepare_cached(concat!( + include_str!("get_card.sql"), + " where nid in (select nid from search_nids) and ord > ?" + ))? + .query_and_then([ordinal as i64], |r| row_to_card(r).map_err(Into::into))? + .collect() + }) } pub(crate) fn all_card_ids_of_note_in_template_order( @@ -455,16 +456,14 @@ impl super::SqliteStorage { Ok(cids) } - /// Place matching card ids into the search table. - pub(crate) fn search_siblings_for_bury( + pub(crate) fn all_siblings_for_bury( &self, cid: CardId, nid: NoteId, include_new: bool, include_reviews: bool, include_day_learn: bool, - ) -> Result<()> { - self.setup_searched_cards_table()?; + ) -> Result> { let params = named_params! { ":card_id": cid, ":note_id": nid, @@ -475,10 +474,27 @@ impl super::SqliteStorage { ":review_queue": CardQueue::Review as i8, ":daylearn_queue": CardQueue::DayLearn as i8, }; - self.db - .prepare_cached(include_str!("siblings_for_bury.sql"))? - .execute(params)?; - Ok(()) + self.with_searched_cards_table(false, || { + self.db + .prepare_cached(include_str!("siblings_for_bury.sql"))? + .execute(params)?; + self.all_searched_cards() + }) + } + + pub(crate) fn with_searched_cards_table( + &self, + preserve_order: bool, + func: impl FnOnce() -> Result, + ) -> Result { + if preserve_order { + self.setup_searched_cards_table_to_preserve_order()?; + } else { + self.setup_searched_cards_table()?; + } + let result = func(); + self.clear_searched_cards_table()?; + result } pub(crate) fn note_ids_of_cards(&self, cids: &[CardId]) -> Result> { @@ -500,7 +516,6 @@ impl super::SqliteStorage { /// Place the ids of cards with notes in 'search_nids' into 'search_cids'. /// Returns number of added cards. pub(crate) fn search_cards_of_notes_into_table(&self) -> Result { - self.setup_searched_cards_table()?; self.db .prepare(include_str!("search_cards_of_notes_into_table.sql"))? .execute([]) @@ -576,12 +591,13 @@ impl super::SqliteStorage { .unwrap() } - pub(crate) fn search_cards_at_or_above_position(&self, start: u32) -> Result<()> { - self.setup_searched_cards_table()?; - self.db - .prepare(include_str!("at_or_above_position.sql"))? - .execute([start, CardType::New as u32])?; - Ok(()) + pub(crate) fn all_cards_at_or_above_position(&self, start: u32) -> Result> { + self.with_searched_cards_table(false, || { + self.db + .prepare(include_str!("at_or_above_position.sql"))? + .execute([start, CardType::New as u32])?; + self.all_searched_cards() + }) } pub(crate) fn setup_searched_cards_table(&self) -> Result<()> { @@ -603,24 +619,13 @@ impl super::SqliteStorage { /// Injects the provided card IDs into the search_cids table, for /// when ids have arrived outside of a search. - /// Clear with clear_searched_cards_table(). - pub(crate) fn set_search_table_to_card_ids( - &mut self, - cards: &[CardId], - preserve_order: bool, - ) -> Result<()> { - if preserve_order { - self.setup_searched_cards_table_to_preserve_order()?; - } else { - self.setup_searched_cards_table()?; - } + pub(crate) fn set_search_table_to_card_ids(&self, cards: &[CardId]) -> Result<()> { let mut stmt = self .db .prepare_cached("insert into search_cids values (?)")?; for cid in cards { stmt.execute([cid])?; } - Ok(()) } diff --git a/rslib/src/storage/note/mod.rs b/rslib/src/storage/note/mod.rs index 11466be46..45b000705 100644 --- a/rslib/src/storage/note/mod.rs +++ b/rslib/src/storage/note/mod.rs @@ -222,22 +222,18 @@ impl super::SqliteStorage { .transpose() } - pub(crate) fn get_note_tags_by_id_list( - &mut self, - note_ids: &[NoteId], - ) -> Result> { - self.set_search_table_to_note_ids(note_ids)?; - let out = self - .db - .prepare_cached(&format!( - "{} where id in (select nid from search_nids)", - include_str!("get_tags.sql") - ))? - .query_and_then([], row_to_note_tags)? - .collect::>>()?; - self.clear_searched_notes_table()?; - Ok(out) + pub(crate) fn get_note_tags_by_id_list(&self, note_ids: &[NoteId]) -> Result> { + self.with_ids_in_searched_notes_table(note_ids, || { + self.db + .prepare_cached(&format!( + "{} where id in (select nid from search_nids)", + include_str!("get_tags.sql") + ))? + .query_and_then([], row_to_note_tags)? + .collect() + }) } + pub(crate) fn for_each_note_tag_in_searched_notes(&self, mut func: F) -> Result<()> where F: FnMut(&str), @@ -297,20 +293,23 @@ impl super::SqliteStorage { Ok(()) } - /// Injects the provided card IDs into the search_nids table, for - /// when ids have arrived outside of a search. - /// Clear with clear_searched_notes_table(). + /// Executes the closure with the note ids placed in the search_nids table. /// WARNING: the column name is nid, not id. - pub(crate) fn set_search_table_to_note_ids(&mut self, notes: &[NoteId]) -> Result<()> { + pub(crate) fn with_ids_in_searched_notes_table( + &self, + note_ids: &[NoteId], + func: impl FnOnce() -> Result, + ) -> Result { self.setup_searched_notes_table()?; let mut stmt = self .db .prepare_cached("insert into search_nids values (?)")?; - for nid in notes { + for nid in note_ids { stmt.execute([nid])?; } - - Ok(()) + let result = func(); + self.clear_searched_notes_table()?; + result } /// Cards will arrive in card id order, not search order. diff --git a/rslib/src/tags/notes.rs b/rslib/src/tags/notes.rs index 85edef63e..bfd74a4e6 100644 --- a/rslib/src/tags/notes.rs +++ b/rslib/src/tags/notes.rs @@ -10,16 +10,18 @@ use crate::{prelude::*, search::SearchNode}; impl Collection { pub(crate) fn all_tags_in_deck(&mut self, deck_id: DeckId) -> Result>> { - self.search_notes_into_table(SearchNode::DeckIdWithChildren(deck_id))?; + let guard = self.search_notes_into_table(SearchNode::DeckIdWithChildren(deck_id))?; let mut all_tags: HashSet> = HashSet::new(); - self.storage.for_each_note_tag_in_searched_notes(|tags| { - for tag in split_tags(tags) { - // A benchmark on a large deck indicates that nothing is gained by using a Cow and skipping - // an allocation in the duplicate case, and this approach is simpler. - all_tags.insert(UniCase::new(tag.to_string())); - } - })?; - self.storage.clear_searched_notes_table()?; + guard + .col + .storage + .for_each_note_tag_in_searched_notes(|tags| { + for tag in split_tags(tags) { + // A benchmark on a large deck indicates that nothing is gained by using a Cow and skipping + // an allocation in the duplicate case, and this approach is simpler. + all_tags.insert(UniCase::new(tag.to_string())); + } + })?; Ok(all_tags) } }