diff --git a/proto/backend.proto b/proto/backend.proto index 32b10b405..c637d9603 100644 --- a/proto/backend.proto +++ b/proto/backend.proto @@ -69,11 +69,20 @@ message BackendInput { SetConfigJson set_config_json = 53; bytes set_all_config = 54; Empty get_all_config = 55; - Empty get_all_notetypes = 56; - bytes set_all_notetypes = 57; + int32 get_changed_notetypes = 56; + AddOrUpdateNotetypeIn add_or_update_notetype = 57; Empty get_all_decks = 58; bytes set_all_decks = 59; Empty all_stock_notetypes = 60; + int64 get_notetype_legacy = 61; + Empty get_notetype_names = 62; + Empty get_notetype_names_and_counts = 63; + string get_notetype_id_by_name = 64; + int64 remove_notetype = 65; + int64 new_note = 66; + AddNoteIn add_note = 67; + Note update_note = 68; + int64 get_note = 69; } } @@ -123,10 +132,19 @@ message BackendOutput { Empty set_config_json = 53; Empty set_all_config = 54; bytes get_all_config = 55; - bytes get_all_notetypes = 56; - Empty set_all_notetypes = 57; + bytes get_changed_notetypes = 56; + int64 add_or_update_notetype = 57; bytes get_all_decks = 58; Empty set_all_decks = 59; + bytes get_notetype_legacy = 61; + NoteTypeNames get_notetype_names = 62; + NoteTypeUseCounts get_notetype_names_and_counts = 63; + int64 get_notetype_id_by_name = 64; + Empty remove_notetype = 65; + Note new_note = 66; + int64 add_note = 67; + Empty update_note = 68; + Note get_note = 69; BackendError error = 2047; } @@ -147,6 +165,7 @@ message BackendError { Empty interrupted = 8; string json_error = 9; string proto_error = 10; + Empty not_found_error = 11; } } @@ -569,3 +588,43 @@ enum StockNoteType { message AllStockNotetypesOut { repeated NoteType notetypes = 1; } + +message NoteTypeNames { + repeated NoteTypeNameID entries = 1; +} + +message NoteTypeUseCounts { + repeated NoteTypeNameIDUseCount entries = 1; +} + +message NoteTypeNameID { + int64 id = 1; + string name = 2; + +} + +message NoteTypeNameIDUseCount { + int64 id = 1; + string name = 2; + uint32 use_count = 3; +} + +message AddOrUpdateNotetypeIn { + bytes json = 1; + bool preserve_usn_and_mtime = 2; +} + +message Note { + int64 id = 1; + string guid = 2; + int64 ntid = 3; + uint32 mtime_secs = 4; + int32 usn = 5; + repeated string tags = 6; + repeated string fields = 7; +} + +message AddNoteIn { + Note note = 1; + int64 deck_id = 2; +} diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index e9fc61a9f..ccda0fd2b 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -20,8 +20,8 @@ use crate::{ media::check::MediaChecker, media::sync::MediaSyncProgress, media::MediaManager, - notes::NoteID, - notetype::{all_stock_notetypes, NoteTypeID, NoteTypeSchema11}, + notes::{Note, NoteID}, + notetype::{all_stock_notetypes, NoteType, NoteTypeID, NoteTypeSchema11}, sched::cutoff::{local_minutes_west_for_stamp, sched_timing_today}, sched::timespan::{answer_button_time, learning_congrats, studied_today, time_span}, search::SortMode, @@ -77,6 +77,7 @@ fn anki_error_to_proto_error(err: AnkiError, i18n: &I18n) -> pb::BackendError { AnkiError::CollectionAlreadyOpen => V::InvalidInput(pb::Empty {}), AnkiError::JSONError { info } => V::JsonError(info), AnkiError::ProtoError { info } => V::ProtoError(info), + AnkiError::NotFound => V::NotFoundError(Empty {}), }; pb::BackendError { @@ -304,10 +305,8 @@ impl Backend { pb::Empty {} }), Value::GetAllConfig(_) => OValue::GetAllConfig(self.get_all_config()?), - Value::GetAllNotetypes(_) => OValue::GetAllNotetypes(self.get_all_notetypes()?), - Value::SetAllNotetypes(bytes) => { - self.set_all_notetypes(&bytes)?; - OValue::SetAllNotetypes(pb::Empty {}) + Value::GetChangedNotetypes(_) => { + OValue::GetChangedNotetypes(self.get_changed_notetypes()?) } Value::GetAllDecks(_) => OValue::GetAllDecks(self.get_all_decks()?), Value::SetAllDecks(bytes) => { @@ -320,6 +319,31 @@ impl Backend { .map(Into::into) .collect(), }), + Value::GetNotetypeLegacy(id) => { + OValue::GetNotetypeLegacy(self.get_notetype_legacy(id)?) + } + Value::GetNotetypeNames(_) => OValue::GetNotetypeNames(self.get_notetype_names()?), + Value::GetNotetypeNamesAndCounts(_) => { + OValue::GetNotetypeNamesAndCounts(self.get_notetype_use_counts()?) + } + + Value::GetNotetypeIdByName(name) => { + OValue::GetNotetypeIdByName(self.get_notetype_id_by_name(name)?) + } + Value::AddOrUpdateNotetype(input) => { + OValue::AddOrUpdateNotetype(self.add_or_update_notetype_legacy(input)?) + } + Value::RemoveNotetype(id) => { + self.remove_notetype(id)?; + OValue::RemoveNotetype(pb::Empty {}) + } + Value::NewNote(ntid) => OValue::NewNote(self.new_note(ntid)?), + Value::AddNote(input) => OValue::AddNote(self.add_note(input)?), + Value::UpdateNote(note) => { + self.update_note(note)?; + OValue::UpdateNote(pb::Empty {}) + } + Value::GetNote(nid) => OValue::GetNote(self.get_note(nid)?), }) } @@ -856,21 +880,12 @@ impl Backend { }) } - fn set_all_notetypes(&self, json: &[u8]) -> Result<()> { - let val: HashMap = serde_json::from_slice(json)?; - self.with_col(|col| { - col.transact(None, |col| { - col.storage.set_schema11_notetypes(val)?; - col.storage.upgrade_notetypes_to_schema15() - }) - }) - } - - fn get_all_notetypes(&self) -> Result> { - self.with_col(|col| { - let nts = col.storage.get_all_notetypes_as_schema11()?; - serde_json::to_vec(&nts).map_err(Into::into) - }) + fn get_changed_notetypes(&self) -> Result> { + todo!("filter by usn"); + // self.with_col(|col| { + // let nts = col.storage.get_all_notetypes_as_schema11()?; + // serde_json::to_vec(&nts).map_err(Into::into) + // }) } fn set_all_decks(&self, json: &[u8]) -> Result<()> { @@ -884,6 +899,104 @@ impl Backend { serde_json::to_vec(&decks).map_err(Into::into) }) } + + fn get_notetype_names(&self) -> Result { + self.with_col(|col| { + let entries: Vec<_> = col + .storage + .get_all_notetype_names()? + .into_iter() + .map(|(id, name)| pb::NoteTypeNameId { id: id.0, name }) + .collect(); + Ok(pb::NoteTypeNames { entries }) + }) + } + + fn get_notetype_use_counts(&self) -> Result { + self.with_col(|col| { + let entries: Vec<_> = col + .storage + .get_notetype_use_counts()? + .into_iter() + .map(|(id, name, use_count)| pb::NoteTypeNameIdUseCount { + id: id.0, + name, + use_count, + }) + .collect(); + Ok(pb::NoteTypeUseCounts { entries }) + }) + } + + fn get_notetype_legacy(&self, id: i64) -> Result> { + self.with_col(|col| { + let schema11: NoteTypeSchema11 = col + .storage + .get_notetype(NoteTypeID(id))? + .ok_or(AnkiError::NotFound)? + .into(); + Ok(serde_json::to_vec(&schema11)?) + }) + } + + fn get_notetype_id_by_name(&self, name: String) -> Result { + self.with_col(|col| { + col.storage + .get_notetype_id(&name) + .map(|nt| nt.unwrap_or(NoteTypeID(0)).0) + }) + } + + fn add_or_update_notetype_legacy(&self, input: pb::AddOrUpdateNotetypeIn) -> Result { + self.with_col(|col| { + let legacy: NoteTypeSchema11 = serde_json::from_slice(&input.json)?; + let mut nt: NoteType = legacy.into(); + if nt.id.0 == 0 { + col.add_notetype(&mut nt)?; + } else { + col.update_notetype(&mut nt, input.preserve_usn_and_mtime)?; + } + Ok(nt.id.0) + }) + } + + fn remove_notetype(&self, _id: i64) -> Result<()> { + println!("fixme: remove notetype"); + Ok(()) + } + + fn new_note(&self, ntid: i64) -> Result { + self.with_col(|col| { + let nt = col + .get_notetype(NoteTypeID(ntid))? + .ok_or(AnkiError::NotFound)?; + Ok(nt.new_note().into()) + }) + } + + fn add_note(&self, input: pb::AddNoteIn) -> Result { + self.with_col(|col| { + let mut note: Note = input.note.ok_or(AnkiError::NotFound)?.into(); + col.add_note(&mut note, DeckID(input.deck_id)) + .map(|_| note.id.0) + }) + } + + fn update_note(&self, pbnote: pb::Note) -> Result<()> { + self.with_col(|col| { + let mut note: Note = pbnote.into(); + col.update_note(&mut note) + }) + } + + fn get_note(&self, nid: i64) -> Result { + self.with_col(|col| { + col.storage + .get_note(NoteID(nid))? + .ok_or(AnkiError::NotFound) + .map(Into::into) + }) + } } fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue { diff --git a/rslib/src/err.rs b/rslib/src/err.rs index 543bcb589..5c3f2a74f 100644 --- a/rslib/src/err.rs +++ b/rslib/src/err.rs @@ -45,6 +45,9 @@ pub enum AnkiError { #[fail(display = "Close the existing collection first.")] CollectionAlreadyOpen, + + #[fail(display = "A requested item was not found.")] + NotFound, } // error helpers diff --git a/rslib/src/notes.rs b/rslib/src/notes.rs index 2922517ac..05660dab1 100644 --- a/rslib/src/notes.rs +++ b/rslib/src/notes.rs @@ -2,7 +2,9 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html use crate::{ + backend_proto as pb, collection::Collection, + decks::DeckID, define_newtype, err::{AnkiError, Result}, notetype::{CardGenContext, NoteField, NoteType, NoteTypeID}, @@ -105,6 +107,36 @@ impl Note { } } +impl From for pb::Note { + fn from(n: Note) -> Self { + pb::Note { + id: n.id.0, + guid: n.guid, + ntid: n.ntid.0, + mtime_secs: n.mtime.0 as u32, + usn: n.usn.0, + tags: n.tags, + fields: n.fields, + } + } +} + +impl From for Note { + fn from(n: pb::Note) -> Self { + Note { + id: NoteID(n.id), + guid: n.guid, + ntid: NoteTypeID(n.ntid), + mtime: TimestampSecs(n.mtime_secs as i64), + usn: Usn(n.usn), + tags: n.tags, + fields: n.fields, + sort_field: None, + checksum: None, + } + } +} + /// Text must be passed to strip_html_preserving_image_filenames() by /// caller prior to passing in here. pub(crate) fn field_checksum(text: &str) -> u32 { @@ -130,20 +162,37 @@ fn anki_base91(mut n: u64) -> String { } impl Collection { - pub fn add_note(&mut self, note: &mut Note) -> Result<()> { + fn canonify_note_tags(&self, note: &mut Note, usn: Usn) -> Result<()> { + // fixme: avoid the excess split/join + note.tags = self + .canonify_tags(¬e.tags.join(" "), usn)? + .0 + .split(' ') + .map(Into::into) + .collect(); + Ok(()) + } + + pub fn add_note(&mut self, note: &mut Note, did: DeckID) -> Result<()> { self.transact(None, |col| { let nt = col .get_notetype(note.ntid)? .ok_or_else(|| AnkiError::invalid_input("missing note type"))?; let ctx = CardGenContext::new(&nt, col.usn()?); - col.add_note_inner(&ctx, note) + col.add_note_inner(&ctx, note, did) }) } - pub(crate) fn add_note_inner(&mut self, ctx: &CardGenContext, note: &mut Note) -> Result<()> { + pub(crate) fn add_note_inner( + &mut self, + ctx: &CardGenContext, + note: &mut Note, + did: DeckID, + ) -> Result<()> { + self.canonify_note_tags(note, ctx.usn)?; note.prepare_for_update(&ctx.notetype, ctx.usn)?; self.storage.add_note(note)?; - self.generate_cards_for_new_note(ctx, note) + self.generate_cards_for_new_note(ctx, note, did) } pub fn update_note(&mut self, note: &mut Note) -> Result<()> { @@ -161,6 +210,7 @@ impl Collection { ctx: &CardGenContext, note: &mut Note, ) -> Result<()> { + self.canonify_note_tags(note, ctx.usn)?; note.prepare_for_update(ctx.notetype, ctx.usn)?; self.generate_cards_for_existing_note(ctx, note)?; self.storage.update_note(note)?; @@ -198,7 +248,7 @@ mod test { let mut note = nt.new_note(); // if no cards are generated, 1 card is added - col.add_note(&mut note).unwrap(); + col.add_note(&mut note, DeckID(1)).unwrap(); let existing = col.storage.existing_cards_for_note(note.id)?; assert_eq!(existing.len(), 1); assert_eq!(existing[0].ord, 0); @@ -220,7 +270,7 @@ mod test { // cloze cards also generate card 0 if no clozes are found let nt = col.get_notetype_by_name("cloze")?.unwrap(); let mut note = nt.new_note(); - col.add_note(&mut note).unwrap(); + col.add_note(&mut note, DeckID(1)).unwrap(); let existing = col.storage.existing_cards_for_note(note.id)?; assert_eq!(existing.len(), 1); assert_eq!(existing[0].ord, 0); diff --git a/rslib/src/notetype/cardgen.rs b/rslib/src/notetype/cardgen.rs index dc51cf0dc..59e14cda2 100644 --- a/rslib/src/notetype/cardgen.rs +++ b/rslib/src/notetype/cardgen.rs @@ -204,8 +204,9 @@ impl Collection { &mut self, ctx: &CardGenContext, note: &Note, + target_deck_id: DeckID, ) -> Result<()> { - self.generate_cards_for_note(ctx, note, false) + self.generate_cards_for_note(ctx, note, &[], Some(target_deck_id)) } pub(crate) fn generate_cards_for_existing_note( @@ -213,25 +214,22 @@ impl Collection { ctx: &CardGenContext, note: &Note, ) -> Result<()> { - self.generate_cards_for_note(ctx, note, true) + let existing = self.storage.existing_cards_for_note(note.id)?; + self.generate_cards_for_note(ctx, note, &existing, None) } fn generate_cards_for_note( &mut self, ctx: &CardGenContext, note: &Note, - check_existing: bool, + existing: &[AlreadyGeneratedCardInfo], + target_deck_id: Option, ) -> Result<()> { - let existing = if check_existing { - self.storage.existing_cards_for_note(note.id)? - } else { - vec![] - }; let cards = ctx.new_cards_required(note, &existing); if cards.is_empty() { return Ok(()); } - self.add_generated_cards(ctx, note.id, &cards) + self.add_generated_cards(ctx, note.id, &cards, target_deck_id) } pub(crate) fn generate_cards_for_notetype(&mut self, ctx: &CardGenContext) -> Result<()> { @@ -246,7 +244,7 @@ impl Collection { continue; } let note = self.storage.get_note(nid)?.unwrap(); - self.generate_cards_for_note(ctx, ¬e, true)?; + self.generate_cards_for_note(ctx, ¬e, &existing_cards, None)?; } Ok(()) @@ -257,11 +255,16 @@ impl Collection { ctx: &CardGenContext, nid: NoteID, cards: &[CardToGenerate], + target_deck_id: Option, ) -> Result<()> { let mut next_pos = None; for c in cards { // fixme: deal with case where invalid deck pointed to - let did = c.did.unwrap_or_else(|| ctx.notetype.target_deck_id()); + // fixme: deprecated note type deck id + let did = c + .did + .or(target_deck_id) + .unwrap_or_else(|| ctx.notetype.target_deck_id()); let due = c.due.unwrap_or_else(|| { if next_pos.is_none() { next_pos = Some(self.get_and_update_next_card_position().unwrap_or(0)); diff --git a/rslib/src/notetype/mod.rs b/rslib/src/notetype/mod.rs index 4bc3d6e56..b25b7df16 100644 --- a/rslib/src/notetype/mod.rs +++ b/rslib/src/notetype/mod.rs @@ -168,16 +168,23 @@ impl NoteType { pub(crate) fn prepare_for_adding(&mut self) -> Result<()> { // defaults to 0 - self.config.target_deck_id = 1; + if self.config.target_deck_id == 0 { + self.config.target_deck_id = 1; + } if self.fields.is_empty() { return Err(AnkiError::invalid_input("1 field required")); } if self.templates.is_empty() { return Err(AnkiError::invalid_input("1 template required")); } + self.prepare_for_update() + } + + pub(crate) fn prepare_for_update(&mut self) -> Result<()> { self.normalize_names(); self.ensure_names_unique(); self.update_requirements(); + // fixme: deal with duplicate note type names on update Ok(()) } @@ -205,15 +212,34 @@ impl From for NoteTypeProto { } impl Collection { + /// Add a new notetype, and allocate it an ID. + pub fn add_notetype(&mut self, nt: &mut NoteType) -> Result<()> { + nt.prepare_for_adding()?; + self.transact(None, |col| col.storage.add_new_notetype(nt)) + } + /// Saves changes to a note type. This will force a full sync if templates /// or fields have been added/removed/reordered. - pub fn update_notetype(&mut self, nt: &mut NoteType) -> Result<()> { + pub fn update_notetype(&mut self, nt: &mut NoteType, preserve_usn: bool) -> Result<()> { + nt.prepare_for_update()?; + if !preserve_usn { + nt.mtime_secs = TimestampSecs::now(); + nt.usn = self.usn()?; + } self.transact(None, |col| { - let existing_notetype = col - .get_notetype(nt.id)? - .ok_or_else(|| AnkiError::invalid_input("no such notetype"))?; - col.update_notes_for_changed_fields(nt, existing_notetype.fields.len())?; - col.update_cards_for_changed_templates(nt, existing_notetype.templates.len())?; + if !preserve_usn { + let existing_notetype = col + .get_notetype(nt.id)? + .ok_or_else(|| AnkiError::invalid_input("no such notetype"))?; + col.update_notes_for_changed_fields(nt, existing_notetype.fields.len())?; + col.update_cards_for_changed_templates(nt, existing_notetype.templates.len())?; + } + + col.storage.update_notetype_config(&nt)?; + col.storage.update_notetype_fields(nt.id, &nt.fields)?; + col.storage + .update_notetype_templates(nt.id, &nt.templates)?; + // fixme: update cache instead of clearing col.state.notetype_cache.remove(&nt.id); diff --git a/rslib/src/notetype/schema11.rs b/rslib/src/notetype/schema11.rs index 41a686ce6..5a4238eb5 100644 --- a/rslib/src/notetype/schema11.rs +++ b/rslib/src/notetype/schema11.rs @@ -195,7 +195,7 @@ impl From for CardRequirementSchema11 { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct NoteFieldSchema11 { pub(crate) name: String, - pub(crate) ord: u16, + pub(crate) ord: Option, #[serde(deserialize_with = "deserialize_bool_from_anything")] pub(crate) sticky: bool, #[serde(deserialize_with = "deserialize_bool_from_anything")] @@ -210,7 +210,7 @@ impl Default for NoteFieldSchema11 { fn default() -> Self { Self { name: String::new(), - ord: 0, + ord: None, sticky: false, rtl: false, font: "Arial".to_string(), @@ -223,7 +223,7 @@ impl Default for NoteFieldSchema11 { impl From for NoteField { fn from(f: NoteFieldSchema11) -> Self { NoteField { - ord: Some(f.ord as u32), + ord: f.ord.map(|o| o as u32), name: f.name, config: NoteFieldConfig { sticky: f.sticky, @@ -243,7 +243,7 @@ impl From for NoteFieldSchema11 { let conf = p.config; NoteFieldSchema11 { name: p.name, - ord: p.ord.unwrap() as u16, + ord: p.ord.map(|o| o as u16), sticky: conf.sticky, rtl: conf.rtl, font: conf.font_name, @@ -256,7 +256,7 @@ impl From for NoteFieldSchema11 { #[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct CardTemplateSchema11 { pub(crate) name: String, - pub(crate) ord: u16, + pub(crate) ord: Option, pub(crate) qfmt: String, #[serde(default)] pub(crate) afmt: String, @@ -277,7 +277,7 @@ pub struct CardTemplateSchema11 { impl From for CardTemplate { fn from(t: CardTemplateSchema11) -> Self { CardTemplate { - ord: Some(t.ord as u32), + ord: t.ord.map(|t| t as u32), name: t.name, mtime_secs: TimestampSecs(0), usn: Usn(0), @@ -302,7 +302,7 @@ impl From for CardTemplateSchema11 { let conf = p.config; CardTemplateSchema11 { name: p.name, - ord: p.ord.unwrap() as u16, + ord: p.ord.map(|o| o as u16), qfmt: conf.q_format, afmt: conf.a_format, bqfmt: conf.q_format_browser, diff --git a/rslib/src/notetype/schemachange.rs b/rslib/src/notetype/schemachange.rs index e731e733c..0f0105873 100644 --- a/rslib/src/notetype/schemachange.rs +++ b/rslib/src/notetype/schemachange.rs @@ -123,7 +123,7 @@ impl Collection { #[cfg(test)] mod test { use super::{ords_changed, TemplateOrdChanges}; - use crate::{collection::open_test_collection, err::Result, search::SortMode}; + use crate::{collection::open_test_collection, decks::DeckID, err::Result, search::SortMode}; #[test] fn ord_changes() { @@ -190,10 +190,10 @@ mod test { let mut note = nt.new_note(); assert_eq!(note.fields.len(), 2); note.fields = vec!["one".into(), "two".into()]; - col.add_note(&mut note)?; + col.add_note(&mut note, DeckID(1))?; nt.add_field("three"); - col.update_notetype(&mut nt)?; + col.update_notetype(&mut nt, false)?; let note = col.storage.get_note(note.id)?.unwrap(); assert_eq!( @@ -202,7 +202,7 @@ mod test { ); nt.fields.remove(1); - col.update_notetype(&mut nt)?; + col.update_notetype(&mut nt, false)?; let note = col.storage.get_note(note.id)?.unwrap(); assert_eq!(note.fields, vec!["one".to_string(), "".into()]); @@ -220,7 +220,7 @@ mod test { let mut note = nt.new_note(); assert_eq!(note.fields.len(), 2); note.fields = vec!["one".into(), "two".into()]; - col.add_note(&mut note)?; + col.add_note(&mut note, DeckID(1))?; assert_eq!( col.search_cards(&format!("nid:{}", note.id), SortMode::NoOrder) @@ -231,7 +231,7 @@ mod test { // add an extra card template nt.add_template("card 2", "{{Front}}", ""); - col.update_notetype(&mut nt)?; + col.update_notetype(&mut nt, false)?; assert_eq!( col.search_cards(&format!("nid:{}", note.id), SortMode::NoOrder) diff --git a/rslib/src/storage/notetype/get_use_counts.sql b/rslib/src/storage/notetype/get_use_counts.sql new file mode 100644 index 000000000..cc667ce4d --- /dev/null +++ b/rslib/src/storage/notetype/get_use_counts.sql @@ -0,0 +1,15 @@ +select + mid, + ( + select + name + from notetypes nt + where + nt.id = mid + ) as name, + count(id) +from notes +group by + mid +order by + name \ No newline at end of file diff --git a/rslib/src/storage/notetype/mod.rs b/rslib/src/storage/notetype/mod.rs index 13e412aa5..6cf875727 100644 --- a/rslib/src/storage/notetype/mod.rs +++ b/rslib/src/storage/notetype/mod.rs @@ -105,7 +105,19 @@ impl SqliteStorage { .collect() } - fn update_notetype_fields(&self, ntid: NoteTypeID, fields: &[NoteField]) -> Result<()> { + /// Returns list of (id, name, use_count) + pub fn get_notetype_use_counts(&self) -> Result> { + self.db + .prepare_cached(include_str!("get_use_counts.sql"))? + .query_and_then(NO_PARAMS, |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))? + .collect() + } + + pub(crate) fn update_notetype_fields( + &self, + ntid: NoteTypeID, + fields: &[NoteField], + ) -> Result<()> { self.db .prepare_cached("delete from fields where ntid=?")? .execute(&[ntid])?; @@ -119,7 +131,7 @@ impl SqliteStorage { Ok(()) } - fn update_notetype_templates( + pub(crate) fn update_notetype_templates( &self, ntid: NoteTypeID, templates: &[CardTemplate], @@ -146,7 +158,7 @@ impl SqliteStorage { Ok(()) } - fn update_notetype_config(&self, nt: &NoteType) -> Result<()> { + pub(crate) fn update_notetype_config(&self, nt: &NoteType) -> Result<()> { assert!(nt.id.0 != 0); let mut stmt = self .db