diff --git a/rslib/src/backend/search/mod.rs b/rslib/src/backend/search/mod.rs index 23efcd423..8a6f9cf8c 100644 --- a/rslib/src/backend/search/mod.rs +++ b/rslib/src/backend/search/mod.rs @@ -25,7 +25,7 @@ impl SearchService for Backend { fn search_cards(&self, input: pb::SearchIn) -> Result { self.with_col(|col| { let order = input.order.unwrap_or_default().value.into(); - let cids = col.search::(&input.search, order)?; + let cids = col.search_cards(&input.search, order)?; Ok(pb::SearchOut { ids: cids.into_iter().map(|v| v.0).collect(), }) @@ -35,7 +35,7 @@ impl SearchService for Backend { fn search_notes(&self, input: pb::SearchIn) -> Result { self.with_col(|col| { let order = input.order.unwrap_or_default().value.into(); - let nids = col.search::(&input.search, order)?; + let nids = col.search_notes(&input.search, order)?; Ok(pb::SearchOut { ids: nids.into_iter().map(|v| v.0).collect(), }) diff --git a/rslib/src/findreplace.rs b/rslib/src/findreplace.rs index 1cf028cf5..379a05a01 100644 --- a/rslib/src/findreplace.rs +++ b/rslib/src/findreplace.rs @@ -120,7 +120,7 @@ mod test { note2.set_field(0, "three aaa")?; col.add_note(&mut note2, DeckId(1))?; - let nids = col.search_notes("")?; + let nids = col.search_notes_unordered("")?; let out = col.find_and_replace(nids.clone(), "(?i)AAA", "BBB", None)?; assert_eq!(out.output, 2); diff --git a/rslib/src/media/check.rs b/rslib/src/media/check.rs index 1fe14316c..ecf571fe8 100644 --- a/rslib/src/media/check.rs +++ b/rslib/src/media/check.rs @@ -361,7 +361,7 @@ where let notetypes = self.ctx.get_all_notetypes()?; let mut collection_modified = false; - let nids = self.ctx.search_notes("")?; + let nids = self.ctx.search_notes_unordered("")?; let usn = self.ctx.usn()?; for nid in nids { self.checked += 1; diff --git a/rslib/src/notes/mod.rs b/rslib/src/notes/mod.rs index 692030eae..1344f1e9f 100644 --- a/rslib/src/notes/mod.rs +++ b/rslib/src/notes/mod.rs @@ -702,7 +702,7 @@ mod test { .unwrap(); let assert_initial = |col: &mut Collection| -> Result<()> { - assert_eq!(col.search_notes("")?.len(), 0); + assert_eq!(col.search_notes_unordered("")?.len(), 0); assert_eq!(col.search_cards("", SortMode::NoOrder)?.len(), 0); assert_eq!( col.storage.db_scalar::("select count() from graves")?, @@ -713,7 +713,7 @@ mod test { }; let assert_after_add = |col: &mut Collection| -> Result<()> { - assert_eq!(col.search_notes("")?.len(), 1); + assert_eq!(col.search_notes_unordered("")?.len(), 1); assert_eq!(col.search_cards("", SortMode::NoOrder)?.len(), 2); assert_eq!( col.storage.db_scalar::("select count() from graves")?, @@ -740,7 +740,7 @@ mod test { assert_initial(&mut col)?; let assert_after_remove = |col: &mut Collection| -> Result<()> { - assert_eq!(col.search_notes("")?.len(), 0); + assert_eq!(col.search_notes_unordered("")?.len(), 0); assert_eq!(col.search_cards("", SortMode::NoOrder)?.len(), 0); // 1 note + 2 cards assert_eq!( @@ -753,7 +753,7 @@ mod test { col.redo()?; assert_after_add(&mut col)?; - let nids = col.search_notes("")?; + let nids = col.search_notes_unordered("")?; col.remove_notes(&nids)?; assert_after_remove(&mut col)?; col.undo()?; diff --git a/rslib/src/notetype/mod.rs b/rslib/src/notetype/mod.rs index 1504a8287..3a78c6577 100644 --- a/rslib/src/notetype/mod.rs +++ b/rslib/src/notetype/mod.rs @@ -485,7 +485,7 @@ impl Collection { fn remove_notetype_inner(&mut self, ntid: NotetypeId) -> Result<()> { // remove associated cards/notes let usn = self.usn()?; - let note_ids = self.search_notes(&format!("mid:{}", ntid))?; + let note_ids = self.search_notes_unordered(ntid)?; self.remove_notes_inner(¬e_ids, usn)?; // remove notetype diff --git a/rslib/src/notetype/schemachange.rs b/rslib/src/notetype/schemachange.rs index 57b2f1e8a..13793e5a1 100644 --- a/rslib/src/notetype/schemachange.rs +++ b/rslib/src/notetype/schemachange.rs @@ -61,7 +61,7 @@ impl Collection { if !ords_changed(&ords, previous_field_count) { if nt.config.sort_field_idx != previous_sort_idx { // only need to update sort field - let nids = self.search_notes(&format!("mid:{}", nt.id))?; + let nids = self.search_notes_unordered(nt.id)?; for nid in nids { let mut note = self.storage.get_note(nid)?.unwrap(); note.prepare_for_update(nt, normalize_text)?; @@ -75,8 +75,7 @@ impl Collection { } self.set_schema_modified()?; - - let nids = self.search_notes(&format!("mid:{}", nt.id))?; + let nids = self.search_notes_unordered(nt.id)?; let usn = self.usn()?; for nid in nids { let mut note = self.storage.get_note(nid)?.unwrap(); @@ -258,9 +257,7 @@ mod test { col.add_note(&mut note, DeckId(1))?; assert_eq!( - col.search_cards(&format!("nid:{}", note.id), SortMode::NoOrder) - .unwrap() - .len(), + col.search_cards(note.id, SortMode::NoOrder).unwrap().len(), 1 ); @@ -269,9 +266,7 @@ mod test { col.update_notetype(&mut nt, false)?; assert_eq!( - col.search_cards(&format!("nid:{}", note.id), SortMode::NoOrder) - .unwrap() - .len(), + col.search_cards(note.id, SortMode::NoOrder).unwrap().len(), 2 ); diff --git a/rslib/src/prelude.rs b/rslib/src/prelude.rs index a1daa90bb..3120b5b8b 100644 --- a/rslib/src/prelude.rs +++ b/rslib/src/prelude.rs @@ -16,6 +16,7 @@ pub use crate::{ notetype::{Notetype, NotetypeId}, ops::{Op, OpChanges, OpOutput}, revlog::RevlogId, + search::TryIntoSearch, timestamp::{TimestampMillis, TimestampSecs}, types::Usn, }; diff --git a/rslib/src/scheduler/bury_and_suspend.rs b/rslib/src/scheduler/bury_and_suspend.rs index 7f5ef8888..a21270aa6 100644 --- a/rslib/src/scheduler/bury_and_suspend.rs +++ b/rslib/src/scheduler/bury_and_suspend.rs @@ -155,7 +155,7 @@ mod test { use crate::{ card::{Card, CardQueue}, collection::{open_test_collection, Collection}, - search::SortMode, + search::{SortMode, StateKind}, }; #[test] @@ -168,7 +168,7 @@ mod test { col.add_card(&mut card).unwrap(); let assert_count = |col: &mut Collection, cnt| { assert_eq!( - col.search_cards("is:buried", SortMode::NoOrder) + col.search_cards(StateKind::Buried, SortMode::NoOrder) .unwrap() .len(), cnt diff --git a/rslib/src/scheduler/new.rs b/rslib/src/scheduler/new.rs index 8b32f8dde..1dce5cb83 100644 --- a/rslib/src/scheduler/new.rs +++ b/rslib/src/scheduler/new.rs @@ -8,8 +8,9 @@ use rand::seq::SliceRandom; use crate::{ card::{CardQueue, CardType}, deckconfig::NewCardOrder, + match_all, prelude::*, - search::SortMode, + search::{Node, SortMode, StateKind}, }; impl Card { @@ -186,7 +187,7 @@ impl Collection { order: NewCardOrder, usn: Usn, ) -> Result { - let cids = self.search_cards(&format!("did:{} is:new", deck), SortMode::NoOrder)?; + let cids = self.search_cards(match_all![deck, StateKind::New], SortMode::NoOrder)?; self.sort_cards_inner(&cids, 1, 1, order.into(), false, usn) } diff --git a/rslib/src/search/mod.rs b/rslib/src/search/mod.rs index d24b424d8..73e405a3c 100644 --- a/rslib/src/search/mod.rs +++ b/rslib/src/search/mod.rs @@ -21,7 +21,6 @@ use crate::{ error::Result, notes::NoteId, prelude::AnkiError, - search::parser::parse, }; #[derive(Debug, PartialEq, Clone, Copy)] @@ -96,13 +95,62 @@ impl Column { } } +pub trait TryIntoSearch { + fn try_into_search(self) -> Result; +} + +impl TryIntoSearch for &str { + fn try_into_search(self) -> Result { + parser::parse(self).map(Node::Group) + } +} + +impl TryIntoSearch for &String { + fn try_into_search(self) -> Result { + parser::parse(self).map(Node::Group) + } +} + +impl TryIntoSearch for T +where + T: Into, +{ + fn try_into_search(self) -> Result { + Ok(self.into()) + } +} + impl Collection { - pub fn search(&mut self, search: &str, mode: SortMode) -> Result> + pub fn search_cards(&mut self, search: N, mode: SortMode) -> Result> where + N: TryIntoSearch, + { + self.search(search, mode) + } + + pub fn search_notes(&mut self, search: N, mode: SortMode) -> Result> + where + N: TryIntoSearch, + { + self.search(search, mode) + } + + pub fn search_notes_unordered(&mut self, search: N) -> Result> + where + N: TryIntoSearch, + { + self.search(search, SortMode::NoOrder) + } +} + +impl Collection { + fn search(&mut self, search: N, mode: SortMode) -> Result> + where + N: TryIntoSearch, T: FromSql + AsReturnItemType, { let item_type = T::as_return_item_type(); - let top_node = Node::Group(parse(search)?); + let top_node = search.try_into_search()?; let writer = SqlWriter::new(self, item_type); let (mut sql, args) = writer.build_query(&top_node, mode.required_table())?; @@ -116,14 +164,6 @@ impl Collection { Ok(ids) } - pub fn search_cards(&mut self, search: &str, mode: SortMode) -> Result> { - self.search(search, mode) - } - - pub fn search_notes(&mut self, search: &str) -> Result> { - self.search(search, SortMode::NoOrder) - } - fn add_order( &mut self, sql: &mut String, @@ -148,12 +188,11 @@ 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: &str, - mode: SortMode, - ) -> Result { - let top_node = Node::Group(parse(search)?); + pub(crate) fn search_cards_into_table(&mut self, search: N, mode: SortMode) -> Result + where + N: TryIntoSearch, + { + let top_node = search.try_into_search()?; let writer = SqlWriter::new(self, ReturnItemType::Cards); let want_order = mode != SortMode::NoOrder; diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index a788037cc..722962eff 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -1,6 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +use itertools::Itertools; use lazy_static::lazy_static; use nom::{ branch::alt, @@ -14,9 +15,8 @@ use nom::{ use regex::{Captures, Regex}; use crate::{ - decks::DeckId, error::{ParseError, Result, SearchErrorKind as FailKind}, - notetype::NotetypeId, + prelude::*, }; type IResult<'a, O> = std::result::Result<(&'a str, O), nom::Err>>; @@ -57,6 +57,28 @@ impl Node { vec![self] } } + + pub fn all(iter: impl IntoIterator) -> Node { + Node::Group(Itertools::intersperse(iter.into_iter(), Node::And).collect()) + } + + pub fn any(iter: impl IntoIterator) -> Node { + Node::Group(Itertools::intersperse(iter.into_iter(), Node::Or).collect()) + } +} + +#[macro_export] +macro_rules! match_all { + ($($param:expr),+ $(,)?) => { + Node::all(vec![$($param.into()),+]) + }; +} + +#[macro_export] +macro_rules! match_any { + ($($param:expr),+ $(,)?) => { + Node::any(vec![$($param.into()),+]) + }; } #[derive(Debug, PartialEq, Clone)] @@ -151,6 +173,42 @@ pub fn parse(input: &str) -> Result> { } } +impl From for Node { + fn from(n: SearchNode) -> Self { + Node::Search(n) + } +} + +impl From for Node { + fn from(id: NotetypeId) -> Self { + Node::Search(SearchNode::NotetypeId(id)) + } +} + +impl From for Node { + fn from(k: TemplateKind) -> Self { + Node::Search(SearchNode::CardTemplate(k)) + } +} + +impl From for Node { + fn from(n: NoteId) -> Self { + Node::Search(SearchNode::NoteIds(format!("{}", n))) + } +} + +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)) + } +} + /// Zero or more nodes inside brackets, eg 'one OR two -three'. /// Empty vec must be handled by caller. fn group_inner(input: &str) -> IResult> { diff --git a/rslib/src/sync/mod.rs b/rslib/src/sync/mod.rs index eb48d4d65..77fe62a2d 100644 --- a/rslib/src/sync/mod.rs +++ b/rslib/src/sync/mod.rs @@ -1428,7 +1428,7 @@ mod test { let deckid = deck.id; let dconfid = dconf.id; let noteid = note.id; - let cardid = col1.search_cards(&format!("nid:{}", note.id), SortMode::NoOrder)?[0]; + let cardid = col1.search_cards(note.id, SortMode::NoOrder)?[0]; let revlogid = RevlogId(123); let compare_sides = |col1: &mut Collection, col2: &mut Collection| -> Result<()> {