Improve temporary table handling (#1976)

* Use all_cards_for_search() helper

* Add CardTableGuard

* Add for_each_card_in_search() helper

* Add all_cards_for_search_in_order() helper

* Add all_cards_for_ids() helper

* Return siblings for bury instead of only searching

* Remove redundant clear_searched_cards_table() calls

* Add with_searched_cards_table()

* Remove false comment

* Add NoteTableGuard

* Add with_ids_in_searched_notes_table() helper

* Make some last routines use table helpers
This commit is contained in:
RumovZ 2022-07-22 09:51:26 +02:00 committed by GitHub
parent 09841c7c0f
commit 173a5bfed5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 243 additions and 181 deletions

View file

@ -274,12 +274,11 @@ impl Collection {
))?; ))?;
let config = self.get_deck_config(config_id, true)?.unwrap(); let config = self.get_deck_config(config_id, true)?.unwrap();
let mut steps_adjuster = RemainingStepsAdjuster::new(&config); let mut steps_adjuster = RemainingStepsAdjuster::new(&config);
self.storage.set_search_table_to_card_ids(cards, false)?;
let sched = self.scheduler_version(); let sched = self.scheduler_version();
let usn = self.usn()?; let usn = self.usn()?;
self.transact(Op::SetCardDeck, |col| { self.transact(Op::SetCardDeck, |col| {
let mut count = 0; 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 { if card.deck_id == deck_id {
continue; continue;
} }
@ -299,11 +298,10 @@ impl Collection {
} }
let flag = flag as u8; let flag = flag as u8;
self.storage.set_search_table_to_card_ids(cards, false)?;
let usn = self.usn()?; let usn = self.usn()?;
self.transact(Op::SetFlag, |col| { self.transact(Op::SetFlag, |col| {
let mut count = 0; 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(); let original = card.clone();
if card.set_flag(flag) { if card.set_flag(flag) {
// To avoid having to rebuild the study queues, we mark the card as requiring // To avoid having to rebuild the study queues, we mark the card as requiring

View file

@ -12,6 +12,7 @@ use crate::{
latex::extract_latex, latex::extract_latex,
prelude::*, prelude::*,
revlog::RevlogEntry, revlog::RevlogEntry,
search::{CardTableGuard, NoteTableGuard},
text::{extract_media_refs, extract_underscored_css_imports, extract_underscored_references}, text::{extract_media_refs, extract_underscored_css_imports, extract_underscored_references},
}; };
@ -37,21 +38,22 @@ impl ExchangeData {
) -> Result<()> { ) -> Result<()> {
self.days_elapsed = col.timing_today()?.days_elapsed; self.days_elapsed = col.timing_today()?.days_elapsed;
self.creation_utc_offset = col.get_creation_utc_offset(); self.creation_utc_offset = col.get_creation_utc_offset();
self.notes = col.gather_notes(search)?; let (notes, guard) = col.gather_notes(search)?;
self.cards = col.gather_cards()?; self.notes = notes;
self.decks = col.gather_decks()?; let (cards, guard) = guard.col.gather_cards()?;
self.notetypes = col.gather_notetypes()?; self.cards = cards;
self.decks = guard.col.gather_decks()?;
self.notetypes = guard.col.gather_notetypes()?;
self.check_ids()?; self.check_ids()?;
if with_scheduling { if with_scheduling {
self.revlog = col.gather_revlog()?; self.revlog = guard.col.gather_revlog()?;
self.deck_configs = col.gather_deck_configs(&self.decks)?; self.deck_configs = guard.col.gather_deck_configs(&self.decks)?;
} else { } else {
self.remove_scheduling_information(col); self.remove_scheduling_information(guard.col);
}; };
col.storage.clear_searched_notes_table()?; Ok(())
col.storage.clear_searched_cards_table()
} }
pub(super) fn gather_media_names( pub(super) fn gather_media_names(
@ -171,14 +173,22 @@ fn svg_getter(notetypes: &[Notetype]) -> impl Fn(NotetypeId) -> bool {
} }
impl Collection { impl Collection {
fn gather_notes(&mut self, search: impl TryIntoSearch) -> Result<Vec<Note>> { fn gather_notes(&mut self, search: impl TryIntoSearch) -> Result<(Vec<Note>, NoteTableGuard)> {
self.search_notes_into_table(search)?; let guard = self.search_notes_into_table(search)?;
self.storage.all_searched_notes() guard
.col
.storage
.all_searched_notes()
.map(|notes| (notes, guard))
} }
fn gather_cards(&mut self) -> Result<Vec<Card>> { fn gather_cards(&mut self) -> Result<(Vec<Card>, CardTableGuard)> {
self.storage.search_cards_of_notes_into_table()?; let guard = self.search_cards_of_notes_into_table()?;
self.storage.all_searched_cards() guard
.col
.storage
.all_searched_cards()
.map(|cards| (cards, guard))
} }
fn gather_decks(&mut self) -> Result<Vec<Deck>> { fn gather_decks(&mut self) -> Result<Vec<Deck>> {

View file

@ -53,16 +53,15 @@ impl Collection {
progress.call(ExportProgress::File)?; progress.call(ExportProgress::File)?;
let mut incrementor = progress.incrementor(ExportProgress::Notes); let mut incrementor = progress.incrementor(ExportProgress::Notes);
self.search_notes_into_table(request.search_node())?; let guard = self.search_notes_into_table(request.search_node())?;
let ctx = NoteContext::new(&request, self)?; let ctx = NoteContext::new(&request, guard.col)?;
let mut writer = note_file_writer_with_header(&request.out_path, &ctx)?; 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()?; incrementor.increment()?;
writer.write_record(ctx.record(&note))?; writer.write_record(ctx.record(&note))?;
Ok(()) Ok(())
})?; })?;
writer.flush()?; writer.flush()?;
self.storage.clear_searched_notes_table()?;
Ok(incrementor.count()) Ok(incrementor.count())
} }

View file

@ -8,7 +8,7 @@ use std::collections::{HashMap, HashSet};
use super::{CardGenContext, Notetype, NotetypeKind}; use super::{CardGenContext, Notetype, NotetypeKind};
use crate::{ use crate::{
prelude::*, prelude::*,
search::{JoinSearches, Node, SearchNode, SortMode, TemplateKind}, search::{JoinSearches, Node, SearchNode, TemplateKind},
storage::comma_separated_ids, storage::comma_separated_ids,
}; };
@ -294,11 +294,9 @@ impl Collection {
if !map.removed.is_empty() { if !map.removed.is_empty() {
let ords = let ords =
SearchBuilder::any(map.removed.iter().map(|o| TemplateKind::Ordinal(*o as u16))); 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.all_cards_for_search(nids.and(ords))? {
for card in self.storage.all_searched_cards()? {
self.remove_card_and_add_grave_undoable(card, usn)?; self.remove_card_and_add_grave_undoable(card, usn)?;
} }
self.storage.clear_searched_cards_table()?;
} }
Ok(()) Ok(())
@ -316,14 +314,12 @@ impl Collection {
.keys() .keys()
.map(|o| TemplateKind::Ordinal(*o as u16)), .map(|o| TemplateKind::Ordinal(*o as u16)),
); );
self.search_cards_into_table(nids.and(ords), SortMode::NoOrder)?; for mut card in self.all_cards_for_search(nids.and(ords))? {
for mut card in self.storage.all_searched_cards()? {
let original = card.clone(); let original = card.clone();
card.template_idx = card.template_idx =
*map.remapped.get(&(card.template_idx as usize)).unwrap() as u16; *map.remapped.get(&(card.template_idx as usize)).unwrap() as u16;
self.update_card_inner(&mut card, original, usn)?; self.update_card_inner(&mut card, original, usn)?;
} }
self.storage.clear_searched_cards_table()?;
} }
Ok(()) Ok(())
@ -349,7 +345,6 @@ impl Collection {
{ {
self.remove_card_and_add_grave_undoable(card, usn)?; self.remove_card_and_add_grave_undoable(card, usn)?;
} }
self.storage.clear_searched_notes_table()?;
} }
Ok(()) Ok(())

View file

@ -8,7 +8,7 @@ use std::collections::HashMap;
use super::{CardGenContext, CardTemplate, Notetype}; use super::{CardGenContext, CardTemplate, Notetype};
use crate::{ use crate::{
prelude::*, prelude::*,
search::{JoinSearches, SortMode, TemplateKind}, search::{JoinSearches, TemplateKind},
}; };
/// True if any ordinals added, removed or reordered. /// True if any ordinals added, removed or reordered.
@ -148,22 +148,18 @@ impl Collection {
if !changes.removed.is_empty() { if !changes.removed.is_empty() {
let ords = let ords =
SearchBuilder::any(changes.removed.iter().cloned().map(TemplateKind::Ordinal)); SearchBuilder::any(changes.removed.iter().cloned().map(TemplateKind::Ordinal));
self.search_cards_into_table(nt.id.and(ords), SortMode::NoOrder)?; for card in self.all_cards_for_search(nt.id.and(ords))? {
for card in self.storage.all_searched_cards()? {
self.remove_card_and_add_grave_undoable(card, usn)?; self.remove_card_and_add_grave_undoable(card, usn)?;
} }
self.storage.clear_searched_cards_table()?;
} }
// update ordinals for cards with a repositioned template // update ordinals for cards with a repositioned template
if !changes.moved.is_empty() { if !changes.moved.is_empty() {
let ords = SearchBuilder::any(changes.moved.keys().cloned().map(TemplateKind::Ordinal)); 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.all_cards_for_search(nt.id.and(ords))? {
for mut card in self.storage.all_searched_cards()? {
let original = card.clone(); let original = card.clone();
card.template_idx = *changes.moved.get(&card.template_idx).unwrap(); card.template_idx = *changes.moved.get(&card.template_idx).unwrap();
self.update_card_inner(&mut card, original, usn)?; self.update_card_inner(&mut card, original, usn)?;
} }
self.storage.clear_searched_cards_table()?;
} }
if should_generate_cards(&changes, nt, old_templates) { if should_generate_cards(&changes, nt, old_templates) {

View file

@ -10,7 +10,7 @@ use crate::{
unbury_deck_request::Mode as UnburyDeckMode, unbury_deck_request::Mode as UnburyDeckMode,
}, },
prelude::*, prelude::*,
search::{JoinSearches, SearchNode, SortMode, StateKind}, search::{JoinSearches, SearchNode, StateKind},
}; };
impl Card { impl Card {
@ -42,32 +42,29 @@ impl Collection {
/// Unbury cards from the previous day. /// Unbury cards from the previous day.
/// Done automatically, and does not mark the cards as modified. /// Done automatically, and does not mark the cards as modified.
pub(crate) fn unbury_on_day_rollover(&mut self, today: u32) -> Result<()> { pub(crate) fn unbury_on_day_rollover(&mut self, today: u32) -> Result<()> {
self.search_cards_into_table("is:buried", SortMode::NoOrder)?; self.for_each_card_in_search(StateKind::Buried, |col, mut card| {
self.storage.for_each_card_in_search(|mut card| {
card.restore_queue_after_bury_or_suspend(); 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) self.set_last_unburied_day(today)
} }
/// Unsuspend/unbury cards in search table, and clear it. /// Unsuspend/unbury cards. Marks the cards as modified.
/// Marks the cards as modified. fn unsuspend_or_unbury_searched_cards(&mut self, cards: Vec<Card>) -> Result<()> {
fn unsuspend_or_unbury_searched_cards(&mut self) -> Result<()> {
let usn = self.usn()?; let usn = self.usn()?;
for original in self.storage.all_searched_cards()? { for original in cards {
let mut card = original.clone(); let mut card = original.clone();
if card.restore_queue_after_bury_or_suspend() { if card.restore_queue_after_bury_or_suspend() {
self.update_card_inner(&mut card, original, usn)?; 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<OpOutput<()>> { pub fn unbury_or_unsuspend_cards(&mut self, cids: &[CardId]) -> Result<OpOutput<()>> {
self.transact(Op::UnburyUnsuspend, |col| { self.transact(Op::UnburyUnsuspend, |col| {
col.storage.set_search_table_to_card_ids(cids, false)?; let cards = col.all_cards_for_ids(cids, false)?;
col.unsuspend_or_unbury_searched_cards() col.unsuspend_or_unbury_searched_cards(cards)
}) })
} }
@ -78,17 +75,19 @@ impl Collection {
UnburyDeckMode::SchedOnly => StateKind::SchedBuried, UnburyDeckMode::SchedOnly => StateKind::SchedBuried,
}; };
self.transact(Op::UnburyUnsuspend, |col| { self.transact(Op::UnburyUnsuspend, |col| {
col.search_cards_into_table( let cards =
SearchNode::DeckIdWithChildren(deck_id).and(state), col.all_cards_for_search(SearchNode::DeckIdWithChildren(deck_id).and(state))?;
SortMode::NoOrder, col.unsuspend_or_unbury_searched_cards(cards)
)?;
col.unsuspend_or_unbury_searched_cards()
}) })
} }
/// Bury/suspend cards in search table, and clear it. /// Bury/suspend cards in search table, and clear it.
/// Marks the cards as modified. /// Marks the cards as modified.
fn bury_or_suspend_searched_cards(&mut self, mode: BuryOrSuspendMode) -> Result<usize> { fn bury_or_suspend_cards_inner(
&mut self,
cards: Vec<Card>,
mode: BuryOrSuspendMode,
) -> Result<usize> {
let mut count = 0; let mut count = 0;
let usn = self.usn()?; let usn = self.usn()?;
let sched = self.scheduler_version(); 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(); let mut card = original.clone();
if card.queue != desired_queue { if card.queue != desired_queue {
// do not bury suspended cards as that would unsuspend them // do not bury suspended cards as that would unsuspend them
@ -121,8 +120,6 @@ impl Collection {
} }
} }
self.storage.clear_searched_cards_table()?;
Ok(count) Ok(count)
} }
@ -136,8 +133,8 @@ impl Collection {
BuryOrSuspendMode::BurySched | BuryOrSuspendMode::BuryUser => Op::Bury, BuryOrSuspendMode::BurySched | BuryOrSuspendMode::BuryUser => Op::Bury,
}; };
self.transact(op, |col| { self.transact(op, |col| {
col.storage.set_search_table_to_card_ids(cids, false)?; let cards = col.all_cards_for_ids(cids, false)?;
col.bury_or_suspend_searched_cards(mode) col.bury_or_suspend_cards_inner(cards, mode)
}) })
} }
@ -149,14 +146,14 @@ impl Collection {
include_reviews: bool, include_reviews: bool,
include_day_learn: bool, include_day_learn: bool,
) -> Result<usize> { ) -> Result<usize> {
self.storage.search_siblings_for_bury( let cards = self.storage.all_siblings_for_bury(
cid, cid,
nid, nid,
include_new, include_new,
include_reviews, include_reviews,
include_day_learn, include_day_learn,
)?; )?;
self.bury_or_suspend_searched_cards(BuryOrSuspendMode::BurySched) self.bury_or_suspend_cards_inner(cards, BuryOrSuspendMode::BurySched)
} }
} }

View file

@ -135,8 +135,7 @@ impl Collection {
); );
let order = order_and_limit_for_search(term, ctx.today); let order = order_and_limit_for_search(term, ctx.today);
self.search_cards_into_table(&search, SortMode::Custom(order))?; for mut card in self.all_cards_for_search_in_order(&search, SortMode::Custom(order))? {
for mut card in self.storage.all_searched_cards_in_search_order()? {
let original = card.clone(); let original = card.clone();
card.move_into_filtered_deck(ctx, position); card.move_into_filtered_deck(ctx, position);
self.update_card_inner(&mut card, original, ctx.usn)?; self.update_card_inner(&mut card, original, ctx.usn)?;

View file

@ -155,8 +155,7 @@ impl Collection {
let usn = self.usn()?; let usn = self.usn()?;
let mut position = self.get_next_card_position(); let mut position = self.get_next_card_position();
self.transact(Op::ScheduleAsNew, |col| { self.transact(Op::ScheduleAsNew, |col| {
col.storage.set_search_table_to_card_ids(cids, true)?; let cards = col.all_cards_for_ids(cids, true)?;
let cards = col.storage.all_searched_cards_in_search_order()?;
for mut card in cards { for mut card in cards {
let original = card.clone(); let original = card.clone();
if card.schedule_as_new(position, reset_counts, restore_position) { 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.update_card_inner(&mut card, original, usn)?;
} }
col.set_next_card_position(position)?; col.set_next_card_position(position)?;
col.storage.clear_searched_cards_table()?;
match context { match context {
Some(ScheduleAsNewContext::Browser) => { Some(ScheduleAsNewContext::Browser) => {
@ -234,8 +232,7 @@ impl Collection {
if shift { if shift {
self.shift_existing_cards(starting_from, step * cids.len() as u32, usn, v2)?; 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.all_cards_for_ids(cids, true)?;
let cards = self.storage.all_searched_cards_in_search_order()?;
let sorter = NewCardSorter::new(&cards, starting_from, step, order); let sorter = NewCardSorter::new(&cards, starting_from, step, order);
let mut count = 0; let mut count = 0;
for mut card in cards { for mut card in cards {
@ -245,7 +242,6 @@ impl Collection {
self.update_card_inner(&mut card, original, usn)?; self.update_card_inner(&mut card, original, usn)?;
} }
} }
self.storage.clear_searched_cards_table()?;
Ok(count) Ok(count)
} }
@ -286,13 +282,11 @@ impl Collection {
} }
fn shift_existing_cards(&mut self, start: u32, by: u32, usn: Usn, v2: bool) -> Result<()> { 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_cards_at_or_above_position(start)? {
for mut card in self.storage.all_searched_cards()? {
let original = card.clone(); let original = card.clone();
card.set_new_position(card.due as u32 + by, v2); card.set_new_position(card.due as u32 + by, v2);
self.update_card_inner(&mut card, original, usn)?; self.update_card_inner(&mut card, original, usn)?;
} }
self.storage.clear_searched_cards_table()?;
Ok(()) Ok(())
} }
} }

View file

@ -112,8 +112,7 @@ impl Collection {
let distribution = Uniform::from(spec.min..=spec.max); let distribution = Uniform::from(spec.min..=spec.max);
let mut decks_initial_ease: HashMap<DeckId, f32> = HashMap::new(); let mut decks_initial_ease: HashMap<DeckId, f32> = HashMap::new();
self.transact(Op::SetDueDate, |col| { self.transact(Op::SetDueDate, |col| {
col.storage.set_search_table_to_card_ids(cids, false)?; for mut card in col.all_cards_for_ids(cids, false)? {
for mut card in col.storage.all_searched_cards()? {
let deck_id = card.original_deck_id.or(card.deck_id); let deck_id = card.original_deck_id.or(card.deck_id);
let ease_factor = match decks_initial_ease.get(&deck_id) { let ease_factor = match decks_initial_ease.get(&deck_id) {
Some(ease) => *ease, Some(ease) => *ease,
@ -139,7 +138,6 @@ impl Collection {
col.log_manually_scheduled_review(&card, &original, usn)?; col.log_manually_scheduled_review(&card, &original, usn)?;
col.update_card_inner(&mut card, original, usn)?; col.update_card_inner(&mut card, original, usn)?;
} }
col.storage.clear_searched_cards_table()?;
if let Some(key) = context { if let Some(key) = context {
col.set_config_string_inner(key, days)?; col.set_config_string_inner(key, days)?;
} }

View file

@ -130,21 +130,21 @@ impl Collection {
} }
fn upgrade_cards_to_v2(&mut self) -> Result<()> { 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 // can't add 'is:learn' here, as it matches on card type, not card queue
"deck:filtered OR is:review", "deck:filtered OR is:review",
SortMode::NoOrder, SortMode::NoOrder,
)?; )?;
if count > 0 { if guard.cards > 0 {
let decks = self.storage.get_decks_map()?; let decks = guard.col.storage.get_decks_map()?;
let configs = self.storage.get_deck_config_map()?; let configs = guard.col.storage.get_deck_config_map()?;
self.storage.for_each_card_in_search(|mut card| { guard.col.storage.for_each_card_in_search(|mut card| {
let filtered_info = get_filter_info_for_card(&card, &decks, &configs); let filtered_info = get_filter_info_for_card(&card, &decks, &configs);
card.upgrade_to_v2(filtered_info); 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)] #[cfg(test)]

View file

@ -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 { impl Collection {
pub fn search_cards<N>(&mut self, search: N, mode: SortMode) -> Result<Vec<CardId>> pub fn search_cards<N>(&mut self, search: N, mode: SortMode) -> Result<Vec<CardId>>
where where
@ -188,12 +214,14 @@ impl Collection {
} }
/// Place the matched card ids into a temporary 'search_cids' table /// Place the matched card ids into a temporary 'search_cids' table
/// instead of returning them. Use clear_searched_cards() to remove it. /// instead of returning them. Returns a guard with a collection reference
/// Returns number of added cards. /// and the number of added cards. When the guard is dropped, the temporary
pub(crate) fn search_cards_into_table<N>(&mut self, search: N, mode: SortMode) -> Result<usize> /// table is cleaned up.
where pub(crate) fn search_cards_into_table(
N: TryIntoSearch, &mut self,
{ search: impl TryIntoSearch,
mode: SortMode,
) -> Result<CardTableGuard> {
let top_node = search.try_into_search()?; let top_node = search.try_into_search()?;
let writer = SqlWriter::new(self, ReturnItemType::Cards); let writer = SqlWriter::new(self, ReturnItemType::Cards);
let want_order = mode != SortMode::NoOrder; let want_order = mode != SortMode::NoOrder;
@ -209,30 +237,64 @@ impl Collection {
} }
let sql = format!("insert into search_cids {}", sql); let sql = format!("insert into search_cids {}", sql);
self.storage let cards = self
.storage
.db .db
.prepare(&sql)? .prepare(&sql)?
.execute(params_from_iter(args)) .execute(params_from_iter(args))?;
.map_err(Into::into)
Ok(CardTableGuard { cards, col: self })
} }
pub(crate) fn all_cards_for_search<N>(&mut self, search: N) -> Result<Vec<Card>> pub(crate) fn all_cards_for_search(&mut self, search: impl TryIntoSearch) -> Result<Vec<Card>> {
where let guard = self.search_cards_into_table(search, SortMode::NoOrder)?;
N: TryIntoSearch, guard.col.storage.all_searched_cards()
{
self.search_cards_into_table(search, SortMode::NoOrder)?;
let cards = self.storage.all_searched_cards();
self.storage.clear_searched_cards_table()?;
cards
} }
/// Place the matched note ids into a temporary 'search_nids' table pub(crate) fn all_cards_for_search_in_order(
/// instead of returning them. Use clear_searched_notes() to remove it. &mut self,
/// Returns number of added notes. search: impl TryIntoSearch,
pub(crate) fn search_notes_into_table<N>(&mut self, search: N) -> Result<usize> mode: SortMode,
where ) -> Result<Vec<Card>> {
N: TryIntoSearch, 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<Vec<Card>> {
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<NoteTableGuard> {
let top_node = search.try_into_search()?; let top_node = search.try_into_search()?;
let writer = SqlWriter::new(self, ReturnItemType::Notes); let writer = SqlWriter::new(self, ReturnItemType::Notes);
let mode = SortMode::NoOrder; let mode = SortMode::NoOrder;
@ -242,11 +304,21 @@ impl Collection {
self.storage.setup_searched_notes_table()?; self.storage.setup_searched_notes_table()?;
let sql = format!("insert into search_nids {}", sql); let sql = format!("insert into search_nids {}", sql);
self.storage let notes = self
.storage
.db .db
.prepare(&sql)? .prepare(&sql)?
.execute(params_from_iter(args)) .execute(params_from_iter(args))?;
.map_err(Into::into)
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<CardTableGuard> {
self.storage.setup_searched_cards_table()?;
let cards = self.storage.search_cards_of_notes_into_table()?;
Ok(CardTableGuard { cards, col: self })
} }
} }

View file

@ -15,9 +15,9 @@ impl Collection {
search: &str, search: &str,
days: u32, days: u32,
) -> Result<pb::GraphsResponse> { ) -> Result<pb::GraphsResponse> {
self.search_cards_into_table(search, SortMode::NoOrder)?; let guard = self.search_cards_into_table(search, SortMode::NoOrder)?;
let all = search.trim().is_empty(); 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<pb::GraphsResponse> { fn graph_data(&mut self, all: bool, days: u32) -> Result<pb::GraphsResponse> {
@ -41,8 +41,6 @@ impl Collection {
.get_pb_revlog_entries_for_searched_cards(revlog_start)? .get_pb_revlog_entries_for_searched_cards(revlog_start)?
}; };
self.storage.clear_searched_cards_table()?;
Ok(pb::GraphsResponse { Ok(pb::GraphsResponse {
cards: cards.into_iter().map(Into::into).collect(), cards: cards.into_iter().map(Into::into).collect(),
revlog, revlog,

View file

@ -408,14 +408,15 @@ impl super::SqliteStorage {
note_ids: &[NoteId], note_ids: &[NoteId],
ordinal: usize, ordinal: usize,
) -> Result<Vec<Card>> { ) -> Result<Vec<Card>> {
self.set_search_table_to_note_ids(note_ids)?; self.with_ids_in_searched_notes_table(note_ids, || {
self.db self.db
.prepare_cached(concat!( .prepare_cached(concat!(
include_str!("get_card.sql"), include_str!("get_card.sql"),
" where nid in (select nid from search_nids) and ord > ?" " 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))? .query_and_then([ordinal as i64], |r| row_to_card(r).map_err(Into::into))?
.collect() .collect()
})
} }
pub(crate) fn all_card_ids_of_note_in_template_order( pub(crate) fn all_card_ids_of_note_in_template_order(
@ -455,16 +456,14 @@ impl super::SqliteStorage {
Ok(cids) Ok(cids)
} }
/// Place matching card ids into the search table. pub(crate) fn all_siblings_for_bury(
pub(crate) fn search_siblings_for_bury(
&self, &self,
cid: CardId, cid: CardId,
nid: NoteId, nid: NoteId,
include_new: bool, include_new: bool,
include_reviews: bool, include_reviews: bool,
include_day_learn: bool, include_day_learn: bool,
) -> Result<()> { ) -> Result<Vec<Card>> {
self.setup_searched_cards_table()?;
let params = named_params! { let params = named_params! {
":card_id": cid, ":card_id": cid,
":note_id": nid, ":note_id": nid,
@ -475,10 +474,27 @@ impl super::SqliteStorage {
":review_queue": CardQueue::Review as i8, ":review_queue": CardQueue::Review as i8,
":daylearn_queue": CardQueue::DayLearn as i8, ":daylearn_queue": CardQueue::DayLearn as i8,
}; };
self.db self.with_searched_cards_table(false, || {
.prepare_cached(include_str!("siblings_for_bury.sql"))? self.db
.execute(params)?; .prepare_cached(include_str!("siblings_for_bury.sql"))?
Ok(()) .execute(params)?;
self.all_searched_cards()
})
}
pub(crate) fn with_searched_cards_table<T>(
&self,
preserve_order: bool,
func: impl FnOnce() -> Result<T>,
) -> Result<T> {
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<HashSet<NoteId>> { pub(crate) fn note_ids_of_cards(&self, cids: &[CardId]) -> Result<HashSet<NoteId>> {
@ -500,7 +516,6 @@ impl super::SqliteStorage {
/// Place the ids of cards with notes in 'search_nids' into 'search_cids'. /// Place the ids of cards with notes in 'search_nids' into 'search_cids'.
/// Returns number of added cards. /// Returns number of added cards.
pub(crate) fn search_cards_of_notes_into_table(&self) -> Result<usize> { pub(crate) fn search_cards_of_notes_into_table(&self) -> Result<usize> {
self.setup_searched_cards_table()?;
self.db self.db
.prepare(include_str!("search_cards_of_notes_into_table.sql"))? .prepare(include_str!("search_cards_of_notes_into_table.sql"))?
.execute([]) .execute([])
@ -576,12 +591,13 @@ impl super::SqliteStorage {
.unwrap() .unwrap()
} }
pub(crate) fn search_cards_at_or_above_position(&self, start: u32) -> Result<()> { pub(crate) fn all_cards_at_or_above_position(&self, start: u32) -> Result<Vec<Card>> {
self.setup_searched_cards_table()?; self.with_searched_cards_table(false, || {
self.db self.db
.prepare(include_str!("at_or_above_position.sql"))? .prepare(include_str!("at_or_above_position.sql"))?
.execute([start, CardType::New as u32])?; .execute([start, CardType::New as u32])?;
Ok(()) self.all_searched_cards()
})
} }
pub(crate) fn setup_searched_cards_table(&self) -> Result<()> { 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 /// Injects the provided card IDs into the search_cids table, for
/// when ids have arrived outside of a search. /// when ids have arrived outside of a search.
/// Clear with clear_searched_cards_table(). pub(crate) fn set_search_table_to_card_ids(&self, cards: &[CardId]) -> Result<()> {
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()?;
}
let mut stmt = self let mut stmt = self
.db .db
.prepare_cached("insert into search_cids values (?)")?; .prepare_cached("insert into search_cids values (?)")?;
for cid in cards { for cid in cards {
stmt.execute([cid])?; stmt.execute([cid])?;
} }
Ok(()) Ok(())
} }

View file

@ -222,22 +222,18 @@ impl super::SqliteStorage {
.transpose() .transpose()
} }
pub(crate) fn get_note_tags_by_id_list( pub(crate) fn get_note_tags_by_id_list(&self, note_ids: &[NoteId]) -> Result<Vec<NoteTags>> {
&mut self, self.with_ids_in_searched_notes_table(note_ids, || {
note_ids: &[NoteId], self.db
) -> Result<Vec<NoteTags>> { .prepare_cached(&format!(
self.set_search_table_to_note_ids(note_ids)?; "{} where id in (select nid from search_nids)",
let out = self include_str!("get_tags.sql")
.db ))?
.prepare_cached(&format!( .query_and_then([], row_to_note_tags)?
"{} where id in (select nid from search_nids)", .collect()
include_str!("get_tags.sql") })
))?
.query_and_then([], row_to_note_tags)?
.collect::<Result<Vec<_>>>()?;
self.clear_searched_notes_table()?;
Ok(out)
} }
pub(crate) fn for_each_note_tag_in_searched_notes<F>(&self, mut func: F) -> Result<()> pub(crate) fn for_each_note_tag_in_searched_notes<F>(&self, mut func: F) -> Result<()>
where where
F: FnMut(&str), F: FnMut(&str),
@ -297,20 +293,23 @@ impl super::SqliteStorage {
Ok(()) Ok(())
} }
/// Injects the provided card IDs into the search_nids table, for /// Executes the closure with the note ids placed in the search_nids table.
/// when ids have arrived outside of a search.
/// Clear with clear_searched_notes_table().
/// WARNING: the column name is nid, not id. /// 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<T>(
&self,
note_ids: &[NoteId],
func: impl FnOnce() -> Result<T>,
) -> Result<T> {
self.setup_searched_notes_table()?; self.setup_searched_notes_table()?;
let mut stmt = self let mut stmt = self
.db .db
.prepare_cached("insert into search_nids values (?)")?; .prepare_cached("insert into search_nids values (?)")?;
for nid in notes { for nid in note_ids {
stmt.execute([nid])?; stmt.execute([nid])?;
} }
let result = func();
Ok(()) self.clear_searched_notes_table()?;
result
} }
/// Cards will arrive in card id order, not search order. /// Cards will arrive in card id order, not search order.

View file

@ -10,16 +10,18 @@ use crate::{prelude::*, search::SearchNode};
impl Collection { impl Collection {
pub(crate) fn all_tags_in_deck(&mut self, deck_id: DeckId) -> Result<HashSet<UniCase<String>>> { pub(crate) fn all_tags_in_deck(&mut self, deck_id: DeckId) -> Result<HashSet<UniCase<String>>> {
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<UniCase<String>> = HashSet::new(); let mut all_tags: HashSet<UniCase<String>> = HashSet::new();
self.storage.for_each_note_tag_in_searched_notes(|tags| { guard
for tag in split_tags(tags) { .col
// A benchmark on a large deck indicates that nothing is gained by using a Cow and skipping .storage
// an allocation in the duplicate case, and this approach is simpler. .for_each_note_tag_in_searched_notes(|tags| {
all_tags.insert(UniCase::new(tag.to_string())); 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.
self.storage.clear_searched_notes_table()?; all_tags.insert(UniCase::new(tag.to_string()));
}
})?;
Ok(all_tags) Ok(all_tags)
} }
} }