diff --git a/proto/backend.proto b/proto/backend.proto index 1cb0c1a43..638786a5a 100644 --- a/proto/backend.proto +++ b/proto/backend.proto @@ -25,7 +25,7 @@ message BackendInput { SchedTimingTodayIn sched_timing_today = 17; Empty deck_tree = 18; SearchCardsIn search_cards = 19; -// BrowserRowsIn browser_rows = 20; + SearchNotesIn search_notes = 20; RenderCardIn render_card = 21; int64 local_minutes_west = 22; string strip_av_tags = 23; @@ -63,7 +63,7 @@ message BackendOutput { TemplateRequirementsOut template_requirements = 16; DeckTreeOut deck_tree = 18; SearchCardsOut search_cards = 19; -// BrowserRowsOut browser_rows = 20; + SearchNotesOut search_notes = 20; RenderCardOut render_card = 21; string add_media_file = 26; Empty sync_media = 27; @@ -333,3 +333,11 @@ message SortOrder { string custom = 3; } } + +message SearchNotesIn { + string search = 1; +} + +message SearchNotesOut { + repeated int64 note_ids = 2; +} diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index fcc99b7c3..fb03431a3 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -616,11 +616,11 @@ where c.nid = n.id and c.id in %s group by nid""" # Finding cards ########################################################################## - def findCards(self, query: str, order: Union[bool, str] = False) -> Sequence[int]: + def find_cards(self, query: str, order: Union[bool, str] = False) -> Sequence[int]: return self.backend.search_cards(query, order) - def findNotes(self, query: str) -> Any: - return anki.find.Finder(self).findNotes(query) + def find_notes(self, query: str) -> Sequence[int]: + return self.backend.search_notes(query) def findReplace( self, @@ -636,6 +636,9 @@ where c.nid = n.id and c.id in %s group by nid""" def findDupes(self, fieldName: str, search: str = "") -> List[Tuple[Any, list]]: return anki.find.findDupes(self, fieldName, search) + findCards = find_cards + findNotes = find_notes + # Stats ########################################################################## diff --git a/pylib/anki/find.py b/pylib/anki/find.py index a2c2b19c6..5beb1d044 100644 --- a/pylib/anki/find.py +++ b/pylib/anki/find.py @@ -595,7 +595,7 @@ def findDupes( # limit search to notes with applicable field name if search: search = "(" + search + ") " - search += "'%s:*'" % fieldName + search += '"%s:*"' % fieldName.replace('"', '"') # go through notes vals: Dict[str, List[int]] = {} dupes = [] diff --git a/pylib/anki/rsbackend.py b/pylib/anki/rsbackend.py index 56507a79c..6f7c98628 100644 --- a/pylib/anki/rsbackend.py +++ b/pylib/anki/rsbackend.py @@ -434,6 +434,11 @@ class RustBackend: pb.BackendInput(search_cards=pb.SearchCardsIn(search=search, order=mode)) ).search_cards.card_ids + def search_notes(self, search: str) -> Sequence[int]: + return self._run_command( + pb.BackendInput(search_notes=pb.SearchNotesIn(search=search)) + ).search_notes.note_ids + def translate_string_in( key: TR, **kwargs: Union[str, int, float] diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index 06f0c30ac..cc9815778 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -14,7 +14,7 @@ use crate::media::sync::MediaSyncProgress; use crate::media::MediaManager; use crate::sched::cutoff::{local_minutes_west_for_stamp, sched_timing_today_v2_new}; use crate::sched::timespan::{answer_button_time, learning_congrats, studied_today, time_span}; -use crate::search::{search_cards, SortMode}; +use crate::search::{search_cards, search_notes, SortMode}; use crate::template::{ render_card, without_legacy_template_directives, FieldMap, FieldRequirements, ParsedTemplate, RenderedNode, @@ -246,6 +246,7 @@ impl Backend { OValue::CloseCollection(Empty {}) } Value::SearchCards(input) => OValue::SearchCards(self.search_cards(input)?), + Value::SearchNotes(input) => OValue::SearchNotes(self.search_notes(input)?), }) } @@ -597,6 +598,15 @@ impl Backend { }) }) } + + fn search_notes(&self, input: pb::SearchNotesIn) -> Result { + self.with_col(|col| { + col.with_ctx(|ctx| { + let nids = search_notes(ctx, &input.search)?; + Ok(pb::SearchNotesOut { note_ids: nids }) + }) + }) + } } fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue { diff --git a/rslib/src/search/mod.rs b/rslib/src/search/mod.rs index 2313c1d39..14cdb1181 100644 --- a/rslib/src/search/mod.rs +++ b/rslib/src/search/mod.rs @@ -1,5 +1,7 @@ mod cards; +mod notes; mod parser; mod sqlwriter; pub(crate) use cards::{search_cards, SortMode}; +pub(crate) use notes::search_notes; diff --git a/rslib/src/search/notes.rs b/rslib/src/search/notes.rs new file mode 100644 index 000000000..50021a92e --- /dev/null +++ b/rslib/src/search/notes.rs @@ -0,0 +1,28 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use super::{parser::Node, sqlwriter::node_to_sql}; +use crate::collection::RequestContext; +use crate::err::Result; +use crate::search::parser::parse; +use crate::types::ObjID; + +pub(crate) fn search_notes<'a, 'b>( + req: &'a mut RequestContext<'b>, + search: &'a str, +) -> Result> { + let top_node = Node::Group(parse(search)?); + let (sql, args) = node_to_sql(req, &top_node)?; + + let sql = format!( + "select n.id from cards c, notes n where c.nid=n.id and {}", + sql + ); + + let mut stmt = req.storage.db.prepare(&sql)?; + let ids: Vec = stmt + .query_map(&args, |row| row.get(0))? + .collect::>()?; + + Ok(ids) +}