support sorting on note type, card template and decks

This commit is contained in:
Damien Elmes 2020-03-20 18:09:15 +10:00
parent 1318118461
commit 00d0447ecb
5 changed files with 101 additions and 8 deletions

View file

@ -2,9 +2,19 @@
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use crate::types::ObjID; use crate::types::ObjID;
use serde::Deserialize as DeTrait;
use serde_aux::field_attributes::deserialize_number_from_string; use serde_aux::field_attributes::deserialize_number_from_string;
use serde_derive::Deserialize; use serde_derive::Deserialize;
use serde_json::Value;
pub(crate) fn default_on_invalid<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: Default + DeTrait<'de>,
D: serde::de::Deserializer<'de>,
{
let v: Value = DeTrait::deserialize(deserializer)?;
Ok(T::deserialize(v).unwrap_or_default())
}
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Config { pub struct Config {
@ -16,7 +26,7 @@ pub struct Config {
pub(crate) rollover: Option<i8>, pub(crate) rollover: Option<i8>,
pub(crate) creation_offset: Option<i32>, pub(crate) creation_offset: Option<i32>,
pub(crate) local_offset: Option<i32>, pub(crate) local_offset: Option<i32>,
#[serde(rename = "sortType")] #[serde(rename = "sortType", deserialize_with = "default_on_invalid")]
pub(crate) browser_sort_kind: SortKind, pub(crate) browser_sort_kind: SortKind,
#[serde(rename = "sortBackwards", default)] #[serde(rename = "sortBackwards", default)]
pub(crate) browser_sort_reverse: bool, pub(crate) browser_sort_reverse: bool,
@ -30,6 +40,8 @@ pub enum SortKind {
NoteMod, NoteMod,
#[serde(rename = "noteFld")] #[serde(rename = "noteFld")]
NoteField, NoteField,
#[serde(rename = "note")]
NoteType,
CardMod, CardMod,
CardReps, CardReps,
CardDue, CardDue,
@ -37,6 +49,10 @@ pub enum SortKind {
CardLapses, CardLapses,
#[serde(rename = "cardIvl")] #[serde(rename = "cardIvl")]
CardInterval, CardInterval,
#[serde(rename = "deck")]
CardDeck,
#[serde(rename = "template")]
CardTemplate,
} }
impl Default for SortKind { impl Default for SortKind {

View file

@ -8,6 +8,7 @@ use crate::config::SortKind;
use crate::err::Result; use crate::err::Result;
use crate::search::parser::parse; use crate::search::parser::parse;
use crate::types::ObjID; use crate::types::ObjID;
use rusqlite::params;
pub(crate) fn search_cards<'a, 'b>( pub(crate) fn search_cards<'a, 'b>(
req: &'a mut RequestContext<'b>, req: &'a mut RequestContext<'b>,
@ -15,11 +16,15 @@ pub(crate) fn search_cards<'a, 'b>(
) -> Result<Vec<ObjID>> { ) -> Result<Vec<ObjID>> {
let top_node = Node::Group(parse(search)?); let top_node = Node::Group(parse(search)?);
let (sql, args) = node_to_sql(req, &top_node)?; let (sql, args) = node_to_sql(req, &top_node)?;
let conf = req.storage.all_config()?;
prepare_sort(req, &conf.browser_sort_kind)?;
let mut sql = format!( let mut sql = format!(
"select c.id from cards c, notes n where c.nid=n.id and {} order by ", "select c.id from cards c, notes n where c.nid=n.id and {}",
sql sql
); );
write_order(req, &mut sql)?; write_order(&mut sql, &conf.browser_sort_kind, conf.browser_sort_reverse)?;
let mut stmt = req.storage.db.prepare(&sql)?; let mut stmt = req.storage.db.prepare(&sql)?;
let ids: Vec<i64> = stmt let ids: Vec<i64> = stmt
@ -30,10 +35,10 @@ pub(crate) fn search_cards<'a, 'b>(
Ok(ids) Ok(ids)
} }
fn write_order(req: &mut RequestContext, sql: &mut String) -> Result<()> { /// Add the order clause to the sql.
let conf = req.storage.all_config()?; fn write_order(sql: &mut String, kind: &SortKind, reverse: bool) -> Result<()> {
let tmp_str; let tmp_str;
sql.push_str(match conf.browser_sort_kind { let order = match kind {
SortKind::NoteCreation => "n.id, c.ord", SortKind::NoteCreation => "n.id, c.ord",
SortKind::NoteMod => "n.mod, c.ord", SortKind::NoteMod => "n.mod, c.ord",
SortKind::NoteField => "n.sfld collate nocase, c.ord", SortKind::NoteField => "n.sfld collate nocase, c.ord",
@ -46,9 +51,76 @@ fn write_order(req: &mut RequestContext, sql: &mut String) -> Result<()> {
} }
SortKind::CardLapses => "c.lapses", SortKind::CardLapses => "c.lapses",
SortKind::CardInterval => "c.ivl", SortKind::CardInterval => "c.ivl",
}); SortKind::CardDeck => "(select v from sort_order where k = c.did)",
if conf.browser_sort_reverse { SortKind::NoteType => "(select v from sort_order where k = n.mid)",
SortKind::CardTemplate => "(select v from sort_order where k1 = n.mid and k2 = c.ord)",
};
if order.is_empty() {
return Ok(());
}
sql.push_str(" order by ");
sql.push_str(order);
if reverse {
sql.push_str(" desc"); sql.push_str(" desc");
} }
Ok(()) Ok(())
} }
// 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<()> {
use SortKind::*;
match kind {
CardDeck | NoteType => {
prepare_sort_order_table(req)?;
let mut stmt = req
.storage
.db
.prepare("insert into sort_order (k,v) values (?,?)")?;
match kind {
CardDeck => {
for (k, v) in req.storage.all_decks()? {
stmt.execute(params![k, v.name])?;
}
}
NoteType => {
for (k, v) in req.storage.all_note_types()? {
stmt.execute(params![k, v.name])?;
}
}
_ => unreachable!(),
}
}
CardTemplate => {
prepare_sort_order_table2(req)?;
let mut stmt = req
.storage
.db
.prepare("insert into sort_order (k1,k2,v) values (?,?,?)")?;
for (ntid, nt) in req.storage.all_note_types()? {
for tmpl in nt.templates {
stmt.execute(params![ntid, tmpl.ord, tmpl.name])?;
}
}
}
_ => (),
}
Ok(())
}
fn prepare_sort_order_table(req: &mut RequestContext) -> Result<()> {
req.storage
.db
.execute_batch(include_str!("sort_order.sql"))?;
Ok(())
}
fn prepare_sort_order_table2(req: &mut RequestContext) -> Result<()> {
req.storage
.db
.execute_batch(include_str!("sort_order2.sql"))?;
Ok(())
}

View file

@ -0,0 +1,2 @@
drop table if exists sort_order;
create temporary table sort_order (k int primary key, v text);

View file

@ -0,0 +1,2 @@
drop table if exists sort_order;
create temporary table sort_order (k1 int, k2 int, v text, primary key (k1, k2)) without rowid;

View file

@ -45,6 +45,7 @@ fn open_or_create_collection_db(path: &Path) -> Result<Connection> {
db.pragma_update(None, "cache_size", &(-40 * 1024))?; db.pragma_update(None, "cache_size", &(-40 * 1024))?;
db.pragma_update(None, "legacy_file_format", &false)?; db.pragma_update(None, "legacy_file_format", &false)?;
db.pragma_update(None, "journal", &"wal")?; db.pragma_update(None, "journal", &"wal")?;
db.pragma_update(None, "temp_store", &"memory")?;
db.set_prepared_statement_cache_capacity(50); db.set_prepared_statement_cache_capacity(50);