client filtering done in query (#629)

This commit is contained in:
jean-santos 2023-03-27 18:33:04 -03:00 committed by GitHub
parent c64674dc23
commit caf2ddfb9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 84 additions and 108 deletions

View File

@ -25,6 +25,17 @@ pub struct Context {
pub hostname: String, pub hostname: String,
} }
#[derive(Default, Clone)]
pub struct OptFilters {
pub exit: Option<i64>,
pub exclude_exit: Option<i64>,
pub cwd: Option<String>,
pub exclude_cwd: Option<String>,
pub before: Option<String>,
pub after: Option<String>,
pub limit: Option<i64>,
}
pub fn current_context() -> Context { pub fn current_context() -> Context {
let Ok(session) = env::var("ATUIN_SESSION") else { let Ok(session) = env::var("ATUIN_SESSION") else {
eprintln!("ERROR: Failed to find $ATUIN_SESSION in the environment. Check that you have correctly set up your shell."); eprintln!("ERROR: Failed to find $ATUIN_SESSION in the environment. Check that you have correctly set up your shell.");
@ -79,9 +90,7 @@ pub trait Database: Send + Sync + 'static {
filter: FilterMode, filter: FilterMode,
context: &Context, context: &Context,
query: &str, query: &str,
limit: Option<i64>, filter_options: OptFilters,
before: Option<i64>,
after: Option<i64>,
) -> Result<Vec<History>>; ) -> Result<Vec<History>>;
async fn query_history(&self, query: &str) -> Result<Vec<History>>; async fn query_history(&self, query: &str) -> Result<Vec<History>>;
@ -340,9 +349,7 @@ impl Database for Sqlite {
filter: FilterMode, filter: FilterMode,
context: &Context, context: &Context,
query: &str, query: &str,
limit: Option<i64>, filter_options: OptFilters,
before: Option<i64>,
after: Option<i64>,
) -> Result<Vec<History>> { ) -> Result<Vec<History>> {
let mut sql = SqlBuilder::select_from("history"); let mut sql = SqlBuilder::select_from("history");
@ -350,18 +357,10 @@ impl Database for Sqlite {
.having("max(timestamp)") .having("max(timestamp)")
.order_desc("timestamp"); .order_desc("timestamp");
if let Some(limit) = limit { if let Some(limit) = filter_options.limit {
sql.limit(limit); sql.limit(limit);
} }
if let Some(after) = after {
sql.and_where_gt("timestamp", after);
}
if let Some(before) = before {
sql.and_where_lt("timestamp", before);
}
match filter { match filter {
FilterMode::Global => &mut sql, FilterMode::Global => &mut sql,
FilterMode::Host => sql.and_where_eq("hostname", quote(&context.hostname)), FilterMode::Host => sql.and_where_eq("hostname", quote(&context.hostname)),
@ -421,6 +420,32 @@ impl Database for Sqlite {
} }
}; };
filter_options
.exit
.map(|exit| sql.and_where_eq("exit", exit));
filter_options
.exclude_exit
.map(|exclude_exit| sql.and_where_ne("exit", exclude_exit));
filter_options
.cwd
.map(|cwd| sql.and_where_eq("cwd", quote(cwd)));
filter_options
.exclude_cwd
.map(|exclude_cwd| sql.and_where_ne("cwd", quote(exclude_cwd)));
filter_options.before.map(|before| {
interim::parse_date_string(before.as_str(), Utc::now(), interim::Dialect::Uk)
.map(|before| sql.and_where_lt("timestamp", quote(before.timestamp_nanos())))
});
filter_options.after.map(|after| {
interim::parse_date_string(after.as_str(), Utc::now(), interim::Dialect::Uk)
.map(|after| sql.and_where_gt("timestamp", quote(after.timestamp_nanos())))
});
let query = sql.sql().expect("bug in search query. please report"); let query = sql.sql().expect("bug in search query. please report");
let res = sqlx::query(&query) let res = sqlx::query(&query)
@ -508,7 +533,15 @@ mod test {
}; };
let results = db let results = db
.search(mode, filter_mode, &context, query, None, None, None) .search(
mode,
filter_mode,
&context,
query,
OptFilters {
..Default::default()
},
)
.await?; .await?;
assert_eq!( assert_eq!(
@ -718,9 +751,9 @@ mod test {
FilterMode::Global, FilterMode::Global,
&context, &context,
"", "",
None, OptFilters {
None, ..Default::default()
None, },
) )
.await .await
.unwrap(); .unwrap();

View File

@ -1,11 +1,10 @@
use atuin_common::utils; use atuin_common::utils;
use chrono::Utc;
use clap::Parser; use clap::Parser;
use eyre::Result; use eyre::Result;
use atuin_client::{ use atuin_client::{
database::current_context,
database::Database, database::Database,
database::{current_context, OptFilters},
history::History, history::History,
settings::{FilterMode, SearchMode, Settings}, settings::{FilterMode, SearchMode, Settings},
}; };
@ -103,19 +102,18 @@ impl Cmd {
} else { } else {
let list_mode = ListMode::from_flags(self.human, self.cmd_only); let list_mode = ListMode::from_flags(self.human, self.cmd_only);
let mut entries = run_non_interactive( let opt_filter = OptFilters {
settings, exit: self.exit,
self.cwd.clone(), exclude_exit: self.exclude_exit,
self.exit, cwd: self.cwd,
self.exclude_exit, exclude_cwd: self.exclude_cwd,
self.exclude_cwd.clone(), before: self.before,
self.before.clone(), after: self.after,
self.after.clone(), limit: self.limit,
self.limit, };
&self.query,
&mut db, let mut entries =
) run_non_interactive(settings, opt_filter.clone(), &self.query, &mut db).await?;
.await?;
if entries.is_empty() { if entries.is_empty() {
std::process::exit(1) std::process::exit(1)
@ -132,19 +130,9 @@ impl Cmd {
db.delete(entry.clone()).await?; db.delete(entry.clone()).await?;
} }
entries = run_non_interactive( entries =
settings, run_non_interactive(settings, opt_filter.clone(), &self.query, &mut db)
self.cwd.clone(), .await?;
self.exit,
self.exclude_exit,
self.exclude_cwd.clone(),
self.before.clone(),
self.after.clone(),
self.limit,
&self.query,
&mut db,
)
.await?;
} }
} else { } else {
super::history::print_list(&entries, list_mode, self.format.as_deref()); super::history::print_list(&entries, list_mode, self.format.as_deref());
@ -159,33 +147,22 @@ impl Cmd {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
async fn run_non_interactive( async fn run_non_interactive(
settings: &Settings, settings: &Settings,
cwd: Option<String>, filter_options: OptFilters,
exit: Option<i64>,
exclude_exit: Option<i64>,
exclude_cwd: Option<String>,
before: Option<String>,
after: Option<String>,
limit: Option<i64>,
query: &[String], query: &[String],
db: &mut impl Database, db: &mut impl Database,
) -> Result<Vec<History>> { ) -> Result<Vec<History>> {
let dir = if cwd.as_deref() == Some(".") { let dir = if filter_options.cwd.as_deref() == Some(".") {
Some(utils::get_current_dir()) Some(utils::get_current_dir())
} else { } else {
cwd filter_options.cwd
}; };
let context = current_context(); let context = current_context();
let before = before.and_then(|b| { let opt_filter = OptFilters {
interim::parse_date_string(b.as_str(), Utc::now(), interim::Dialect::Uk) cwd: dir,
.map_or(None, |d| Some(d.timestamp_nanos())) ..filter_options
}); };
let after = after.and_then(|a| {
interim::parse_date_string(a.as_str(), Utc::now(), interim::Dialect::Uk)
.map_or(None, |d| Some(d.timestamp_nanos()))
});
let results = db let results = db
.search( .search(
@ -193,45 +170,9 @@ async fn run_non_interactive(
settings.filter_mode, settings.filter_mode,
&context, &context,
query.join(" ").as_str(), query.join(" ").as_str(),
limit, opt_filter,
before,
after,
) )
.await?; .await?;
// TODO: This filtering would be better done in the SQL query, I just
// need a nice way of building queries.
let results: Vec<History> = results
.iter()
.filter(|h| {
if let Some(exit) = exit {
if h.exit != exit {
return false;
}
}
if let Some(exit) = exclude_exit {
if h.exit == exit {
return false;
}
}
if let Some(cwd) = &exclude_cwd {
if h.cwd.as_str() == cwd.as_str() {
return false;
}
}
if let Some(cwd) = &dir {
if h.cwd.as_str() != cwd.as_str() {
return false;
}
}
true
})
.map(std::borrow::ToOwned::to_owned)
.collect();
Ok(results) Ok(results)
} }

View File

@ -1,5 +1,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use atuin_client::{database::Database, history::History, settings::SearchMode}; use atuin_client::{
database::Database, database::OptFilters, history::History, settings::SearchMode,
};
use eyre::Result; use eyre::Result;
use super::{SearchEngine, SearchState}; use super::{SearchEngine, SearchState};
@ -19,9 +21,10 @@ impl SearchEngine for Search {
state.filter_mode, state.filter_mode,
&state.context, &state.context,
state.input.as_str(), state.input.as_str(),
Some(200), OptFilters {
None, limit: Some(200),
None, ..Default::default()
},
) )
.await? .await?
.into_iter() .into_iter()

View File

@ -13,8 +13,7 @@ use semver::Version;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use atuin_client::{ use atuin_client::{
database::current_context, database::{current_context, Database},
database::Database,
history::History, history::History,
settings::{ExitMode, FilterMode, SearchMode, Settings}, settings::{ExitMode, FilterMode, SearchMode, Settings},
}; };