diff options
-rw-r--r-- | Makefile | 12 | ||||
-rw-r--r-- | build-aux/Jenkinsfile.full | 29 | ||||
-rw-r--r-- | build-aux/Jenkinsfile.pr | 4 | ||||
-rw-r--r-- | nouveau/nouveau.yaml | 3 | ||||
-rw-r--r-- | nouveau/src/main/java/org/apache/couchdb/nouveau/api/IndexDefinition.java | 31 | ||||
-rw-r--r-- | nouveau/src/main/java/org/apache/couchdb/nouveau/core/Index.java | 2 | ||||
-rw-r--r-- | nouveau/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java | 10 | ||||
-rw-r--r-- | nouveau/src/main/java/org/apache/couchdb/nouveau/core/UpdatesOutOfOrderException.java | 7 | ||||
-rw-r--r-- | src/nouveau/src/nouveau_fabric_info.erl | 2 | ||||
-rw-r--r-- | src/nouveau/src/nouveau_fabric_search.erl | 4 | ||||
-rw-r--r-- | src/nouveau/src/nouveau_httpd.erl | 2 | ||||
-rw-r--r-- | src/nouveau/src/nouveau_maps.erl | 121 | ||||
-rw-r--r-- | test/elixir/test/config/nouveau.elixir | 4 | ||||
-rw-r--r-- | test/elixir/test/config/test-config.ini | 3 | ||||
-rw-r--r-- | test/elixir/test/nouveau_test.exs | 82 |
15 files changed, 280 insertions, 36 deletions
@@ -294,7 +294,11 @@ elixir-source-checks: elixir-init .PHONY: build-report # target: build-report - Generate a build report build-report: - build-aux/show-test-results.py --suites=10 --tests=10 > test-results.log + build-aux/show-test-results.py --suites=10 --tests=10 > test-results.log || true + cat ./dev/logs/node1.log || true + cat ./dev/logs/nouveau.log || true + cat ./tmp/couch.log || true + cat test-results.log || true .PHONY: check-qs # target: check-qs - Run query server tests (ruby and rspec required!) @@ -474,7 +478,7 @@ clean: @rm -f dev/*.beam dev/devnode.* dev/pbkdf2.pyc log/crash.log @rm -f dev/erlserver.pem dev/couch_ssl_dist.conf ifeq ($(with_nouveau), 1) - @cd nouveau && mvn clean + @cd nouveau && mvn -B clean endif @@ -545,7 +549,7 @@ derived: # Build nouveau nouveau: ifeq ($(with_nouveau), 1) - @cd nouveau && mvn -D maven.test.skip=true + @cd nouveau && mvn -B -D maven.test.skip=true endif .PHONY: nouveau-test @@ -554,7 +558,7 @@ nouveau-test: nouveau-test-maven nouveau-test-elixir .PHONY: nouveau-test-maven nouveau-test-maven: couch nouveau ifeq ($(with_nouveau), 1) - @cd nouveau && mvn test -P allTests + @cd nouveau && mvn -B test -P allTests endif .PHONY: nouveau-test-elixir diff --git a/build-aux/Jenkinsfile.full b/build-aux/Jenkinsfile.full index 50dec78fc..8f0e9e31e 100644 --- a/build-aux/Jenkinsfile.full +++ b/build-aux/Jenkinsfile.full @@ -26,42 +26,49 @@ meta = [ 'centos7': [ name: 'CentOS 7', spidermonkey_vsn: '1.8.5', + enable_nouveau: true, image: "apache/couchdbci-centos:7-erlang-${ERLANG_VERSION}" ], 'centos8': [ name: 'CentOS 8', spidermonkey_vsn: '60', + enable_nouveau: true, image: "apache/couchdbci-centos:8-erlang-${ERLANG_VERSION}" ], 'bionic': [ name: 'Ubuntu 18.04', spidermonkey_vsn: '1.8.5', + enable_nouveau: true, image: "apache/couchdbci-ubuntu:bionic-erlang-${ERLANG_VERSION}" ], 'focal': [ name: 'Ubuntu 20.04', spidermonkey_vsn: '68', + enable_nouveau: true, image: "apache/couchdbci-ubuntu:focal-erlang-${ERLANG_VERSION}" ], 'jammy': [ name: 'Ubuntu 22.04', spidermonkey_vsn: '91', + enable_nouveau: true, image: "apache/couchdbci-ubuntu:jammy-erlang-${ERLANG_VERSION}" ], 'buster': [ name: 'Debian 10', spidermonkey_vsn: '60', + enable_nouveau: true, image: "apache/couchdbci-debian:buster-erlang-${ERLANG_VERSION}" ], 'bullseye-arm64': [ name: 'Debian 11 ARM', spidermonkey_vsn: '78', + enable_nouveau: true, image: "apache/couchdbci-debian:bullseye-erlang-${ERLANG_VERSION}", node_label: 'arm64v8' ], @@ -69,6 +76,7 @@ meta = [ 'bullseye-ppc64': [ name: 'Debian 11 POWER', spidermonkey_vsn: '78', + enable_nouveau: true, image: "apache/couchdbci-debian:bullseye-erlang-${ERLANG_VERSION}", node_label: 'ppc64le' ], @@ -76,6 +84,7 @@ meta = [ 'bullseye-s390x': [ name: 'Debian 11 s390x', spidermonkey_vsn: '78', + enable_nouveau: true, image: "apache/couchdbci-debian:bullseye-erlang-${ERLANG_VERSION}", node_label: 's390x' ], @@ -83,6 +92,7 @@ meta = [ 'bullseye': [ name: 'Debian 11', spidermonkey_vsn: '78', + enable_nouveau: true, image: "apache/couchdbci-debian:bullseye-erlang-${ERLANG_VERSION}" ], @@ -96,10 +106,19 @@ meta = [ 'macos': [ name: 'macOS', spidermonkey_vsn: '91', + enable_nouveau: false, gnu_make: 'make' ] ] +def String configure(config) { + result = "./configure --skip-deps --spidermonkey-version ${config.spidermonkey_vsn}" + if (config.enable_nouveau) { + result += " --enable-nouveau" + } + return result +} + // Credit to https://stackoverflow.com/a/69222555 for this technique. // We can use the scripted pipeline syntax to dynamically generate stages, // and inject them into a map that we pass to the `parallel` step in a script. @@ -130,13 +149,14 @@ def generateNativeStage(platform) { sh( script: "mkdir -p ${platform}/build", label: 'Create build directories' ) sh( script: "tar -xf apache-couchdb-*.tar.gz -C ${platform}/build --strip-components=1", label: 'Unpack release' ) dir( "${platform}/build" ) { - sh "./configure --skip-deps --spidermonkey-version ${meta[platform].spidermonkey_vsn}" + sh "${configure(meta[platform])}" sh '$MAKE' sh '$MAKE eunit' sh '$MAKE elixir-suite' sh '$MAKE exunit' sh '$MAKE mango-test' sh '$MAKE weatherreport-test' + sh '$MAKE nouveau-test' } } } @@ -183,13 +203,14 @@ def generateContainerStage(platform) { sh( script: "mkdir -p ${platform}/build", label: 'Create build directories' ) sh( script: "tar -xf apache-couchdb-*.tar.gz -C ${platform}/build --strip-components=1", label: 'Unpack release' ) dir( "${platform}/build" ) { - sh "./configure --skip-deps --spidermonkey-version ${meta[platform].spidermonkey_vsn}" + sh "${configure(meta[platform])}" sh 'make' sh 'make eunit' sh 'make elixir-suite' sh 'make exunit' sh 'make mango-test' sh 'make weatherreport-test' + sh 'make nouveau-test' } } catch (err) { @@ -256,7 +277,7 @@ pipeline { // We need the jenkins user mapped inside of the image // npm config cache below deals with /home/jenkins not mapping correctly // inside the image - DOCKER_ARGS = '-e npm_config_cache=npm-cache -e HOME=. -v=/etc/passwd:/etc/passwd -v /etc/group:/etc/group' + DOCKER_ARGS = '-e npm_config_cache=npm-cache -e HOME=. -v=/etc/passwd:/etc/passwd -v /etc/group:/etc/group -v /root/.m2:/root/.m2' } options { @@ -280,7 +301,7 @@ pipeline { steps { timeout(time: 15, unit: "MINUTES") { sh (script: 'rm -rf apache-couchdb-*', label: 'Clean workspace of any previous release artifacts' ) - sh "./configure --spidermonkey-version 78" + sh "./configure --spidermonkey-version 78 --enable-nouveau" sh 'make dist' } } diff --git a/build-aux/Jenkinsfile.pr b/build-aux/Jenkinsfile.pr index 4b749e1ac..46c5e47a0 100644 --- a/build-aux/Jenkinsfile.pr +++ b/build-aux/Jenkinsfile.pr @@ -20,7 +20,7 @@ mkdir build cd build tar -xf ${WORKSPACE}/apache-couchdb-*.tar.gz cd apache-couchdb-* -./configure +./configure --enable-nouveau make check || (make build-report && false) ''' @@ -218,7 +218,7 @@ pipeline { steps { sh ''' rm -rf apache-couchdb-* - ./configure --spidermonkey-version 78 + ./configure --spidermonkey-version 78 --enable-nouveau make dist chmod -R a+w * . ''' diff --git a/nouveau/nouveau.yaml b/nouveau/nouveau.yaml index 59176bb7a..9e45bd711 100644 --- a/nouveau/nouveau.yaml +++ b/nouveau/nouveau.yaml @@ -3,6 +3,9 @@ commitIntervalSeconds: 30 idleSeconds: 60 rootDir: target/indexes +logging: + level: WARN + server: applicationConnectors: - type: h2c diff --git a/nouveau/src/main/java/org/apache/couchdb/nouveau/api/IndexDefinition.java b/nouveau/src/main/java/org/apache/couchdb/nouveau/api/IndexDefinition.java index 7d3919c41..3e0f08d64 100644 --- a/nouveau/src/main/java/org/apache/couchdb/nouveau/api/IndexDefinition.java +++ b/nouveau/src/main/java/org/apache/couchdb/nouveau/api/IndexDefinition.java @@ -62,6 +62,37 @@ public class IndexDefinition { } @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((defaultAnalyzer == null) ? 0 : defaultAnalyzer.hashCode()); + result = prime * result + ((fieldAnalyzers == null) ? 0 : fieldAnalyzers.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + IndexDefinition other = (IndexDefinition) obj; + if (defaultAnalyzer == null) { + if (other.defaultAnalyzer != null) + return false; + } else if (!defaultAnalyzer.equals(other.defaultAnalyzer)) + return false; + if (fieldAnalyzers == null) { + if (other.fieldAnalyzers != null) + return false; + } else if (!fieldAnalyzers.equals(other.fieldAnalyzers)) + return false; + return true; + } + + @Override public String toString() { return "IndexDefinition [defaultAnalyzer=" + defaultAnalyzer + ", fieldAnalyzers=" + fieldAnalyzers + "]"; diff --git a/nouveau/src/main/java/org/apache/couchdb/nouveau/core/Index.java b/nouveau/src/main/java/org/apache/couchdb/nouveau/core/Index.java index 7d893a9e2..a08b17816 100644 --- a/nouveau/src/main/java/org/apache/couchdb/nouveau/core/Index.java +++ b/nouveau/src/main/java/org/apache/couchdb/nouveau/core/Index.java @@ -152,7 +152,7 @@ public abstract class Index implements Closeable { protected final void assertUpdateSeqIsLower(final long updateSeq) throws UpdatesOutOfOrderException { assert Thread.holdsLock(this); if (!(updateSeq > this.updateSeq)) { - throw new UpdatesOutOfOrderException(); + throw new UpdatesOutOfOrderException(this.updateSeq, updateSeq); } } diff --git a/nouveau/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java b/nouveau/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java index ddc7c3f7f..f662780bb 100644 --- a/nouveau/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java +++ b/nouveau/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java @@ -125,8 +125,13 @@ public final class IndexManager implements Managed { } } - public void create(final String name, IndexDefinition indexDefinition) throws IOException { + public void create(final String name, IndexDefinition newIndexDefinition) throws IOException { if (exists(name)) { + final IndexDefinition currentIndexDefinition = loadIndexDefinition(name); + if (newIndexDefinition.equals(currentIndexDefinition)) { + // Idempotent success. + return; + } throw new WebApplicationException("Index already exists", Status.EXPECTATION_FAILED); } // Validate index definiton @@ -134,11 +139,12 @@ public final class IndexManager implements Managed { // Persist definition final Path path = indexDefinitionPath(name); + if (Files.exists(path)) { throw new FileAlreadyExistsException(name + " already exists"); } Files.createDirectories(path.getParent()); - objectMapper.writeValue(path.toFile(), indexDefinition); + objectMapper.writeValue(path.toFile(), newIndexDefinition); } public boolean exists(final String name) { diff --git a/nouveau/src/main/java/org/apache/couchdb/nouveau/core/UpdatesOutOfOrderException.java b/nouveau/src/main/java/org/apache/couchdb/nouveau/core/UpdatesOutOfOrderException.java index 3b89f41d2..fe7c1b899 100644 --- a/nouveau/src/main/java/org/apache/couchdb/nouveau/core/UpdatesOutOfOrderException.java +++ b/nouveau/src/main/java/org/apache/couchdb/nouveau/core/UpdatesOutOfOrderException.java @@ -15,10 +15,11 @@ package org.apache.couchdb.nouveau.core; import java.io.IOException; -public class UpdatesOutOfOrderException extends IOException { +public final class UpdatesOutOfOrderException extends IOException { - public UpdatesOutOfOrderException() { - super("Updates applied in the wrong order"); + public UpdatesOutOfOrderException(final long currentSeq, final long attemptedSeq) { + super(String.format("Updates applied in the wrong order (current seq: %d, attempted seq: %d)", + currentSeq, attemptedSeq)); } } diff --git a/src/nouveau/src/nouveau_fabric_info.erl b/src/nouveau/src/nouveau_fabric_info.erl index 59e47094f..b5f928075 100644 --- a/src/nouveau/src/nouveau_fabric_info.erl +++ b/src/nouveau/src/nouveau_fabric_info.erl @@ -70,7 +70,7 @@ handle_message({ok, Info}, Worker, {Counters, Acc0}) -> nil -> C1 = fabric_dict:store(Worker, ok, Counters), C2 = fabric_view:remove_overlapping_shards(Worker, C1), - Acc1 = maps:merge_with(fun merge_info/3, Info, Acc0), + Acc1 = nouveau_maps:merge_with(fun merge_info/3, Info, Acc0), case fabric_dict:any(nil, C2) of true -> {ok, {C2, Acc1}}; diff --git a/src/nouveau/src/nouveau_fabric_search.erl b/src/nouveau/src/nouveau_fabric_search.erl index 4e528cc93..ca101ba9c 100644 --- a/src/nouveau/src/nouveau_fabric_search.erl +++ b/src/nouveau/src/nouveau_fabric_search.erl @@ -217,5 +217,5 @@ merge_facets(FacetsA, null, _Limit) -> merge_facets(null, FacetsB, _Limit) -> FacetsB; merge_facets(FacetsA, FacetsB, _Limit) -> - Combiner = fun(_, V1, V2) -> maps:merge_with(fun(_, V3, V4) -> V3 + V4 end, V1, V2) end, - maps:merge_with(Combiner, FacetsA, FacetsB). + Combiner = fun(_, V1, V2) -> nouveau_maps:merge_with(fun(_, V3, V4) -> V3 + V4 end, V1, V2) end, + nouveau_maps:merge_with(Combiner, FacetsA, FacetsB). diff --git a/src/nouveau/src/nouveau_httpd.erl b/src/nouveau/src/nouveau_httpd.erl index 999acc7ea..8d27048a1 100644 --- a/src/nouveau/src/nouveau_httpd.erl +++ b/src/nouveau/src/nouveau_httpd.erl @@ -200,7 +200,7 @@ validate_query_arg(sort, Sort) -> validate_query_arg(ranges, undefined) -> null; validate_query_arg(ranges, {json, Ranges}) when is_map(Ranges) -> - maps:foreach(fun is_valid_range/2, Ranges), + nouveau_maps:foreach(fun is_valid_range/2, Ranges), Ranges; validate_query_arg(ranges, Ranges) -> validate_query_arg(ranges, {json, ?JSON_DECODE(Ranges, [return_maps])}); diff --git a/src/nouveau/src/nouveau_maps.erl b/src/nouveau/src/nouveau_maps.erl new file mode 100644 index 000000000..81a145557 --- /dev/null +++ b/src/nouveau/src/nouveau_maps.erl @@ -0,0 +1,121 @@ +%% +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2021. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- + +%% backport of OTP 24 map functions not present in 23. + +-module(nouveau_maps). + +-export([merge_with/3, foreach/2]). + +-if(?OTP_RELEASE >= 24). +merge_with(Combiner, Map1, Map2) -> + maps:merge_with(Combiner, Map1, Map2). + +foreach(Fun, MapOrIter) -> + maps:foreach(Fun, MapOrIter). + +-else. + +merge_with(Combiner, Map1, Map2) when + is_map(Map1), + is_map(Map2), + is_function(Combiner, 3) +-> + case map_size(Map1) > map_size(Map2) of + true -> + Iterator = maps:iterator(Map2), + merge_with_1( + maps:next(Iterator), + Map1, + Map2, + Combiner + ); + false -> + Iterator = maps:iterator(Map1), + merge_with_1( + maps:next(Iterator), + Map2, + Map1, + fun(K, V1, V2) -> Combiner(K, V2, V1) end + ) + end; +merge_with(Combiner, Map1, Map2) -> + error_with_info( + error_type_merge_intersect(Map1, Map2, Combiner), + [Combiner, Map1, Map2] + ). + +merge_with_1({K, V2, Iterator}, Map1, Map2, Combiner) -> + case Map1 of + #{K := V1} -> + NewMap1 = Map1#{K := Combiner(K, V1, V2)}, + merge_with_1(maps:next(Iterator), NewMap1, Map2, Combiner); + #{} -> + merge_with_1(maps:next(Iterator), maps:put(K, V2, Map1), Map2, Combiner) + end; +merge_with_1(none, Result, _, _) -> + Result. + +foreach(Fun, MapOrIter) when is_function(Fun, 2) -> + Iter = + if + is_map(MapOrIter) -> maps:iterator(MapOrIter); + true -> MapOrIter + end, + try maps:next(Iter) of + Next -> + foreach_1(Fun, Next) + catch + error:_ -> + error_with_info({badmap, MapOrIter}, [Fun, MapOrIter]) + end; +foreach(Pred, Map) -> + badarg_with_info([Pred, Map]). + +foreach_1(Fun, {K, V, Iter}) -> + Fun(K, V), + foreach_1(Fun, maps:next(Iter)); +foreach_1(_Fun, none) -> + ok. + +%% We must inline these functions so that the stacktrace points to +%% the correct function. +-compile({inline, [badarg_with_info/1, error_with_info/2]}). + +badarg_with_info(Args) -> + erlang:error(badarg, Args, [{error_info, #{module => erl_stdlib_errors}}]). + +error_with_info(Reason, Args) -> + erlang:error(Reason, Args, [{error_info, #{module => erl_stdlib_errors}}]). + +error_type_two_maps(M1, M2) when is_map(M1) -> + {badmap, M2}; +error_type_two_maps(M1, _M2) -> + {badmap, M1}. + +error_type_merge_intersect(M1, M2, Combiner) when is_function(Combiner, 3) -> + error_type_two_maps(M1, M2); +error_type_merge_intersect(_M1, _M2, _Combiner) -> + badarg. + +-endif. diff --git a/test/elixir/test/config/nouveau.elixir b/test/elixir/test/config/nouveau.elixir index 90390a9d6..fdcc66de2 100644 --- a/test/elixir/test/config/nouveau.elixir +++ b/test/elixir/test/config/nouveau.elixir @@ -12,6 +12,8 @@ "sort by numeric field (desc)", "counts", "ranges", - "ranges (open)" + "ranges (open)", + "mango search by number", + "mango search by string" ] } diff --git a/test/elixir/test/config/test-config.ini b/test/elixir/test/config/test-config.ini index fb47c5a4c..423cc492c 100644 --- a/test/elixir/test/config/test-config.ini +++ b/test/elixir/test/config/test-config.ini @@ -1,3 +1,6 @@ +[log] +level = warn + [chttpd] authentication_handlers = {chttpd_auth, jwt_authentication_handler}, {chttpd_auth, proxy_authentication_handler}, {chttpd_auth, cookie_authentication_handler}, {chttpd_auth, default_authentication_handler} diff --git a/test/elixir/test/nouveau_test.exs b/test/elixir/test/nouveau_test.exs index ee5d20542..896ff6154 100644 --- a/test/elixir/test/nouveau_test.exs +++ b/test/elixir/test/nouveau_test.exs @@ -22,6 +22,7 @@ defmodule NouveauTest do def create_ddoc(db_name, opts \\ %{}) do default_ddoc = %{ + autoupdate: false, nouveau: %{ bar: %{ default_analyzer: "standard", @@ -42,22 +43,47 @@ defmodule NouveauTest do assert Map.has_key?(resp.body, "ok") == true end + def create_mango_index(db_name) do + body = %{ + type: "nouveau", + index: %{ + fields: [ + %{name: "foo", type: "string"}, + %{name: "bar", type: "number"} + ] + } + } + + resp = Couch.post("/#{db_name}/_index", body: body) + assert resp.status_code in [200] + end + def get_ids(resp) do %{:body => %{"hits" => hits}} = resp Enum.map(hits, fn hit -> hit["doc"]["_id"] end) end + def get_mango_ids(resp) do + %{:body => %{"docs" => docs}} = resp + Enum.map(docs, fn doc -> doc["_id"] end) + end + def get_bookmark(resp) do %{:body => %{"bookmark" => bookmark}} = resp bookmark end + def assert_status_code(resp, code) do + assert resp.status_code == code, + "status code: #{resp.status_code}, resp body: #{:jiffy.encode(resp.body)}" + end + test "search analyze", context do url = "/_nouveau_analyze" resp = Couch.post(url, headers: ["Content-Type": "application/json"], body: %{analyzer: "standard", text: "hello there"}) - assert resp.status_code == 200, "error #{resp.status_code} #{:jiffy.encode(resp.body)}" + assert_status_code(resp, 200) assert resp.body == %{"tokens" => ["hello", "there"]} end @@ -70,11 +96,11 @@ defmodule NouveauTest do # query it so it builds url = "/#{db_name}/_design/foo/_nouveau/bar" resp = Couch.get(url, query: %{q: "*:*", include_docs: true}) - assert resp.status_code == 200, "error #{resp.status_code} #{:jiffy.encode(resp.body)}" + assert_status_code(resp, 200) url = "/#{db_name}/_design/foo/_nouveau_info/bar" resp = Couch.get(url) - assert resp.status_code == 200, "error #{resp.status_code} #{:jiffy.encode(resp.body)}" + assert_status_code(resp, 200) info = Map.get(resp.body, "search_index") assert Map.get(info, "disk_size") > 0 assert Map.get(info, "num_docs") > 0 @@ -89,7 +115,7 @@ defmodule NouveauTest do url = "/#{db_name}/_design/foo/_nouveau/bar" resp = Couch.get(url, query: %{q: "*:*", include_docs: true}) - assert resp.status_code == 200, "error #{resp.status_code} #{:jiffy.encode(resp.body)}" + assert_status_code(resp, 200) ids = get_ids(resp) # nouveau sorts by _id as tie-breaker assert ids == ["doc1", "doc2", "doc3", "doc4"] @@ -103,7 +129,7 @@ defmodule NouveauTest do url = "/#{db_name}/_design/foo/_nouveau/bar" resp = Couch.post(url, body: %{q: "*:*", include_docs: true}) - assert resp.status_code == 200, "error #{resp.status_code} #{:jiffy.encode(resp.body)}" + assert_status_code(resp, 200) ids = get_ids(resp) assert ids == ["doc1", "doc2", "doc3", "doc4"] end @@ -118,13 +144,13 @@ defmodule NouveauTest do # page 1 resp = Couch.post(url, body: %{q: "*:*", limit: 2, include_docs: true}) - assert resp.status_code == 200, "error #{resp.status_code} #{:jiffy.encode(resp.body)}" + assert_status_code(resp, 200) ids = get_ids(resp) assert ids == ["doc1", "doc2"] # page 2 resp = Couch.post(url, body: %{q: "*:*", limit: 2, bookmark: get_bookmark(resp), include_docs: true}) - assert resp.status_code == 200, "error #{resp.status_code} #{:jiffy.encode(resp.body)}" + assert_status_code(resp, 200) ids = get_ids(resp) assert ids == ["doc3", "doc4"] end @@ -137,7 +163,7 @@ defmodule NouveauTest do url = "/#{db_name}/_design/foo/_nouveau/bar" resp = Couch.post(url, body: %{q: "foo:bar", include_docs: true}) - assert resp.status_code == 200, "error #{resp.status_code} #{:jiffy.encode(resp.body)}" + assert_status_code(resp, 200) ids = get_ids(resp) assert ids == ["doc3"] end @@ -150,7 +176,7 @@ defmodule NouveauTest do url = "/#{db_name}/_design/foo/_nouveau/bar" resp = Couch.post(url, body: %{q: "*:*", sort: "foo<string>", include_docs: true}) - assert resp.status_code == 200, "error #{resp.status_code} #{:jiffy.encode(resp.body)}" + assert_status_code(resp, 200) ids = get_ids(resp) assert ids == ["doc3", "doc1", "doc4", "doc2"] end @@ -163,7 +189,7 @@ defmodule NouveauTest do url = "/#{db_name}/_design/foo/_nouveau/bar" resp = Couch.post(url, body: %{q: "*:*", sort: "-foo<string>", include_docs: true}) - assert resp.status_code == 200, "error #{resp.status_code} #{:jiffy.encode(resp.body)}" + assert_status_code(resp, 200) ids = get_ids(resp) assert ids == ["doc2", "doc4", "doc1", "doc3"] end @@ -176,7 +202,7 @@ defmodule NouveauTest do url = "/#{db_name}/_design/foo/_nouveau/bar" resp = Couch.post(url, body: %{q: "*:*", sort: "bar<double>", include_docs: true}) - assert resp.status_code == 200, "error #{resp.status_code} #{:jiffy.encode(resp.body)}" + assert_status_code(resp, 200) ids = get_ids(resp) assert ids == ["doc1", "doc3", "doc4", "doc2"] end @@ -189,7 +215,7 @@ defmodule NouveauTest do url = "/#{db_name}/_design/foo/_nouveau/bar" resp = Couch.post(url, body: %{q: "*:*", sort: "-bar<double>", include_docs: true}) - assert resp.status_code == 200, "error #{resp.status_code} #{:jiffy.encode(resp.body)}" + assert_status_code(resp, 200) ids = get_ids(resp) assert ids == ["doc2", "doc4", "doc3", "doc1"] end @@ -202,7 +228,7 @@ defmodule NouveauTest do url = "/#{db_name}/_design/foo/_nouveau/bar" resp = Couch.post(url, body: %{q: "*:*", counts: ["foo"], include_docs: true}) - assert resp.status_code == 200, "error #{resp.status_code} #{:jiffy.encode(resp.body)}" + assert_status_code(resp, 200) %{:body => %{"counts" => counts}} = resp assert counts == %{"foo" => %{"bar" => 1, "baz" => 1, "foo" => 1, "foobar" => 1}} end @@ -218,7 +244,7 @@ defmodule NouveauTest do %{label: "cheap", min: 0, max: 42}, %{label: "expensive", min: 42, min_inclusive: false, max: 1000}]}, include_docs: true}) - assert resp.status_code == 200, "error #{resp.status_code} #{:jiffy.encode(resp.body)}" + assert_status_code(resp, 200) %{:body => %{"ranges" => ranges}} = resp assert ranges == %{"bar" => %{"cheap" => 3, "expensive" => 1}} end @@ -234,9 +260,35 @@ defmodule NouveauTest do %{label: "cheap", max: 42}, %{label: "expensive", min: 42, min_inclusive: false}]}, include_docs: true}) - assert resp.status_code == 200, "error #{resp.status_code} #{:jiffy.encode(resp.body)}" + assert_status_code(resp, 200) %{:body => %{"ranges" => ranges}} = resp assert ranges == %{"bar" => %{"cheap" => 3, "expensive" => 1}} end + @tag :with_db + test "mango search by number", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_mango_index(db_name) + + url = "/#{db_name}/_find" + resp = Couch.post(url, body: %{selector: %{bar: %{"$gt": 5}}}) + assert_status_code(resp, 200) + ids = get_mango_ids(resp) + assert ids == ["doc2", "doc3", "doc4"] + end + + @tag :with_db + test "mango search by string", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_mango_index(db_name) + + url = "/#{db_name}/_find" + resp = Couch.post(url, body: %{selector: %{foo: %{"$eq": "foo"}}}) + assert_status_code(resp, 200) + ids = get_mango_ids(resp) + assert ids == ["doc4"] + end + end |