drop separate RequestContext/StorageContext
This commit is contained in:
parent
2810d3883b
commit
fdeca610b0
@ -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<String> {
|
||||
pub(super) fn db_command_bytes(ctx: &SqliteStorage, input: &[u8]) -> Result<String> {
|
||||
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<Str
|
||||
Ok(serde_json::to_string(&resp)?)
|
||||
}
|
||||
|
||||
pub(super) fn db_query_row(ctx: &StorageContext, sql: &str, args: &[SqlValue]) -> Result<DBResult> {
|
||||
pub(super) fn db_query_row(ctx: &SqliteStorage, sql: &str, args: &[SqlValue]) -> Result<DBResult> {
|
||||
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<DBResult> {
|
||||
pub(super) fn db_query(ctx: &SqliteStorage, sql: &str, args: &[SqlValue]) -> Result<DBResult> {
|
||||
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<SqlValue>],
|
||||
) -> Result<DBResult> {
|
||||
|
@ -590,48 +590,44 @@ impl Backend {
|
||||
}
|
||||
|
||||
pub fn db_command(&self, input: &[u8]) -> Result<String> {
|
||||
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<pb::SearchCardsOut> {
|
||||
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<pb::SearchNotesOut> {
|
||||
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<pb::GetCardOut> {
|
||||
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),
|
||||
})
|
||||
|
@ -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"));
|
||||
|
@ -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<P: Into<PathBuf>>(
|
||||
@ -16,24 +16,30 @@ pub fn open_collection<P: Into<PathBuf>>(
|
||||
log: Logger,
|
||||
) -> Result<Collection> {
|
||||
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<F, R>(&self, func: F) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut RequestContext) -> Result<R>,
|
||||
{
|
||||
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<F, R>(&self, op: Option<CollectionOp>, func: F) -> Result<R>
|
||||
pub(crate) fn transact<F, R>(&mut self, op: Option<CollectionOp>, func: F) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut RequestContext) -> Result<R>,
|
||||
F: FnOnce(&mut Collection) -> Result<R>,
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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<String>,
|
||||
}
|
||||
|
||||
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<P> MediaChecker<'_, '_, P>
|
||||
impl<P> 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")?;
|
||||
|
||||
|
@ -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<Vec<CardID>> {
|
||||
@ -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"))?;
|
||||
|
@ -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<Vec<NoteID>> {
|
||||
pub(crate) fn search_notes<'a>(req: &'a mut Collection, search: &'a str) -> Result<Vec<NoteID>> {
|
||||
let top_node = Node::Group(parse(search)?);
|
||||
let (sql, args) = node_to_sql(req, &top_node)?;
|
||||
|
||||
|
@ -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<String>,
|
||||
}
|
||||
|
||||
pub(super) fn node_to_sql(req: &mut RequestContext, node: &Node) -> Result<(String, Vec<String>)> {
|
||||
pub(super) fn node_to_sql(req: &mut Collection, node: &Node) -> Result<(String, Vec<String>)> {
|
||||
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<u8>) -> 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<String>) {
|
||||
fn s(req: &mut Collection, search: &str) -> (String, Vec<String>) {
|
||||
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(())
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ impl FromSql for CardQueue {
|
||||
}
|
||||
}
|
||||
|
||||
impl super::StorageContext<'_> {
|
||||
pub fn get_card(&mut self, cid: CardID) -> Result<Option<Card>> {
|
||||
impl super::SqliteStorage {
|
||||
pub fn get_card(&self, cid: CardID) -> Result<Option<Card>> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
mod card;
|
||||
mod sqlite;
|
||||
|
||||
pub(crate) use sqlite::{SqliteStorage, StorageContext};
|
||||
pub(crate) use sqlite::SqliteStorage;
|
||||
|
@ -38,6 +38,9 @@ pub struct SqliteStorage {
|
||||
// currently crate-visible for dbproxy
|
||||
pub(crate) db: Connection,
|
||||
|
||||
server: bool,
|
||||
usn: Option<Usn>,
|
||||
|
||||
// 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<Self> {
|
||||
pub(crate) fn open_or_create(path: &Path, server: bool) -> Result<Self> {
|
||||
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<Usn>,
|
||||
|
||||
timing_today: Option<SchedTimingToday>,
|
||||
}
|
||||
|
||||
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<SchedTimingToday> {
|
||||
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<SchedTimingToday> {
|
||||
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,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user