make it more ergonomic to search directly via nodes in Rust

This commit is contained in:
Damien Elmes 2021-04-30 11:37:55 +10:00
parent 2902b64e82
commit 9d604f1ad0
12 changed files with 136 additions and 42 deletions

View File

@ -25,7 +25,7 @@ impl SearchService for Backend {
fn search_cards(&self, input: pb::SearchIn) -> Result<pb::SearchOut> {
self.with_col(|col| {
let order = input.order.unwrap_or_default().value.into();
let cids = col.search::<CardId>(&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<pb::SearchOut> {
self.with_col(|col| {
let order = input.order.unwrap_or_default().value.into();
let nids = col.search::<NoteId>(&input.search, order)?;
let nids = col.search_notes(&input.search, order)?;
Ok(pb::SearchOut {
ids: nids.into_iter().map(|v| v.0).collect(),
})

View File

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

View File

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

View File

@ -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::<u32>("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::<u32>("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()?;

View File

@ -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(&note_ids, usn)?;
// remove notetype

View File

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

View File

@ -16,6 +16,7 @@ pub use crate::{
notetype::{Notetype, NotetypeId},
ops::{Op, OpChanges, OpOutput},
revlog::RevlogId,
search::TryIntoSearch,
timestamp::{TimestampMillis, TimestampSecs},
types::Usn,
};

View File

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

View File

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

View File

@ -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<Node, AnkiError>;
}
impl TryIntoSearch for &str {
fn try_into_search(self) -> Result<Node, AnkiError> {
parser::parse(self).map(Node::Group)
}
}
impl TryIntoSearch for &String {
fn try_into_search(self) -> Result<Node, AnkiError> {
parser::parse(self).map(Node::Group)
}
}
impl<T> TryIntoSearch for T
where
T: Into<Node>,
{
fn try_into_search(self) -> Result<Node, AnkiError> {
Ok(self.into())
}
}
impl Collection {
pub fn search<T>(&mut self, search: &str, mode: SortMode) -> Result<Vec<T>>
pub fn search_cards<N>(&mut self, search: N, mode: SortMode) -> Result<Vec<CardId>>
where
N: TryIntoSearch,
{
self.search(search, mode)
}
pub fn search_notes<N>(&mut self, search: N, mode: SortMode) -> Result<Vec<NoteId>>
where
N: TryIntoSearch,
{
self.search(search, mode)
}
pub fn search_notes_unordered<N>(&mut self, search: N) -> Result<Vec<NoteId>>
where
N: TryIntoSearch,
{
self.search(search, SortMode::NoOrder)
}
}
impl Collection {
fn search<T, N>(&mut self, search: N, mode: SortMode) -> Result<Vec<T>>
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<Vec<CardId>> {
self.search(search, mode)
}
pub fn search_notes(&mut self, search: &str) -> Result<Vec<NoteId>> {
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<usize> {
let top_node = Node::Group(parse(search)?);
pub(crate) fn search_cards_into_table<N>(&mut self, search: N, mode: SortMode) -> Result<usize>
where
N: TryIntoSearch,
{
let top_node = search.try_into_search()?;
let writer = SqlWriter::new(self, ReturnItemType::Cards);
let want_order = mode != SortMode::NoOrder;

View File

@ -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<ParseError<'a>>>;
@ -57,6 +57,28 @@ impl Node {
vec![self]
}
}
pub fn all(iter: impl IntoIterator<Item = Node>) -> Node {
Node::Group(Itertools::intersperse(iter.into_iter(), Node::And).collect())
}
pub fn any(iter: impl IntoIterator<Item = Node>) -> 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<Vec<Node>> {
}
}
impl From<SearchNode> for Node {
fn from(n: SearchNode) -> Self {
Node::Search(n)
}
}
impl From<NotetypeId> for Node {
fn from(id: NotetypeId) -> Self {
Node::Search(SearchNode::NotetypeId(id))
}
}
impl From<TemplateKind> for Node {
fn from(k: TemplateKind) -> Self {
Node::Search(SearchNode::CardTemplate(k))
}
}
impl From<NoteId> for Node {
fn from(n: NoteId) -> Self {
Node::Search(SearchNode::NoteIds(format!("{}", n)))
}
}
impl From<DeckId> for Node {
fn from(id: DeckId) -> Self {
Node::Search(SearchNode::DeckId(id))
}
}
impl From<StateKind> 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<Vec<Node>> {

View File

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