Add fuzzy text search mode (#142)

This commit is contained in:
Frank Hamand 2021-06-01 08:38:19 +01:00 committed by GitHub
parent f0130571a6
commit 0b9dc6696b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 132 additions and 3 deletions

35
Cargo.lock generated
View File

@ -37,6 +37,27 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "async-stream"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625"
dependencies = [
"async-stream-impl",
"futures-core",
]
[[package]]
name = "async-stream-impl"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "async-trait"
version = "0.1.50"
@ -129,6 +150,7 @@ dependencies = [
"sodiumoxide",
"sqlx",
"tokio",
"tokio-test",
"urlencoding",
"uuid",
"whoami",
@ -2323,6 +2345,19 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-test"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3"
dependencies = [
"async-stream",
"bytes",
"futures-core",
"tokio",
"tokio-stream",
]
[[package]]
name = "tokio-tungstenite"
version = "0.13.0"

View File

@ -40,3 +40,6 @@ humantime = "2.1.0"
itertools = "0.10.0"
shellexpand = "2"
sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "uuid", "chrono", "sqlite" ] }
[dev-dependencies]
tokio-test = "*"

View File

@ -24,5 +24,5 @@
# sync_address = "https://api.atuin.sh"
## which search mode to use
## possible values: prefix, fulltext
## possible values: prefix, fulltext, fuzzy
# search_mode = "prefix"

View File

@ -6,6 +6,7 @@ use chrono::prelude::*;
use chrono::Utc;
use eyre::Result;
use itertools::Itertools;
use sqlx::sqlite::{
SqliteConnectOptions, SqliteJournalMode, SqlitePool, SqlitePoolOptions, SqliteRow,
@ -286,6 +287,7 @@ impl Database for Sqlite {
let query = match search_mode {
SearchMode::Prefix => query,
SearchMode::FullText => format!("%{}", query),
SearchMode::Fuzzy => query.split("").join("%"),
};
let res = sqlx::query(
@ -318,3 +320,89 @@ impl Database for Sqlite {
Ok(res)
}
}
#[cfg(test)]
mod test {
use super::*;
async fn new_history_item(db: &mut impl Database, cmd: &str) -> Result<()> {
let history = History::new(
chrono::Utc::now(),
cmd.to_string(),
"/home/ellie".to_string(),
0,
1,
Some("beep boop".to_string()),
Some("booop".to_string()),
);
return db.save(&history).await;
}
#[tokio::test(flavor = "multi_thread")]
async fn test_search_prefix() {
let mut db = Sqlite::new("sqlite::memory:").await.unwrap();
new_history_item(&mut db, "ls /home/ellie").await.unwrap();
let mut results = db.search(None, SearchMode::Prefix, "ls").await.unwrap();
assert_eq!(results.len(), 1);
results = db.search(None, SearchMode::Prefix, "/home").await.unwrap();
assert_eq!(results.len(), 0);
results = db.search(None, SearchMode::Prefix, "ls ").await.unwrap();
assert_eq!(results.len(), 0);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_search_fulltext() {
let mut db = Sqlite::new("sqlite::memory:").await.unwrap();
new_history_item(&mut db, "ls /home/ellie").await.unwrap();
let mut results = db.search(None, SearchMode::FullText, "ls").await.unwrap();
assert_eq!(results.len(), 1);
results = db
.search(None, SearchMode::FullText, "/home")
.await
.unwrap();
assert_eq!(results.len(), 1);
results = db.search(None, SearchMode::FullText, "ls ").await.unwrap();
assert_eq!(results.len(), 0);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_search_fuzzy() {
let mut db = Sqlite::new("sqlite::memory:").await.unwrap();
new_history_item(&mut db, "ls /home/ellie").await.unwrap();
new_history_item(&mut db, "ls /home/frank").await.unwrap();
new_history_item(&mut db, "cd /home/ellie").await.unwrap();
new_history_item(&mut db, "/home/ellie/.bin/rustup")
.await
.unwrap();
let mut results = db.search(None, SearchMode::Fuzzy, "ls /").await.unwrap();
assert_eq!(results.len(), 2);
results = db.search(None, SearchMode::Fuzzy, "l/h/").await.unwrap();
assert_eq!(results.len(), 2);
results = db.search(None, SearchMode::Fuzzy, "/h/e").await.unwrap();
assert_eq!(results.len(), 3);
results = db.search(None, SearchMode::Fuzzy, "/hmoe/").await.unwrap();
assert_eq!(results.len(), 0);
results = db
.search(None, SearchMode::Fuzzy, "ellie/home")
.await
.unwrap();
assert_eq!(results.len(), 0);
results = db.search(None, SearchMode::Fuzzy, "lsellie").await.unwrap();
assert_eq!(results.len(), 1);
results = db.search(None, SearchMode::Fuzzy, " ").await.unwrap();
assert_eq!(results.len(), 3);
}
}

View File

@ -17,6 +17,9 @@ pub enum SearchMode {
#[serde(rename = "fulltext")]
FullText,
#[serde(rename = "fuzzy")]
Fuzzy,
}
// FIXME: Can use upstream Dialect enum if https://github.com/stevedonovan/chrono-english/pull/16 is merged

View File

@ -96,8 +96,8 @@ key = "~/.atuin-session"
### `search_mode`
Which search mode to use. Atuin supports both "prefix" and full text search
modes. The former will essentially search for "query*", and the latter "*query\*"
Which search mode to use. Atuin supports "prefix", full text and "fuzzy" search
modes. The prefix search for "query\*", fulltext "\*query\*", and fuzzy "\*q\*u\*e\*r\*y\*"
Defaults to "prefix"