Skip to content

Commit

Permalink
Port user schema to exact match search
Browse files Browse the repository at this point in the history
  • Loading branch information
kpanic committed Oct 14, 2019
1 parent 1e3876a commit bfeb19e
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 30 deletions.
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ iex> YourRepo.insert_or_update(Bombadil.index(SearchIndex, payload: %{"book" =>
{:ok, struct}

# Full string provided
iex> YourRepo.all(Bombadil.search("Lord of the Rings"))
iex> YourRepo.all(Bombadil.search(SearchIndex, "Lord of the Rings"))
# Raw SQL: SELECT s0."id", s0."payload" FROM "search_index" AS s0 WHERE (to_tsvector('simple', payload::text) @@ plainto_tsquery('simple', 'Lord of the Rings'))
[
%Bombadil.Ecto.Schema.SearchIndex{
Expand All @@ -51,7 +51,7 @@ iex> YourRepo.all(Bombadil.search("Lord of the Rings"))

# One word provided (treated as case-insensitive)

iex> YourRepo.all(Bombadil.search("lord"))
iex> YourRepo.all(Bombadil.search(SearchIndex, "lord"))
# Raw SQL: SELECT s0."id", s0."payload" FROM "search_index" AS s0 WHERE (to_tsvector('simple', payload::text) @@ plainto_tsquery('simple', 'lord'))
[
%Bombadil.Ecto.Schema.SearchIndex{
Expand All @@ -63,7 +63,7 @@ iex> YourRepo.all(Bombadil.search("lord"))

# No results

iex> YourRepo.all(Bombadil.search("lordz"))
iex> YourRepo.all(Bombadil.search(SearchIndex, "lordz"))
# Raw SQL: SELECT s0."id", s0."payload" FROM "search_index" AS s0 WHERE (to_tsvector('simple', payload::text) @@ plainto_tsquery('simple', 'lordz'))
[]
```
Expand Down Expand Up @@ -95,7 +95,7 @@ iex> YourRepo.all(Bombadil.fuzzy_search(SearchIndex, "lard of the ringz asdf"))
```elixir
iex> YourRepo.insert_or_update(Bombadil.index(SearchIndex, payload: %{"character" => "Tom Bombadil"}))
{:ok, struct}
iex> YourRepo.all(Bombadil.search([%{"book" => "rings"}]))
iex> YourRepo.all(Bombadil.search(SearchIndex, [%{"book" => "rings"}]))
# Raw SQL: SELECT s0."id", s0."payload" FROM "search_index" AS s0 WHERE (FALSE OR to_tsvector('simple', (payload->'book')::text) @@ plainto_tsquery('simple', 'rings'))
[
%Bombadil.Ecto.Schema.SearchIndex{
Expand All @@ -104,7 +104,7 @@ iex> YourRepo.all(Bombadil.search([%{"book" => "rings"}]))
id: 1
}
]
iex> YourRepo.all(Bombadil.search([%{"character" => "bombadil"}]))
iex> YourRepo.all(Bombadil.search(SearchIndex, [%{"character" => "bombadil"}]))
# Raw SQL: SELECT s0."id", s0."payload" FROM "search_index" AS s0 WHERE (FALSE OR to_tsvector('simple', (payload->'character')::text) @@ plainto_tsquery('simple', 'bombadil'))
[
%Bombadil.Ecto.Schema.SearchIndex{
Expand Down Expand Up @@ -170,7 +170,7 @@ iex> YourRepo.all(Bombadil.fuzzy_search(SearchIndex, "lord of the ringz", contex
# Encoding to JSON

```elixir
iex> Bombadil.search("rings") |> Jason.encode!()
iex> Bombadil.search(SearchIndex, "rings") |> Jason.encode!()
"[{\"payload\":{\"book\":\"Lord of the Rings\"}}]"
```

Expand Down Expand Up @@ -290,8 +290,6 @@ And implement indexing and search for your use case by using the

# TODO

[ ] Port user schema to `Bombadil.search`

[ ] Support other fields, rather than jsonb (?)

## Thank you(s)
Expand Down
9 changes: 5 additions & 4 deletions lib/bombadil.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule Bombadil do
alias Bombadil.Ecto.Schema.SearchIndex
iex> YourEctoRepo.all(Bombadil.search("Lord of the Rings"))
iex> YourEctoRepo.all(Bombadil.search(SearchIndex, "Lord of the Rings"))
[
%Bombadil.Ecto.Schema.SearchIndex{
__meta__: #Ecto.Schema.Metadata<:loaded, "search_index">,
Expand All @@ -26,8 +26,8 @@ defmodule Bombadil do
}
]
"""
@spec search(String.t()) :: Ecto.Query.t()
defdelegate search(query), to: Bombadil.Search
@spec search(Ecto.Schema.t(), String.t()) :: Ecto.Query.t()
defdelegate search(schema, query, opts \\ []), to: Bombadil.Search

@doc """
Fuzzy search data of a string (or substring)
Expand Down Expand Up @@ -59,6 +59,7 @@ defmodule Bombadil do
YourEctoRepo.insert_or_update(Bombadil.index(SearchIndex, payload: %{"book" => "Lord of the Rings"}))
"""
@spec index(Ecto.Schema.t(), Keyword.t() | Ecto.Changeset.t(), Keyword.t()) :: Ecto.Changeset.t()
@spec index(Ecto.Schema.t(), Keyword.t() | Ecto.Changeset.t(), Keyword.t()) ::
Ecto.Changeset.t()
defdelegate index(schema, payload, params \\ []), to: Bombadil.Index
end
23 changes: 11 additions & 12 deletions lib/bombadil/search.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,18 @@ defmodule Bombadil.Search do

import Ecto.Query

alias Bombadil.Ecto.Schema.SearchIndex

# EXPLORE websearch
# SELECT websearch_to_tsquery('english', '"supernovae stars" -crab');
# TODO: Convert to opts the options ;)
def search(search_query, opts \\ [operator: :or])
def search(schema, search_query, opts \\ [])

def search(search_query, opts) when is_list(search_query) do
operator = Keyword.get(opts, :operator, :or)
def search(schema, search_query, opts) when is_list(search_query) do
search_query = to_tuple_list(search_query)
construct_extact_match_query(search_query, operator)
construct_extact_match_query(schema, search_query, opts)
end

def search(search_query, opts) when is_binary(search_query) do
operator = Keyword.get(opts, :operator, :or)
construct_extact_match_query(search_query, operator)
def search(schema, search_query, opts) when is_binary(search_query) do
construct_extact_match_query(schema, search_query, opts)
end

def fuzzy_search(schema, search_query, opts)
Expand All @@ -31,9 +27,12 @@ defmodule Bombadil.Search do
construct_fuzzy_query(schema, search_query, opts)
end

defp construct_extact_match_query(search_query, operator) do
from(i in SearchIndex,
where: ^Bombadil.Criteria.prepare(search_query, operator)
defp construct_extact_match_query(schema, search_query, opts) do
context = Keyword.get(opts, :context, %{})

from(i in schema,
where: ^Bombadil.Criteria.prepare(search_query),
where: ^Enum.into(context, [])
)
end

Expand Down
64 changes: 58 additions & 6 deletions test/bombadil_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ defmodule BombadilTest do
)

assert [%_{payload: %{"ask" => "ciao"}, test: "I was generated by config dynamically!"}] =
TestRepo.all(Bombadil.search([%{"ask" => "ciao"}]))
TestRepo.all(Bombadil.search(SearchIndex, [%{"ask" => "ciao"}]))
end
end

Expand All @@ -34,15 +34,17 @@ defmodule BombadilTest do
)

assert [%_{payload: %{"ask" => "ciao"}}, %_{payload: %{"ask" => "ciao2"}}] =
TestRepo.all(Bombadil.search([%{"ask" => "ciao"}, %{"ask" => "ciao2"}]))
TestRepo.all(
Bombadil.search(SearchIndex, [%{"ask" => "ciao"}, %{"ask" => "ciao2"}])
)
end

test "search payload (exact match with one criteria)" do
assert {:ok, _} =
TestRepo.insert_or_update(Bombadil.index(SearchIndex, payload: %{"ask" => "ciao"}))

assert [%_{payload: %{"ask" => "ciao"}}] =
TestRepo.all(Bombadil.search([%{"ask" => "ciao"}]))
TestRepo.all(Bombadil.search(SearchIndex, [%{"ask" => "ciao"}]))
end

test "search payload (does not match)" do
Expand All @@ -51,7 +53,7 @@ defmodule BombadilTest do
Bombadil.index(SearchIndex, payload: %{"ask" => "hello"})
)

assert [] = TestRepo.all(Bombadil.search([%{"ask" => "ciao"}]))
assert [] = TestRepo.all(Bombadil.search(SearchIndex, [%{"ask" => "ciao"}]))
end
end

Expand All @@ -65,7 +67,8 @@ defmodule BombadilTest do
Bombadil.index(SearchIndex, payload: %{"ask" => "ciao2"})
)

assert [%_{payload: %{"ask" => "ciao2"}}] = TestRepo.all(Bombadil.search("ciao2"))
assert [%_{payload: %{"ask" => "ciao2"}}] =
TestRepo.all(Bombadil.search(SearchIndex, "ciao2"))
end

test "simple match with metadata" do
Expand All @@ -77,7 +80,7 @@ defmodule BombadilTest do
)

assert [%_{payload: %{"ask" => "hello world", "metadata" => [%{"meta" => "data"}]}}] =
TestRepo.all(Bombadil.search("hello world"))
TestRepo.all(Bombadil.search(SearchIndex, "hello world"))
end
end

Expand Down Expand Up @@ -146,6 +149,55 @@ defmodule BombadilTest do
end

describe "search with a context" do
test "and exact match" do
assert {:ok, _} =
TestRepo.insert_or_update(
Bombadil.index(SearchIndex, item_id: 42, payload: %{"ask" => "hello exact match"})
)

assert {:ok, _} =
TestRepo.insert_or_update(
Bombadil.index(SearchIndex,
item_id: 42,
payload: %{"ask" => "I am hiding with the same id, don't find me!"}
)
)

assert {:ok, _} =
TestRepo.insert_or_update(
Bombadil.index(SearchIndex,
item_id: 24,
payload: %{"dont_look_at_me" => "hello exact match"}
)
)

assert [
%Bombadil.Ecto.Schema.SearchIndex{
id: _id,
item_id: 42,
payload: %{"ask" => "hello exact match"},
test: nil
}
] =
TestRepo.all(
Bombadil.search(SearchIndex, [%{"ask" => "hello exact match"}],
context: %{item_id: 42}
)
)

assert [
%Bombadil.Ecto.Schema.SearchIndex{
id: _id,
item_id: 42,
payload: %{"ask" => "hello exact match"},
test: nil
}
] =
TestRepo.all(
Bombadil.search(SearchIndex, "hello exact match", context: %{item_id: 42})
)
end

test "and fuzzy search" do
assert {:ok, _} =
TestRepo.insert_or_update(
Expand Down

0 comments on commit bfeb19e

Please sign in to comment.