diff --git a/crates/collab/migrations/20220505144506_add_trigram_index_to_users.sql b/crates/collab/migrations/20220505144506_add_trigram_index_to_users.sql new file mode 100644 index 0000000000..3d6fd3179a --- /dev/null +++ b/crates/collab/migrations/20220505144506_add_trigram_index_to_users.sql @@ -0,0 +1,2 @@ +CREATE EXTENSION IF NOT EXISTS pg_trgm; +CREATE INDEX trigram_index_users_on_github_login ON users USING GIN(github_login gin_trgm_ops); diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 28375b6685..6356715eae 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -10,6 +10,7 @@ use time::OffsetDateTime; pub trait Db: Send + Sync { async fn create_user(&self, github_login: &str, admin: bool) -> Result; async fn get_all_users(&self) -> Result>; + async fn fuzzy_search_users(&self, query: &str, limit: u32) -> Result>; async fn get_user_by_id(&self, id: UserId) -> Result>; async fn get_users_by_ids(&self, ids: Vec) -> Result>; async fn get_user_by_github_login(&self, github_login: &str) -> Result>; @@ -99,6 +100,21 @@ impl Db for PostgresDb { Ok(sqlx::query_as(query).fetch_all(&self.pool).await?) } + async fn fuzzy_search_users(&self, name_query: &str, limit: u32) -> Result> { + let query = " + SELECT users.* + FROM users + WHERE github_login % $1 + ORDER BY github_login <-> $1 + LIMIT $2 + "; + Ok(sqlx::query_as(query) + .bind(name_query) + .bind(limit) + .fetch_all(&self.pool) + .await?) + } + async fn get_user_by_id(&self, id: UserId) -> Result> { let users = self.get_users_by_ids(vec![id]).await?; Ok(users.into_iter().next()) @@ -640,6 +656,31 @@ pub mod tests { ); } + #[tokio::test(flavor = "multi_thread")] + async fn test_fuzzy_search_users() { + let test_db = TestDb::postgres().await; + let db = test_db.db(); + for github_login in [ + "nathansobo", + "nathansobot", + "nathanszabo", + "maxbrunsfeld", + "as-cii", + ] { + db.create_user(github_login, false).await.unwrap(); + } + + let results = db + .fuzzy_search_users("nathasbo", 10) + .await + .unwrap() + .into_iter() + .map(|user| user.github_login) + .collect::>(); + + assert_eq!(results, &["nathansobo", "nathanszabo", "nathansobot"]); + } + pub struct TestDb { pub db: Option>, pub url: String, @@ -749,6 +790,10 @@ pub mod tests { unimplemented!() } + async fn fuzzy_search_users(&self, _: &str, _: u32) -> Result> { + unimplemented!() + } + async fn get_user_by_id(&self, id: UserId) -> Result> { Ok(self.get_users_by_ids(vec![id]).await?.into_iter().next()) }