summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeonardo Pires <leonardo.pires@gmail.com>2020-02-13 07:13:23 -0300
committerJuanjo Rodriguez <jjrodrig@gmail.com>2020-04-14 09:14:25 +0200
commita4f240c82df7c4dbaa26d9081d3ad3bd183350a7 (patch)
treeed77d46ce5793a07969a2bc278634d1dd6547019
parent013f1071a711aba8616902e9d74ba7d3c0299b9a (diff)
downloadcouchdb-a4f240c82df7c4dbaa26d9081d3ad3bd183350a7.tar.gz
Port reduce_false.js and reduce_builtin.js to Elixir (#2541)
Port reduce_false.js and reduce_builtin.js to Elixir
-rw-r--r--test/elixir/README.md4
-rw-r--r--test/elixir/test/reduce_builtin_test.exs282
-rw-r--r--test/elixir/test/reduce_false_test.exs50
3 files changed, 334 insertions, 2 deletions
diff --git a/test/elixir/README.md b/test/elixir/README.md
index 5ba79a8d8..07ad01078 100644
--- a/test/elixir/README.md
+++ b/test/elixir/README.md
@@ -64,8 +64,8 @@ X means done, - means partially
- [X] Port purge.js
- [ ] Port reader_acl.js
- [X] Port recreate_doc.js
- - [ ] Port reduce_builtin.js
- - [ ] Port reduce_false.js
+ - [X] Port reduce_builtin.js
+ - [X] Port reduce_false.js
- [ ] Port reduce_false_temp.js
- [X] Port reduce.js
- [X] Port replication.js
diff --git a/test/elixir/test/reduce_builtin_test.exs b/test/elixir/test/reduce_builtin_test.exs
new file mode 100644
index 000000000..d13ada1b3
--- /dev/null
+++ b/test/elixir/test/reduce_builtin_test.exs
@@ -0,0 +1,282 @@
+defmodule ReduceBuiltinTest do
+ use CouchTestCase
+
+ @moduletag :views
+
+ @moduledoc """
+ Test CouchDB view builtin reduce functions
+ This is a port of the reduce_builtin.js suite
+ """
+
+ def random_ddoc(db_name) do
+ "/#{db_name}/_design/#{:erlang.monotonic_time()}"
+ end
+
+ def summate(n) do
+ (n + 1) * n / 2
+ end
+
+ def sumsqr(n) do
+ 1..n |> Enum.reduce(0, fn i, acc -> acc + i * i end)
+ end
+
+ def check_approx_distinct(expected, estimated) do
+ # see https://en.wikipedia.org/wiki/HyperLogLog
+ err = 1.04 / :math.sqrt(:math.pow(2, 11 - 1))
+ abs(expected - estimated) < expected * err
+ end
+
+ def query_rows(ddoc_url, builtin_fun, query \\ nil) do
+ http_opts = if query, do: [query: query], else: []
+ Couch.get("#{ddoc_url}/_view/builtin#{builtin_fun}", http_opts).body["rows"]
+ end
+
+ def query_value(ddoc_url, builtin_fun, query \\ nil) do
+ hd(query_rows(ddoc_url, builtin_fun, query))["value"]
+ end
+
+ @tag :with_db
+ test "Builtin reduce functions", context do
+ db_name = context[:db_name]
+ num_docs = 500
+
+ docs = make_docs(1..num_docs)
+
+ resp = Couch.post("/#{db_name}/_bulk_docs", body: %{:docs => docs}, query: %{w: 3})
+ assert resp.status_code in [201, 202]
+
+ ddoc_url = random_ddoc(db_name)
+
+ map = ~s"""
+ function (doc) {
+ emit(doc.integer, doc.integer);
+ emit(doc.integer, doc.integer);
+ };
+ """
+
+ design_doc = %{
+ :views => %{
+ :builtin_sum => %{:map => map, :reduce => "_sum"},
+ :builtin_count => %{:map => map, :reduce => "_count"},
+ :builtin_stats => %{:map => map, :reduce => "_stats"},
+ :builtin_approx_count_distinct => %{
+ :map => map,
+ :reduce => "_approx_count_distinct"
+ }
+ }
+ }
+
+ assert Couch.put(ddoc_url, body: design_doc).body["ok"]
+
+ value = ddoc_url |> query_value("_sum")
+ assert value == 2 * summate(num_docs)
+ value = ddoc_url |> query_value("_count")
+ assert value == 1000
+ value = ddoc_url |> query_value("_stats")
+ assert value["sum"] == 2 * summate(num_docs)
+ assert value["count"] == 1000
+ assert value["min"] == 1
+ assert value["max"] == 500
+ assert value["sumsqr"] == 2 * sumsqr(num_docs)
+ value = ddoc_url |> query_value("_approx_count_distinct")
+ assert check_approx_distinct(num_docs, value)
+
+ value = ddoc_url |> query_value("_sum", %{startkey: 4, endkey: 4})
+ assert value == 8
+ value = ddoc_url |> query_value("_count", %{startkey: 4, endkey: 4})
+ assert value == 2
+ value = ddoc_url |> query_value("_approx_count_distinct", %{startkey: 4, endkey: 4})
+ assert check_approx_distinct(1, value)
+
+ value = ddoc_url |> query_value("_sum", %{startkey: 4, endkey: 5})
+ assert value == 18
+ value = ddoc_url |> query_value("_count", %{startkey: 4, endkey: 5})
+ assert value == 4
+ value = ddoc_url |> query_value("_approx_count_distinct", %{startkey: 4, endkey: 5})
+ assert check_approx_distinct(2, value)
+
+ value = ddoc_url |> query_value("_sum", %{startkey: 4, endkey: 6})
+ assert value == 30
+ value = ddoc_url |> query_value("_count", %{startkey: 4, endkey: 6})
+ assert value == 6
+ value = ddoc_url |> query_value("_approx_count_distinct", %{startkey: 4, endkey: 6})
+ assert check_approx_distinct(3, value)
+
+ assert [row0, row1, row2] = ddoc_url |> query_rows("_sum", %{group: true, limit: 3})
+ assert row0["value"] == 2
+ assert row1["value"] == 4
+ assert row2["value"] == 6
+
+ assert [row0, row1, row2] =
+ ddoc_url |> query_rows("_approx_count_distinct", %{group: true, limit: 3})
+
+ assert check_approx_distinct(1, row0["value"])
+ assert check_approx_distinct(1, row1["value"])
+ assert check_approx_distinct(1, row2["value"])
+
+ 1..div(500, 2)
+ |> Enum.take_every(30)
+ |> Enum.each(fn i ->
+ value = ddoc_url |> query_value("_sum", %{startkey: i, endkey: num_docs - i})
+ assert value == 2 * (summate(num_docs - i) - summate(i - 1))
+ end)
+ end
+
+ @tag :with_db
+ test "Builtin reduce functions with trailings", context do
+ db_name = context[:db_name]
+ num_docs = 500
+
+ docs = make_docs(1..num_docs)
+
+ resp = Couch.post("/#{db_name}/_bulk_docs", body: %{:docs => docs}, query: %{w: 3})
+ assert resp.status_code in [201, 202]
+
+ # test for trailing characters after builtin functions, desired behaviour
+ # is to disregard any trailing characters
+ # I think the behavior should be a prefix test, so that even "_statsorama"
+ # or "_stats\nare\awesome" should work just as "_stats" does. - JChris
+ ["\n", "orama", "\nare\nawesome", " ", " \n "]
+ |> Enum.each(fn trailing ->
+ ddoc_url = random_ddoc(db_name)
+
+ map = ~s"""
+ function (doc) {
+ emit(doc.integer, doc.integer);
+ emit(doc.integer, doc.integer);
+ };
+ """
+
+ design_doc = %{
+ :views => %{
+ :builtin_sum => %{:map => map, :reduce => "_sum#{trailing}"},
+ :builtin_count => %{:map => map, :reduce => "_count#{trailing}"},
+ :builtin_stats => %{:map => map, :reduce => "_stats#{trailing}"},
+ :builtin_approx_count_distinct => %{
+ :map => map,
+ :reduce => "_approx_count_distinct#{trailing}"
+ }
+ }
+ }
+
+ assert Couch.put(ddoc_url, body: design_doc).body["ok"]
+
+ value = ddoc_url |> query_value("_sum")
+ assert value == 2 * summate(num_docs)
+ value = ddoc_url |> query_value("_count")
+ assert value == 1000
+ value = ddoc_url |> query_value("_stats")
+ assert value["sum"] == 2 * summate(num_docs)
+ assert value["count"] == 1000
+ assert value["min"] == 1
+ assert value["max"] == 500
+ assert value["sumsqr"] == 2 * sumsqr(num_docs)
+ end)
+ end
+
+ @tag :with_db
+ test "Builtin count and sum reduce for key as array", context do
+ db_name = context[:db_name]
+
+ ddoc_url = random_ddoc(db_name)
+
+ map_one = ~s"""
+ function (doc) {
+ emit(doc.keys, 1);
+ };
+ """
+
+ map_ones_array = ~s"""
+ function (doc) {
+ emit(doc.keys, [1, 1]);
+ };
+ """
+
+ design_doc = %{
+ :views => %{
+ :builtin_one_sum => %{:map => map_one, :reduce => "_sum"},
+ :builtin_one_count => %{:map => map_one, :reduce => "_count"},
+ :builtin_ones_array_sum => %{:map => map_ones_array, :reduce => "_sum"}
+ }
+ }
+
+ assert Couch.put(ddoc_url, body: design_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"]}
+ ]
+
+ resp = Couch.post("/#{db_name}/_bulk_docs", body: %{docs: docs}, query: %{w: 3})
+ assert resp.status_code in [201, 202]
+
+ total_docs = 1 + (i - 1) * 10 * 11 + (j + 1) * 11
+ assert Couch.get("/#{db_name}").body["doc_count"] == total_docs
+ end
+
+ ["_sum", "_count"]
+ |> Enum.each(fn builtin ->
+ builtin = "_one#{builtin}"
+
+ # group by exact key match
+ rows = query_rows(ddoc_url, builtin, %{group: true})
+ 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}
+
+ # make sure group reduce and limit params provide valid json
+ assert [row0, _] = query_rows(ddoc_url, builtin, %{group: true, limit: 2})
+ assert row0 == %{"key" => ["a"], "value" => 20 * i}
+
+ # group by the first element in the key array
+ rows = query_rows(ddoc_url, builtin, %{group_level: 1})
+ assert Enum.at(rows, 0) == %{"key" => ["a"], "value" => 70 * i}
+ assert Enum.at(rows, 1) == %{"key" => ["d"], "value" => 40 * i}
+
+ # group by the first 2 elements in the key array
+ rows = query_rows(ddoc_url, builtin, %{group_level: 2})
+ 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}
+ end)
+
+ rows = query_rows(ddoc_url, "_ones_array_sum", %{group: true})
+ assert Enum.at(rows, 0) == %{"key" => ["a"], "value" => [20 * i, 20 * i]}
+ assert Enum.at(rows, 1) == %{"key" => ["a", "b"], "value" => [20 * i, 20 * i]}
+ assert Enum.at(rows, 2) == %{"key" => ["a", "b", "c"], "value" => [10 * i, 10 * i]}
+ assert Enum.at(rows, 3) == %{"key" => ["a", "b", "d"], "value" => [10 * i, 10 * i]}
+
+ assert [row0, _] = query_rows(ddoc_url, "_ones_array_sum", %{group: true, limit: 2})
+ assert row0 == %{"key" => ["a"], "value" => [20 * i, 20 * i]}
+
+ rows = query_rows(ddoc_url, "_ones_array_sum", %{group_level: 1})
+ assert Enum.at(rows, 0) == %{"key" => ["a"], "value" => [70 * i, 70 * i]}
+ assert Enum.at(rows, 1) == %{"key" => ["d"], "value" => [40 * i, 40 * i]}
+
+ rows = query_rows(ddoc_url, "_ones_array_sum", %{group_level: 2})
+ assert Enum.at(rows, 0) == %{"key" => ["a"], "value" => [20 * i, 20 * i]}
+ assert Enum.at(rows, 1) == %{"key" => ["a", "b"], "value" => [40 * i, 40 * i]}
+ assert Enum.at(rows, 2) == %{"key" => ["a", "c"], "value" => [10 * i, 10 * i]}
+ assert Enum.at(rows, 3) == %{"key" => ["d"], "value" => [10 * i, 10 * i]}
+ assert Enum.at(rows, 4) == %{"key" => ["d", "a"], "value" => [10 * i, 10 * i]}
+ assert Enum.at(rows, 5) == %{"key" => ["d", "b"], "value" => [10 * i, 10 * i]}
+ assert Enum.at(rows, 6) == %{"key" => ["d", "c"], "value" => [10 * i, 10 * i]}
+ end
+ end
+end
diff --git a/test/elixir/test/reduce_false_test.exs b/test/elixir/test/reduce_false_test.exs
new file mode 100644
index 000000000..675c11dbd
--- /dev/null
+++ b/test/elixir/test/reduce_false_test.exs
@@ -0,0 +1,50 @@
+defmodule ReduceFalseTest do
+ use CouchTestCase
+
+ @moduletag :views
+
+ @moduledoc """
+ Test CouchDB view without reduces
+ This is a port of the reduce_false.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/summate"
+ num_docs = 5
+
+ map = ~s"""
+ function (doc) {
+ emit(doc.integer, doc.integer);
+ };
+ """
+
+ reduce = "function (keys, values) { return sum(values); };"
+ red_doc = %{:views => %{:summate => %{:map => map, :reduce => reduce}}}
+ assert Couch.put("/#{db_name}/_design/foo", body: red_doc).body["ok"]
+
+ docs = make_docs(1..num_docs)
+ resp = Couch.post("/#{db_name}/_bulk_docs", body: %{:docs => docs}, query: %{w: 3})
+ assert resp.status_code in [201, 202]
+
+ # Test that the reduce works
+ rows = Couch.get(view_url).body["rows"]
+ assert length(rows) == 1
+ assert hd(rows)["value"] == summate(num_docs)
+
+ # Test that we got our docs back
+ rows = Couch.get(view_url, query: %{reduce: false}).body["rows"]
+ assert length(rows) == 5
+
+ rows
+ |> Enum.with_index(1)
+ |> Enum.each(fn {row, i} ->
+ assert i == row["value"]
+ end)
+ end
+end