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 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

View File

@ -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<Vec<Note>> {
self.search_notes_into_table(search)?;
self.storage.all_searched_notes()
fn gather_notes(&mut self, search: impl TryIntoSearch) -> Result<(Vec<Note>, 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<Vec<Card>> {
self.storage.search_cards_of_notes_into_table()?;
self.storage.all_searched_cards()
fn gather_cards(&mut self) -> Result<(Vec<Card>, 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<Vec<Deck>> {

View File

@ -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(&note))?;
Ok(())
})?;
writer.flush()?;
self.storage.clear_searched_notes_table()?;
Ok(incrementor.count())
}

View File

@ -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(())

View File

@ -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) {

View File

@ -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<Card>) -> 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<OpOutput<()>> {
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<usize> {
fn bury_or_suspend_cards_inner(
&mut self,
cards: Vec<Card>,
mode: BuryOrSuspendMode,
) -> Result<usize> {
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<usize> {
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)
}
}

View File

@ -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)?;

View File

@ -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(())
}
}

View File

@ -112,8 +112,7 @@ impl Collection {
let distribution = Uniform::from(spec.min..=spec.max);
let mut decks_initial_ease: HashMap<DeckId, f32> = 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)?;
}

View File

@ -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)]

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 {
pub fn search_cards<N>(&mut self, search: N, mode: SortMode) -> Result<Vec<CardId>>
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<N>(&mut self, search: N, mode: SortMode) -> Result<usize>
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<CardTableGuard> {
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<N>(&mut self, search: N) -> Result<Vec<Card>>
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<Vec<Card>> {
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<N>(&mut self, search: N) -> Result<usize>
where
N: TryIntoSearch,
{
pub(crate) fn all_cards_for_search_in_order(
&mut self,
search: impl TryIntoSearch,
mode: SortMode,
) -> Result<Vec<Card>> {
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 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<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,
days: u32,
) -> 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();
self.graph_data(all, days)
guard.col.graph_data(all, days)
}
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)?
};
self.storage.clear_searched_cards_table()?;
Ok(pb::GraphsResponse {
cards: cards.into_iter().map(Into::into).collect(),
revlog,

View File

@ -408,14 +408,15 @@ impl super::SqliteStorage {
note_ids: &[NoteId],
ordinal: usize,
) -> Result<Vec<Card>> {
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<Vec<Card>> {
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<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>> {
@ -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<usize> {
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<Vec<Card>> {
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(())
}

View File

@ -222,22 +222,18 @@ impl super::SqliteStorage {
.transpose()
}
pub(crate) fn get_note_tags_by_id_list(
&mut self,
note_ids: &[NoteId],
) -> Result<Vec<NoteTags>> {
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::<Result<Vec<_>>>()?;
self.clear_searched_notes_table()?;
Ok(out)
pub(crate) fn get_note_tags_by_id_list(&self, note_ids: &[NoteId]) -> Result<Vec<NoteTags>> {
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<F>(&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<T>(
&self,
note_ids: &[NoteId],
func: impl FnOnce() -> Result<T>,
) -> Result<T> {
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.

View File

@ -10,16 +10,18 @@ use crate::{prelude::*, search::SearchNode};
impl Collection {
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();
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)
}
}