From 04f0ea85996b199bf063ae97234038053baaa771 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 14 Apr 2020 16:47:26 +1000 Subject: [PATCH] start work on field changes, and add search_cards to col --- rslib/src/backend/mod.rs | 4 +- rslib/src/notetype/mod.rs | 16 ++++++ rslib/src/notetype/schemachange.rs | 73 ++++++++++++++++++++++++ rslib/src/search/cards.rs | 92 +++++++++++++++--------------- rslib/src/search/mod.rs | 2 +- rslib/src/storage/notetype/mod.rs | 2 +- 6 files changed, 138 insertions(+), 51 deletions(-) create mode 100644 rslib/src/notetype/schemachange.rs diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index 2001964f3..6fbf317f8 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -24,7 +24,7 @@ use crate::{ notetype::{all_stock_notetypes, NoteTypeID, NoteTypeSchema11}, sched::cutoff::{local_minutes_west_for_stamp, sched_timing_today}, sched::timespan::{answer_button_time, learning_congrats, studied_today, time_span}, - search::{search_cards, SortMode}, + search::SortMode, template::{ render_card, without_legacy_template_directives, FieldMap, FieldRequirements, ParsedTemplate, RenderedNode, @@ -696,7 +696,7 @@ impl Backend { } else { SortMode::FromConfig }; - let cids = search_cards(col, &input.search, order)?; + let cids = col.search_cards(&input.search, order)?; Ok(pb::SearchCardsOut { card_ids: cids.into_iter().map(|v| v.0).collect(), }) diff --git a/rslib/src/notetype/mod.rs b/rslib/src/notetype/mod.rs index 6f51c6a07..0287a2ed2 100644 --- a/rslib/src/notetype/mod.rs +++ b/rslib/src/notetype/mod.rs @@ -3,6 +3,7 @@ mod fields; mod schema11; +mod schemachange; mod stock; mod templates; @@ -16,7 +17,9 @@ pub use stock::all_stock_notetypes; pub use templates::CardTemplate; use crate::{ + collection::Collection, define_newtype, + err::{AnkiError, Result}, template::{without_legacy_template_directives, FieldRequirements, ParsedTemplate}, text::ensure_string_in_nfc, timestamp::TimestampSecs, @@ -175,3 +178,16 @@ impl From for NoteTypeProto { } } } + +impl Collection { + pub fn update_notetype(&mut self, nt: &mut NoteType) -> Result<()> { + self.transact(None, |col| { + let existing_notetype = col + .storage + .get_full_notetype(nt.id)? + .ok_or_else(|| AnkiError::invalid_input("no such notetype"))?; + col.update_notes_for_changed_fields(nt, existing_notetype.fields.len())?; + Ok(()) + }) + } +} diff --git a/rslib/src/notetype/schemachange.rs b/rslib/src/notetype/schemachange.rs new file mode 100644 index 000000000..16e3bf702 --- /dev/null +++ b/rslib/src/notetype/schemachange.rs @@ -0,0 +1,73 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use super::{NoteField, NoteType}; +use crate::{collection::Collection, err::Result}; + +/// If any fields added, removed or reordered, returns a list of the new +/// field length, comprised of the original ordinals. +fn field_change_map(fields: &[NoteField], previous_field_count: usize) -> Option>> { + let map: Vec<_> = fields.iter().map(|f| f.ord).collect(); + let changed = map.len() != previous_field_count + || map + .iter() + .enumerate() + .any(|(idx, f)| f != &Some(idx as u32)); + if changed { + Some(map) + } else { + None + } +} + +impl Collection { + /// Caller must create transaction + pub(crate) fn update_notes_for_changed_fields( + &mut self, + nt: &NoteType, + previous_field_count: usize, + ) -> Result<()> { + let change_map = match field_change_map(&nt.fields, previous_field_count) { + None => { + // nothing to do + return Ok(()); + } + Some(map) => map, + }; + + let nids = self.search_notes(&format!("mid:{}", nt.id))?; + let usn = self.usn()?; + for nid in nids { + let mut note = self.storage.get_note(nid)?.unwrap(); + note.fields = change_map + .iter() + .map(|f| { + if let Some(idx) = f { + note.fields + .get(*idx as usize) + .map(AsRef::as_ref) + .unwrap_or("") + } else { + "" + } + }) + .map(Into::into) + .collect(); + note.prepare_for_update(nt.config.sort_field_idx as usize, usn); + self.storage.update_note(¬e)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use crate::collection::open_test_collection; + + #[test] + fn fields() { + let mut _col = open_test_collection(); + + // fixme: need note adding before we can check this + } +} diff --git a/rslib/src/search/cards.rs b/rslib/src/search/cards.rs index 00bcb2e92..0e6346548 100644 --- a/rslib/src/search/cards.rs +++ b/rslib/src/search/cards.rs @@ -10,51 +10,49 @@ use crate::err::Result; use crate::search::parser::parse; use rusqlite::params; -pub(crate) enum SortMode { +pub enum SortMode { NoOrder, FromConfig, Builtin { kind: SortKind, reverse: bool }, Custom(String), } -pub(crate) fn search_cards<'a, 'b>( - req: &'b mut Collection, - search: &'a str, - order: SortMode, -) -> Result> { - let top_node = Node::Group(parse(search)?); - let (sql, args) = node_to_sql(req, &top_node)?; +impl Collection { + pub fn search_cards(&mut self, search: &str, order: SortMode) -> Result> { + let top_node = Node::Group(parse(search)?); + let (sql, args) = node_to_sql(self, &top_node)?; - let mut sql = format!( - "select c.id from cards c, notes n where c.nid=n.id and {}", - sql - ); + let mut sql = format!( + "select c.id from cards c, notes n where c.nid=n.id and {}", + sql + ); - match order { - SortMode::NoOrder => (), - SortMode::FromConfig => { - let kind = req.get_browser_sort_kind(); - prepare_sort(req, &kind)?; - sql.push_str(" order by "); - write_order(&mut sql, &kind, req.get_browser_sort_reverse())?; - } - SortMode::Builtin { kind, reverse } => { - prepare_sort(req, &kind)?; - sql.push_str(" order by "); - write_order(&mut sql, &kind, reverse)?; - } - SortMode::Custom(order_clause) => { - sql.push_str(" order by "); - sql.push_str(&order_clause); + match order { + SortMode::NoOrder => (), + SortMode::FromConfig => { + let kind = self.get_browser_sort_kind(); + prepare_sort(self, &kind)?; + sql.push_str(" order by "); + write_order(&mut sql, &kind, self.get_browser_sort_reverse())?; + } + SortMode::Builtin { kind, reverse } => { + prepare_sort(self, &kind)?; + sql.push_str(" order by "); + write_order(&mut sql, &kind, reverse)?; + } + SortMode::Custom(order_clause) => { + sql.push_str(" order by "); + sql.push_str(&order_clause); + } } + + let mut stmt = self.storage.db.prepare(&sql)?; + let ids: Vec<_> = stmt + .query_map(&args, |row| row.get(0))? + .collect::>()?; + + Ok(ids) } - - let mut stmt = req.storage.db.prepare(&sql)?; - let ids: Vec<_> = stmt - .query_map(&args, |row| row.get(0))? - .collect::>()?; - - Ok(ids) } /// Add the order clause to the sql. @@ -96,24 +94,24 @@ fn write_order(sql: &mut String, kind: &SortKind, reverse: bool) -> Result<()> { // In the future these items should be moved from JSON into separate SQL tables, // - for now we use a temporary deck to sort them. -fn prepare_sort(req: &mut Collection, kind: &SortKind) -> Result<()> { +fn prepare_sort(col: &mut Collection, kind: &SortKind) -> Result<()> { use SortKind::*; match kind { CardDeck | NoteType => { - prepare_sort_order_table(req)?; - let mut stmt = req + prepare_sort_order_table(col)?; + let mut stmt = col .storage .db .prepare("insert into sort_order (k,v) values (?,?)")?; match kind { CardDeck => { - for (k, v) in req.storage.get_all_decks()? { + for (k, v) in col.storage.get_all_decks()? { stmt.execute(params![k, v.name()])?; } } NoteType => { - for (k, v) in req.storage.get_all_notetypes_as_schema11()? { + for (k, v) in col.storage.get_all_notetypes_as_schema11()? { stmt.execute(params![k, v.name])?; } } @@ -121,13 +119,13 @@ fn prepare_sort(req: &mut Collection, kind: &SortKind) -> Result<()> { } } CardTemplate => { - prepare_sort_order_table2(req)?; - let mut stmt = req + prepare_sort_order_table2(col)?; + let mut stmt = col .storage .db .prepare("insert into sort_order (k1,k2,v) values (?,?,?)")?; - for (ntid, nt) in req.storage.get_all_notetypes_as_schema11()? { + for (ntid, nt) in col.storage.get_all_notetypes_as_schema11()? { for tmpl in nt.tmpls { stmt.execute(params![ntid, tmpl.ord, tmpl.name])?; } @@ -139,15 +137,15 @@ fn prepare_sort(req: &mut Collection, kind: &SortKind) -> Result<()> { Ok(()) } -fn prepare_sort_order_table(req: &mut Collection) -> Result<()> { - req.storage +fn prepare_sort_order_table(col: &mut Collection) -> Result<()> { + col.storage .db .execute_batch(include_str!("sort_order.sql"))?; Ok(()) } -fn prepare_sort_order_table2(req: &mut Collection) -> Result<()> { - req.storage +fn prepare_sort_order_table2(col: &mut Collection) -> Result<()> { + col.storage .db .execute_batch(include_str!("sort_order2.sql"))?; Ok(()) diff --git a/rslib/src/search/mod.rs b/rslib/src/search/mod.rs index e924f1d39..4ab4dfbef 100644 --- a/rslib/src/search/mod.rs +++ b/rslib/src/search/mod.rs @@ -3,4 +3,4 @@ mod notes; mod parser; mod sqlwriter; -pub(crate) use cards::{search_cards, SortMode}; +pub use cards::SortMode; diff --git a/rslib/src/storage/notetype/mod.rs b/rslib/src/storage/notetype/mod.rs index 0c77fc7a4..7551e904c 100644 --- a/rslib/src/storage/notetype/mod.rs +++ b/rslib/src/storage/notetype/mod.rs @@ -73,7 +73,7 @@ impl SqliteStorage { .collect() } - fn get_full_notetype(&self, ntid: NoteTypeID) -> Result> { + pub(crate) fn get_full_notetype(&self, ntid: NoteTypeID) -> Result> { match self.get_notetype_core(ntid)? { Some(mut nt) => { nt.fields = self.get_notetype_fields(ntid)?;