diff --git a/atuin-client/src/database.rs b/atuin-client/src/database.rs index fa39f71b..1a577a70 100644 --- a/atuin-client/src/database.rs +++ b/atuin-client/src/database.rs @@ -25,6 +25,17 @@ pub struct Context { pub hostname: String, } +#[derive(Default, Clone)] +pub struct OptFilters { + pub exit: Option, + pub exclude_exit: Option, + pub cwd: Option, + pub exclude_cwd: Option, + pub before: Option, + pub after: Option, + pub limit: Option, +} + pub fn current_context() -> Context { 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."); @@ -79,9 +90,7 @@ pub trait Database: Send + Sync + 'static { filter: FilterMode, context: &Context, query: &str, - limit: Option, - before: Option, - after: Option, + filter_options: OptFilters, ) -> Result>; async fn query_history(&self, query: &str) -> Result>; @@ -340,9 +349,7 @@ impl Database for Sqlite { filter: FilterMode, context: &Context, query: &str, - limit: Option, - before: Option, - after: Option, + filter_options: OptFilters, ) -> Result> { let mut sql = SqlBuilder::select_from("history"); @@ -350,18 +357,10 @@ impl Database for Sqlite { .having("max(timestamp)") .order_desc("timestamp"); - if let Some(limit) = limit { + if let Some(limit) = filter_options.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 { FilterMode::Global => &mut sql, 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 res = sqlx::query(&query) @@ -508,7 +533,15 @@ mod test { }; let results = db - .search(mode, filter_mode, &context, query, None, None, None) + .search( + mode, + filter_mode, + &context, + query, + OptFilters { + ..Default::default() + }, + ) .await?; assert_eq!( @@ -718,9 +751,9 @@ mod test { FilterMode::Global, &context, "", - None, - None, - None, + OptFilters { + ..Default::default() + }, ) .await .unwrap(); diff --git a/src/command/client/search.rs b/src/command/client/search.rs index c407eb08..fb3a1a39 100644 --- a/src/command/client/search.rs +++ b/src/command/client/search.rs @@ -1,11 +1,10 @@ use atuin_common::utils; -use chrono::Utc; use clap::Parser; use eyre::Result; use atuin_client::{ - database::current_context, database::Database, + database::{current_context, OptFilters}, history::History, settings::{FilterMode, SearchMode, Settings}, }; @@ -103,19 +102,18 @@ impl Cmd { } else { let list_mode = ListMode::from_flags(self.human, self.cmd_only); - let mut entries = run_non_interactive( - settings, - self.cwd.clone(), - self.exit, - self.exclude_exit, - self.exclude_cwd.clone(), - self.before.clone(), - self.after.clone(), - self.limit, - &self.query, - &mut db, - ) - .await?; + let opt_filter = OptFilters { + exit: self.exit, + exclude_exit: self.exclude_exit, + cwd: self.cwd, + exclude_cwd: self.exclude_cwd, + before: self.before, + after: self.after, + limit: self.limit, + }; + + let mut entries = + run_non_interactive(settings, opt_filter.clone(), &self.query, &mut db).await?; if entries.is_empty() { std::process::exit(1) @@ -132,19 +130,9 @@ impl Cmd { db.delete(entry.clone()).await?; } - entries = run_non_interactive( - settings, - self.cwd.clone(), - self.exit, - self.exclude_exit, - self.exclude_cwd.clone(), - self.before.clone(), - self.after.clone(), - self.limit, - &self.query, - &mut db, - ) - .await?; + entries = + run_non_interactive(settings, opt_filter.clone(), &self.query, &mut db) + .await?; } } else { super::history::print_list(&entries, list_mode, self.format.as_deref()); @@ -159,33 +147,22 @@ impl Cmd { #[allow(clippy::too_many_arguments)] async fn run_non_interactive( settings: &Settings, - cwd: Option, - exit: Option, - exclude_exit: Option, - exclude_cwd: Option, - before: Option, - after: Option, - limit: Option, + filter_options: OptFilters, query: &[String], db: &mut impl Database, ) -> Result> { - let dir = if cwd.as_deref() == Some(".") { + let dir = if filter_options.cwd.as_deref() == Some(".") { Some(utils::get_current_dir()) } else { - cwd + filter_options.cwd }; let context = current_context(); - let before = before.and_then(|b| { - interim::parse_date_string(b.as_str(), Utc::now(), interim::Dialect::Uk) - .map_or(None, |d| Some(d.timestamp_nanos())) - }); - - 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 opt_filter = OptFilters { + cwd: dir, + ..filter_options + }; let results = db .search( @@ -193,45 +170,9 @@ async fn run_non_interactive( settings.filter_mode, &context, query.join(" ").as_str(), - limit, - before, - after, + opt_filter, ) .await?; - // TODO: This filtering would be better done in the SQL query, I just - // need a nice way of building queries. - let results: Vec = 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) } diff --git a/src/command/client/search/engines/db.rs b/src/command/client/search/engines/db.rs index 5a35da10..b4f24561 100644 --- a/src/command/client/search/engines/db.rs +++ b/src/command/client/search/engines/db.rs @@ -1,5 +1,7 @@ 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 super::{SearchEngine, SearchState}; @@ -19,9 +21,10 @@ impl SearchEngine for Search { state.filter_mode, &state.context, state.input.as_str(), - Some(200), - None, - None, + OptFilters { + limit: Some(200), + ..Default::default() + }, ) .await? .into_iter() diff --git a/src/command/client/search/interactive.rs b/src/command/client/search/interactive.rs index a7ee9bae..5038fc83 100644 --- a/src/command/client/search/interactive.rs +++ b/src/command/client/search/interactive.rs @@ -13,8 +13,7 @@ use semver::Version; use unicode_width::UnicodeWidthStr; use atuin_client::{ - database::current_context, - database::Database, + database::{current_context, Database}, history::History, settings::{ExitMode, FilterMode, SearchMode, Settings}, };