Skip to content

Commit bd3dbc1

Browse files
committed
[wip] implement $data operator in mango_selector:match
1 parent 2efd2e3 commit bd3dbc1

File tree

3 files changed

+131
-10
lines changed

3 files changed

+131
-10
lines changed

src/mango/src/mango_selector.erl

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
cmp,
2929
verbose = false,
3030
negate = false,
31-
path = []
31+
path = [],
32+
stack = []
3233
}).
3334

3435
-record(failure, {
@@ -426,6 +427,22 @@ match({[]}, _, #ctx{verbose = false}) ->
426427
true;
427428
match({[]}, _, #ctx{verbose = true}) ->
428429
[];
430+
% Resolve $data lookups before evaluating the surrounding operator
431+
match({[{Op, {[{<<"$data">>, Path}]}}]}, Value, #ctx{stack = Stack, verbose = Verbose} = Ctx) ->
432+
case mango_doc:get_field_from_stack(Path, Stack) of
433+
not_found ->
434+
case Verbose of
435+
true -> [#failure{op = data, type = not_found, ctx = Ctx#ctx{path = Path}}];
436+
false -> false
437+
end;
438+
bad_path ->
439+
case Verbose of
440+
true -> [#failure{op = data, type = bad_path, ctx = Ctx#ctx{path = Path}}];
441+
false -> false
442+
end;
443+
Found ->
444+
match({[{Op, Found}]}, Value, Ctx)
445+
end;
429446
% We need to treat an empty array as always true. This will be applied
430447
% for $or, $in, $all, $nin as well.
431448
match({[{<<"$and">>, []}]}, _, #ctx{verbose = false}) ->
@@ -710,28 +727,28 @@ match({[{<<"$", _/binary>> = Op, _}]}, _, _) ->
710727
% We need to traverse value to find field. The call to
711728
% mango_doc:get_field/2 may return either not_found or
712729
% bad_path in which case matching fails.
713-
match({[{Field, Cond}]}, Value, #ctx{verbose = Verb, path = Path} = Ctx) ->
730+
match({[{Field, Cond}]}, Value, #ctx{verbose = Verb, path = Path, stack = Stack} = Ctx) ->
714731
InnerCtx = Ctx#ctx{path = [Field | Path]},
715-
case mango_doc:get_field(Value, Field) of
716-
not_found when Cond == {[{<<"$exists">>, false}]} ->
732+
case mango_doc:get_field_with_stack(Value, Field, Stack) of
733+
{not_found, _} when Cond == {[{<<"$exists">>, false}]} ->
717734
case Verb of
718735
true -> [];
719736
false -> true
720737
end;
721-
not_found ->
738+
{not_found, _} ->
722739
case Verb of
723740
true -> [#failure{op = field, type = not_found, ctx = InnerCtx}];
724741
false -> false
725742
end;
726-
bad_path ->
743+
{bad_path, _} ->
727744
case Verb of
728745
true -> [#failure{op = field, type = bad_path, ctx = InnerCtx}];
729746
false -> false
730747
end;
731-
SubValue when Field == <<"_id">> ->
732-
match(Cond, SubValue, InnerCtx#ctx{cmp = fun mango_json:cmp_raw/2});
733-
SubValue ->
734-
match(Cond, SubValue, InnerCtx)
748+
{SubValue, NewStack} when Field == <<"_id">> ->
749+
match(Cond, SubValue, InnerCtx#ctx{cmp = fun mango_json:cmp_raw/2, stack = NewStack});
750+
{SubValue, NewStack} ->
751+
match(Cond, SubValue, InnerCtx#ctx{stack = NewStack})
735752
end;
736753
match({[_, _ | _] = _Props} = Sel, _Value, _Ctx) ->
737754
error({unnormalized_selector, Sel}).
@@ -1933,6 +1950,68 @@ match_object_test() ->
19331950
?assertEqual(true, match_int(SelShort, Doc4)),
19341951
?assertEqual(false, match_int(SelShort, Doc5)).
19351952

1953+
match_data_test() ->
1954+
SelAbs = normalize({[{<<"a">>, {[{<<"$gt">>, {[{<<"$data">>, <<"b">>}]}}]}}]}),
1955+
?assertEqual(true, match_int(SelAbs, {[{<<"a">>, 2}, {<<"b">>, 1}]})),
1956+
?assertEqual(false, match_int(SelAbs, {[{<<"a">>, 2}, {<<"b">>, 2}]})),
1957+
1958+
?assertEqual(false, match_int(SelAbs, {[{<<"a">>, 2}]})),
1959+
?assertMatch(
1960+
[#failure{op = data, type = not_found, params = [], ctx = #ctx{path = [<<"b">>]}}],
1961+
match_int(SelAbs, {[{<<"a">>, 2}]}, true)
1962+
),
1963+
1964+
?assertEqual(false, match_int(SelAbs, {[{<<"b">>, 2}]})),
1965+
?assertMatch(
1966+
[#failure{op = field, type = not_found, params = [], ctx = #ctx{path = [<<"a">>]}}],
1967+
match_int(SelAbs, {[{<<"b">>, 2}]}, true)
1968+
),
1969+
1970+
SelRel = normalize({[{<<"a.b">>, {[{<<"$gt">>, {[{<<"$data">>, <<".c">>}]}}]}}]}),
1971+
?assertEqual(true, match_int(SelRel, {[{<<"a">>, {[{<<"b">>, 2}, {<<"c">>, 1}]}}]})),
1972+
?assertEqual(false, match_int(SelRel, {[{<<"a">>, {[{<<"b">>, 2}, {<<"c">>, 2}]}}]})),
1973+
1974+
SelRelOutOfBounds = normalize({[{<<"a">>, {[{<<"$gt">>, {[{<<"$data">>, <<"..b">>}]}}]}}]}),
1975+
?assertEqual(false, match_int(SelRelOutOfBounds, {[{<<"a">>, 1}]})),
1976+
1977+
SelRelAllMatch = normalize(
1978+
{[
1979+
{<<"a">>,
1980+
{[
1981+
{<<"$allMatch">>,
1982+
{[
1983+
{<<"b">>, {[{<<"$gt">>, {[{<<"$data">>, <<".c">>}]}}]}}
1984+
]}}
1985+
]}}
1986+
]}
1987+
),
1988+
?assertEqual(
1989+
true,
1990+
match_int(
1991+
SelRelAllMatch,
1992+
{[
1993+
{<<"a">>, [
1994+
{[{<<"b">>, 2}, {<<"c">>, 1}]},
1995+
{[{<<"b">>, 5}, {<<"c">>, 3}]},
1996+
{[{<<"b">>, 7}, {<<"c">>, 6}]}
1997+
]}
1998+
]}
1999+
)
2000+
),
2001+
?assertEqual(
2002+
false,
2003+
match_int(
2004+
SelRelAllMatch,
2005+
{[
2006+
{<<"a">>, [
2007+
{[{<<"b">>, 2}, {<<"c">>, 1}]},
2008+
{[{<<"b">>, 2}, {<<"c">>, 3}]},
2009+
{[{<<"b">>, 7}, {<<"c">>, 6}]}
2010+
]}
2011+
]}
2012+
)
2013+
).
2014+
19362015
match_and_test() ->
19372016
% $and with an empty array matches anything
19382017
SelEmpty = normalize({[{<<"x">>, {[{<<"$and">>, []}]}}]}),

test/elixir/test/config/suite.elixir

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,7 @@
524524
"converting a Mango VDU to JavaScript updates its effects",
525525
"deleting a Mango VDU removes its effects",
526526
"Mango VDU rejects a doc if any existing ddoc fails to match",
527+
"Mango VDU allows comparisons via $data",
527528
],
528529
"SecurityValidationTest": [
529530
"Author presence and user security",

test/elixir/test/validate_doc_update_test.exs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,45 @@ defmodule ValidateDocUpdateTest do
212212
assert resp.status_code == 403
213213
assert resp.body["error"] == "forbidden"
214214
end
215+
216+
@tag :with_db
217+
test "Mango VDU allows comparisons via $data", context do
218+
db = context[:db_name]
219+
220+
resp = Couch.put("/#{db}/_design/mango-test", body: %{
221+
language: "query",
222+
223+
validate_doc_update: %{
224+
"$or" => [
225+
%{ "oldDoc" => :null },
226+
%{ "oldDoc.tags" => %{ "$size" => 0 } },
227+
%{ "newDoc.tags" => %{ "$all" => %{ "$data" => "oldDoc.tags" } } }
228+
]
229+
}
230+
})
231+
assert resp.status_code == 201
232+
233+
resp = Couch.put("/#{db}/doc", body: %{
234+
"tags" => ["a"]
235+
})
236+
assert resp.status_code == 201
237+
rev = resp.body["rev"]
238+
239+
resp = Couch.put("/#{db}/doc", query: %{rev: rev}, body: %{
240+
"tags" => ["a", "b"]
241+
})
242+
assert resp.status_code == 201
243+
rev = resp.body["rev"]
244+
245+
resp = Couch.put("/#{db}/doc", query: %{rev: rev}, body: %{
246+
"tags" => ["b", "a", "c"]
247+
})
248+
assert resp.status_code == 201
249+
rev = resp.body["rev"]
250+
251+
resp = Couch.put("/#{db}/doc", query: %{rev: rev}, body: %{
252+
"tags" => ["b", "c"]
253+
})
254+
assert resp.status_code == 403
255+
end
215256
end

0 commit comments

Comments
 (0)