Cache and Sqlite3 cleanups
parent
e396b2f72b
commit
27edd96493
135
ui/src/cache.rs
135
ui/src/cache.rs
|
@ -19,13 +19,18 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
|
||||||
use melib::backends::{FolderHash, MailBackend};
|
|
||||||
use melib::mailbox::*;
|
|
||||||
use melib::thread::{ThreadHash, ThreadNode};
|
|
||||||
use std::sync::RwLock;
|
|
||||||
*/
|
|
||||||
use melib::email::{Flag, UnixTimestamp};
|
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)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Query {
|
pub enum Query {
|
||||||
|
@ -52,103 +57,11 @@ pub enum Query {
|
||||||
Not(Box<Query>),
|
Not(Box<Query>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
enum CacheType {
|
|
||||||
Sqlite3,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Cache {
|
|
||||||
collection: Collection,
|
|
||||||
kind: CacheType,
|
|
||||||
backend: Box<dyn MailBackend>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cache {
|
|
||||||
pub fn build_index(&mut self) {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(backend: Box<dyn MailBackend>) -> 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<dyn BackendOp> {
|
|
||||||
//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 {
|
pub mod query_parser {
|
||||||
use super::Query::{self, *};
|
use super::Query::{self, *};
|
||||||
use melib::parsec::*;
|
use melib::parsec::*;
|
||||||
|
|
||||||
pub fn subject<'a>() -> impl Parser<'a, Query> {
|
fn subject<'a>() -> impl Parser<'a, Query> {
|
||||||
prefix(
|
prefix(
|
||||||
whitespace_wrap(match_literal("subject:")),
|
whitespace_wrap(match_literal("subject:")),
|
||||||
whitespace_wrap(literal()),
|
whitespace_wrap(literal()),
|
||||||
|
@ -156,7 +69,7 @@ pub mod query_parser {
|
||||||
.map(|term| Query::Subject(term))
|
.map(|term| Query::Subject(term))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from<'a>() -> impl Parser<'a, Query> {
|
fn from<'a>() -> impl Parser<'a, Query> {
|
||||||
prefix(
|
prefix(
|
||||||
whitespace_wrap(match_literal("from:")),
|
whitespace_wrap(match_literal("from:")),
|
||||||
whitespace_wrap(literal()),
|
whitespace_wrap(literal()),
|
||||||
|
@ -164,7 +77,7 @@ pub mod query_parser {
|
||||||
.map(|term| Query::From(term))
|
.map(|term| Query::From(term))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn or<'a>() -> impl Parser<'a, Query> {
|
fn or<'a>() -> impl Parser<'a, Query> {
|
||||||
move |input| {
|
move |input| {
|
||||||
whitespace_wrap(match_literal_anycase("or"))
|
whitespace_wrap(match_literal_anycase("or"))
|
||||||
.parse(input)
|
.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| {
|
move |input| {
|
||||||
whitespace_wrap(either(
|
whitespace_wrap(either(
|
||||||
match_literal_anycase("not"),
|
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| {
|
move |input| {
|
||||||
whitespace_wrap(match_literal_anycase("and"))
|
whitespace_wrap(match_literal_anycase("and"))
|
||||||
.parse(input)
|
.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)
|
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| {
|
move |input| {
|
||||||
delimited(
|
delimited(
|
||||||
whitespace_wrap(match_literal("(")),
|
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> {
|
pub fn query<'a>() -> impl Parser<'a, Query> {
|
||||||
move |input| {
|
move |input| {
|
||||||
let (rest, query_a): (&'a str, Query) = if let Ok(q) = parentheses_query().parse(input)
|
let (rest, query_a): (&'a str, Query) = if let Ok(q) = parentheses_query().parse(input)
|
||||||
|
|
|
@ -43,29 +43,29 @@ fn escape_double_quote(w: &str) -> Cow<str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
//#[inline(always)]
|
||||||
fn fts5_bareword(w: &str) -> Cow<str> {
|
//fn fts5_bareword(w: &str) -> Cow<str> {
|
||||||
if w == "AND" || w == "OR" || w == "NOT" {
|
// if w == "AND" || w == "OR" || w == "NOT" {
|
||||||
Cow::from(w)
|
// Cow::from(w)
|
||||||
} else {
|
// } else {
|
||||||
if !w.is_ascii() {
|
// if !w.is_ascii() {
|
||||||
Cow::from(format!("\"{}\"", escape_double_quote(w)))
|
// Cow::from(format!("\"{}\"", escape_double_quote(w)))
|
||||||
} else {
|
// } else {
|
||||||
for &b in w.as_bytes() {
|
// for &b in w.as_bytes() {
|
||||||
if !(b > 0x2f && b < 0x3a)
|
// if !(b > 0x2f && b < 0x3a)
|
||||||
|| !(b > 0x40 && b < 0x5b)
|
// || !(b > 0x40 && b < 0x5b)
|
||||||
|| !(b > 0x60 && b < 0x7b)
|
// || !(b > 0x60 && b < 0x7b)
|
||||||
|| b != 0x60
|
// || b != 0x60
|
||||||
|| b != 26
|
// || b != 26
|
||||||
{
|
// {
|
||||||
return Cow::from(format!("\"{}\"", escape_double_quote(w)));
|
// return Cow::from(format!("\"{}\"", escape_double_quote(w)));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
Cow::from(w)
|
// Cow::from(w)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
pub fn open_db() -> Result<Connection> {
|
pub fn open_db() -> Result<Connection> {
|
||||||
let data_dir =
|
let data_dir =
|
||||||
xdg::BaseDirectories::with_prefix("meli").map_err(|e| MeliError::new(e.to_string()))?;
|
xdg::BaseDirectories::with_prefix("meli").map_err(|e| MeliError::new(e.to_string()))?;
|
||||||
|
@ -302,11 +302,6 @@ pub fn search(
|
||||||
SortOrder::Desc => "DESC",
|
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
|
let mut stmt = conn
|
||||||
.prepare(
|
.prepare(
|
||||||
debug!(format!(
|
debug!(format!(
|
||||||
|
@ -334,69 +329,43 @@ pub fn search(
|
||||||
results
|
results
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from(term: &str) -> Result<StackVec<EnvelopeHash>> {
|
/// Translates a `Query` to an Sqlite3 expression in a `String`.
|
||||||
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<Vec<u8>, 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::<Result<StackVec<EnvelopeHash>>>();
|
|
||||||
results
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn query_to_sql(q: &Query) -> String {
|
pub fn query_to_sql(q: &Query) -> String {
|
||||||
fn rec(q: &Query, s: &mut String) {
|
fn rec(q: &Query, s: &mut String) {
|
||||||
match q {
|
match q {
|
||||||
Subject(t) => {
|
Subject(t) => {
|
||||||
s.push_str(" subject LIKE \"%");
|
s.push_str("subject LIKE \"%");
|
||||||
s.extend(escape_double_quote(t).chars());
|
s.extend(escape_double_quote(t).chars());
|
||||||
s.push_str("%\"");
|
s.push_str("%\" ");
|
||||||
}
|
}
|
||||||
From(t) => {
|
From(t) => {
|
||||||
s.push_str(" _from LIKE \"%");
|
s.push_str("_from LIKE \"%");
|
||||||
s.extend(escape_double_quote(t).chars());
|
s.extend(escape_double_quote(t).chars());
|
||||||
s.push_str("%\"");
|
s.push_str("%\" ");
|
||||||
}
|
}
|
||||||
AllText(t) => {
|
AllText(t) => {
|
||||||
s.push_str(" body_text LIKE \"%");
|
s.push_str("body_text LIKE \"%");
|
||||||
s.extend(escape_double_quote(t).chars());
|
s.extend(escape_double_quote(t).chars());
|
||||||
s.push_str("%\"");
|
s.push_str("%\" ");
|
||||||
}
|
}
|
||||||
And(q1, q2) => {
|
And(q1, q2) => {
|
||||||
s.push_str(" (");
|
s.push_str("(");
|
||||||
rec(q1, s);
|
rec(q1, s);
|
||||||
s.push_str(") ");
|
s.push_str(") AND (");
|
||||||
|
|
||||||
s.push_str(" AND ");
|
|
||||||
s.push_str(" (");
|
|
||||||
rec(q2, s);
|
rec(q2, s);
|
||||||
s.push_str(") ");
|
s.push_str(") ");
|
||||||
}
|
}
|
||||||
Or(q1, q2) => {
|
Or(q1, q2) => {
|
||||||
s.push_str(" (");
|
s.push_str("(");
|
||||||
rec(q1, s);
|
rec(q1, s);
|
||||||
s.push_str(") ");
|
s.push_str(") OR (");
|
||||||
s.push_str(" OR ");
|
|
||||||
s.push_str(" (");
|
|
||||||
rec(q2, s);
|
rec(q2, s);
|
||||||
s.push_str(") ");
|
s.push_str(") ");
|
||||||
}
|
}
|
||||||
Not(q) => {
|
Not(q) => {
|
||||||
s.push_str(" NOT ");
|
s.push_str("NOT (");
|
||||||
s.push_str("(");
|
|
||||||
rec(q, s);
|
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();
|
let mut ret = String::new();
|
||||||
rec(q, &mut ret);
|
rec(q, &mut ret);
|
||||||
ret
|
ret
|
||||||
|
|
||||||
//"SELECT hash FROM envelopes INNER JOIN fts ON fts.rowid = envelopes.id WHERE fts MATCH ? ORDER BY {} {};"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_query_to_sql() {
|
fn test_query_to_sql() {
|
||||||
assert_eq!(
|
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)
|
&query_to_sql(&query().parse_complete("subject: test and i").unwrap().1)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
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_to_sql(
|
||||||
&query()
|
&query()
|
||||||
.parse_complete(
|
.parse_complete(
|
||||||
|
|
Loading…
Reference in New Issue