diff options
author | Vincent Ambo <tazjin@gmail.com> | 2018-04-14T18·28+0200 |
---|---|---|
committer | Vincent Ambo <github@tazj.in> | 2018-04-14T20·21+0200 |
commit | 31b0a550f2b96a1de5de65308420788c9a6aa5df (patch) | |
tree | 27f6396644f753aef6a8ebf48ba0bd6438e71029 | |
parent | 2d8db520107c25c5bc7d1ba861047fa9b7eaea5f (diff) |
feat(db): Implement handling of 'SearchPosts' message
Adds support for executing full-text search across a forum instance by sending the `SearchPosts` message with a search query to the DB actor. The struct used for results is mapped manually to the expected query result as the query is embedded via raw SQL.
-rw-r--r-- | src/db.rs | 41 | ||||
-rw-r--r-- | src/models.rs | 21 |
2 files changed, 61 insertions, 1 deletions
diff --git a/src/db.rs b/src/db.rs index 5a66fbb0fc74..416e3fdd0f52 100644 --- a/src/db.rs +++ b/src/db.rs @@ -17,7 +17,8 @@ //! This module implements the database connection actor. use actix::prelude::*; -use diesel; +use diesel::{self, sql_query}; +use diesel::sql_types::Text; use diesel::prelude::*; use diesel::r2d2::{Pool, ConnectionManager}; use models::*; @@ -138,3 +139,41 @@ impl Handler<CreatePost> for DbExecutor { .get_result(&conn)?) } } + +/// Message used to search for posts +#[derive(Deserialize)] +pub struct SearchPosts { pub query: String } + +impl Message for SearchPosts { + type Result = Result<Vec<SearchResult>>; +} + +/// Raw PostgreSQL query used to perform full-text search on posts +/// with a supplied phrase. For now, the query language is hardcoded +/// to English and only "plain" queries (i.e. no searches for exact +/// matches or more advanced query syntax) are supported. +const SEARCH_QUERY: &'static str = r#" +WITH search_query (query) AS (VALUES (plainto_tsquery('english', $1))) +SELECT post_id, + thread_id, + author, + title, + ts_headline('english', body, query) AS headline + FROM search_index, search_query + WHERE document @@ query + ORDER BY ts_rank(document, query) DESC +"#; + +impl Handler<SearchPosts> for DbExecutor { + type Result = <SearchPosts as Message>::Result; + + fn handle(&mut self, msg: SearchPosts, _: &mut Self::Context) -> Self::Result { + let conn = self.0.get()?; + + let search_results = sql_query(SEARCH_QUERY) + .bind::<Text, _>(msg.query) + .get_results::<SearchResult>(&conn)?; + + Ok(search_results) + } +} diff --git a/src/models.rs b/src/models.rs index 9d3405e1540f..927a78513669 100644 --- a/src/models.rs +++ b/src/models.rs @@ -16,6 +16,7 @@ use chrono::prelude::{DateTime, Utc}; use schema::{threads, posts}; +use diesel::sql_types::{Text, Integer}; #[derive(Identifiable, Queryable, Serialize)] pub struct Thread { @@ -69,3 +70,23 @@ pub struct NewPost { pub author_name: String, pub author_email: String, } + +/// This struct models the response of a full-text search query. It +/// does not use a table/schema definition struct like the other +/// tables, as no table of this type actually exists. +#[derive(QueryableByName, Debug)] +pub struct SearchResult { + #[sql_type = "Integer"] + pub post_id: i32, + #[sql_type = "Integer"] + pub thread_id: i32, + #[sql_type = "Text"] + pub author: String, + #[sql_type = "Text"] + pub title: String, + + /// Headline represents the result of Postgres' ts_headline() + /// function, which highlights search terms in the search results. + #[sql_type = "Text"] + pub headline: String, +} |