Skip to content

Commit

Permalink
update sqlite tests for case-insensitive like operator
Browse files Browse the repository at this point in the history
  • Loading branch information
woylie committed Sep 12, 2024
1 parent 0bdfeb9 commit 1f740c5
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 63 deletions.
25 changes: 17 additions & 8 deletions test/adapters/ecto/cases/flop_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -387,14 +387,14 @@ defmodule Flop.Adapters.Ecto.FlopTest do
end
end

property "applies like filter" do
property "applies like filter", %{ecto_adapter: ecto_adapter} do
check all pet_count <- integer(@pet_count_range),
pets = insert_list_and_sort(pet_count, :pet_with_owner),
field <- filterable_pet_field(:string),
pet <- member_of(pets),
value = Pet.get_field(pet, field),
query_value <- substring(value) do
expected = filter_items(pets, field, :like, query_value)
expected = filter_items(pets, field, :like, query_value, ecto_adapter)

assert query_pets_with_owners(%{
filters: [%{field: field, op: :like, value: query_value}]
Expand Down Expand Up @@ -434,14 +434,15 @@ defmodule Flop.Adapters.Ecto.FlopTest do
end
end

property "applies not like filter" do
property "applies not like filter", %{ecto_adapter: ecto_adapter} do
check all pet_count <- integer(@pet_count_range),
pets = insert_list_and_sort(pet_count, :pet_with_owner),
field <- filterable_pet_field(:string),
pet <- member_of(pets),
value = Pet.get_field(pet, field),
query_value <- substring(value) do
expected = filter_items(pets, field, :not_like, query_value)
expected =
filter_items(pets, field, :not_like, query_value, ecto_adapter)

assert query_pets_with_owners(%{
filters: [%{field: field, op: :not_like, value: query_value}]
Expand Down Expand Up @@ -486,14 +487,21 @@ defmodule Flop.Adapters.Ecto.FlopTest do
end
end

property "applies like_and filter" do
property "applies like_and filter", %{ecto_adapter: ecto_adapter} do
check all pet_count <- integer(@pet_count_range),
pets = insert_list_and_sort(pet_count, :pet_with_owner),
field <- filterable_pet_field(:string),
pet <- member_of(pets),
value = Pet.get_field(pet, field),
search_text_or_list <- search_text_or_list(value) do
expected = filter_items(pets, field, :like_and, search_text_or_list)
expected =
filter_items(
pets,
field,
:like_and,
search_text_or_list,
ecto_adapter
)

assert query_pets_with_owners(%{
filters: [
Expand All @@ -505,14 +513,15 @@ defmodule Flop.Adapters.Ecto.FlopTest do
end
end

property "applies like_or filter" do
property "applies like_or filter", %{ecto_adapter: ecto_adapter} do
check all pet_count <- integer(@pet_count_range),
pets = insert_list_and_sort(pet_count, :pet_with_owner),
field <- filterable_pet_field(:string),
pet <- member_of(pets),
value = Pet.get_field(pet, field),
search_text_or_list <- search_text_or_list(value) do
expected = filter_items(pets, field, :like_or, search_text_or_list)
expected =
filter_items(pets, field, :like_or, search_text_or_list, ecto_adapter)

assert query_pets_with_owners(%{
filters: [
Expand Down
4 changes: 4 additions & 0 deletions test/adapters/ecto/postgres/test_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ defmodule Flop.Integration.Case do
setup do
:ok = Sandbox.checkout(Flop.Repo)
end

setup do
%{ecto_adapter: :postgres}
end
end

Code.require_file("migration.exs", __DIR__)
Expand Down
4 changes: 4 additions & 0 deletions test/adapters/ecto/sqlite/test_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ defmodule Flop.Integration.Case do
setup do
:ok = Sandbox.checkout(Flop.Repo)
end

setup do
%{ecto_adapter: :sqlite}
end
end

Code.require_file("migration.exs", __DIR__)
Expand Down
150 changes: 95 additions & 55 deletions test/support/test_util.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,30 @@ defmodule Flop.TestUtil do
The function supports regular fields, join fields and compound fields. The
associations need to be preloaded if join fields are used.
"""
def filter_items(items, field, op, value \\ nil)
def filter_items(items, field, op, value \\ nil, ecto_adapter \\ nil)

def filter_items([], _, _, _), do: []
def filter_items([], _, _, _, _), do: []

def filter_items([%module{} = struct | _] = items, field, op, value)
def filter_items(
[struct | _] = items,
field,
op,
value,
ecto_adapter
)
when is_atom(field) do
case Flop.Schema.field_info(struct, field) do
%FieldInfo{ecto_type: ecto_type, extra: %{type: :join}} = field_info
when not is_nil(ecto_type) ->
filter_func = matches?(op, value, ecto_type)
filter_func = matches?(op, value, ecto_adapter)

Enum.filter(items, fn item ->
item |> get_field(field_info) |> filter_func.()
end)

%FieldInfo{extra: %{type: type}} = field_info
when type in [:normal, :join] ->
ecto_type = module.__schema__(:type, field)
filter_func = matches?(op, value, ecto_type)
filter_func = matches?(op, value, ecto_adapter)

Enum.filter(items, fn item ->
item |> get_field(field_info) |> filter_func.()
Expand All @@ -49,12 +54,12 @@ defmodule Flop.TestUtil do
%FieldInfo{extra: %{type: :compound, fields: fields}} ->
Enum.filter(
items,
&apply_filter_to_compound_fields(&1, fields, op, value)
&apply_filter_to_compound_fields(&1, fields, op, value, ecto_adapter)
)
end
end

defp apply_filter_to_compound_fields(_pet, _fields, op, _value)
defp apply_filter_to_compound_fields(_pet, _fields, op, _value, _ecto_adapter)
when op in [
:==,
:=~,
Expand All @@ -70,20 +75,26 @@ defmodule Flop.TestUtil do
true
end

defp apply_filter_to_compound_fields(pet, fields, :empty, value) do
filter_func = matches?(:empty, value)
defp apply_filter_to_compound_fields(pet, fields, :empty, value, ecto_adapter) do
filter_func = matches?(:empty, value, ecto_adapter)

Enum.all?(fields, fn field ->
field_info = Flop.Schema.field_info(%Pet{}, field)
pet |> get_field(field_info) |> filter_func.()
end)
end

defp apply_filter_to_compound_fields(pet, fields, :like_and, value) do
defp apply_filter_to_compound_fields(
pet,
fields,
:like_and,
value,
ecto_adapter
) do
value = if is_binary(value), do: String.split(value), else: value

Enum.all?(value, fn substring ->
filter_func = matches?(:like, substring)
filter_func = matches?(:like, substring, ecto_adapter)

Enum.any?(fields, fn field ->
field_info = Flop.Schema.field_info(%Pet{}, field)
Expand All @@ -92,11 +103,17 @@ defmodule Flop.TestUtil do
end)
end

defp apply_filter_to_compound_fields(pet, fields, :ilike_and, value) do
defp apply_filter_to_compound_fields(
pet,
fields,
:ilike_and,
value,
ecto_adapter
) do
value = if is_binary(value), do: String.split(value), else: value

Enum.all?(value, fn substring ->
filter_func = matches?(:ilike, substring)
filter_func = matches?(:ilike, substring, ecto_adapter)

Enum.any?(fields, fn field ->
field_info = Flop.Schema.field_info(%Pet{}, field)
Expand All @@ -105,11 +122,17 @@ defmodule Flop.TestUtil do
end)
end

defp apply_filter_to_compound_fields(pet, fields, :like_or, value) do
defp apply_filter_to_compound_fields(
pet,
fields,
:like_or,
value,
ecto_adapter
) do
value = if is_binary(value), do: String.split(value), else: value

Enum.any?(value, fn substring ->
filter_func = matches?(:like, substring)
filter_func = matches?(:like, substring, ecto_adapter)

Enum.any?(fields, fn field ->
field_info = Flop.Schema.field_info(%Pet{}, field)
Expand All @@ -118,11 +141,17 @@ defmodule Flop.TestUtil do
end)
end

defp apply_filter_to_compound_fields(pet, fields, :ilike_or, value) do
defp apply_filter_to_compound_fields(
pet,
fields,
:ilike_or,
value,
ecto_adapter
) do
value = if is_binary(value), do: String.split(value), else: value

Enum.any?(value, fn substring ->
filter_func = matches?(:ilike, substring)
filter_func = matches?(:ilike, substring, ecto_adapter)

Enum.any?(fields, fn field ->
field_info = Flop.Schema.field_info(%Pet{}, field)
Expand All @@ -131,8 +160,8 @@ defmodule Flop.TestUtil do
end)
end

defp apply_filter_to_compound_fields(pet, fields, op, value) do
filter_func = matches?(op, value)
defp apply_filter_to_compound_fields(pet, fields, op, value, ecto_adapter) do
filter_func = matches?(op, value, ecto_adapter)

Enum.any?(fields, fn field ->
field_info = Flop.Schema.field_info(%Pet{}, field)
Expand All @@ -146,63 +175,74 @@ defmodule Flop.TestUtil do
defp get_field(pet, %FieldInfo{extra: %{type: :join, path: [a, b]}}),
do: pet |> Map.fetch!(a) |> Map.fetch!(b)

defp matches?(op, v, _), do: matches?(op, v)
defp matches?(:==, v), do: &(&1 == v)
defp matches?(:!=, v), do: &(&1 != v)
defp matches?(:empty, _), do: &empty?(&1)
defp matches?(:not_empty, _), do: &(!empty?(&1))
defp matches?(:<=, v), do: &(&1 <= v)
defp matches?(:<, v), do: &(&1 < v)
defp matches?(:>, v), do: &(&1 > v)
defp matches?(:>=, v), do: &(&1 >= v)
defp matches?(:in, v), do: &(&1 in v)
defp matches?(:not_in, v), do: &(&1 not in v)
defp matches?(:contains, v), do: &(v in &1)
defp matches?(:not_contains, v), do: &(v not in &1)
defp matches?(:like, v), do: &(&1 =~ v)
defp matches?(:not_like, v), do: &(&1 =~ v == false)
defp matches?(:=~, v), do: matches?(:ilike, v)

defp matches?(:ilike, v) do
defp matches?(:==, v, _), do: &(&1 == v)
defp matches?(:!=, v, _), do: &(&1 != v)
defp matches?(:empty, _, _), do: &empty?(&1)
defp matches?(:not_empty, _, _), do: &(!empty?(&1))
defp matches?(:<=, v, _), do: &(&1 <= v)
defp matches?(:<, v, _), do: &(&1 < v)
defp matches?(:>, v, _), do: &(&1 > v)
defp matches?(:>=, v, _), do: &(&1 >= v)
defp matches?(:in, v, _), do: &(&1 in v)
defp matches?(:not_in, v, _), do: &(&1 not in v)
defp matches?(:contains, v, _), do: &(v in &1)
defp matches?(:not_contains, v, _), do: &(v not in &1)

defp matches?(:like, v, :sqlite) do
v = String.downcase(v)
&(String.downcase(&1) =~ v)
end

defp matches?(:not_ilike, v) do
defp matches?(:like, v, _), do: &(&1 =~ v)

defp matches?(:not_like, v, :sqlite) do
v = String.downcase(v)
&(String.downcase(&1) =~ v == false)
end

defp matches?(:like_and, v) when is_binary(v) do
values = String.split(v)
&Enum.all?(values, fn v -> &1 =~ v end)
end

defp matches?(:like_and, v), do: &Enum.all?(v, fn v -> &1 =~ v end)
defp matches?(:not_like, v, _), do: &(&1 =~ v == false)
defp matches?(:=~, v, ecto_adapter), do: matches?(:ilike, v, ecto_adapter)

defp matches?(:like_or, v) when is_binary(v) do
values = String.split(v)
&Enum.any?(values, fn v -> &1 =~ v end)
defp matches?(:ilike, v, _) do
v = String.downcase(v)
&(String.downcase(&1) =~ v)
end

defp matches?(:like_or, v), do: &Enum.any?(v, fn v -> &1 =~ v end)
defp matches?(:not_ilike, v, _) do
v = String.downcase(v)
&(String.downcase(&1) =~ v == false)
end

defp matches?(:ilike_and, v) when is_binary(v) do
defp matches?(:like_and, v, :sqlite) when is_binary(v) do
values = v |> String.downcase() |> String.split()
&Enum.all?(values, fn v -> String.downcase(&1) =~ v end)
end

defp matches?(:ilike_and, v) do
values = Enum.map(v, &String.downcase/1)
&Enum.all?(values, fn v -> String.downcase(&1) =~ v end)
defp matches?(:like_and, v, _) when is_binary(v) do
values = String.split(v)
&Enum.all?(values, fn v -> &1 =~ v end)
end

defp matches?(:ilike_or, v) when is_binary(v) do
defp matches?(:like_and, v, _), do: &Enum.all?(v, fn v -> &1 =~ v end)

defp matches?(:like_or, v, :sqlite) when is_binary(v) do
values = v |> String.downcase() |> String.split()
&Enum.any?(values, fn v -> String.downcase(&1) =~ v end)
end

defp matches?(:ilike_or, v) do
defp matches?(:like_or, v, _) when is_binary(v) do
values = String.split(v)
&Enum.any?(values, fn v -> &1 =~ v end)
end

defp matches?(:like_or, v, _), do: &Enum.any?(v, fn v -> &1 =~ v end)

defp matches?(:ilike_and, v, _) do
values = Enum.map(v, &String.downcase/1)
&Enum.all?(values, fn v -> String.downcase(&1) =~ v end)
end

defp matches?(:ilike_or, v, _) do
values = Enum.map(v, &String.downcase/1)
&Enum.any?(values, fn v -> String.downcase(&1) =~ v end)
end
Expand Down

0 comments on commit 1f740c5

Please sign in to comment.