mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
Fix 'Expression tree is too large' when user has many decks
+ Allow 'did:' searches to match multiple decks at once
This commit is contained in:
parent
67acdc3034
commit
0e6104a96b
7 changed files with 23 additions and 24 deletions
|
@ -19,6 +19,7 @@ use crate::prelude::*;
|
||||||
use crate::scheduler::fsrs::memory_state::UpdateMemoryStateRequest;
|
use crate::scheduler::fsrs::memory_state::UpdateMemoryStateRequest;
|
||||||
use crate::search::JoinSearches;
|
use crate::search::JoinSearches;
|
||||||
use crate::search::SearchNode;
|
use crate::search::SearchNode;
|
||||||
|
use crate::storage::comma_separated_ids;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UpdateDeckConfigsRequest {
|
pub struct UpdateDeckConfigsRequest {
|
||||||
|
@ -164,7 +165,7 @@ impl Collection {
|
||||||
let usn = self.usn()?;
|
let usn = self.usn()?;
|
||||||
let today = self.timing_today()?.days_elapsed;
|
let today = self.timing_today()?.days_elapsed;
|
||||||
let selected_config = req.configs.last().unwrap();
|
let selected_config = req.configs.last().unwrap();
|
||||||
let mut decks_needing_memory_recompute: HashMap<DeckConfigId, Vec<SearchNode>> =
|
let mut decks_needing_memory_recompute: HashMap<DeckConfigId, Vec<DeckId>> =
|
||||||
Default::default();
|
Default::default();
|
||||||
let fsrs_toggled = self.get_config_bool(BoolKey::Fsrs) != req.fsrs;
|
let fsrs_toggled = self.get_config_bool(BoolKey::Fsrs) != req.fsrs;
|
||||||
if fsrs_toggled {
|
if fsrs_toggled {
|
||||||
|
@ -216,7 +217,7 @@ impl Collection {
|
||||||
decks_needing_memory_recompute
|
decks_needing_memory_recompute
|
||||||
.entry(current_config_id)
|
.entry(current_config_id)
|
||||||
.or_default()
|
.or_default()
|
||||||
.push(SearchNode::DeckIdWithoutChildren(deck_id));
|
.push(deck_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.adjust_remaining_steps_in_deck(deck_id, previous_config, current_config, usn)?;
|
self.adjust_remaining_steps_in_deck(deck_id, previous_config, current_config, usn)?;
|
||||||
|
@ -224,7 +225,7 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !decks_needing_memory_recompute.is_empty() {
|
if !decks_needing_memory_recompute.is_empty() {
|
||||||
let input: Vec<(Option<UpdateMemoryStateRequest>, Vec<SearchNode>)> =
|
let input: Vec<(Option<UpdateMemoryStateRequest>, SearchNode)> =
|
||||||
decks_needing_memory_recompute
|
decks_needing_memory_recompute
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(conf_id, search)| {
|
.map(|(conf_id, search)| {
|
||||||
|
@ -240,7 +241,10 @@ impl Collection {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Ok((weights, search))
|
Ok((
|
||||||
|
weights,
|
||||||
|
SearchNode::DeckIdsWithoutChildren(comma_separated_ids(&search)),
|
||||||
|
))
|
||||||
})
|
})
|
||||||
.collect::<Result<_>>()?;
|
.collect::<Result<_>>()?;
|
||||||
self.update_memory_state(input)?;
|
self.update_memory_state(input)?;
|
||||||
|
|
|
@ -16,7 +16,6 @@ use crate::revlog::RevlogReviewKind;
|
||||||
use crate::scheduler::fsrs::weights::single_card_revlog_to_items;
|
use crate::scheduler::fsrs::weights::single_card_revlog_to_items;
|
||||||
use crate::scheduler::fsrs::weights::Weights;
|
use crate::scheduler::fsrs::weights::Weights;
|
||||||
use crate::scheduler::states::fuzz::with_review_fuzz;
|
use crate::scheduler::states::fuzz::with_review_fuzz;
|
||||||
use crate::search::JoinSearches;
|
|
||||||
use crate::search::Negated;
|
use crate::search::Negated;
|
||||||
use crate::search::SearchNode;
|
use crate::search::SearchNode;
|
||||||
use crate::search::StateKind;
|
use crate::search::StateKind;
|
||||||
|
@ -43,13 +42,13 @@ impl Collection {
|
||||||
/// memory state should be removed.
|
/// memory state should be removed.
|
||||||
pub(crate) fn update_memory_state(
|
pub(crate) fn update_memory_state(
|
||||||
&mut self,
|
&mut self,
|
||||||
entries: Vec<(Option<UpdateMemoryStateRequest>, Vec<SearchNode>)>,
|
entries: Vec<(Option<UpdateMemoryStateRequest>, SearchNode)>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let timing = self.timing_today()?;
|
let timing = self.timing_today()?;
|
||||||
let usn = self.usn()?;
|
let usn = self.usn()?;
|
||||||
for (req, search) in entries {
|
for (req, search) in entries {
|
||||||
let search = SearchBuilder::any(search.into_iter())
|
let search =
|
||||||
.and(SearchNode::State(StateKind::New).negated());
|
SearchBuilder::all([search.into(), SearchNode::State(StateKind::New).negated()]);
|
||||||
let revlog = self.revlog_for_srs(search)?;
|
let revlog = self.revlog_for_srs(search)?;
|
||||||
let reschedule = req.as_ref().map(|e| e.reschedule).unwrap_or_default();
|
let reschedule = req.as_ref().map(|e| e.reschedule).unwrap_or_default();
|
||||||
let last_reviews = if reschedule {
|
let last_reviews = if reschedule {
|
||||||
|
|
|
@ -281,7 +281,7 @@ impl Collection {
|
||||||
usn: Usn,
|
usn: Usn,
|
||||||
) -> Result<usize> {
|
) -> Result<usize> {
|
||||||
let cids = self.search_cards(
|
let cids = self.search_cards(
|
||||||
SearchNode::DeckIdWithoutChildren(deck).and(StateKind::New),
|
SearchNode::DeckIdsWithoutChildren(deck.to_string()).and(StateKind::New),
|
||||||
SortMode::NoOrder,
|
SortMode::NoOrder,
|
||||||
)?;
|
)?;
|
||||||
self.sort_cards_inner(&cids, 1, 1, order.into(), false, usn)
|
self.sort_cards_inner(&cids, 1, 1, order.into(), false, usn)
|
||||||
|
|
|
@ -11,6 +11,7 @@ use super::SearchNode;
|
||||||
use super::StateKind;
|
use super::StateKind;
|
||||||
use super::TemplateKind;
|
use super::TemplateKind;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::storage::comma_separated_ids;
|
||||||
use crate::text::escape_anki_wildcards_for_search_node;
|
use crate::text::escape_anki_wildcards_for_search_node;
|
||||||
|
|
||||||
pub trait Negated {
|
pub trait Negated {
|
||||||
|
@ -123,7 +124,7 @@ impl SearchBuilder {
|
||||||
|
|
||||||
/// Construct [SearchBuilder] matching any given deck, excluding children.
|
/// Construct [SearchBuilder] matching any given deck, excluding children.
|
||||||
pub fn from_decks(decks: &[DeckId]) -> Self {
|
pub fn from_decks(decks: &[DeckId]) -> Self {
|
||||||
Self::any(decks.iter().copied().map(SearchNode::DeckIdWithoutChildren))
|
SearchNode::DeckIdsWithoutChildren(comma_separated_ids(decks)).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct [SearchBuilder] matching learning, but not relearning cards.
|
/// Construct [SearchBuilder] matching learning, but not relearning cards.
|
||||||
|
@ -160,7 +161,7 @@ impl SearchNode {
|
||||||
if with_children {
|
if with_children {
|
||||||
Self::DeckIdWithChildren(did.into())
|
Self::DeckIdWithChildren(did.into())
|
||||||
} else {
|
} else {
|
||||||
Self::DeckIdWithoutChildren(did.into())
|
Self::DeckIdsWithoutChildren(did.into().to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,8 +60,8 @@ pub enum SearchNode {
|
||||||
EditedInDays(u32),
|
EditedInDays(u32),
|
||||||
CardTemplate(TemplateKind),
|
CardTemplate(TemplateKind),
|
||||||
Deck(String),
|
Deck(String),
|
||||||
/// Matches cards in a single deck (original_deck_id is not checked).
|
/// Matches cards in a list of decks (original_deck_id is not checked).
|
||||||
DeckIdWithoutChildren(DeckId),
|
DeckIdsWithoutChildren(String),
|
||||||
/// Matches cards in a deck or its children (original_deck_id is not
|
/// Matches cards in a deck or its children (original_deck_id is not
|
||||||
/// checked).
|
/// checked).
|
||||||
DeckIdWithChildren(DeckId),
|
DeckIdWithChildren(DeckId),
|
||||||
|
@ -346,7 +346,7 @@ fn search_node_for_text_with_argument<'a>(
|
||||||
"introduced" => parse_introduced(val)?,
|
"introduced" => parse_introduced(val)?,
|
||||||
"rated" => parse_rated(val)?,
|
"rated" => parse_rated(val)?,
|
||||||
"is" => parse_state(val)?,
|
"is" => parse_state(val)?,
|
||||||
"did" => parse_did(val)?,
|
"did" => SearchNode::DeckIdsWithoutChildren(check_id_list(val, key)?.into()),
|
||||||
"mid" => parse_mid(val)?,
|
"mid" => parse_mid(val)?,
|
||||||
"nid" => SearchNode::NoteIds(check_id_list(val, key)?.into()),
|
"nid" => SearchNode::NoteIds(check_id_list(val, key)?.into()),
|
||||||
"cid" => SearchNode::CardIds(check_id_list(val, key)?.into()),
|
"cid" => SearchNode::CardIds(check_id_list(val, key)?.into()),
|
||||||
|
@ -614,10 +614,6 @@ fn parse_state(s: &str) -> ParseResult<SearchNode> {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_did(s: &str) -> ParseResult<SearchNode> {
|
|
||||||
parse_i64(s, "did:").map(|n| SearchNode::DeckIdWithoutChildren(n.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_mid(s: &str) -> ParseResult<SearchNode> {
|
fn parse_mid(s: &str) -> ParseResult<SearchNode> {
|
||||||
parse_i64(s, "mid:").map(|n| SearchNode::NotetypeId(n.into()))
|
parse_i64(s, "mid:").map(|n| SearchNode::NotetypeId(n.into()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,8 +160,8 @@ impl SqlWriter<'_> {
|
||||||
SearchNode::NotetypeId(ntid) => {
|
SearchNode::NotetypeId(ntid) => {
|
||||||
write!(self.sql, "n.mid = {}", ntid).unwrap();
|
write!(self.sql, "n.mid = {}", ntid).unwrap();
|
||||||
}
|
}
|
||||||
SearchNode::DeckIdWithoutChildren(did) => {
|
SearchNode::DeckIdsWithoutChildren(dids) => {
|
||||||
write!(self.sql, "c.did = {}", did).unwrap();
|
write!(self.sql, "c.did in ({})", dids).unwrap();
|
||||||
}
|
}
|
||||||
SearchNode::DeckIdWithChildren(did) => self.write_deck_id_with_children(*did)?,
|
SearchNode::DeckIdWithChildren(did) => self.write_deck_id_with_children(*did)?,
|
||||||
SearchNode::Notetype(notetype) => self.write_notetype(&norm(notetype)),
|
SearchNode::Notetype(notetype) => self.write_notetype(&norm(notetype)),
|
||||||
|
@ -965,7 +965,7 @@ impl SearchNode {
|
||||||
SearchNode::AddedInDays(_) => RequiredTable::Cards,
|
SearchNode::AddedInDays(_) => RequiredTable::Cards,
|
||||||
SearchNode::IntroducedInDays(_) => RequiredTable::Cards,
|
SearchNode::IntroducedInDays(_) => RequiredTable::Cards,
|
||||||
SearchNode::Deck(_) => RequiredTable::Cards,
|
SearchNode::Deck(_) => RequiredTable::Cards,
|
||||||
SearchNode::DeckIdWithoutChildren(_) => RequiredTable::Cards,
|
SearchNode::DeckIdsWithoutChildren(_) => RequiredTable::Cards,
|
||||||
SearchNode::DeckIdWithChildren(_) => RequiredTable::Cards,
|
SearchNode::DeckIdWithChildren(_) => RequiredTable::Cards,
|
||||||
SearchNode::Rated { .. } => RequiredTable::Cards,
|
SearchNode::Rated { .. } => RequiredTable::Cards,
|
||||||
SearchNode::State(_) => RequiredTable::Cards,
|
SearchNode::State(_) => RequiredTable::Cards,
|
||||||
|
@ -973,6 +973,7 @@ impl SearchNode {
|
||||||
SearchNode::CardIds(_) => RequiredTable::Cards,
|
SearchNode::CardIds(_) => RequiredTable::Cards,
|
||||||
SearchNode::Property { .. } => RequiredTable::Cards,
|
SearchNode::Property { .. } => RequiredTable::Cards,
|
||||||
SearchNode::CustomData { .. } => RequiredTable::Cards,
|
SearchNode::CustomData { .. } => RequiredTable::Cards,
|
||||||
|
SearchNode::Preset(_) => RequiredTable::Cards,
|
||||||
|
|
||||||
SearchNode::UnqualifiedText(_) => RequiredTable::Notes,
|
SearchNode::UnqualifiedText(_) => RequiredTable::Notes,
|
||||||
SearchNode::SingleField { .. } => RequiredTable::Notes,
|
SearchNode::SingleField { .. } => RequiredTable::Notes,
|
||||||
|
@ -989,7 +990,6 @@ impl SearchNode {
|
||||||
SearchNode::WholeCollection => RequiredTable::CardsOrNotes,
|
SearchNode::WholeCollection => RequiredTable::CardsOrNotes,
|
||||||
|
|
||||||
SearchNode::CardTemplate(_) => RequiredTable::CardsAndNotes,
|
SearchNode::CardTemplate(_) => RequiredTable::CardsAndNotes,
|
||||||
SearchNode::Preset(_) => RequiredTable::Cards,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ use std::mem;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::decks::DeckId as DeckIdType;
|
|
||||||
use crate::notetype::NotetypeId as NotetypeIdType;
|
use crate::notetype::NotetypeId as NotetypeIdType;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::search::parser::parse;
|
use crate::search::parser::parse;
|
||||||
|
@ -69,7 +68,7 @@ fn write_search_node(node: &SearchNode) -> String {
|
||||||
IntroducedInDays(u) => format!("introduced:{}", u),
|
IntroducedInDays(u) => format!("introduced:{}", u),
|
||||||
CardTemplate(t) => write_template(t),
|
CardTemplate(t) => write_template(t),
|
||||||
Deck(s) => maybe_quote(&format!("deck:{}", s)),
|
Deck(s) => maybe_quote(&format!("deck:{}", s)),
|
||||||
DeckIdWithoutChildren(DeckIdType(i)) => format!("did:{}", i),
|
DeckIdsWithoutChildren(s) => format!("did:{}", s),
|
||||||
// not exposed on the GUI end
|
// not exposed on the GUI end
|
||||||
DeckIdWithChildren(_) => "".to_string(),
|
DeckIdWithChildren(_) => "".to_string(),
|
||||||
NotetypeId(NotetypeIdType(i)) => format!("mid:{}", i),
|
NotetypeId(NotetypeIdType(i)) => format!("mid:{}", i),
|
||||||
|
|
Loading…
Reference in a new issue