diff --git a/lib/mongo_ecto/normalized_query.ex b/lib/mongo_ecto/normalized_query.ex index 6d20576..32aa9e4 100644 --- a/lib/mongo_ecto/normalized_query.ex +++ b/lib/mongo_ecto/normalized_query.ex @@ -345,6 +345,28 @@ defmodule Mongo.Ecto.NormalizedQuery do defp offset_limit(nil, _params, _pk, _query, _where), do: nil + defp offset_limit( + %Query.QueryExpr{expr: {:^, l, [idx]}}, + params, + pk, + %Query{wheres: wheres} = query, + "limit clause" = where + ) do + where_params_offset = where_params_offset(wheres) + value({:^, l, [where_params_offset + 1]}, params, pk, query, where) + end + + defp offset_limit( + %Query.QueryExpr{expr: {:^, l, [idx]}}, + params, + pk, + %Query{wheres: wheres} = query, + "offset clause" = where + ) do + where_params_offset = where_params_offset(wheres) + value({:^, l, [where_params_offset + 2]}, params, pk, query, where) + end + defp offset_limit(%Query.QueryExpr{expr: expr}, params, pk, query, where), do: value(expr, params, pk, query, where) @@ -466,9 +488,10 @@ defmodule Mongo.Ecto.NormalizedQuery do {field(left, pk, query, place), ["$in": []]} end - defp pair({:in, _, [left, {:^, _, [ix, len]}]}, params, pk, query, place) do + defp pair({:in, _, [left, _]} = expr, params, pk, query, place) do args = - ix..(ix + len - 1) + expr + |> where_params_range() |> Enum.map(&elem(params, &1)) |> Enum.map(&value(&1, params, pk, query, place)) @@ -483,9 +506,10 @@ defmodule Mongo.Ecto.NormalizedQuery do {field(left, pk, query, place), [{binary_op(op), value(right, params, pk, query, place)}]} end - defp pair({:not, _, [{:in, _, [left, {:^, _, [ix, len]}]}]}, params, pk, query, place) do + defp pair({:not, _, [{:in, _, [left, _]}]} = expr, params, pk, query, place) do args = - ix..(ix + len - 1) + expr + |> where_params_range() |> Enum.map(&elem(params, &1)) |> Enum.map(&value(&1, params, pk, query, place)) @@ -545,4 +569,25 @@ defmodule Mongo.Ecto.NormalizedQuery do defp error(place) do raise ArgumentError, "Invalid expression for MongoDB adapter in #{place}" end + + defp where_params_offset(wheres) do + wheres + |> Enum.map(fn %Query.BooleanExpr{expr: expr} -> + _from..to = where_params_range(expr) + to + end) + |> Enum.max(& &1) + end + + defp where_params_range({_, _, [_, {:^, _, [0, 0]}]}), do: 0..0 + defp where_params_range({_, _, [_, {:^, _, [idx, len]}]}), do: idx..(idx + len - 1) + defp where_params_range({_, _, [_, {:^, _, [idx]}]}), do: idx..idx + defp where_params_range({:not, _, [expr]}), do: where_params_range(expr) + + defp where_params_range({_, _, exprs}) do + ranges = Enum.map(exprs, &where_params_range(&1)) |> Enum.filter(& &1) + from.._to = Enum.at(ranges, 0) + _from..to = Enum.at(ranges, -1) + from..to + end end diff --git a/test/mongo_ecto_test.exs b/test/mongo_ecto_test.exs index 28cf4bf..24beaa1 100644 --- a/test/mongo_ecto_test.exs +++ b/test/mongo_ecto_test.exs @@ -91,6 +91,27 @@ defmodule Mongo.EctoTest do assert 10 == TestRepo.one(query) end + test "where in ids + dynamic limit + dynamic offset" do + visits = 3 + exclude_visits = [4, 5] + post1 = TestRepo.insert!(%Post{visits: visits}) + post2 = TestRepo.insert!(%Post{visits: visits}) + post3 = TestRepo.insert!(%Post{visits: visits}) + ids = [post1.id, post2.id, post3.id] + limit = 1 + offset = 2 + + query = + from( + p in Post, + where: p.visits == ^visits and not (p.visits in ^exclude_visits) and p.id in ^ids, + limit: ^limit, + offset: ^offset + ) + + assert TestRepo.all(query) == [post3] + end + # test "partial update in map" do # post = TestRepo.insert!(%Post{meta: %{author: %{name: "michal"}, other: "value"}}) # TestRepo.update_all(Post, set: [meta: change_map("author.name", "michal")])