From 27edd9649370abcb30be2b5922668b42fb0d24be Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Thu, 7 Nov 2019 22:32:42 +0200 Subject: [PATCH] Cache and Sqlite3 cleanups --- ui/src/cache.rs | 135 +++++++++++----------------------------------- ui/src/sqlite3.rs | 109 +++++++++++++------------------------ 2 files changed, 68 insertions(+), 176 deletions(-) diff --git a/ui/src/cache.rs b/ui/src/cache.rs index ab2ef3d36..cda461748 100644 --- a/ui/src/cache.rs +++ b/ui/src/cache.rs @@ -19,13 +19,18 @@ * along with meli. If not, see . */ -/* -use melib::backends::{FolderHash, MailBackend}; -use melib::mailbox::*; -use melib::thread::{ThreadHash, ThreadNode}; -use std::sync::RwLock; -*/ use melib::email::{Flag, UnixTimestamp}; +use melib::parsec::*; +use melib::{ + backends::{FolderHash, MailBackend}, + email::EnvelopeHash, + thread::{SortField, SortOrder}, + Result, StackVec, +}; +use std::sync::{Arc, RwLock}; + +pub use query_parser::query; +use Query::*; #[derive(Debug, PartialEq)] pub enum Query { @@ -52,103 +57,11 @@ pub enum Query { Not(Box), } -/* -enum CacheType { - Sqlite3, -} - -pub struct Cache { - collection: Collection, - kind: CacheType, - backend: Box, -} - -impl Cache { - pub fn build_index(&mut self) { - unimplemented!() - } - - pub fn new(backend: Box) -> Self { - unimplemented!() - } - pub fn get_env(&self, h: &EnvelopeHash) -> &Envelope { - &self.collection[h] - } - pub fn get_env_mut(&mut self, h: &EnvelopeHash) -> &mut Envelope { - self.collection.entry(*h).or_default() - } - pub fn contains_key(&self, h: EnvelopeHash) -> bool { - self.collection.contains_key(&h) - } - /* - pub fn operation(&self, h: EnvelopeHash) -> Box { - //let operation = self.backend.operation(h, m.folder.hash()) - unimplemented!() - unreachable!() - } - */ - pub fn thread_to_mail_mut(&mut self, h: ThreadHash, f: FolderHash) -> &mut Envelope { - self.collection - .envelopes - .entry(self.collection.threads[&f].thread_to_mail(h)) - .or_default() - } - pub fn thread_to_mail(&self, h: ThreadHash, f: FolderHash) -> &Envelope { - &self.collection.envelopes[&self.collection.threads[&f].thread_to_mail(h)] - } - pub fn threaded_mail(&self, h: ThreadHash, f: FolderHash) -> EnvelopeHash { - self.collection.threads[&f].thread_to_mail(h) - } - pub fn mail_and_thread( - &mut self, - i: EnvelopeHash, - f: FolderHash, - ) -> (&mut Envelope, &ThreadNode) { - let thread; - { - let x = &mut self.collection.envelopes.entry(i).or_default(); - thread = &self.collection.threads[&f][&x.thread()]; - } - (self.collection.envelopes.entry(i).or_default(), thread) - } - pub fn thread(&self, h: ThreadHash, f: FolderHash) -> &ThreadNode { - &self.collection.threads[&f].thread_nodes()[&h] - } -} -*/ -impl std::ops::Not for Query { - type Output = Query; - fn not(self) -> Query { - match self { - Query::Not(q) => *q, - q => Query::Not(Box::new(q)), - } - } -} - -impl std::ops::BitAnd for Query { - type Output = Self; - - fn bitand(self, rhs: Self) -> Self { - Query::And(Box::new(self), Box::new(rhs)) - } -} - -impl std::ops::BitOr for Query { - type Output = Self; - - fn bitor(self, rhs: Self) -> Self { - Query::Or(Box::new(self), Box::new(rhs)) - } -} - -pub use query_parser::query; - pub mod query_parser { use super::Query::{self, *}; use melib::parsec::*; - pub fn subject<'a>() -> impl Parser<'a, Query> { + fn subject<'a>() -> impl Parser<'a, Query> { prefix( whitespace_wrap(match_literal("subject:")), whitespace_wrap(literal()), @@ -156,7 +69,7 @@ pub mod query_parser { .map(|term| Query::Subject(term)) } - pub fn from<'a>() -> impl Parser<'a, Query> { + fn from<'a>() -> impl Parser<'a, Query> { prefix( whitespace_wrap(match_literal("from:")), whitespace_wrap(literal()), @@ -164,7 +77,7 @@ pub mod query_parser { .map(|term| Query::From(term)) } - pub fn or<'a>() -> impl Parser<'a, Query> { + fn or<'a>() -> impl Parser<'a, Query> { move |input| { whitespace_wrap(match_literal_anycase("or")) .parse(input) @@ -172,7 +85,7 @@ pub mod query_parser { } } - pub fn not<'a>() -> impl Parser<'a, Query> { + fn not<'a>() -> impl Parser<'a, Query> { move |input| { whitespace_wrap(either( match_literal_anycase("not"), @@ -183,7 +96,7 @@ pub mod query_parser { } } - pub fn and<'a>() -> impl Parser<'a, Query> { + fn and<'a>() -> impl Parser<'a, Query> { move |input| { whitespace_wrap(match_literal_anycase("and")) .parse(input) @@ -191,11 +104,11 @@ pub mod query_parser { } } - pub fn literal<'a>() -> impl Parser<'a, String> { + fn literal<'a>() -> impl Parser<'a, String> { move |input| either(quoted_string(), string()).parse(input) } - pub fn parentheses_query<'a>() -> impl Parser<'a, Query> { + fn parentheses_query<'a>() -> impl Parser<'a, Query> { move |input| { delimited( whitespace_wrap(match_literal("(")), @@ -206,6 +119,18 @@ pub mod query_parser { } } + /// Parser from `String` to `Query`. + /// + /// # Invocation + /// ``` + /// use ui::cache::query; + /// use ui::cache::Query; + /// use melib::parsec::Parser; + /// + /// let input = "test"; + /// let query = query().parse(input); + /// assert_eq!(Ok(("", Query::AllText("test".to_string()))), query); + /// ``` pub fn query<'a>() -> impl Parser<'a, Query> { move |input| { let (rest, query_a): (&'a str, Query) = if let Ok(q) = parentheses_query().parse(input) diff --git a/ui/src/sqlite3.rs b/ui/src/sqlite3.rs index ae75723fa..9cacd0c4c 100644 --- a/ui/src/sqlite3.rs +++ b/ui/src/sqlite3.rs @@ -43,29 +43,29 @@ fn escape_double_quote(w: &str) -> Cow { } } -#[inline(always)] -fn fts5_bareword(w: &str) -> Cow { - if w == "AND" || w == "OR" || w == "NOT" { - Cow::from(w) - } else { - if !w.is_ascii() { - Cow::from(format!("\"{}\"", escape_double_quote(w))) - } else { - for &b in w.as_bytes() { - if !(b > 0x2f && b < 0x3a) - || !(b > 0x40 && b < 0x5b) - || !(b > 0x60 && b < 0x7b) - || b != 0x60 - || b != 26 - { - return Cow::from(format!("\"{}\"", escape_double_quote(w))); - } - } - Cow::from(w) - } - } -} - +//#[inline(always)] +//fn fts5_bareword(w: &str) -> Cow { +// if w == "AND" || w == "OR" || w == "NOT" { +// Cow::from(w) +// } else { +// if !w.is_ascii() { +// Cow::from(format!("\"{}\"", escape_double_quote(w))) +// } else { +// for &b in w.as_bytes() { +// if !(b > 0x2f && b < 0x3a) +// || !(b > 0x40 && b < 0x5b) +// || !(b > 0x60 && b < 0x7b) +// || b != 0x60 +// || b != 26 +// { +// return Cow::from(format!("\"{}\"", escape_double_quote(w))); +// } +// } +// Cow::from(w) +// } +// } +//} +// pub fn open_db() -> Result { let data_dir = xdg::BaseDirectories::with_prefix("meli").map_err(|e| MeliError::new(e.to_string()))?; @@ -302,11 +302,6 @@ pub fn search( SortOrder::Desc => "DESC", }; - /* - debug!("SELECT hash FROM envelopes INNER JOIN fts ON fts.rowid = envelopes.id WHERE fts MATCH ? ORDER BY {} {};", sort_field, sort_order); - let mut stmt = conn.prepare( - format!("SELECT hash FROM envelopes INNER JOIN fts ON fts.rowid = envelopes.id WHERE fts MATCH ? ORDER BY {} {};", sort_field, sort_order).as_str()) - */ let mut stmt = conn .prepare( debug!(format!( @@ -334,69 +329,43 @@ pub fn search( results } -pub fn from(term: &str) -> Result> { - let conn = open_db()?; - let mut stmt = conn - .prepare("SELECT hash FROM envelopes WHERE _from LIKE ?;") - .map_err(|e| MeliError::new(e.to_string()))?; - - let results = stmt - .query_map(&[term.trim()], |row| Ok(row.get(0)?)) - .map_err(|e| MeliError::new(e.to_string()))? - .map(|r: std::result::Result, rusqlite::Error>| { - Ok(u64::from_be_bytes( - r.map_err(|e| MeliError::new(e.to_string()))? - .as_slice() - .try_into() - .map_err(|e: std::array::TryFromSliceError| MeliError::new(e.to_string()))?, - )) - }) - .collect::>>(); - results -} - +/// Translates a `Query` to an Sqlite3 expression in a `String`. pub fn query_to_sql(q: &Query) -> String { fn rec(q: &Query, s: &mut String) { match q { Subject(t) => { - s.push_str(" subject LIKE \"%"); + s.push_str("subject LIKE \"%"); s.extend(escape_double_quote(t).chars()); - s.push_str("%\""); + s.push_str("%\" "); } From(t) => { - s.push_str(" _from LIKE \"%"); + s.push_str("_from LIKE \"%"); s.extend(escape_double_quote(t).chars()); - s.push_str("%\""); + s.push_str("%\" "); } AllText(t) => { - s.push_str(" body_text LIKE \"%"); + s.push_str("body_text LIKE \"%"); s.extend(escape_double_quote(t).chars()); - s.push_str("%\""); + s.push_str("%\" "); } And(q1, q2) => { - s.push_str(" ("); + s.push_str("("); rec(q1, s); - s.push_str(") "); - - s.push_str(" AND "); - s.push_str(" ("); + s.push_str(") AND ("); rec(q2, s); s.push_str(") "); } Or(q1, q2) => { - s.push_str(" ("); + s.push_str("("); rec(q1, s); - s.push_str(") "); - s.push_str(" OR "); - s.push_str(" ("); + s.push_str(") OR ("); rec(q2, s); s.push_str(") "); } Not(q) => { - s.push_str(" NOT "); - s.push_str("("); + s.push_str("NOT ("); rec(q, s); - s.push_str(")"); + s.push_str(") "); } _ => {} } @@ -404,18 +373,16 @@ pub fn query_to_sql(q: &Query) -> String { let mut ret = String::new(); rec(q, &mut ret); ret - - //"SELECT hash FROM envelopes INNER JOIN fts ON fts.rowid = envelopes.id WHERE fts MATCH ? ORDER BY {} {};" } #[test] fn test_query_to_sql() { assert_eq!( - " subject LIKE \"%test%\" AND body_text LIKE \"%i%\"", + "(subject LIKE \"%test%\" ) AND (body_text LIKE \"%i%\" ) ", &query_to_sql(&query().parse_complete("subject: test and i").unwrap().1) ); assert_eq!( - " subject LIKE \"%github%\" OR ( _from LIKE \"%epilys%\" AND ( subject LIKE \"%lib%\" OR subject LIKE \"%meli%\") ) ", + "(subject LIKE \"%github%\" ) OR ((_from LIKE \"%epilys%\" ) AND ((subject LIKE \"%lib%\" ) OR (subject LIKE \"%meli%\" ) ) ) ", &query_to_sql( &query() .parse_complete(