-
Notifications
You must be signed in to change notification settings - Fork 4
Full Text Search
Full Text Search is included on all resources by default. It uses PostgreSQL's native support.
New tables can be added with a single ecto migration. Search for results with a single function call:
import Artemis.Helpers.Search
User
+ |> search_filter(%{query: "search-value"})
|> Repo.all()
Name | GitHub |
---|---|
Chris Laskey | @chrislaskey |
Jack Hefner III | @jmhthethird |
PostgreSQL natively supports Full Text Search. Using an Ecto migration the following behaviours are defined:
- A new
tsv_search
column and index is added to each table that will support search. - A PostgreSQL Coalesce function is created, which defines the custom list of table columns that should be searchable.
- A PostgreSQL Trigger is defined to call the coalesce function and update the
tsv_search
value whenever a new row is inserted or updated.
The value of this pattern is:
- Search isn't exclusive. The search query can be used in combination with other Repo functions like
order
,select
andwhere
clauses. - Minimal setup. Extending search to a new resource can be done in a single ecto migration. A single function call adds search capabilities to a Context.
- Fewer architecture pieces. Since PostgreSQL natively supports Full Text Search, additional dedicated search platforms like ElasticSearch are not required.
The downsides of this pattern are:
- Inherent design limitations of Full Text Search:
- Limited support for phrases. Multi-word phrases are broken down into individual words, which are searched separately. This means word order is not guaranteed.
- Limited support for partial searches. Partial searches only return results if the search value starts a word. Any search values for the middle or end of words will not return results. Can be mitigated by pairing with a combination of Trigram search and LIKE statements.
View a complete migration file.
Define a column to store full text search data
alter table(:users) do
add :tsv_search, :tsvector
end
Create a GIN index on the full text search data column
create index(:users, [:tsv_search], name: :users_search_vector, using: "GIN")
Coalesce the searchable fields into a single, space-separted, value. In the example below the following user attributes are included in search:
- name
- first_name
- last_name
execute("""
CREATE FUNCTION create_search_data_users() RETURNS trigger AS $$
begin
new.tsv_search :=
to_tsvector(
'pg_catalog.english',
coalesce(new.email, ' ') || ' ' ||
coalesce(new.name, ' ') || ' ' ||
coalesce(new.first_name, ' ') || ' ' ||
coalesce(new.last_name, ' ')
);
return new;
end
$$ LANGUAGE plpgsql;
""")
Call the function on INSERT
and UPDATE
actions
execute("""
CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
ON users FOR EACH ROW EXECUTE PROCEDURE create_search_data_users();
""")
Helper functions are defined in Artemis.Helpers.Search
.
These make it so adding a support for a search query can be done with a single addition of search_filter
:
import Artemis.Helpers.Search
User
+ |> search_filter(%{query: "search-value"})
|> Repo.all()
The search_filter
function takes an existing Ecto query and a set of params. It:
- Verifies the
query
key is set in the params - Splits the query into separate words
- Adds a search query for each word
Helper macros are also created to make ad-hoc querying Postgres Full Text Search values easier:
to_tsquery(query)
ts_rank_cd(tsv, query)
Thoughts? Feedback? View the discussion thread for this pattern.