diff --git a/rslib/src/scheduler/bury_and_suspend.rs b/rslib/src/scheduler/bury_and_suspend.rs index 59523d045..e8e0efc69 100644 --- a/rslib/src/scheduler/bury_and_suspend.rs +++ b/rslib/src/scheduler/bury_and_suspend.rs @@ -9,7 +9,7 @@ use crate::{ card::CardQueue, config::SchedulerVersion, prelude::*, - search::{SortMode, StateKind}, + search::{SearchNode, SortMode, StateKind}, }; impl Card { @@ -77,7 +77,10 @@ impl Collection { UnburyDeckMode::SchedOnly => StateKind::SchedBuried, }; self.transact(Op::UnburyUnsuspend, |col| { - col.search_cards_into_table(match_all![deck_id, state], SortMode::NoOrder)?; + col.search_cards_into_table( + match_all![SearchNode::DeckIdWithChildren(deck_id), state], + SortMode::NoOrder, + )?; col.unsuspend_or_unbury_searched_cards() }) } diff --git a/rslib/src/scheduler/new.rs b/rslib/src/scheduler/new.rs index 30eedd557..f0c9cdb49 100644 --- a/rslib/src/scheduler/new.rs +++ b/rslib/src/scheduler/new.rs @@ -9,7 +9,7 @@ use crate::{ card::{CardQueue, CardType}, deckconfig::NewCardInsertOrder, prelude::*, - search::{SortMode, StateKind}, + search::{SearchNode, SortMode, StateKind}, }; impl Card { @@ -186,7 +186,10 @@ impl Collection { order: NewCardInsertOrder, usn: Usn, ) -> Result { - let cids = self.search_cards(match_all![deck, StateKind::New], SortMode::NoOrder)?; + let cids = self.search_cards( + match_all![SearchNode::DeckIdWithoutChildren(deck), StateKind::New], + SortMode::NoOrder, + )?; self.sort_cards_inner(&cids, 1, 1, order.into(), false, usn) } diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index aa7571fb4..8b4dd355d 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -95,7 +95,11 @@ pub enum SearchNode { EditedInDays(u32), CardTemplate(TemplateKind), Deck(String), - DeckId(DeckId), + /// Matches cards in a single deck (original_deck_id is not checked). + DeckIdWithoutChildren(DeckId), + /// Matches cards in a deck or its children (original_deck_id is not + /// checked). + DeckIdWithChildren(DeckId), IntroducedInDays(u32), NotetypeId(NotetypeId), Notetype(String), @@ -197,12 +201,6 @@ impl From for Node { } } -impl From for Node { - fn from(id: DeckId) -> Self { - Node::Search(SearchNode::DeckId(id)) - } -} - impl From for Node { fn from(k: StateKind) -> Self { Node::Search(SearchNode::State(k)) @@ -619,7 +617,7 @@ fn parse_state(s: &str) -> ParseResult { } fn parse_did(s: &str) -> ParseResult { - parse_i64(s, "did:").map(|n| SearchNode::DeckId(n.into())) + parse_i64(s, "did:").map(|n| SearchNode::DeckIdWithoutChildren(n.into())) } fn parse_mid(s: &str) -> ParseResult { diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index 0ca9f0cfa..a0c948193 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -140,9 +140,10 @@ impl SqlWriter<'_> { SearchNode::NotetypeId(ntid) => { write!(self.sql, "n.mid = {}", ntid).unwrap(); } - SearchNode::DeckId(did) => { + SearchNode::DeckIdWithoutChildren(did) => { write!(self.sql, "c.did = {}", did).unwrap(); } + SearchNode::DeckIdWithChildren(did) => self.write_deck_id_with_children(*did)?, SearchNode::Notetype(notetype) => self.write_notetype(&norm(notetype)), SearchNode::Rated { days, ease } => self.write_rated(">", -i64::from(*days), ease)?, @@ -370,6 +371,19 @@ impl SqlWriter<'_> { Ok(()) } + fn write_deck_id_with_children(&mut self, deck_id: DeckId) -> Result<()> { + if let Some(parent) = self.col.get_deck(deck_id)? { + let ids = self.col.storage.deck_id_with_children(&parent)?; + let mut buf = String::new(); + ids_to_string(&mut buf, &ids); + write!(self.sql, "c.did in {}", buf,).unwrap(); + } else { + self.sql.push_str("false") + } + + Ok(()) + } + fn write_template(&mut self, template: &TemplateKind) { match template { TemplateKind::Ordinal(n) => { @@ -567,7 +581,8 @@ impl SearchNode { SearchNode::AddedInDays(_) => RequiredTable::Cards, SearchNode::IntroducedInDays(_) => RequiredTable::Cards, SearchNode::Deck(_) => RequiredTable::Cards, - SearchNode::DeckId(_) => RequiredTable::Cards, + SearchNode::DeckIdWithoutChildren(_) => RequiredTable::Cards, + SearchNode::DeckIdWithChildren(_) => RequiredTable::Cards, SearchNode::Rated { .. } => RequiredTable::Cards, SearchNode::State(_) => RequiredTable::Cards, SearchNode::Flag(_) => RequiredTable::Cards, diff --git a/rslib/src/search/writer.rs b/rslib/src/search/writer.rs index c3b6526fd..05e61f048 100644 --- a/rslib/src/search/writer.rs +++ b/rslib/src/search/writer.rs @@ -87,7 +87,9 @@ fn write_search_node(node: &SearchNode) -> String { IntroducedInDays(u) => format!("introduced:{}", u), CardTemplate(t) => write_template(t), Deck(s) => maybe_quote(&format!("deck:{}", s)), - DeckId(DeckIdType(i)) => format!("did:{}", i), + DeckIdWithoutChildren(DeckIdType(i)) => format!("did:{}", i), + // not exposed on the GUI end + DeckIdWithChildren(_) => "".to_string(), NotetypeId(NotetypeIdType(i)) => format!("mid:{}", i), Notetype(s) => maybe_quote(&format!("note:{}", s)), Rated { days, ease } => write_rated(days, ease), diff --git a/rslib/src/storage/deck/mod.rs b/rslib/src/storage/deck/mod.rs index 482ccca2f..1f14e080f 100644 --- a/rslib/src/storage/deck/mod.rs +++ b/rslib/src/storage/deck/mod.rs @@ -207,6 +207,17 @@ impl SqliteStorage { .collect() } + pub(crate) fn deck_id_with_children(&self, parent: &Deck) -> Result> { + let prefix_start = format!("{}\x1f", parent.name); + let prefix_end = format!("{}\x20", parent.name); + self.db + .prepare_cached("select id from decks where id = ? or (name >= ? and name < ?)")? + .query_and_then(params![parent.id, prefix_start, prefix_end], |row| { + row.get(0).map_err(Into::into) + })? + .collect() + } + pub(crate) fn deck_with_children(&self, deck_id: DeckId) -> Result> { let deck = self.get_deck(deck_id)?.ok_or(AnkiError::NotFound)?; let prefix_start = format!("{}\x1f", deck.name);