blob: 5c90a25424458e9a6ce019d87e2a5a5d0a9df047 [file] [log] [blame]
defmodule ReduceTest do
use CouchTestCase
@moduletag :views
@moduledoc """
Test CouchDB view reduces
This is a port of the reduce.js suite
"""
def summate(n) do
(n + 1) * n / 2
end
@tag :with_db
test "Basic reduce functions", context do
db_name = context[:db_name]
view_url = "/#{db_name}/_design/foo/_view/bar"
num_docs = 500
map = ~s"""
function (doc) {
emit(doc.integer, doc.integer);
emit(doc.integer, doc.integer);
};
"""
reduce = "function (keys, values) { return sum(values); };"
red_doc = %{:views => %{:bar => %{:map => map, :reduce => reduce}}}
assert Couch.put("/#{db_name}/_design/foo", body: red_doc).body["ok"]
docs = make_docs(1..num_docs)
assert Couch.post("/#{db_name}/_bulk_docs", body: %{:docs => docs}, query: %{w: 3}).status_code == 201
rows = Couch.get(view_url).body["rows"]
assert hd(rows)["value"] == 2 * summate(num_docs)
query = %{:startkey => 4, :endkey => 4}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 8
query = %{:startkey => 4, :endkey => 5}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 18
query = %{:startkey => 4, :endkey => 6}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 30
query = %{:group => true, :limit => 3}
rows = Couch.get(view_url, query: query).body["rows"]
assert Enum.at(rows, 0)["value"] == 2
assert Enum.at(rows, 1)["value"] == 4
assert Enum.at(rows, 2)["value"] == 6
half_num_docs = Integer.floor_div(num_docs, 2)
max = Integer.floor_div(num_docs, 30) + 1
for i <- 1..max, i * 30 + 1 < half_num_docs do
i = i * 30 + 1
query = %{:startkey => i, :endkey => num_docs - i}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 2 * (summate(num_docs - i) - summate(i - 1))
end
end
@tag :with_db
test "More complex array key view row testing", context do
db_name = context[:db_name]
view_url = "/#{db_name}/_design/foo/_view/bar"
map = "function (doc) { emit(doc.keys, 1); };"
reduce = "function (keys, values) { return sum(values); };"
red_doc = %{:views => %{bar: %{map: map, reduce: reduce}}}
assert Couch.put("/#{db_name}/_design/foo", body: red_doc).body["ok"]
for i <- 1..5 do
for j <- 0..9 do
docs = [
%{keys: ["a"]},
%{keys: ["a"]},
%{keys: ["a", "b"]},
%{keys: ["a", "b"]},
%{keys: ["a", "b", "c"]},
%{keys: ["a", "b", "d"]},
%{keys: ["a", "c", "d"]},
%{keys: ["d"]},
%{keys: ["d", "a"]},
%{keys: ["d", "b"]},
%{keys: ["d", "c"]}
]
assert Couch.post("/#{db_name}/_bulk_docs", body: %{docs: docs}, query: %{w: 3}).status_code == 201
total_docs = 1 + (i - 1) * 10 * 11 + (j + 1) * 11
assert Couch.get("/#{db_name}").body["doc_count"] == total_docs
end
# test group by exact key match
query = %{group: true}
rows = Couch.get(view_url, query: query).body["rows"]
assert Enum.at(rows, 0) == %{"key" => ["a"], "value" => 20 * i}
assert Enum.at(rows, 0) == %{"key" => ["a"], "value" => 20 * i}
assert Enum.at(rows, 0) == %{"key" => ["a"], "value" => 20 * i}
assert Enum.at(rows, 1) == %{"key" => ["a", "b"], "value" => 20 * i}
assert Enum.at(rows, 2) == %{"key" => ["a", "b", "c"], "value" => 10 * i}
assert Enum.at(rows, 3) == %{"key" => ["a", "b", "d"], "value" => 10 * i}
# test group reduce and limit params provide valid json
query = %{group: true, limit: 2}
rows = Couch.get(view_url, query: query).body["rows"]
assert Enum.at(rows, 0) == %{"key" => ["a"], "value" => 20 * i}
assert length(rows) == 2
# test group by the first element in the key array
query = %{group_level: 2}
rows = Couch.get(view_url, query: query).body["rows"]
assert Enum.at(rows, 0) == %{"key" => ["a"], "value" => 20 * i}
assert Enum.at(rows, 1) == %{"key" => ["a", "b"], "value" => 40 * i}
assert Enum.at(rows, 2) == %{"key" => ["a", "c"], "value" => 10 * i}
assert Enum.at(rows, 3) == %{"key" => ["d"], "value" => 10 * i}
assert Enum.at(rows, 4) == %{"key" => ["d", "a"], "value" => 10 * i}
assert Enum.at(rows, 5) == %{"key" => ["d", "b"], "value" => 10 * i}
assert Enum.at(rows, 6) == %{"key" => ["d", "c"], "value" => 10 * i}
# test endkey with inclusive_end=true
query = %{group_level: 2, endkey: ~s(["d"]), inclusive_end: true}
rows = Couch.get(view_url, query: query).body["rows"]
assert Enum.at(rows, 0) == %{"key" => ["a"], "value" => 20 * i}
assert Enum.at(rows, 1) == %{"key" => ["a", "b"], "value" => 40 * i}
assert Enum.at(rows, 2) == %{"key" => ["a", "c"], "value" => 10 * i}
assert Enum.at(rows, 3) == %{"key" => ["d"], "value" => 10 * i}
assert length(rows) == 4
# test endkey with inclusive_end=false
query = %{group_level: 2, endkey: ~s(["d"]), inclusive_end: false}
rows = Couch.get(view_url, query: query).body["rows"]
assert Enum.at(rows, 0) == %{"key" => ["a"], "value" => 20 * i}
assert Enum.at(rows, 1) == %{"key" => ["a", "b"], "value" => 40 * i}
assert Enum.at(rows, 2) == %{"key" => ["a", "c"], "value" => 10 * i}
assert length(rows) == 3
end
end
@tag :with_db
test "More complex reductions that need to use the combine option", context do
db_name = context[:db_name]
view_url = "/#{db_name}/_design/foo/_view/bar"
map = "function (doc) { emit(doc.val, doc.val); };"
reduce = ~s"""
function (keys, values, rereduce) {
// This computes the standard deviation of the mapped results
var stdDeviation=0.0;
var count=0;
var total=0.0;
var sqrTotal=0.0;
if (!rereduce) {
// This is the reduce phase, we are reducing over emitted values from
// the map functions.
for(var i in values) {
total = total + values[i];
sqrTotal = sqrTotal + (values[i] * values[i]);
}
count = values.length;
} else {
// This is the rereduce phase, we are re-reducing previosuly
// reduced values.
for(var i in values) {
count = count + values[i].count;
total = total + values[i].total;
sqrTotal = sqrTotal + values[i].sqrTotal;
}
}
var variance = (sqrTotal - ((total * total)/count)) / count;
stdDeviation = Math.sqrt(variance);
// the reduce result. It contains enough information to be rereduced
// with other reduce results.
return {"stdDeviation":stdDeviation,"count":count,
"total":total,"sqrTotal":sqrTotal};
}
"""
red_doc = %{:views => %{:bar => %{:map => map, :reduce => reduce}}}
assert Couch.put("/#{db_name}/_design/foo", body: red_doc).body["ok"]
Enum.each(1..10, fn _ ->
docs = for i <- 1..10, do: %{val: i * 10}
assert Couch.post("/#{db_name}/_bulk_docs", body: %{:docs => docs}, query: %{w: 3}).status_code == 201
end)
rows = Couch.get(view_url).body["rows"]
assert_in_delta hd(rows)["value"]["stdDeviation"], 28.722813232690143, 0.0000000001
end
@tag :with_db
test "Reduce pagination", context do
db_name = context[:db_name]
view_url = "/#{db_name}/_design/foo/_view/bar"
ddoc = %{
_id: "_design/foo",
language: "javascript",
views: %{
bar: %{
reduce: "_count",
map: ~s"""
function(doc) {
emit(doc.int, doc._id);
emit(doc.int + 1, doc._id);
emit(doc.int + 2, doc._id);
}
"""
}
}
}
assert Couch.put("/#{db_name}/_design/foo", body: ddoc).body["ok"]
docs = for i <- 0..1122, do: %{_id: Integer.to_string(i), int: i}
assert Couch.post("/#{db_name}/_bulk_docs", body: %{:docs => docs}, query: %{w: 3}).status_code == 201
rand_val = fn -> :rand.uniform(100_000_000) end
# ?group=false tests
query = %{startkey: 400, endkey: 402, foobar: rand_val.()}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 9
query = %{startkey: 402, endkey: 400, foobar: rand_val.(), descending: true}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 9
query = %{startkey: 400, endkey: 402, foobar: rand_val.(), inclusive_end: false}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 6
query = %{startkey: 402, endkey: 400, foobar: rand_val.(), inclusive_end: false, descending: true}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 6
query = %{startkey: 400, endkey: 402, foobar: rand_val.(), endkey_docid: "400"}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 7
query = %{startkey: 400, endkey: 402, foobar: rand_val.(), endkey_docid: "400", inclusive_end: false}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 6
query = %{startkey: 400, endkey: 402, foobar: rand_val.(), endkey_docid: "401"}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 8
query = %{startkey: 400, endkey: 402, foobar: rand_val.(), endkey_docid: "401", inclusive_end: false}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 7
query = %{startkey: 400, endkey: 402, foobar: rand_val.(), endkey_docid: "402"}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 9
query = %{startkey: 400, endkey: 402, foobar: rand_val.(), endkey_docid: "402", inclusive_end: false}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 8
query = %{startkey: 402, endkey: 400, foobar: rand_val.(), endkey_docid: "398", descending: true}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 9
query = %{
startkey: 402,
endkey: 400,
foobar: rand_val.(),
endkey_docid: "398",
descending: true,
inclusive_end: false
}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 8
query = %{startkey: 402, endkey: 400, foobar: rand_val.(), endkey_docid: "399", descending: true}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 8
query = %{
startkey: 402,
endkey: 400,
foobar: rand_val.(),
endkey_docid: "399",
descending: true,
inclusive_end: false
}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 7
query = %{startkey: 402, endkey: 400, foobar: rand_val.(), endkey_docid: "400", descending: true}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 7
query = %{
startkey: 402,
endkey: 400,
foobar: rand_val.(),
endkey_docid: "400",
descending: true,
inclusive_end: false
}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 6
query = %{startkey: 402, endkey: 400, foobar: rand_val.(), startkey_docid: "400", descending: true}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 7
query = %{
startkey: 402,
endkey: 400,
foobar: rand_val.(),
startkey_docid: "401",
descending: true,
inclusive_end: false
}
rows = Couch.get(view_url, query: query).body["rows"]
assert hd(rows)["value"] == 5
# ?group=true tests
query = %{:group => true, startkey: 400, endkey: 402, foobar: rand_val.()}
rows = Couch.get(view_url, query: query).body["rows"]
assert length(rows) == 3
assert Enum.at(rows, 0)["key"] == 400
assert Enum.at(rows, 0)["value"] == 3
assert Enum.at(rows, 1)["key"] == 401
assert Enum.at(rows, 1)["value"] == 3
assert Enum.at(rows, 2)["key"] == 402
assert Enum.at(rows, 2)["value"] == 3
query = %{:group => true, startkey: 402, endkey: 400, foobar: rand_val.(), descending: true}
rows = Couch.get(view_url, query: query).body["rows"]
assert length(rows) == 3
assert Enum.at(rows, 0)["key"] == 402
assert Enum.at(rows, 0)["value"] == 3
assert Enum.at(rows, 1)["key"] == 401
assert Enum.at(rows, 1)["value"] == 3
assert Enum.at(rows, 2)["key"] == 400
assert Enum.at(rows, 2)["value"] == 3
query = %{:group => true, startkey: 400, endkey: 402, foobar: rand_val.(), inclusive_end: false}
rows = Couch.get(view_url, query: query).body["rows"]
assert length(rows) == 2
assert Enum.at(rows, 0)["key"] == 400
assert Enum.at(rows, 0)["value"] == 3
assert Enum.at(rows, 1)["key"] == 401
assert Enum.at(rows, 1)["value"] == 3
query = %{:group => true, startkey: 402, endkey: 400, foobar: rand_val.(), inclusive_end: false, descending: true}
rows = Couch.get(view_url, query: query).body["rows"]
assert length(rows) == 2
assert Enum.at(rows, 0)["key"] == 402
assert Enum.at(rows, 0)["value"] == 3
assert Enum.at(rows, 1)["key"] == 401
assert Enum.at(rows, 1)["value"] == 3
query = %{:group => true, startkey: 400, endkey: 402, foobar: rand_val.(), endkey_docid: "401"}
rows = Couch.get(view_url, query: query).body["rows"]
assert length(rows) == 3
assert Enum.at(rows, 0)["key"] == 400
assert Enum.at(rows, 0)["value"] == 3
assert Enum.at(rows, 1)["key"] == 401
assert Enum.at(rows, 1)["value"] == 3
assert Enum.at(rows, 2)["key"] == 402
assert Enum.at(rows, 2)["value"] == 2
query = %{:group => true, startkey: 400, endkey: 402, foobar: rand_val.(), endkey_docid: "400"}
rows = Couch.get(view_url, query: query).body["rows"]
assert length(rows) == 3
assert Enum.at(rows, 0)["key"] == 400
assert Enum.at(rows, 0)["value"] == 3
assert Enum.at(rows, 1)["key"] == 401
assert Enum.at(rows, 1)["value"] == 3
assert Enum.at(rows, 2)["key"] == 402
assert Enum.at(rows, 2)["value"] == 1
query = %{:group => true, startkey: 402, endkey: 400, foobar: rand_val.(), startkey_docid: "401", descending: true}
rows = Couch.get(view_url, query: query).body["rows"]
assert length(rows) == 3
assert Enum.at(rows, 0)["key"] == 402
assert Enum.at(rows, 0)["value"] == 2
assert Enum.at(rows, 1)["key"] == 401
assert Enum.at(rows, 1)["value"] == 3
assert Enum.at(rows, 2)["key"] == 400
assert Enum.at(rows, 2)["value"] == 3
query = %{:group => true, startkey: 402, endkey: 400, foobar: rand_val.(), startkey_docid: "400", descending: true}
rows = Couch.get(view_url, query: query).body["rows"]
assert length(rows) == 3
assert Enum.at(rows, 0)["key"] == 402
assert Enum.at(rows, 0)["value"] == 1
assert Enum.at(rows, 1)["key"] == 401
assert Enum.at(rows, 1)["value"] == 3
assert Enum.at(rows, 2)["key"] == 400
assert Enum.at(rows, 2)["value"] == 3
query = %{
:group => true,
startkey: 402,
endkey: 400,
foobar: rand_val.(),
startkey_docid: "401",
descending: true,
inclusive_end: false
}
rows = Couch.get(view_url, query: query).body["rows"]
assert length(rows) == 2
assert Enum.at(rows, 0)["key"] == 402
assert Enum.at(rows, 0)["value"] == 2
assert Enum.at(rows, 1)["key"] == 401
assert Enum.at(rows, 1)["value"] == 3
query = %{
:group => true,
startkey: 402,
endkey: 400,
foobar: rand_val.(),
startkey_docid: "400",
descending: true,
inclusive_end: false
}
rows = Couch.get(view_url, query: query).body["rows"]
assert length(rows) == 2
assert Enum.at(rows, 0)["key"] == 402
assert Enum.at(rows, 0)["value"] == 1
assert Enum.at(rows, 1)["key"] == 401
assert Enum.at(rows, 1)["value"] == 3
query = %{
:group => true,
startkey: 402,
endkey: 400,
foobar: rand_val.(),
endkey_docid: "398",
descending: true,
inclusive_end: true
}
rows = Couch.get(view_url, query: query).body["rows"]
assert length(rows) == 3
assert Enum.at(rows, 0)["key"] == 402
assert Enum.at(rows, 0)["value"] == 3
assert Enum.at(rows, 1)["key"] == 401
assert Enum.at(rows, 1)["value"] == 3
assert Enum.at(rows, 2)["key"] == 400
assert Enum.at(rows, 2)["value"] == 3
query = %{
:group => true,
startkey: 402,
endkey: 400,
foobar: rand_val.(),
endkey_docid: "399",
descending: true,
inclusive_end: true
}
rows = Couch.get(view_url, query: query).body["rows"]
assert length(rows) == 3
assert Enum.at(rows, 0)["key"] == 402
assert Enum.at(rows, 0)["value"] == 3
assert Enum.at(rows, 1)["key"] == 401
assert Enum.at(rows, 1)["value"] == 3
assert Enum.at(rows, 2)["key"] == 400
assert Enum.at(rows, 2)["value"] == 2
query = %{
:group => true,
startkey: 402,
endkey: 400,
foobar: rand_val.(),
endkey_docid: "399",
descending: true,
inclusive_end: false
}
rows = Couch.get(view_url, query: query).body["rows"]
assert length(rows) == 3
assert Enum.at(rows, 0)["key"] == 402
assert Enum.at(rows, 0)["value"] == 3
assert Enum.at(rows, 1)["key"] == 401
assert Enum.at(rows, 1)["value"] == 3
assert Enum.at(rows, 2)["key"] == 400
assert Enum.at(rows, 2)["value"] == 1
query = %{
:group => true,
startkey: 402,
endkey: 400,
foobar: rand_val.(),
endkey_docid: "400",
descending: true,
inclusive_end: false
}
rows = Couch.get(view_url, query: query).body["rows"]
assert length(rows) == 2
assert Enum.at(rows, 0)["key"] == 402
assert Enum.at(rows, 0)["value"] == 3
assert Enum.at(rows, 1)["key"] == 401
assert Enum.at(rows, 1)["value"] == 3
end
end