From fdeca610b084541045c446012edb1d9237b08bf6 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sun, 29 Mar 2020 09:58:33 +1000 Subject: [PATCH] drop separate RequestContext/StorageContext --- rslib/src/backend/dbproxy.rs | 10 +- rslib/src/backend/mod.rs | 50 +++--- rslib/src/card.rs | 4 +- rslib/src/collection.rs | 83 ++++------ rslib/src/media/check.rs | 24 ++- rslib/src/search/cards.rs | 10 +- rslib/src/search/notes.rs | 7 +- rslib/src/search/sqlwriter.rs | 299 +++++++++++++++++----------------- rslib/src/storage/card/mod.rs | 15 +- rslib/src/storage/mod.rs | 2 +- rslib/src/storage/sqlite.rs | 64 +++----- 11 files changed, 254 insertions(+), 314 deletions(-) diff --git a/rslib/src/backend/dbproxy.rs b/rslib/src/backend/dbproxy.rs index aa4cb173c..a458ed67f 100644 --- a/rslib/src/backend/dbproxy.rs +++ b/rslib/src/backend/dbproxy.rs @@ -2,7 +2,7 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html use crate::err::Result; -use crate::storage::StorageContext; +use crate::storage::SqliteStorage; use rusqlite::types::{FromSql, FromSqlError, ToSql, ToSqlOutput, ValueRef}; use rusqlite::OptionalExtension; use serde_derive::{Deserialize, Serialize}; @@ -67,7 +67,7 @@ impl FromSql for SqlValue { } } -pub(super) fn db_command_bytes(ctx: &StorageContext, input: &[u8]) -> Result { +pub(super) fn db_command_bytes(ctx: &SqliteStorage, input: &[u8]) -> Result { let req: DBRequest = serde_json::from_slice(input)?; let resp = match req { DBRequest::Query { @@ -98,7 +98,7 @@ pub(super) fn db_command_bytes(ctx: &StorageContext, input: &[u8]) -> Result Result { +pub(super) fn db_query_row(ctx: &SqliteStorage, sql: &str, args: &[SqlValue]) -> Result { let mut stmt = ctx.db.prepare_cached(sql)?; let columns = stmt.column_count(); @@ -122,7 +122,7 @@ pub(super) fn db_query_row(ctx: &StorageContext, sql: &str, args: &[SqlValue]) - Ok(DBResult::Rows(rows)) } -pub(super) fn db_query(ctx: &StorageContext, sql: &str, args: &[SqlValue]) -> Result { +pub(super) fn db_query(ctx: &SqliteStorage, sql: &str, args: &[SqlValue]) -> Result { let mut stmt = ctx.db.prepare_cached(sql)?; let columns = stmt.column_count(); @@ -141,7 +141,7 @@ pub(super) fn db_query(ctx: &StorageContext, sql: &str, args: &[SqlValue]) -> Re } pub(super) fn db_execute_many( - ctx: &StorageContext, + ctx: &SqliteStorage, sql: &str, args: &[Vec], ) -> Result { diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index 27c2bc95f..cab32fd88 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -590,48 +590,44 @@ impl Backend { } pub fn db_command(&self, input: &[u8]) -> Result { - self.with_col(|col| col.with_ctx(|ctx| db_command_bytes(&ctx.storage, input))) + self.with_col(|col| db_command_bytes(&col.storage, input)) } fn search_cards(&self, input: pb::SearchCardsIn) -> Result { self.with_col(|col| { - col.with_ctx(|ctx| { - let order = if let Some(order) = input.order { - use pb::sort_order::Value as V; - match order.value { - Some(V::None(_)) => SortMode::NoOrder, - Some(V::Custom(s)) => SortMode::Custom(s), - Some(V::FromConfig(_)) => SortMode::FromConfig, - Some(V::Builtin(b)) => SortMode::Builtin { - kind: sort_kind_from_pb(b.kind), - reverse: b.reverse, - }, - None => SortMode::FromConfig, - } - } else { - SortMode::FromConfig - }; - let cids = search_cards(ctx, &input.search, order)?; - Ok(pb::SearchCardsOut { - card_ids: cids.into_iter().map(|v| v.0).collect(), - }) + let order = if let Some(order) = input.order { + use pb::sort_order::Value as V; + match order.value { + Some(V::None(_)) => SortMode::NoOrder, + Some(V::Custom(s)) => SortMode::Custom(s), + Some(V::FromConfig(_)) => SortMode::FromConfig, + Some(V::Builtin(b)) => SortMode::Builtin { + kind: sort_kind_from_pb(b.kind), + reverse: b.reverse, + }, + None => SortMode::FromConfig, + } + } else { + SortMode::FromConfig + }; + let cids = search_cards(col, &input.search, order)?; + Ok(pb::SearchCardsOut { + card_ids: cids.into_iter().map(|v| v.0).collect(), }) }) } 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.into_iter().map(|v| v.0).collect(), - }) + let nids = search_notes(col, &input.search)?; + Ok(pb::SearchNotesOut { + note_ids: nids.into_iter().map(|v| v.0).collect(), }) }) } fn get_card(&self, cid: i64) -> Result { - let card = self.with_col(|col| col.with_ctx(|ctx| ctx.storage.get_card(CardID(cid))))?; + let card = self.with_col(|col| col.storage.get_card(CardID(cid)))?; Ok(pb::GetCardOut { card: card.map(card_to_pb), }) diff --git a/rslib/src/card.rs b/rslib/src/card.rs index 5b7bc08d6..f84aa33ec 100644 --- a/rslib/src/card.rs +++ b/rslib/src/card.rs @@ -5,7 +5,7 @@ use crate::decks::DeckID; use crate::define_newtype; use crate::err::{AnkiError, Result}; use crate::notes::NoteID; -use crate::{collection::RequestContext, timestamp::TimestampSecs, types::Usn}; +use crate::{collection::Collection, timestamp::TimestampSecs, types::Usn}; use num_enum::TryFromPrimitive; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -86,7 +86,7 @@ impl Default for Card { } } -impl RequestContext<'_> { +impl Collection { pub(crate) fn update_card(&mut self, card: &mut Card) -> Result<()> { if card.id.0 == 0 { return Err(AnkiError::invalid_input("card id not set")); diff --git a/rslib/src/collection.rs b/rslib/src/collection.rs index 5db81874e..53a820ac3 100644 --- a/rslib/src/collection.rs +++ b/rslib/src/collection.rs @@ -4,7 +4,7 @@ use crate::err::{AnkiError, Result}; use crate::i18n::I18n; use crate::log::Logger; -use crate::storage::{SqliteStorage, StorageContext}; +use crate::storage::SqliteStorage; use std::path::PathBuf; pub fn open_collection>( @@ -16,24 +16,30 @@ pub fn open_collection>( log: Logger, ) -> Result { let col_path = path.into(); - let storage = SqliteStorage::open_or_create(&col_path)?; + let storage = SqliteStorage::open_or_create(&col_path, server)?; let col = Collection { storage, col_path, media_folder: media_folder.into(), media_db: media_db.into(), - server, i18n, log, - state: CollectionState::Normal, + state: CollectionState { + task_state: CollectionTaskState::Normal, + }, }; Ok(col) } +#[derive(Debug)] +pub struct CollectionState { + task_state: CollectionTaskState, +} + #[derive(Debug, PartialEq)] -pub enum CollectionState { +pub enum CollectionTaskState { Normal, // in this state, the DB must not be closed MediaSyncRunning, @@ -45,7 +51,6 @@ pub struct Collection { pub(crate) col_path: PathBuf, pub(crate) media_folder: PathBuf, pub(crate) media_db: PathBuf, - pub(crate) server: bool, pub(crate) i18n: I18n, pub(crate) log: Logger, state: CollectionState, @@ -53,63 +58,35 @@ pub struct Collection { pub(crate) enum CollectionOp {} -pub(crate) struct RequestContext<'a> { - pub storage: StorageContext<'a>, - pub i18n: &'a I18n, - pub log: &'a Logger, - pub should_commit: bool, -} - impl Collection { - /// Call the provided closure with a RequestContext that exists for - /// the duration of the call. The request will cache prepared sql - /// statements, so should be passed down the call tree. - /// - /// This function should be used for read-only requests. To mutate - /// the database, use transact() instead. - pub(crate) fn with_ctx(&self, func: F) -> Result - where - F: FnOnce(&mut RequestContext) -> Result, - { - let mut ctx = RequestContext { - storage: self.storage.context(self.server), - i18n: &self.i18n, - log: &self.log, - should_commit: true, - }; - func(&mut ctx) - } - /// Execute the provided closure in a transaction, rolling back if /// an error is returned. - pub(crate) fn transact(&self, op: Option, func: F) -> Result + pub(crate) fn transact(&mut self, op: Option, func: F) -> Result where - F: FnOnce(&mut RequestContext) -> Result, + F: FnOnce(&mut Collection) -> Result, { - self.with_ctx(|ctx| { - ctx.storage.begin_rust_trx()?; + self.storage.begin_rust_trx()?; - let mut res = func(ctx); + let mut res = func(self); - if res.is_ok() && ctx.should_commit { - if let Err(e) = ctx.storage.mark_modified() { - res = Err(e); - } else if let Err(e) = ctx.storage.commit_rust_op(op) { - res = Err(e); - } + if res.is_ok() { + if let Err(e) = self.storage.mark_modified() { + res = Err(e); + } else if let Err(e) = self.storage.commit_rust_op(op) { + res = Err(e); } + } - if res.is_err() || !ctx.should_commit { - ctx.storage.rollback_rust_trx()?; - } + if res.is_err() { + self.storage.rollback_rust_trx()?; + } - res - }) + res } pub(crate) fn set_media_sync_running(&mut self) -> Result<()> { - if self.state == CollectionState::Normal { - self.state = CollectionState::MediaSyncRunning; + if self.state.task_state == CollectionTaskState::Normal { + self.state.task_state = CollectionTaskState::MediaSyncRunning; Ok(()) } else { Err(AnkiError::invalid_input("media sync already running")) @@ -117,8 +94,8 @@ impl Collection { } pub(crate) fn set_media_sync_finished(&mut self) -> Result<()> { - if self.state == CollectionState::MediaSyncRunning { - self.state = CollectionState::Normal; + if self.state.task_state == CollectionTaskState::MediaSyncRunning { + self.state.task_state = CollectionTaskState::Normal; Ok(()) } else { Err(AnkiError::invalid_input("media sync not running")) @@ -126,6 +103,6 @@ impl Collection { } pub(crate) fn can_close(&self) -> bool { - self.state == CollectionState::Normal + self.state.task_state == CollectionTaskState::Normal } } diff --git a/rslib/src/media/check.rs b/rslib/src/media/check.rs index dd1c4bde3..763997edb 100644 --- a/rslib/src/media/check.rs +++ b/rslib/src/media/check.rs @@ -1,7 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -use crate::collection::RequestContext; +use crate::collection::Collection; use crate::err::{AnkiError, DBErrorKind, Result}; use crate::i18n::{tr_args, tr_strs, FString}; use crate::latex::extract_latex_expanding_clozes; @@ -44,26 +44,26 @@ struct MediaFolderCheck { oversize: Vec, } -pub struct MediaChecker<'a, 'b, P> +pub struct MediaChecker<'a, P> where P: FnMut(usize) -> bool, { - ctx: &'a mut RequestContext<'b>, + ctx: &'a Collection, mgr: &'a MediaManager, progress_cb: P, checked: usize, progress_updated: Instant, } -impl

MediaChecker<'_, '_, P> +impl

MediaChecker<'_, P> where P: FnMut(usize) -> bool, { - pub(crate) fn new<'a, 'b>( - ctx: &'a mut RequestContext<'b>, + pub(crate) fn new<'a>( + ctx: &'a mut Collection, mgr: &'a MediaManager, progress_cb: P, - ) -> MediaChecker<'a, 'b, P> { + ) -> MediaChecker<'a, P> { MediaChecker { ctx, mgr, @@ -411,10 +411,6 @@ where Ok(()) })?; - if !collection_modified { - self.ctx.should_commit = false; - } - Ok(referenced_files) } } @@ -561,7 +557,7 @@ pub(crate) mod test { #[test] fn media_check() -> Result<()> { - let (_dir, mgr, col) = common_setup()?; + let (_dir, mgr, mut col) = common_setup()?; // add some test files fs::write(&mgr.media_folder.join("zerobytes"), "")?; @@ -637,7 +633,7 @@ Unused: unused.jpg #[test] fn trash_handling() -> Result<()> { - let (_dir, mgr, col) = common_setup()?; + let (_dir, mgr, mut col) = common_setup()?; let trash_folder = trash_folder(&mgr.media_folder)?; fs::write(trash_folder.join("test.jpg"), "test")?; @@ -687,7 +683,7 @@ Unused: unused.jpg #[test] fn unicode_normalization() -> Result<()> { - let (_dir, mgr, col) = common_setup()?; + let (_dir, mgr, mut col) = common_setup()?; fs::write(&mgr.media_folder.join("ぱぱ.jpg"), "nfd encoding")?; diff --git a/rslib/src/search/cards.rs b/rslib/src/search/cards.rs index db9c8effa..a8d23ef4f 100644 --- a/rslib/src/search/cards.rs +++ b/rslib/src/search/cards.rs @@ -4,7 +4,7 @@ use super::{parser::Node, sqlwriter::node_to_sql}; use crate::card::CardID; use crate::card::CardType; -use crate::collection::RequestContext; +use crate::collection::Collection; use crate::config::SortKind; use crate::err::Result; use crate::search::parser::parse; @@ -18,7 +18,7 @@ pub(crate) enum SortMode { } pub(crate) fn search_cards<'a, 'b>( - req: &'a mut RequestContext<'b>, + req: &'b mut Collection, search: &'a str, order: SortMode, ) -> Result> { @@ -96,7 +96,7 @@ 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 RequestContext, kind: &SortKind) -> Result<()> { +fn prepare_sort(req: &mut Collection, kind: &SortKind) -> Result<()> { use SortKind::*; match kind { CardDeck | NoteType => { @@ -139,14 +139,14 @@ fn prepare_sort(req: &mut RequestContext, kind: &SortKind) -> Result<()> { Ok(()) } -fn prepare_sort_order_table(req: &mut RequestContext) -> Result<()> { +fn prepare_sort_order_table(req: &mut Collection) -> Result<()> { req.storage .db .execute_batch(include_str!("sort_order.sql"))?; Ok(()) } -fn prepare_sort_order_table2(req: &mut RequestContext) -> Result<()> { +fn prepare_sort_order_table2(req: &mut Collection) -> Result<()> { req.storage .db .execute_batch(include_str!("sort_order2.sql"))?; diff --git a/rslib/src/search/notes.rs b/rslib/src/search/notes.rs index 5af735809..8c137eb57 100644 --- a/rslib/src/search/notes.rs +++ b/rslib/src/search/notes.rs @@ -2,15 +2,12 @@ // 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::collection::Collection; use crate::err::Result; use crate::notes::NoteID; use crate::search::parser::parse; -pub(crate) fn search_notes<'a, 'b>( - req: &'a mut RequestContext<'b>, - search: &'a str, -) -> Result> { +pub(crate) fn search_notes<'a>(req: &'a mut Collection, search: &'a str) -> Result> { let top_node = Node::Group(parse(search)?); let (sql, args) = node_to_sql(req, &top_node)?; diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index f1348ba58..0887c6ff9 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -10,26 +10,26 @@ use crate::notes::field_checksum; use crate::notetypes::NoteTypeID; use crate::text::matches_wildcard; use crate::text::without_combining; -use crate::{collection::RequestContext, text::strip_html_preserving_image_filenames}; +use crate::{collection::Collection, text::strip_html_preserving_image_filenames}; use std::fmt::Write; -struct SqlWriter<'a, 'b> { - req: &'a mut RequestContext<'b>, +struct SqlWriter<'a> { + col: &'a mut Collection, sql: String, args: Vec, } -pub(super) fn node_to_sql(req: &mut RequestContext, node: &Node) -> Result<(String, Vec)> { +pub(super) fn node_to_sql(req: &mut Collection, node: &Node) -> Result<(String, Vec)> { let mut sctx = SqlWriter::new(req); sctx.write_node_to_sql(&node)?; Ok((sctx.sql, sctx.args)) } -impl SqlWriter<'_, '_> { - fn new<'a, 'b>(req: &'a mut RequestContext<'b>) -> SqlWriter<'a, 'b> { +impl SqlWriter<'_> { + fn new(col: &mut Collection) -> SqlWriter<'_> { let sql = String::new(); let args = vec![]; - SqlWriter { req, sql, args } + SqlWriter { col, sql, args } } fn write_node_to_sql(&mut self, node: &Node) -> Result<()> { @@ -129,7 +129,7 @@ impl SqlWriter<'_, '_> { } fn write_rated(&mut self, days: u32, ease: Option) -> Result<()> { - let today_cutoff = self.req.storage.timing_today()?.next_day_at; + let today_cutoff = self.col.storage.timing_today()?.next_day_at; let days = days.min(365) as i64; let target_cutoff_ms = (today_cutoff - 86_400 * days) * 1_000; write!( @@ -148,7 +148,7 @@ impl SqlWriter<'_, '_> { } fn write_prop(&mut self, op: &str, kind: &PropertyKind) -> Result<()> { - let timing = self.req.storage.timing_today()?; + let timing = self.col.storage.timing_today()?; match kind { PropertyKind::Due(days) => { let day = days + (timing.days_elapsed as i32); @@ -173,7 +173,7 @@ impl SqlWriter<'_, '_> { } fn write_state(&mut self, state: &StateKind) -> Result<()> { - let timing = self.req.storage.timing_today()?; + let timing = self.col.storage.timing_today()?; match state { StateKind::New => write!(self.sql, "c.type = {}", CardQueue::New as i8), StateKind::Review => write!(self.sql, "c.type = {}", CardQueue::Review as i8), @@ -212,14 +212,14 @@ impl SqlWriter<'_, '_> { "filtered" => write!(self.sql, "c.odid > 0").unwrap(), deck => { let all_decks: Vec<_> = self - .req + .col .storage .all_decks()? .into_iter() .map(|(_, v)| v) .collect(); let dids_with_children = if deck == "current" { - let config = self.req.storage.all_config()?; + let config = self.col.storage.all_config()?; let mut dids_with_children = vec![config.current_deck_id]; let current = get_deck(&all_decks, config.current_deck_id) .ok_or_else(|| AnkiError::invalid_input("invalid current deck"))?; @@ -251,7 +251,7 @@ impl SqlWriter<'_, '_> { write!(self.sql, "c.ord = {}", n).unwrap(); } TemplateKind::Name(name) => { - let note_types = self.req.storage.all_note_types()?; + let note_types = self.col.storage.all_note_types()?; let mut id_ords = vec![]; for nt in note_types.values() { for tmpl in &nt.templates { @@ -280,7 +280,7 @@ impl SqlWriter<'_, '_> { fn write_note_type(&mut self, nt_name: &str) -> Result<()> { let mut ntids: Vec<_> = self - .req + .col .storage .all_note_types()? .values() @@ -295,7 +295,7 @@ impl SqlWriter<'_, '_> { } fn write_single_field(&mut self, field_name: &str, val: &str, is_re: bool) -> Result<()> { - let note_types = self.req.storage.all_note_types()?; + let note_types = self.col.storage.all_note_types()?; let mut field_map = vec![]; for nt in note_types.values() { @@ -354,7 +354,7 @@ impl SqlWriter<'_, '_> { } fn write_added(&mut self, days: u32) -> Result<()> { - let timing = self.req.storage.timing_today()?; + let timing = self.col.storage.timing_today()?; let cutoff = (timing.next_day_at - (86_400 * (days as i64))) * 1_000; write!(self.sql, "c.id > {}", cutoff).unwrap(); Ok(()) @@ -384,7 +384,11 @@ where #[cfg(test)] mod test { use super::ids_to_string; - use crate::{collection::open_collection, i18n::I18n, log}; + use crate::{ + collection::{open_collection, Collection}, + i18n::I18n, + log, + }; use std::{fs, path::PathBuf}; use tempfile::tempdir; @@ -409,7 +413,7 @@ mod test { use super::*; // shortcut - fn s(req: &mut RequestContext, search: &str) -> (String, Vec) { + fn s(req: &mut Collection, search: &str) -> (String, Vec) { let node = Node::Group(parse(search).unwrap()); node_to_sql(req, &node).unwrap() } @@ -423,7 +427,7 @@ mod test { fs::write(&col_path, MEDIACHECK_ANKI2).unwrap(); let i18n = I18n::new(&[""], "", log::terminal()); - let col = open_collection( + let mut col = open_collection( &col_path, &PathBuf::new(), &PathBuf::new(), @@ -433,149 +437,144 @@ mod test { ) .unwrap(); - col.with_ctx(|ctx| { - // unqualified search - assert_eq!( - s(ctx, "test"), - ( - "((n.sfld like ?1 escape '\\' or n.flds like ?1 escape '\\'))".into(), - vec!["%test%".into()] + let ctx = &mut col; + // unqualified search + assert_eq!( + s(ctx, "test"), + ( + "((n.sfld like ?1 escape '\\' or n.flds like ?1 escape '\\'))".into(), + vec!["%test%".into()] + ) + ); + assert_eq!(s(ctx, "te%st").1, vec!["%te%st%".to_string()]); + // user should be able to escape sql wildcards + assert_eq!(s(ctx, r#"te\%s\_t"#).1, vec!["%te\\%s\\_t%".to_string()]); + + // qualified search + assert_eq!( + s(ctx, "front:te*st"), + ( + concat!( + "(((n.mid = 1581236385344 and field_at_index(n.flds, 0) like ?1) or ", + "(n.mid = 1581236385345 and field_at_index(n.flds, 0) like ?1) or ", + "(n.mid = 1581236385346 and field_at_index(n.flds, 0) like ?1) or ", + "(n.mid = 1581236385347 and field_at_index(n.flds, 0) like ?1)))" ) - ); - assert_eq!(s(ctx, "te%st").1, vec!["%te%st%".to_string()]); - // user should be able to escape sql wildcards - assert_eq!(s(ctx, r#"te\%s\_t"#).1, vec!["%te\\%s\\_t%".to_string()]); + .into(), + vec!["te%st".into()] + ) + ); - // qualified search - assert_eq!( - s(ctx, "front:te*st"), - ( - concat!( - "(((n.mid = 1581236385344 and field_at_index(n.flds, 0) like ?1) or ", - "(n.mid = 1581236385345 and field_at_index(n.flds, 0) like ?1) or ", - "(n.mid = 1581236385346 and field_at_index(n.flds, 0) like ?1) or ", - "(n.mid = 1581236385347 and field_at_index(n.flds, 0) like ?1)))" - ) - .into(), - vec!["te%st".into()] + // added + let timing = ctx.storage.timing_today().unwrap(); + assert_eq!( + s(ctx, "added:3").0, + format!("(c.id > {})", (timing.next_day_at - (86_400 * 3)) * 1_000) + ); + + // deck + assert_eq!(s(ctx, "deck:default"), ("(c.did in (1))".into(), vec![],)); + assert_eq!(s(ctx, "deck:current"), ("(c.did in (1))".into(), vec![],)); + assert_eq!(s(ctx, "deck:missing"), ("(c.did in ())".into(), vec![],)); + assert_eq!(s(ctx, "deck:d*"), ("(c.did in (1))".into(), vec![],)); + assert_eq!(s(ctx, "deck:filtered"), ("(c.odid > 0)".into(), vec![],)); + + // card + assert_eq!(s(ctx, "card:front"), ("(false)".into(), vec![],)); + assert_eq!( + s(ctx, r#""card:card 1""#), + ( + concat!( + "(((n.mid = 1581236385344 and c.ord = 0) or ", + "(n.mid = 1581236385345 and c.ord = 0) or ", + "(n.mid = 1581236385346 and c.ord = 0) or ", + "(n.mid = 1581236385347 and c.ord = 0)))" ) - ); + .into(), + vec![], + ) + ); - // added - let timing = ctx.storage.timing_today().unwrap(); - assert_eq!( - s(ctx, "added:3").0, - format!("(c.id > {})", (timing.next_day_at - (86_400 * 3)) * 1_000) - ); + // IDs + assert_eq!(s(ctx, "mid:3"), ("(n.mid = 3)".into(), vec![])); + assert_eq!(s(ctx, "nid:3"), ("(n.id in (3))".into(), vec![])); + assert_eq!(s(ctx, "nid:3,4"), ("(n.id in (3,4))".into(), vec![])); + assert_eq!(s(ctx, "cid:3,4"), ("(c.id in (3,4))".into(), vec![])); - // deck - assert_eq!(s(ctx, "deck:default"), ("(c.did in (1))".into(), vec![],)); - assert_eq!(s(ctx, "deck:current"), ("(c.did in (1))".into(), vec![],)); - assert_eq!(s(ctx, "deck:missing"), ("(c.did in ())".into(), vec![],)); - assert_eq!(s(ctx, "deck:d*"), ("(c.did in (1))".into(), vec![],)); - assert_eq!(s(ctx, "deck:filtered"), ("(c.odid > 0)".into(), vec![],)); + // flags + assert_eq!(s(ctx, "flag:2"), ("((c.flags & 7) == 2)".into(), vec![])); + assert_eq!(s(ctx, "flag:0"), ("((c.flags & 7) == 0)".into(), vec![])); - // card - assert_eq!(s(ctx, "card:front"), ("(false)".into(), vec![],)); - assert_eq!( - s(ctx, r#""card:card 1""#), - ( - concat!( - "(((n.mid = 1581236385344 and c.ord = 0) or ", - "(n.mid = 1581236385345 and c.ord = 0) or ", - "(n.mid = 1581236385346 and c.ord = 0) or ", - "(n.mid = 1581236385347 and c.ord = 0)))" - ) - .into(), - vec![], - ) - ); + // dupes + assert_eq!( + s(ctx, "dupes:123,test"), + ( + "((n.mid = 123 and n.csum = 2840236005 and field_at_index(n.flds, 0) = ?)".into(), + vec!["test".into()] + ) + ); - // IDs - assert_eq!(s(ctx, "mid:3"), ("(n.mid = 3)".into(), vec![])); - assert_eq!(s(ctx, "nid:3"), ("(n.id in (3))".into(), vec![])); - assert_eq!(s(ctx, "nid:3,4"), ("(n.id in (3,4))".into(), vec![])); - assert_eq!(s(ctx, "cid:3,4"), ("(c.id in (3,4))".into(), vec![])); + // tags + assert_eq!( + s(ctx, "tag:one"), + ("(n.tags like ? escape '\\')".into(), vec!["% one %".into()]) + ); + assert_eq!( + s(ctx, "tag:o*e"), + ("(n.tags like ? escape '\\')".into(), vec!["% o%e %".into()]) + ); + assert_eq!(s(ctx, "tag:none"), ("(n.tags = '')".into(), vec![])); + assert_eq!(s(ctx, "tag:*"), ("(true)".into(), vec![])); - // flags - assert_eq!(s(ctx, "flag:2"), ("((c.flags & 7) == 2)".into(), vec![])); - assert_eq!(s(ctx, "flag:0"), ("((c.flags & 7) == 0)".into(), vec![])); + // state + assert_eq!( + s(ctx, "is:suspended").0, + format!("(c.queue = {})", CardQueue::Suspended as i8) + ); + assert_eq!( + s(ctx, "is:new").0, + format!("(c.type = {})", CardQueue::New as i8) + ); - // dupes - assert_eq!( - s(ctx, "dupes:123,test"), - ( - "((n.mid = 123 and n.csum = 2840236005 and field_at_index(n.flds, 0) = ?)" - .into(), - vec!["test".into()] - ) - ); + // rated + assert_eq!( + s(ctx, "rated:2").0, + format!( + "(c.id in (select cid from revlog where id>{}))", + (timing.next_day_at - (86_400 * 2)) * 1_000 + ) + ); + assert_eq!( + s(ctx, "rated:400:1").0, + format!( + "(c.id in (select cid from revlog where id>{} and ease=1))", + (timing.next_day_at - (86_400 * 365)) * 1_000 + ) + ); - // tags - assert_eq!( - s(ctx, "tag:one"), - ("(n.tags like ? escape '\\')".into(), vec!["% one %".into()]) - ); - assert_eq!( - s(ctx, "tag:o*e"), - ("(n.tags like ? escape '\\')".into(), vec!["% o%e %".into()]) - ); - assert_eq!(s(ctx, "tag:none"), ("(n.tags = '')".into(), vec![])); - assert_eq!(s(ctx, "tag:*"), ("(true)".into(), vec![])); + // props + assert_eq!(s(ctx, "prop:lapses=3").0, "(lapses = 3)".to_string()); + assert_eq!(s(ctx, "prop:ease>=2.5").0, "(factor >= 2500)".to_string()); + assert_eq!( + s(ctx, "prop:due!=-1").0, + format!( + "((c.queue in (2,3) and due != {}))", + timing.days_elapsed - 1 + ) + ); - // state - assert_eq!( - s(ctx, "is:suspended").0, - format!("(c.queue = {})", CardQueue::Suspended as i8) - ); - assert_eq!( - s(ctx, "is:new").0, - format!("(c.type = {})", CardQueue::New as i8) - ); + // note types by name + assert_eq!(&s(ctx, "note:basic").0, "(n.mid in (1581236385347))"); + assert_eq!( + &s(ctx, "note:basic*").0, + "(n.mid in (1581236385345,1581236385346,1581236385347,1581236385344))" + ); - // rated - assert_eq!( - s(ctx, "rated:2").0, - format!( - "(c.id in (select cid from revlog where id>{}))", - (timing.next_day_at - (86_400 * 2)) * 1_000 - ) - ); - assert_eq!( - s(ctx, "rated:400:1").0, - format!( - "(c.id in (select cid from revlog where id>{} and ease=1))", - (timing.next_day_at - (86_400 * 365)) * 1_000 - ) - ); - - // props - assert_eq!(s(ctx, "prop:lapses=3").0, "(lapses = 3)".to_string()); - assert_eq!(s(ctx, "prop:ease>=2.5").0, "(factor >= 2500)".to_string()); - assert_eq!( - s(ctx, "prop:due!=-1").0, - format!( - "((c.queue in (2,3) and due != {}))", - timing.days_elapsed - 1 - ) - ); - - // note types by name - assert_eq!(&s(ctx, "note:basic").0, "(n.mid in (1581236385347))"); - assert_eq!( - &s(ctx, "note:basic*").0, - "(n.mid in (1581236385345,1581236385346,1581236385347,1581236385344))" - ); - - // regex - assert_eq!( - s(ctx, r"re:\bone"), - ("(n.flds regexp ?)".into(), vec![r"(?i)\bone".into()]) - ); - - Ok(()) - }) - .unwrap(); + // regex + assert_eq!( + s(ctx, r"re:\bone"), + ("(n.flds regexp ?)".into(), vec![r"(?i)\bone".into()]) + ); Ok(()) } diff --git a/rslib/src/storage/card/mod.rs b/rslib/src/storage/card/mod.rs index 3d99313b3..e3afe74c0 100644 --- a/rslib/src/storage/card/mod.rs +++ b/rslib/src/storage/card/mod.rs @@ -31,8 +31,8 @@ impl FromSql for CardQueue { } } -impl super::StorageContext<'_> { - pub fn get_card(&mut self, cid: CardID) -> Result> { +impl super::SqliteStorage { + pub fn get_card(&self, cid: CardID) -> Result> { let mut stmt = self.db.prepare_cached(include_str!("get_card.sql"))?; stmt.query_row(params![cid], |row| { Ok(Card { @@ -60,7 +60,7 @@ impl super::StorageContext<'_> { .map_err(Into::into) } - pub(crate) fn update_card(&mut self, card: &Card) -> Result<()> { + pub(crate) fn update_card(&self, card: &Card) -> Result<()> { let mut stmt = self.db.prepare_cached(include_str!("update_card.sql"))?; stmt.execute(params![ card.nid, @@ -85,7 +85,7 @@ impl super::StorageContext<'_> { Ok(()) } - pub(crate) fn add_card(&mut self, card: &mut Card) -> Result<()> { + pub(crate) fn add_card(&self, card: &mut Card) -> Result<()> { let now = TimestampMillis::now().0; let mut stmt = self.db.prepare_cached(include_str!("add_card.sql"))?; stmt.execute(params![ @@ -120,12 +120,11 @@ mod test { #[test] fn add_card() { - let storage = SqliteStorage::open_or_create(Path::new(":memory:")).unwrap(); - let mut ctx = storage.context(false); + let storage = SqliteStorage::open_or_create(Path::new(":memory:"), false).unwrap(); let mut card = Card::default(); - ctx.add_card(&mut card).unwrap(); + storage.add_card(&mut card).unwrap(); let id1 = card.id; - ctx.add_card(&mut card).unwrap(); + storage.add_card(&mut card).unwrap(); assert_ne!(id1, card.id); } } diff --git a/rslib/src/storage/mod.rs b/rslib/src/storage/mod.rs index 7a21f0336..e66290ed5 100644 --- a/rslib/src/storage/mod.rs +++ b/rslib/src/storage/mod.rs @@ -1,4 +1,4 @@ mod card; mod sqlite; -pub(crate) use sqlite::{SqliteStorage, StorageContext}; +pub(crate) use sqlite::SqliteStorage; diff --git a/rslib/src/storage/sqlite.rs b/rslib/src/storage/sqlite.rs index fd38cd057..9a6911994 100644 --- a/rslib/src/storage/sqlite.rs +++ b/rslib/src/storage/sqlite.rs @@ -38,6 +38,9 @@ pub struct SqliteStorage { // currently crate-visible for dbproxy pub(crate) db: Connection, + server: bool, + usn: Option, + // fixme: stored in wrong location? path: PathBuf, } @@ -163,7 +166,7 @@ fn trace(s: &str) { } impl SqliteStorage { - pub(crate) fn open_or_create(path: &Path) -> Result { + pub(crate) fn open_or_create(path: &Path, server: bool) -> Result { let db = open_or_create_collection_db(path)?; let (create, ver) = schema_version(&db)?; @@ -193,34 +196,13 @@ impl SqliteStorage { let storage = Self { db, path: path.to_owned(), + server, + usn: None, }; Ok(storage) } - pub(crate) fn context(&self, server: bool) -> StorageContext { - StorageContext::new(&self.db, server) - } -} - -pub(crate) struct StorageContext<'a> { - pub(crate) db: &'a Connection, - server: bool, - usn: Option, - - timing_today: Option, -} - -impl StorageContext<'_> { - fn new(db: &Connection, server: bool) -> StorageContext { - StorageContext { - db, - server, - usn: None, - timing_today: None, - } - } - // Standard transaction start/stop ////////////////////////////////////// @@ -276,8 +258,6 @@ impl StorageContext<'_> { Ok(()) } - ////////////////////////////////////////// - pub(crate) fn mark_modified(&self) -> Result<()> { self.db .prepare_cached("update col set mod=?")? @@ -330,24 +310,20 @@ impl StorageContext<'_> { Ok(note_types) } - #[allow(dead_code)] - pub(crate) fn timing_today(&mut self) -> Result { - if self.timing_today.is_none() { - let crt: i64 = self - .db - .prepare_cached("select crt from col")? - .query_row(NO_PARAMS, |row| row.get(0))?; - let conf = self.all_config()?; - let now_offset = if self.server { conf.local_offset } else { None }; + pub(crate) fn timing_today(&self) -> Result { + let crt: i64 = self + .db + .prepare_cached("select crt from col")? + .query_row(NO_PARAMS, |row| row.get(0))?; + let conf = self.all_config()?; + let now_offset = if self.server { conf.local_offset } else { None }; - self.timing_today = Some(sched_timing_today( - crt, - TimestampSecs::now().0, - conf.creation_offset, - now_offset, - conf.rollover, - )); - } - Ok(*self.timing_today.as_ref().unwrap()) + Ok(sched_timing_today( + crt, + TimestampSecs::now().0, + conf.creation_offset, + now_offset, + conf.rollover, + )) } }