summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTony Sun <tony.sun427@gmail.com>2017-10-25 11:08:45 -0700
committerGitHub <noreply@github.com>2017-10-25 11:08:45 -0700
commit53d33e593d6b64e3ddb9251df5e01efa36f7c8da (patch)
treeb3630d7a57ceddc29156ef055ecef26c9a0926bf
parent54ac3adaabbac689cf344dfc9c53abd137c8ecbb (diff)
parent3ae2e5c94d8c275c50274809cb52eeffadb2b6b6 (diff)
downloadcouchdb-flexible-js-tests.tar.gz
Merge branch 'master' into flexible-js-testsflexible-js-tests
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md11
-rw-r--r--INSTALL.Unix.md5
-rw-r--r--Jenkinsfile83
-rw-r--r--src/couch/src/couch_httpd.erl6
-rw-r--r--src/ddoc_cache/src/ddoc_cache_lru.erl7
-rw-r--r--src/mango/src/mango_cursor.erl2
-rw-r--r--src/mango/src/mango_cursor_view.erl23
-rw-r--r--src/mango/src/mango_error.erl2
-rw-r--r--src/mango/src/mango_idx.erl55
-rw-r--r--src/mango/src/mango_idx_special.erl4
-rw-r--r--src/mango/src/mango_idx_text.erl4
-rw-r--r--src/mango/src/mango_idx_view.erl18
-rw-r--r--src/mango/src/mango_idx_view.hrl13
-rw-r--r--src/mango/src/mango_selector.erl2
-rw-r--r--src/mango/test/01-index-crud-test.py126
-rw-r--r--src/mango/test/02-basic-find-test.py48
-rw-r--r--src/mango/test/03-operator-test.py5
-rw-r--r--src/mango/test/05-index-selection-test.py137
-rw-r--r--src/mango/test/09-text-sort-test.py50
-rw-r--r--src/mango/test/10-disable-array-length-field-test.py14
-rw-r--r--src/mango/test/11-ignore-design-docs-test.py (renamed from src/mango/test/11-ignore-design-docs.py)0
-rw-r--r--src/mango/test/12-use-correct-index-test.py (renamed from src/mango/test/12-use-correct-index.py)17
-rw-r--r--src/mango/test/14-json-pagination-test.py (renamed from src/mango/test/14-json-pagination.py)0
-rw-r--r--src/mango/test/16-index-selectors-test.py (renamed from src/mango/test/16-index-selectors.py)30
-rw-r--r--src/mango/test/17-multi-type-value-test.py90
-rw-r--r--src/mango/test/README.md23
-rw-r--r--src/mango/test/mango.py36
27 files changed, 581 insertions, 230 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index f364446c3..a64a17e7b 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -22,17 +22,10 @@
Does it provides any behaviour that the end users
could notice? -->
-## GitHub issue number
-
-<!-- If this is a significant change, please file a separate issue at:
- https://github.com/apache/couchdb/issues
- and include the number here and in commit message(s) using
- syntax like "Fixes #472" or "Fixes apache/couchdb#472". -->
-
-## Related Pull Requests
+## Related Issues or Pull Requests
<!-- If your changes affects multiple components in different
- repositories please put links to those pull requests here. -->
+ repositories please put links to those issues or pull requests here. -->
## Checklist
diff --git a/INSTALL.Unix.md b/INSTALL.Unix.md
index 3ed2091f0..eb7e72c76 100644
--- a/INSTALL.Unix.md
+++ b/INSTALL.Unix.md
@@ -135,6 +135,11 @@ You can install this by running:
pkg install gmake
+You can install the remaining dependencies by running:
+
+ pkg install npm4 help2man openssl icu curl git \
+ autoconf automake libtool node spidermonkey185
+
## Installing
Once you have satisfied the dependencies you should run:
diff --git a/Jenkinsfile b/Jenkinsfile
index d5212279e..fed976afc 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -182,11 +182,11 @@ pipeline {
deleteDir()
} // node
},
- ubuntu1204erlang183: {
+ ubuntu1404erlangdefault: {
node(label: 'couchdbtest') {
timeout(time: 45, unit: "MINUTES") {
- sh 'docker pull couchdbdev/ubuntu-12.04-erlang-18.3'
- withDockerContainer(image: 'couchdbdev/ubuntu-12.04-erlang-18.3') {
+ sh 'docker pull couchdbdev/ubuntu-14.04-erlang-default'
+ withDockerContainer(image: 'couchdbdev/ubuntu-14.04-erlang-default') {
sh 'rm -f apache-couchdb-*.tar.gz'
unstash 'tarball'
sh '''
@@ -205,11 +205,51 @@ pipeline {
deleteDir()
} // node
},
- ubuntu1404erlangdefault: {
+ ubuntu1404erlang183: {
+ node(label: 'couchdbtest') {
+ timeout(time: 60, unit: "MINUTES") {
+ sh 'docker pull couchdbdev/ubuntu-14.04-erlang-18.3'
+ withDockerContainer(image: 'couchdbdev/ubuntu-14.04-erlang-18.3') {
+ sh 'rm -f apache-couchdb-*.tar.gz'
+ unstash 'tarball'
+ sh '''
+ cwd=$(pwd)
+ mkdir -p $COUCHDB_IO_LOG_DIR
+
+ # Build CouchDB from tarball
+ builddir=$(mktemp -d)
+ cd $builddir
+ tar -xf $cwd/apache-couchdb-*.tar.gz
+ cd apache-couchdb-*
+ ./configure --with-curl
+ make all
+ make check || (build-aux/logfile-uploader.py && false)
+
+ # Build CouchDB packages
+ cd $builddir
+ git clone https://github.com/apache/couchdb-pkg
+ mkdir couchdb
+ cp $cwd/apache-couchdb-*.tar.gz couchdb
+ tar -xf $cwd/apache-couchdb-*.tar.gz -C couchdb
+ cd couchdb-pkg
+ platform=$(lsb_release -cs)
+ make $platform PLATFORM=$platform
+
+ # Cleanup & save for posterity
+ rm -rf $cwd/pkgs/$platform && mkdir -p $cwd/pkgs/$platform
+ mv ../couchdb/*deb $cwd/pkgs/$platform || true
+ '''
+ } // withDocker
+ } // timeout
+ archiveArtifacts artifacts: 'pkgs/**', fingerprint: true
+ deleteDir()
+ } // node
+ },
+ ubuntu1604erlangdefault: {
node(label: 'couchdbtest') {
timeout(time: 45, unit: "MINUTES") {
- sh 'docker pull couchdbdev/ubuntu-14.04-erlang-default'
- withDockerContainer(image: 'couchdbdev/ubuntu-14.04-erlang-default') {
+ sh 'docker pull couchdbdev/ubuntu-16.04-erlang-default'
+ withDockerContainer(image: 'couchdbdev/ubuntu-16.04-erlang-default') {
sh 'rm -f apache-couchdb-*.tar.gz'
unstash 'tarball'
sh '''
@@ -228,11 +268,11 @@ pipeline {
deleteDir()
} // node
},
- ubuntu1404erlang183: {
+ ubuntu1604erlang183: {
node(label: 'couchdbtest') {
timeout(time: 60, unit: "MINUTES") {
- sh 'docker pull couchdbdev/ubuntu-14.04-erlang-18.3'
- withDockerContainer(image: 'couchdbdev/ubuntu-14.04-erlang-18.3') {
+ sh 'docker pull couchdbdev/ubuntu-16.04-erlang-18.3'
+ withDockerContainer(image: 'couchdbdev/ubuntu-16.04-erlang-18.3') {
sh 'rm -f apache-couchdb-*.tar.gz'
unstash 'tarball'
sh '''
@@ -268,11 +308,11 @@ pipeline {
deleteDir()
} // node
},
- ubuntu1604erlangdefault: {
+ debian8erlangdefault: {
node(label: 'couchdbtest') {
timeout(time: 45, unit: "MINUTES") {
- sh 'docker pull couchdbdev/ubuntu-16.04-erlang-default'
- withDockerContainer(image: 'couchdbdev/ubuntu-16.04-erlang-default') {
+ sh 'docker pull couchdbdev/debian-8-erlang-default'
+ withDockerContainer(image: 'couchdbdev/debian-8-erlang-default') {
sh 'rm -f apache-couchdb-*.tar.gz'
unstash 'tarball'
sh '''
@@ -291,11 +331,11 @@ pipeline {
deleteDir()
} // node
},
- ubuntu1604erlang183: {
+ debian8erlang183: {
node(label: 'couchdbtest') {
timeout(time: 60, unit: "MINUTES") {
- sh 'docker pull couchdbdev/ubuntu-16.04-erlang-18.3'
- withDockerContainer(image: 'couchdbdev/ubuntu-16.04-erlang-18.3') {
+ sh 'docker pull couchdbdev/debian-8-erlang-18.3'
+ withDockerContainer(image: 'couchdbdev/debian-8-erlang-18.3') {
sh 'rm -f apache-couchdb-*.tar.gz'
unstash 'tarball'
sh '''
@@ -331,11 +371,11 @@ pipeline {
deleteDir()
} // node
},
- debian8erlangdefault: {
+ debian9erlangdefault: {
node(label: 'couchdbtest') {
timeout(time: 45, unit: "MINUTES") {
- sh 'docker pull couchdbdev/debian-8-erlang-default'
- withDockerContainer(image: 'couchdbdev/debian-8-erlang-default') {
+ sh 'docker pull couchdbdev/debian-9-erlang-default'
+ withDockerContainer(image: 'couchdbdev/debian-9-erlang-default') {
sh 'rm -f apache-couchdb-*.tar.gz'
unstash 'tarball'
sh '''
@@ -354,11 +394,11 @@ pipeline {
deleteDir()
} // node
},
- debian8erlang183: {
+ debian9erlang183: {
node(label: 'couchdbtest') {
timeout(time: 60, unit: "MINUTES") {
- sh 'docker pull couchdbdev/debian-8-erlang-18.3'
- withDockerContainer(image: 'couchdbdev/debian-8-erlang-18.3') {
+ sh 'docker pull couchdbdev/debian-9-erlang-18.3'
+ withDockerContainer(image: 'couchdbdev/debian-9-erlang-18.3') {
sh 'rm -f apache-couchdb-*.tar.gz'
unstash 'tarball'
sh '''
@@ -429,6 +469,7 @@ pipeline {
reprepro -b couchdb-pkg/repo includedeb jessie pkgs/jessie/*deb
reprepro -b couchdb-pkg/repo includedeb trusty pkgs/trusty/*deb
reprepro -b couchdb-pkg/repo includedeb xenial pkgs/xenial/*deb
+ reprepro -b couchdb-pkg/repo includedeb stretch pkgs/stretch/*deb
'''
echo 'Building CentOS repos...'
sh '''
diff --git a/src/couch/src/couch_httpd.erl b/src/couch/src/couch_httpd.erl
index b3bbd5baa..1694ac87f 100644
--- a/src/couch/src/couch_httpd.erl
+++ b/src/couch/src/couch_httpd.erl
@@ -536,7 +536,7 @@ host_for_request(#httpd{mochi_req=MochiReq}) ->
Value -> Value
end.
-absolute_uri(#httpd{mochi_req=MochiReq}=Req, Path) ->
+absolute_uri(#httpd{mochi_req=MochiReq}=Req, [$/ | _] = Path) ->
Host = host_for_request(Req),
XSsl = config:get("httpd", "x_forwarded_ssl", "X-Forwarded-Ssl"),
Scheme = case MochiReq:get_header_value(XSsl) of
@@ -552,7 +552,9 @@ absolute_uri(#httpd{mochi_req=MochiReq}=Req, Path) ->
end
end
end,
- Scheme ++ "://" ++ Host ++ Path.
+ Scheme ++ "://" ++ Host ++ Path;
+absolute_uri(_Req, _Path) ->
+ throw({bad_request, "path must begin with a /."}).
unquote(UrlEncodedString) ->
mochiweb_util:unquote(UrlEncodedString).
diff --git a/src/ddoc_cache/src/ddoc_cache_lru.erl b/src/ddoc_cache/src/ddoc_cache_lru.erl
index 810cbef63..e94934d04 100644
--- a/src/ddoc_cache/src/ddoc_cache_lru.erl
+++ b/src/ddoc_cache/src/ddoc_cache_lru.erl
@@ -39,6 +39,9 @@
-include("ddoc_cache.hrl").
+-define(OPENER, ddoc_cache_opener).
+
+
-record(st, {
pids, % pid -> key
dbs, % dbname -> docid -> key -> pid
@@ -143,11 +146,11 @@ handle_call(Msg, _From, St) ->
handle_cast({evict, DbName}, St) ->
- gen_server:abcast(mem3:nodes(), ?MODULE, {do_evict, DbName}),
+ gen_server:abcast(mem3:nodes(), ?OPENER, {do_evict, DbName}),
{noreply, St};
handle_cast({refresh, DbName, DDocIds}, St) ->
- gen_server:abcast(mem3:nodes(), ?MODULE, {do_refresh, DbName, DDocIds}),
+ gen_server:abcast(mem3:nodes(), ?OPENER, {do_evict, DbName, DDocIds}),
{noreply, St};
handle_cast({do_evict, DbName}, St) ->
diff --git a/src/mango/src/mango_cursor.erl b/src/mango/src/mango_cursor.erl
index e0792b737..98b2d52bd 100644
--- a/src/mango/src/mango_cursor.erl
+++ b/src/mango/src/mango_cursor.erl
@@ -46,7 +46,7 @@
create(Db, Selector0, Opts) ->
Selector = mango_selector:normalize(Selector0),
- UsableIndexes = mango_idx:get_usable_indexes(Db, Selector0, Opts),
+ UsableIndexes = mango_idx:get_usable_indexes(Db, Selector, Opts),
{use_index, IndexSpecified} = proplists:lookup(use_index, Opts),
case {length(UsableIndexes), length(IndexSpecified)} of
diff --git a/src/mango/src/mango_cursor_view.erl b/src/mango/src/mango_cursor_view.erl
index 59dd52226..3fcec07be 100644
--- a/src/mango/src/mango_cursor_view.erl
+++ b/src/mango/src/mango_cursor_view.erl
@@ -29,7 +29,7 @@
-include_lib("couch/include/couch_db.hrl").
-include_lib("couch_mrview/include/couch_mrview.hrl").
-include("mango_cursor.hrl").
-
+-include("mango_idx_view.hrl").
create(Db, Indexes, Selector, Opts) ->
FieldRanges = mango_idx_view:field_ranges(Selector),
@@ -61,18 +61,37 @@ explain(Cursor) ->
BaseArgs = base_args(Cursor),
Args = apply_opts(Opts, BaseArgs),
+
[{mrargs, {[
{include_docs, Args#mrargs.include_docs},
{view_type, Args#mrargs.view_type},
{reduce, Args#mrargs.reduce},
{start_key, Args#mrargs.start_key},
- {end_key, Args#mrargs.end_key},
+ {end_key, maybe_replace_max_json(Args#mrargs.end_key)},
{direction, Args#mrargs.direction},
{stable, Args#mrargs.stable},
{update, Args#mrargs.update}
]}}].
+% replace internal values that cannot
+% be represented as a valid UTF-8 string
+% with a token for JSON serialization
+maybe_replace_max_json([]) ->
+ [];
+
+maybe_replace_max_json(?MAX_STR) ->
+ <<"<MAX>">>;
+
+maybe_replace_max_json([H | T] = EndKey) when is_list(EndKey) ->
+ H1 = if H == ?MAX_JSON_OBJ -> <<"<MAX>">>;
+ true -> H
+ end,
+ [H1 | maybe_replace_max_json(T)];
+
+maybe_replace_max_json(EndKey) ->
+ EndKey.
+
base_args(#cursor{index = Idx} = Cursor) ->
#mrargs{
view_type = map,
diff --git a/src/mango/src/mango_error.erl b/src/mango/src/mango_error.erl
index 4f8ae204d..4c55ef3f6 100644
--- a/src/mango/src/mango_error.erl
+++ b/src/mango/src/mango_error.erl
@@ -43,7 +43,7 @@ info(mango_cursor, {no_usable_index, selector_unsupported}) ->
{
400,
<<"no_usable_index">>,
- <<"There is no index available for this selector.">>
+ <<"The index specified with \"use_index\" is not usable for the query.">>
};
info(mango_json_bookmark, {invalid_bookmark, BadBookmark}) ->
diff --git a/src/mango/src/mango_idx.erl b/src/mango/src/mango_idx.erl
index c5f870d5b..4dd2e180d 100644
--- a/src/mango/src/mango_idx.erl
+++ b/src/mango/src/mango_idx.erl
@@ -20,7 +20,6 @@
-export([
list/1,
recover/1,
- for_sort/2,
new/2,
validate_new/2,
@@ -36,7 +35,7 @@
def/1,
opts/1,
columns/1,
- is_usable/2,
+ is_usable/3,
start_key/2,
end_key/2,
cursor_mod/1,
@@ -57,9 +56,8 @@ list(Db) ->
{ok, Indexes} = ddoc_cache:open(db_to_name(Db), ?MODULE),
Indexes.
-get_usable_indexes(Db, Selector0, Opts) ->
- Selector = mango_selector:normalize(Selector0),
+get_usable_indexes(Db, Selector, Opts) ->
ExistingIndexes = mango_idx:list(Db),
if ExistingIndexes /= [] -> ok; true ->
?MANGO_ERROR({no_usable_index, no_indexes_defined})
@@ -70,13 +68,17 @@ get_usable_indexes(Db, Selector0, Opts) ->
?MANGO_ERROR({no_usable_index, no_index_matching_name})
end,
- SortIndexes = mango_idx:for_sort(FilteredIndexes, Opts),
- if SortIndexes /= [] -> ok; true ->
- ?MANGO_ERROR({no_usable_index, missing_sort_index})
- end,
+ SortFields = get_sort_fields(Opts),
+ UsableFilter = fun(I) -> is_usable(I, Selector, SortFields) end,
+ UsableIndexes0 = lists:filter(UsableFilter, FilteredIndexes),
+
+ case maybe_filter_by_sort_fields(UsableIndexes0, SortFields) of
+ {ok, SortIndexes} ->
+ SortIndexes;
+ {error, no_usable_index} ->
+ ?MANGO_ERROR({no_usable_index, missing_sort_index})
+ end.
- UsableFilter = fun(I) -> mango_idx:is_usable(I, Selector) end,
- lists:filter(UsableFilter, SortIndexes).
recover(Db) ->
{ok, DDocs0} = mango_util:open_ddocs(Db),
@@ -93,33 +95,38 @@ recover(Db) ->
end, DDocs)}.
-for_sort(Indexes, Opts) ->
- % If a sort was specified we have to find an index that
- % can satisfy the request.
+get_sort_fields(Opts) ->
case lists:keyfind(sort, 1, Opts) of
- {sort, {SProps}} when is_list(SProps) ->
- for_sort_int(Indexes, {SProps});
+ {sort, Sort} ->
+ mango_sort:fields(Sort);
_ ->
- Indexes
+ []
end.
-for_sort_int(Indexes, Sort) ->
- Fields = mango_sort:fields(Sort),
+maybe_filter_by_sort_fields(Indexes, []) ->
+ {ok, Indexes};
+
+maybe_filter_by_sort_fields(Indexes, SortFields) ->
FilterFun = fun(Idx) ->
Cols = mango_idx:columns(Idx),
case {mango_idx:type(Idx), Cols} of
{_, all_fields} ->
true;
{<<"text">>, _} ->
- sets:is_subset(sets:from_list(Fields), sets:from_list(Cols));
+ sets:is_subset(sets:from_list(SortFields), sets:from_list(Cols));
{<<"json">>, _} ->
- lists:prefix(Fields, Cols);
+ lists:prefix(SortFields, Cols);
{<<"special">>, _} ->
- lists:prefix(Fields, Cols)
+ lists:prefix(SortFields, Cols)
end
end,
- lists:filter(FilterFun, Indexes).
+ case lists:filter(FilterFun, Indexes) of
+ [] ->
+ {error, no_usable_index};
+ FilteredIndexes ->
+ {ok, FilteredIndexes}
+ end.
new(Db, Opts) ->
@@ -250,9 +257,9 @@ columns(#idx{}=Idx) ->
Mod:columns(Idx).
-is_usable(#idx{}=Idx, Selector) ->
+is_usable(#idx{}=Idx, Selector, SortFields) ->
Mod = idx_mod(Idx),
- Mod:is_usable(Idx, Selector).
+ Mod:is_usable(Idx, Selector, SortFields).
start_key(#idx{}=Idx, Ranges) ->
diff --git a/src/mango/src/mango_idx_special.erl b/src/mango/src/mango_idx_special.erl
index a8f94002b..12da1cbe5 100644
--- a/src/mango/src/mango_idx_special.erl
+++ b/src/mango/src/mango_idx_special.erl
@@ -20,7 +20,7 @@
from_ddoc/1,
to_json/1,
columns/1,
- is_usable/2,
+ is_usable/3,
start_key/1,
end_key/1
]).
@@ -63,7 +63,7 @@ columns(#idx{def=all_docs}) ->
[<<"_id">>].
-is_usable(#idx{def=all_docs}, Selector) ->
+is_usable(#idx{def=all_docs}, Selector, _) ->
Fields = mango_idx_view:indexable_fields(Selector),
lists:member(<<"_id">>, Fields).
diff --git a/src/mango/src/mango_idx_text.erl b/src/mango/src/mango_idx_text.erl
index e00c241d2..e4ffc91db 100644
--- a/src/mango/src/mango_idx_text.erl
+++ b/src/mango/src/mango_idx_text.erl
@@ -22,7 +22,7 @@
from_ddoc/1,
to_json/1,
columns/1,
- is_usable/2,
+ is_usable/3,
get_default_field_options/1
]).
@@ -125,7 +125,7 @@ columns(Idx) ->
end.
-is_usable(Idx, Selector) ->
+is_usable(Idx, Selector, _) ->
case columns(Idx) of
all_fields ->
true;
diff --git a/src/mango/src/mango_idx_view.erl b/src/mango/src/mango_idx_view.erl
index 4cb039c4a..f1041bbaf 100644
--- a/src/mango/src/mango_idx_view.erl
+++ b/src/mango/src/mango_idx_view.erl
@@ -20,7 +20,7 @@
remove/2,
from_ddoc/1,
to_json/1,
- is_usable/2,
+ is_usable/3,
columns/1,
start_key/1,
end_key/1,
@@ -34,6 +34,7 @@
-include_lib("couch/include/couch_db.hrl").
-include("mango.hrl").
-include("mango_idx.hrl").
+-include("mango_idx_view.hrl").
validate_new(#idx{}=Idx, _Db) ->
@@ -113,12 +114,17 @@ columns(Idx) ->
[Key || {Key, _} <- Fields].
-is_usable(Idx, Selector) ->
- % This index is usable if all of the columns are
+is_usable(Idx, Selector, SortFields) ->
+ % This index is usable if all of the columns are
% restricted by the selector such that they are required to exist
% and the selector is not a text search (so requires a text index)
RequiredFields = columns(Idx),
- mango_selector:has_required_fields(Selector, RequiredFields)
+
+ % sort fields are required to exist in the results so
+ % we don't need to check the selector for these
+ RequiredFields1 = ordsets:subtract(lists:usort(RequiredFields), lists:usort(SortFields)),
+
+ mango_selector:has_required_fields(Selector, RequiredFields1)
andalso not is_text_search(Selector).
@@ -158,11 +164,11 @@ start_key([{'$eq', Key, '$eq', Key} | Rest]) ->
end_key([]) ->
- [{[]}];
+ [?MAX_JSON_OBJ];
end_key([{_, _, '$lt', Key} | Rest]) ->
case mango_json:special(Key) of
true ->
- [{[]}];
+ [?MAX_JSON_OBJ];
false ->
[Key | end_key(Rest)]
end;
diff --git a/src/mango/src/mango_idx_view.hrl b/src/mango/src/mango_idx_view.hrl
new file mode 100644
index 000000000..0d213e56e
--- /dev/null
+++ b/src/mango/src/mango_idx_view.hrl
@@ -0,0 +1,13 @@
+% 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.
+
+-define(MAX_JSON_OBJ, {<<255, 255, 255, 255>>}). \ No newline at end of file
diff --git a/src/mango/src/mango_selector.erl b/src/mango/src/mango_selector.erl
index fe3998683..4ff36945a 100644
--- a/src/mango/src/mango_selector.erl
+++ b/src/mango/src/mango_selector.erl
@@ -609,7 +609,7 @@ has_required_fields([{[{Field, Cond}]} | Rest], RequiredFields) ->
_ ->
has_required_fields(Rest, lists:delete(Field, RequiredFields))
end.
-
+
%%%%%%%% module tests below %%%%%%%%
diff --git a/src/mango/test/01-index-crud-test.py b/src/mango/test/01-index-crud-test.py
index 617bfd523..cf5b91865 100644
--- a/src/mango/test/01-index-crud-test.py
+++ b/src/mango/test/01-index-crud-test.py
@@ -36,7 +36,7 @@ class IndexCrudTests(mango.DbPerClass):
try:
self.db.create_index(fields)
except Exception as e:
- assert e.response.status_code == 400
+ self.assertEqual(e.response.status_code, 400)
else:
raise AssertionError("bad create index")
@@ -55,7 +55,7 @@ class IndexCrudTests(mango.DbPerClass):
try:
self.db.create_index(["foo"], idx_type=bt)
except Exception as e:
- assert e.response.status_code == 400, (bt, e.response.status_code)
+ self.assertEqual(e.response.status_code, 400, (bt, e.response.status_code))
else:
raise AssertionError("bad create index")
@@ -71,13 +71,13 @@ class IndexCrudTests(mango.DbPerClass):
try:
self.db.create_index(["foo"], name=bn)
except Exception as e:
- assert e.response.status_code == 400
+ self.assertEqual(e.response.status_code, 400)
else:
raise AssertionError("bad create index")
try:
self.db.create_index(["foo"], ddoc=bn)
except Exception as e:
- assert e.response.status_code == 400
+ self.assertEqual(e.response.status_code, 400)
else:
raise AssertionError("bad create index")
@@ -88,7 +88,7 @@ class IndexCrudTests(mango.DbPerClass):
for idx in self.db.list_indexes():
if idx["name"] != "idx_01":
continue
- assert idx["def"]["fields"] == [{"foo": "asc"}, {"bar": "asc"}]
+ self.assertEqual(idx["def"]["fields"], [{"foo": "asc"}, {"bar": "asc"}])
return
raise AssertionError("index not created")
@@ -106,7 +106,7 @@ class IndexCrudTests(mango.DbPerClass):
for idx in self.db.list_indexes():
if idx["name"] != "idx_02":
continue
- assert idx["def"]["fields"] == [{"baz": "asc"}, {"foo": "asc"}]
+ self.assertEqual(idx["def"]["fields"], [{"baz": "asc"}, {"foo": "asc"}])
return
raise AssertionError("index not created")
@@ -118,9 +118,9 @@ class IndexCrudTests(mango.DbPerClass):
continue
ddocid = idx["ddoc"]
doc = self.db.open_doc(ddocid)
- assert doc["_id"] == ddocid
+ self.assertEqual(doc["_id"], ddocid)
info = self.db.ddoc_info(ddocid)
- assert info["name"] == ddocid.split('_design/')[-1]
+ self.assertEqual(info["name"], ddocid.split('_design/')[-1])
def test_delete_idx_escaped(self):
self.db.create_index(["foo", "bar"], name="idx_01")
@@ -130,10 +130,10 @@ class IndexCrudTests(mango.DbPerClass):
for idx in self.db.list_indexes():
if idx["name"] != "idx_del_1":
continue
- assert idx["def"]["fields"] == [{"bing": "asc"}]
+ self.assertEqual(idx["def"]["fields"], [{"bing": "asc"}])
self.db.delete_index(idx["ddoc"].replace("/", "%2F"), idx["name"])
post_indexes = self.db.list_indexes()
- assert pre_indexes == post_indexes
+ self.assertEqual(pre_indexes, post_indexes)
def test_delete_idx_unescaped(self):
pre_indexes = self.db.list_indexes()
@@ -142,10 +142,10 @@ class IndexCrudTests(mango.DbPerClass):
for idx in self.db.list_indexes():
if idx["name"] != "idx_del_2":
continue
- assert idx["def"]["fields"] == [{"bing": "asc"}]
+ self.assertEqual(idx["def"]["fields"], [{"bing": "asc"}])
self.db.delete_index(idx["ddoc"], idx["name"])
post_indexes = self.db.list_indexes()
- assert pre_indexes == post_indexes
+ self.assertEqual(pre_indexes, post_indexes)
def test_delete_idx_no_design(self):
pre_indexes = self.db.list_indexes()
@@ -154,10 +154,10 @@ class IndexCrudTests(mango.DbPerClass):
for idx in self.db.list_indexes():
if idx["name"] != "idx_del_3":
continue
- assert idx["def"]["fields"] == [{"bing": "asc"}]
+ self.assertEqual(idx["def"]["fields"], [{"bing": "asc"}])
self.db.delete_index(idx["ddoc"].split("/")[-1], idx["name"])
post_indexes = self.db.list_indexes()
- assert pre_indexes == post_indexes
+ self.assertEqual(pre_indexes, post_indexes)
def test_bulk_delete(self):
fields = ["field1"]
@@ -182,8 +182,8 @@ class IndexCrudTests(mango.DbPerClass):
ret = self.db.bulk_delete(docids)
- assert ret["fail"][0]["id"] == "_design/this_is_not_an_index_name"
- assert len(ret["success"]) == 3
+ self.assertEqual(ret["fail"][0]["id"], "_design/this_is_not_an_index_name")
+ self.assertEqual(len(ret["success"]), 3)
for idx in self.db.list_indexes():
assert idx["type"] != "json"
@@ -197,18 +197,18 @@ class IndexCrudTests(mango.DbPerClass):
for idx in self.db.list_indexes():
if idx["name"] != "idx_recreate":
continue
- assert idx["def"]["fields"] == [{"bing": "asc"}]
+ self.assertEqual(idx["def"]["fields"], [{"bing": "asc"}])
self.db.delete_index(idx["ddoc"], idx["name"])
break
post_indexes = self.db.list_indexes()
- assert pre_indexes == post_indexes
+ self.assertEqual(pre_indexes, post_indexes)
- def test_delete_misisng(self):
+ def test_delete_missing(self):
# Missing design doc
try:
self.db.delete_index("this_is_not_a_design_doc_id", "foo")
except Exception as e:
- assert e.response.status_code == 404
+ self.assertEqual(e.response.status_code, 404)
else:
raise AssertionError("bad index delete")
@@ -221,7 +221,7 @@ class IndexCrudTests(mango.DbPerClass):
try:
self.db.delete_index(ddocid, "this_is_not_an_index_name")
except Exception as e:
- assert e.response.status_code == 404
+ self.assertEqual(e.response.status_code, 404)
else:
raise AssertionError("bad index delete")
@@ -229,11 +229,54 @@ class IndexCrudTests(mango.DbPerClass):
try:
self.db.delete_index(ddocid, idx["name"], idx_type="not_a_real_type")
except Exception as e:
- assert e.response.status_code == 404
+ self.assertEqual(e.response.status_code, 404)
else:
raise AssertionError("bad index delete")
- @unittest.skipUnless(mango.has_text_service(), "requires text service")
+ def test_limit_skip_index(self):
+ fields = ["field1"]
+ ret = self.db.create_index(fields, name="idx_01")
+ assert ret is True
+
+ fields = ["field2"]
+ ret = self.db.create_index(fields, name="idx_02")
+ assert ret is True
+
+ fields = ["field3"]
+ ret = self.db.create_index(fields, name="idx_03")
+ assert ret is True
+
+ fields = ["field4"]
+ ret = self.db.create_index(fields, name="idx_04")
+ assert ret is True
+
+ fields = ["field5"]
+ ret = self.db.create_index(fields, name="idx_05")
+ assert ret is True
+
+ self.assertEqual(len(self.db.list_indexes(limit=2)), 2)
+ self.assertEqual(len(self.db.list_indexes(limit=5,skip=4)), 2)
+ self.assertEqual(len(self.db.list_indexes(skip=5)), 1)
+ self.assertEqual(len(self.db.list_indexes(skip=6)), 0)
+ self.assertEqual(len(self.db.list_indexes(skip=100)), 0)
+ self.assertEqual(len(self.db.list_indexes(limit=10000000)), 6)
+
+ try:
+ self.db.list_indexes(skip=-1)
+ except Exception as e:
+ self.assertEqual(e.response.status_code, 500)
+
+ try:
+ self.db.list_indexes(limit=0)
+ except Exception as e:
+ self.assertEqual(e.response.status_code, 500)
+
+
+@unittest.skipUnless(mango.has_text_service(), "requires text service")
+class IndexCrudTextTests(mango.DbPerClass):
+ def setUp(self):
+ self.db.recreate()
+
def test_create_text_idx(self):
fields = [
{"name":"stringidx", "type" : "string"},
@@ -244,14 +287,13 @@ class IndexCrudTests(mango.DbPerClass):
for idx in self.db.list_indexes():
if idx["name"] != "text_idx_01":
continue
- assert idx["def"]["fields"] == [
+ self.assertEqual(idx["def"]["fields"], [
{"stringidx": "string"},
{"booleanidx": "boolean"}
- ]
+ ])
return
raise AssertionError("index not created")
- @unittest.skipUnless(mango.has_text_service(), "requires text service")
def test_create_bad_text_idx(self):
bad_fields = [
True,
@@ -270,10 +312,10 @@ class IndexCrudTests(mango.DbPerClass):
try:
self.db.create_text_index(fields=fields)
except Exception as e:
- assert e.response.status_code == 400
+ self.assertEqual(e.response.status_code, 400)
else:
raise AssertionError("bad create text index")
-
+
def test_limit_skip_index(self):
fields = ["field1"]
ret = self.db.create_index(fields, name="idx_01")
@@ -291,28 +333,26 @@ class IndexCrudTests(mango.DbPerClass):
ret = self.db.create_index(fields, name="idx_04")
assert ret is True
- fields = ["field5"]
- ret = self.db.create_index(fields, name="idx_05")
+ fields = [
+ {"name":"stringidx", "type" : "string"},
+ {"name":"booleanidx", "type": "boolean"}
+ ]
+ ret = self.db.create_text_index(fields=fields, name="idx_05")
assert ret is True
- skip_add = 0
-
- if mango.has_text_service():
- skip_add = 1
-
- assert len(self.db.list_indexes(limit=2)) == 2
- assert len(self.db.list_indexes(limit=5,skip=4)) == 2 + skip_add
- assert len(self.db.list_indexes(skip=5)) == 1 + skip_add
- assert len(self.db.list_indexes(skip=6)) == 0 + skip_add
- assert len(self.db.list_indexes(skip=100)) == 0
- assert len(self.db.list_indexes(limit=10000000)) == 6 + skip_add
+ self.assertEqual(len(self.db.list_indexes(limit=2)), 2)
+ self.assertEqual(len(self.db.list_indexes(limit=5,skip=4)), 2)
+ self.assertEqual(len(self.db.list_indexes(skip=5)), 1)
+ self.assertEqual(len(self.db.list_indexes(skip=6)), 0)
+ self.assertEqual(len(self.db.list_indexes(skip=100)), 0)
+ self.assertEqual(len(self.db.list_indexes(limit=10000000)), 6)
try:
self.db.list_indexes(skip=-1)
except Exception as e:
- assert e.response.status_code == 500
+ self.assertEqual(e.response.status_code, 500)
try:
self.db.list_indexes(limit=0)
except Exception as e:
- assert e.response.status_code == 500
+ self.assertEqual(e.response.status_code, 500)
diff --git a/src/mango/test/02-basic-find-test.py b/src/mango/test/02-basic-find-test.py
index a8725ffa8..82554a112 100644
--- a/src/mango/test/02-basic-find-test.py
+++ b/src/mango/test/02-basic-find-test.py
@@ -222,6 +222,52 @@ class BasicFindTests(mango.UserDocsTests):
docs2 = list(reversed(sorted(docs1, key=lambda d: d["age"])))
assert docs1 is not docs2 and docs1 == docs2
+ def test_sort_desc_complex(self):
+ docs = self.db.find({
+ "company": {"$lt": "M"},
+ "$or": [
+ {"company": "Dreamia"},
+ {"manager": True}
+ ]
+ }, sort=[{"company":"desc"}, {"manager":"desc"}])
+
+ companies_returned = list(d["company"] for d in docs)
+ desc_companies = sorted(companies_returned, reverse=True)
+ self.assertEqual(desc_companies, companies_returned)
+
+ def test_sort_with_primary_sort_not_in_selector(self):
+ try:
+ docs = self.db.find({
+ "name.last": {"$lt": "M"}
+ }, sort=[{"name.first":"desc"}])
+ except Exception as e:
+ self.assertEqual(e.response.status_code, 400)
+ resp = e.response.json()
+ self.assertEqual(resp["error"], "no_usable_index")
+ else:
+ raise AssertionError("expected find error")
+
+ def test_sort_exists_true(self):
+ docs1 = self.db.find({"age": {"$gt": 0, "$exists": True}}, sort=[{"age":"asc"}])
+ docs2 = list(sorted(docs1, key=lambda d: d["age"]))
+ assert docs1 is not docs2 and docs1 == docs2
+
+ def test_sort_desc_complex_error(self):
+ try:
+ self.db.find({
+ "company": {"$lt": "M"},
+ "$or": [
+ {"company": "Dreamia"},
+ {"manager": True}
+ ]
+ }, sort=[{"company":"desc"}])
+ except Exception as e:
+ self.assertEqual(e.response.status_code, 400)
+ resp = e.response.json()
+ self.assertEqual(resp["error"], "no_usable_index")
+ else:
+ raise AssertionError("expected find error")
+
def test_fields(self):
selector = {"age": {"$gt": 0}}
docs = self.db.find(selector, fields=["user_id", "location.address"])
@@ -273,5 +319,5 @@ class BasicFindTests(mango.UserDocsTests):
assert explain["mrargs"]["update"] == True
assert explain["mrargs"]["reduce"] == False
assert explain["mrargs"]["start_key"] == [0]
- assert explain["mrargs"]["end_key"] == [{}]
+ assert explain["mrargs"]["end_key"] == ["<MAX>"]
assert explain["mrargs"]["include_docs"] == True
diff --git a/src/mango/test/03-operator-test.py b/src/mango/test/03-operator-test.py
index 1af39f205..239cc7d41 100644
--- a/src/mango/test/03-operator-test.py
+++ b/src/mango/test/03-operator-test.py
@@ -174,6 +174,9 @@ class OperatorTests:
for d in docs:
self.assertIn("twitter", d)
+ # ideally this work be consistent across index types but, alas, it is not
+ @unittest.skipUnless(not mango.has_text_service(),
+ "text indexes do not support range queries across type boundaries")
def test_lt_includes_null_but_not_missing(self):
docs = self.db.find({
"twitter": {"$lt": 1}
@@ -183,6 +186,8 @@ class OperatorTests:
for d in docs:
self.assertEqual(d["twitter"], None)
+ @unittest.skipUnless(not mango.has_text_service(),
+ "text indexes do not support range queries across type boundaries")
def test_lte_includes_null_but_not_missing(self):
docs = self.db.find({
"twitter": {"$lt": 1}
diff --git a/src/mango/test/05-index-selection-test.py b/src/mango/test/05-index-selection-test.py
index 1cc210382..05571a7e8 100644
--- a/src/mango/test/05-index-selection-test.py
+++ b/src/mango/test/05-index-selection-test.py
@@ -15,12 +15,7 @@ import user_docs
import unittest
-class IndexSelectionTests(mango.UserDocsTests):
- @classmethod
- def setUpClass(klass):
- super(IndexSelectionTests, klass).setUpClass()
- if mango.has_text_service():
- user_docs.add_text_indexes(klass.db, {})
+class IndexSelectionTests:
def test_basic(self):
resp = self.db.find({"age": 123}, explain=True)
@@ -33,30 +28,6 @@ class IndexSelectionTests(mango.UserDocsTests):
}, explain=True)
self.assertEqual(resp["index"]["type"], "json")
- @unittest.skipUnless(mango.has_text_service(), "requires text service")
- def test_with_text(self):
- resp = self.db.find({
- "$text" : "Stephanie",
- "name.first": "Stephanie",
- "name.last": "This doesn't have to match anything."
- }, explain=True)
- self.assertEqual(resp["index"]["type"], "text")
-
- @unittest.skipUnless(mango.has_text_service(), "requires text service")
- def test_no_view_index(self):
- resp = self.db.find({"name.first": "Ohai!"}, explain=True)
- self.assertEqual(resp["index"]["type"], "text")
-
- @unittest.skipUnless(mango.has_text_service(), "requires text service")
- def test_with_or(self):
- resp = self.db.find({
- "$or": [
- {"name.first": "Stephanie"},
- {"name.last": "This doesn't have to match anything."}
- ]
- }, explain=True)
- self.assertEqual(resp["index"]["type"], "text")
-
def test_use_most_columns(self):
# ddoc id for the age index
ddocid = "_design/ad3d537c03cd7c6a43cf8dff66ef70ea54c2b40f"
@@ -92,36 +63,6 @@ class IndexSelectionTests(mango.UserDocsTests):
else:
raise AssertionError("bad find")
- def test_uses_all_docs_when_fields_do_not_match_selector(self):
- # index exists on ["company", "manager"] but not ["company"]
- # so we should fall back to all docs (so we include docs
- # with no "manager" field)
- selector = {
- "company": "Pharmex"
- }
- docs = self.db.find(selector)
- self.assertEqual(len(docs), 1)
- self.assertEqual(docs[0]["company"], "Pharmex")
- self.assertNotIn("manager", docs[0])
-
- resp_explain = self.db.find(selector, explain=True)
- self.assertEqual(resp_explain["index"]["type"], "special")
-
- def test_uses_all_docs_when_selector_doesnt_require_fields_to_exist(self):
- # as in test above, use a selector that doesn't overlap with the index
- # due to an explicit exists clause
- selector = {
- "company": "Pharmex",
- "manager": {"$exists": False}
- }
- docs = self.db.find(selector)
- self.assertEqual(len(docs), 1)
- self.assertEqual(docs[0]["company"], "Pharmex")
- self.assertNotIn("manager", docs[0])
-
- resp_explain = self.db.find(selector, explain=True)
- self.assertEqual(resp_explain["index"]["type"], "special")
-
def test_uses_index_when_no_range_or_equals(self):
# index on ["manager"] should be valid because
# selector requires "manager" to exist. The
@@ -200,7 +141,77 @@ class IndexSelectionTests(mango.UserDocsTests):
with self.assertRaises(KeyError):
self.db.save_doc(design_doc)
- @unittest.skipUnless(mango.has_text_service(), "requires text service")
+
+class JSONIndexSelectionTests(mango.UserDocsTests, IndexSelectionTests):
+
+ @classmethod
+ def setUpClass(klass):
+ super(JSONIndexSelectionTests, klass).setUpClass()
+
+ def test_uses_all_docs_when_fields_do_not_match_selector(self):
+ # index exists on ["company", "manager"] but not ["company"]
+ # so we should fall back to all docs (so we include docs
+ # with no "manager" field)
+ selector = {
+ "company": "Pharmex"
+ }
+ docs = self.db.find(selector)
+ self.assertEqual(len(docs), 1)
+ self.assertEqual(docs[0]["company"], "Pharmex")
+ self.assertNotIn("manager", docs[0])
+
+ resp_explain = self.db.find(selector, explain=True)
+
+ self.assertEqual(resp_explain["index"]["type"], "special")
+
+ def test_uses_all_docs_when_selector_doesnt_require_fields_to_exist(self):
+ # as in test above, use a selector that doesn't overlap with the index
+ # due to an explicit exists clause
+ selector = {
+ "company": "Pharmex",
+ "manager": {"$exists": False}
+ }
+ docs = self.db.find(selector)
+ self.assertEqual(len(docs), 1)
+ self.assertEqual(docs[0]["company"], "Pharmex")
+ self.assertNotIn("manager", docs[0])
+
+ resp_explain = self.db.find(selector, explain=True)
+ self.assertEqual(resp_explain["index"]["type"], "special")
+
+
+@unittest.skipUnless(mango.has_text_service(), "requires text service")
+class TextIndexSelectionTests(mango.UserDocsTests, IndexSelectionTests):
+
+ @classmethod
+ def setUpClass(klass):
+ super(TextIndexSelectionTests, klass).setUpClass()
+
+ def setUp(self):
+ self.db.recreate()
+ user_docs.add_text_indexes(self.db, {})
+
+ def test_with_text(self):
+ resp = self.db.find({
+ "$text" : "Stephanie",
+ "name.first": "Stephanie",
+ "name.last": "This doesn't have to match anything."
+ }, explain=True)
+ self.assertEqual(resp["index"]["type"], "text")
+
+ def test_no_view_index(self):
+ resp = self.db.find({"name.first": "Ohai!"}, explain=True)
+ self.assertEqual(resp["index"]["type"], "text")
+
+ def test_with_or(self):
+ resp = self.db.find({
+ "$or": [
+ {"name.first": "Stephanie"},
+ {"name.last": "This doesn't have to match anything."}
+ ]
+ }, explain=True)
+ self.assertEqual(resp["index"]["type"], "text")
+
def test_manual_bad_text_idx(self):
design_doc = {
"_id": "_design/bad_text_index",
@@ -243,8 +254,8 @@ class MultiTextIndexSelectionTests(mango.UserDocsTests):
klass.db.create_text_index(ddoc="foo", analyzer="keyword")
klass.db.create_text_index(ddoc="bar", analyzer="email")
- def test_view_ok_with_multi_text(self):
- resp = self.db.find({"name.last": "A last name"}, explain=True)
+ def test_fallback_to_json_with_multi_text(self):
+ resp = self.db.find({"name.first": "A first name", "name.last": "A last name"}, explain=True)
self.assertEqual(resp["index"]["type"], "json")
def test_multi_text_index_is_error(self):
diff --git a/src/mango/test/09-text-sort-test.py b/src/mango/test/09-text-sort-test.py
index 1c5557227..a1a644c79 100644
--- a/src/mango/test/09-text-sort-test.py
+++ b/src/mango/test/09-text-sort-test.py
@@ -19,60 +19,60 @@ class SortTests(mango.UserDocsTextTests):
def test_number_sort(self):
q = {"age": {"$gt": 0}}
docs = self.db.find(q, sort=["age:number"])
- assert len(docs) == 15
- assert docs[0]["age"] == 22
+ self.assertEqual(len(docs), 15)
+ self.assertEqual(docs[0]["age"], 22)
def test_number_sort_desc(self):
q = {"age": {"$gt": 0}}
docs = self.db.find(q, sort=[{"age": "desc"}])
- assert len(docs) == 15
- assert docs[0]["age"] == 79
+ self.assertEqual(len(docs), 15)
+ self.assertEqual(docs[0]["age"], 79)
q = {"manager": True}
docs = self.db.find(q, sort=[{"age:number": "desc"}])
- assert len(docs) == 11
- assert docs[0]["age"] == 79
+ self.assertEqual(len(docs), 10)
+ self.assertEqual(docs[0]["age"], 79)
def test_string_sort(self):
q = {"email": {"$gt": None}}
docs = self.db.find(q, sort=["email:string"])
- assert len(docs) == 15
- assert docs[0]["email"] == "abbottwatson@talkola.com"
+ self.assertEqual(len(docs), 15)
+ self.assertEqual(docs[0]["email"], "abbottwatson@talkola.com")
def test_notype_sort(self):
q = {"email": {"$gt": None}}
try:
self.db.find(q, sort=["email"])
except Exception as e:
- assert e.response.status_code == 400
+ self.assertEqual(e.response.status_code, 400)
else:
raise AssertionError("Should have thrown error for sort")
def test_array_sort(self):
q = {"favorites": {"$exists": True}}
docs = self.db.find(q, sort=["favorites.[]:string"])
- assert len(docs) == 15
- assert docs[0]["user_id"] == 8
+ self.assertEqual(len(docs), 15)
+ self.assertEqual(docs[0]["user_id"], 8)
def test_multi_sort(self):
q = {"name": {"$exists": True}}
docs = self.db.find(q, sort=["name.last:string", "age:number"])
- assert len(docs) == 15
- assert docs[0]["name"] == {"last":"Ewing","first":"Shelly"}
- assert docs[1]["age"] == 22
+ self.assertEqual(len(docs), 15)
+ self.assertEqual(docs[0]["name"], {"last":"Ewing","first":"Shelly"})
+ self.assertEqual(docs[1]["age"], 22)
def test_guess_type_sort(self):
q = {"$or": [{"age":{"$gt": 0}}, {"email": {"$gt": None}}]}
docs = self.db.find(q, sort=["age"])
- assert len(docs) == 15
- assert docs[0]["age"] == 22
+ self.assertEqual(len(docs), 15)
+ self.assertEqual(docs[0]["age"], 22)
def test_guess_dup_type_sort(self):
q = {"$and": [{"age":{"$gt": 0}}, {"email": {"$gt": None}},
{"age":{"$lte": 100}}]}
docs = self.db.find(q, sort=["age"])
- assert len(docs) == 15
- assert docs[0]["age"] == 22
+ self.assertEqual(len(docs), 15)
+ self.assertEqual(docs[0]["age"], 22)
def test_ambiguous_type_sort(self):
q = {"$or": [{"age":{"$gt": 0}}, {"email": {"$gt": None}},
@@ -80,7 +80,7 @@ class SortTests(mango.UserDocsTextTests):
try:
self.db.find(q, sort=["age"])
except Exception as e:
- assert e.response.status_code == 400
+ self.assertEqual(e.response.status_code, 400)
else:
raise AssertionError("Should have thrown error for sort")
@@ -88,14 +88,14 @@ class SortTests(mango.UserDocsTextTests):
q = {"$or": [{"age":{"$gt": 0}}, {"email": {"$gt": None}},
{"name.last": "Harvey"}]}
docs = self.db.find(q, sort=["name.last", "age"])
- assert len(docs) == 15
- assert docs[0]["name"] == {"last":"Ewing","first":"Shelly"}
- assert docs[1]["age"] == 22
+ self.assertEqual(len(docs), 15)
+ self.assertEqual(docs[0]["name"], {"last":"Ewing","first":"Shelly"})
+ self.assertEqual(docs[1]["age"], 22)
def test_guess_mix_sort(self):
q = {"$or": [{"age":{"$gt": 0}}, {"email": {"$gt": None}},
{"name.last": "Harvey"}]}
docs = self.db.find(q, sort=["name.last:string", "age"])
- assert len(docs) == 15
- assert docs[0]["name"] == {"last":"Ewing","first":"Shelly"}
- assert docs[1]["age"] == 22
+ self.assertEqual(len(docs), 15)
+ self.assertEqual(docs[0]["name"], {"last":"Ewing","first":"Shelly"})
+ self.assertEqual(docs[1]["age"], 22)
diff --git a/src/mango/test/10-disable-array-length-field-test.py b/src/mango/test/10-disable-array-length-field-test.py
index 0715f1db9..ce7713b63 100644
--- a/src/mango/test/10-disable-array-length-field-test.py
+++ b/src/mango/test/10-disable-array-length-field-test.py
@@ -13,28 +13,24 @@
import mango
import unittest
-
+@unittest.skipUnless(mango.has_text_service(), "requires text service")
class DisableIndexArrayLengthsTest(mango.UserDocsTextTests):
- @classmethod
- def setUpClass(klass):
- super(DisableIndexArrayLengthsTest, klass).setUpClass()
- if mango.has_text_service():
- klass.db.create_text_index(ddoc="disable_index_array_lengths",
+ def setUp(klass):
+ self.db.recreate()
+ self.db.create_text_index(ddoc="disable_index_array_lengths",
analyzer="keyword",
index_array_lengths=False)
- klass.db.create_text_index(ddoc="explicit_enable_index_array_lengths",
+ self.db.create_text_index(ddoc="explicit_enable_index_array_lengths",
analyzer="keyword",
index_array_lengths=True)
- @unittest.skipUnless(mango.has_text_service(), "requires text service")
def test_disable_index_array_length(self):
docs = self.db.find({"favorites": {"$size": 4}},
use_index="disable_index_array_lengths")
for d in docs:
assert len(d["favorites"]) == 0
- @unittest.skipUnless(mango.has_text_service(), "requires text service")
def test_enable_index_array_length(self):
docs = self.db.find({"favorites": {"$size": 4}},
use_index="explicit_enable_index_array_lengths")
diff --git a/src/mango/test/11-ignore-design-docs.py b/src/mango/test/11-ignore-design-docs-test.py
index ea7165e3f..ea7165e3f 100644
--- a/src/mango/test/11-ignore-design-docs.py
+++ b/src/mango/test/11-ignore-design-docs-test.py
diff --git a/src/mango/test/12-use-correct-index.py b/src/mango/test/12-use-correct-index-test.py
index 84b425343..5a2b24d3f 100644
--- a/src/mango/test/12-use-correct-index.py
+++ b/src/mango/test/12-use-correct-index-test.py
@@ -68,15 +68,15 @@ class ChooseCorrectIndexForDocs(mango.DbPerClass):
self.assertEqual(explain["index"]["ddoc"], '_design/bbb')
def test_choose_index_alphabetically(self):
- self.db.create_index(["name", "age", "user_id"], ddoc="aaa")
- self.db.create_index(["name", "age", "location"], ddoc="bbb")
+ self.db.create_index(["name"], ddoc="aaa")
+ self.db.create_index(["name"], ddoc="bbb")
self.db.create_index(["name"], ddoc="zzz")
explain = self.db.find({"name": "Eddie", "age": {"$gte": 12}}, explain=True)
self.assertEqual(explain["index"]["ddoc"], '_design/aaa')
def test_choose_index_most_accurate(self):
- self.db.create_index(["name", "location", "user_id"], ddoc="aaa")
- self.db.create_index(["name", "age", "user_id"], ddoc="bbb")
+ self.db.create_index(["name", "age", "user_id"], ddoc="aaa")
+ self.db.create_index(["name", "age"], ddoc="bbb")
self.db.create_index(["name"], ddoc="zzz")
explain = self.db.find({"name": "Eddie", "age": {"$gte": 12}}, explain=True)
self.assertEqual(explain["index"]["ddoc"], '_design/bbb')
@@ -105,3 +105,12 @@ class ChooseCorrectIndexForDocs(mango.DbPerClass):
self.db.create_index(["a", "d", "e"])
explain = self.db.find({"a": {"$gt": 0}, "b": {"$gt": 0}, "c": {"$gt": 0}}, explain=True)
self.assertEqual(explain["index"]["def"]["fields"], [{'a': 'asc'}, {'b': 'asc'}, {'c': 'asc'}])
+
+ def test_can_query_with_range_on_secondary_column(self):
+ self.db.create_index(["age", "name"], ddoc="bbb")
+ selector = {"age": 10, "name": {"$gte": 0}}
+ docs = self.db.find(selector)
+ self.assertEqual(len(docs), 1)
+ explain = self.db.find(selector, explain=True)
+ self.assertEqual(explain["index"]["ddoc"], "_design/bbb")
+ self.assertEqual(explain["mrargs"]["end_key"], [10, '<MAX>'])
diff --git a/src/mango/test/14-json-pagination.py b/src/mango/test/14-json-pagination-test.py
index ea06e0a2a..ea06e0a2a 100644
--- a/src/mango/test/14-json-pagination.py
+++ b/src/mango/test/14-json-pagination-test.py
diff --git a/src/mango/test/16-index-selectors.py b/src/mango/test/16-index-selectors-test.py
index 3ce659ecf..456b396c7 100644
--- a/src/mango/test/16-index-selectors.py
+++ b/src/mango/test/16-index-selectors-test.py
@@ -73,6 +73,28 @@ DOCS = [
},
]
+oldschoolnoselectorddoc = {
+ "_id": "_design/oldschoolnoselector",
+ "language": "query",
+ "views": {
+ "oldschoolnoselector": {
+ "map": {
+ "fields": {
+ "location": "asc"
+ }
+ },
+ "reduce": "_count",
+ "options": {
+ "def": {
+ "fields": [
+ "location"
+ ]
+ }
+ }
+ }
+ }
+}
+
oldschoolddoc = {
"_id": "_design/oldschool",
"language": "query",
@@ -178,6 +200,14 @@ class IndexSelectorJson(mango.DbPerClass):
resp = self.db.find(selector, explain=True)
self.assertEqual(resp["index"]["name"], "NotSelected")
+ def test_old_selector_with_no_selector_still_supported(self):
+ selector = {"location": {"$gte": "FRA"}}
+ self.db.save_doc(oldschoolnoselectorddoc)
+ resp = self.db.find(selector, explain=True, use_index='oldschoolnoselector')
+ self.assertEqual(resp["index"]["name"], "oldschoolnoselector")
+ docs = self.db.find(selector, use_index='oldschoolnoselector')
+ self.assertEqual(len(docs), 3)
+
def test_old_selector_still_supported(self):
selector = {"location": {"$gte": "FRA"}}
self.db.save_doc(oldschoolddoc)
diff --git a/src/mango/test/17-multi-type-value-test.py b/src/mango/test/17-multi-type-value-test.py
new file mode 100644
index 000000000..d838447d5
--- /dev/null
+++ b/src/mango/test/17-multi-type-value-test.py
@@ -0,0 +1,90 @@
+# 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.
+
+import copy
+import mango
+import unittest
+
+DOCS = [
+ {
+ "_id": "1",
+ "name": "Jimi",
+ "age": 10
+ },
+ {
+ "_id": "2",
+ "name": {"forename":"Eddie"},
+ "age": 20
+ },
+ {
+ "_id": "3",
+ "name": None,
+ "age": 30
+ },
+ {
+ "_id": "4",
+ "name": 1,
+ "age": 40
+ },
+ {
+ "_id": "5",
+ "forename": "Sam",
+ "age": 50
+ }
+]
+
+
+class MultiValueFieldTests:
+
+ def test_can_query_with_name(self):
+ docs = self.db.find({"name": {"$exists": True}})
+ self.assertEqual(len(docs), 4)
+ for d in docs:
+ self.assertIn("name", d)
+
+ def test_can_query_with_name_subfield(self):
+ docs = self.db.find({"name.forename": {"$exists": True}})
+ self.assertEqual(len(docs), 1)
+ self.assertEqual(docs[0]["_id"], "2")
+
+ def test_can_query_with_name_range(self):
+ docs = self.db.find({"name": {"$gte": 0}})
+ # expect to include "Jimi", 1 and {"forename":"Eddie"}
+ self.assertEqual(len(docs), 3)
+ for d in docs:
+ self.assertIn("name", d)
+
+ def test_can_query_with_age_and_name_range(self):
+ docs = self.db.find({"age": {"$gte": 0, "$lt": 40}, "name": {"$gte": 0}})
+ # expect to include "Jimi", 1 and {"forename":"Eddie"}
+ self.assertEqual(len(docs), 2)
+ for d in docs:
+ self.assertIn("name", d)
+
+
+
+class MultiValueFieldJSONTests(mango.DbPerClass, MultiValueFieldTests):
+ def setUp(self):
+ self.db.recreate()
+ self.db.save_docs(copy.deepcopy(DOCS))
+ self.db.create_index(["name"])
+ self.db.create_index(["age", "name"])
+
+# @unittest.skipUnless(mango.has_text_service(), "requires text service")
+# class MultiValueFieldTextTests(MultiValueFieldDocsNoIndexes, OperatorTests):
+# pass
+
+
+class MultiValueFieldAllDocsTests(mango.DbPerClass, MultiValueFieldTests):
+ def setUp(self):
+ self.db.recreate()
+ self.db.save_docs(copy.deepcopy(DOCS))
diff --git a/src/mango/test/README.md b/src/mango/test/README.md
index fc2cd62e5..3c99cab9d 100644
--- a/src/mango/test/README.md
+++ b/src/mango/test/README.md
@@ -7,6 +7,23 @@ To run these, do this in the Mango top level directory:
$ virtualenv venv
$ source venv/bin/activate
- $ pip install nose requests
- $ pip install hypothesis
- $ nosetests
+ $ make pip-install
+ $ make test
+
+To run an individual test suite:
+ nosetests --nocapture test/12-use-correct-index.py
+
+To run the tests with text index support:
+ MANGO_TEXT_INDEXES=1 nosetests --nocapture test
+
+
+Test configuration
+==================
+
+The following environment variables can be used to configure the test fixtures:
+
+ * `COUCH_HOST` - root url (including port) of the CouchDB instance to run the tests against. Default is `"http://127.0.0.1:15984"`.
+ * `COUCH_USER` - CouchDB username (with admin premissions). Default is `"testuser"`.
+ * `COUCH_PASSWORD` - CouchDB password. Default is `"testpass"`.
+ * `COUCH_AUTH_HEADER` - Optional Authorization header value. If specified, this is used instead of basic authentication with the username/password variables above.
+ * `MANGO_TEXT_INDEXES` - Set to `"1"` to run the tests only applicable to text indexes.
diff --git a/src/mango/test/mango.py b/src/mango/test/mango.py
index a275a23d0..ed4cafbe5 100644
--- a/src/mango/test/mango.py
+++ b/src/mango/test/mango.py
@@ -27,28 +27,46 @@ def random_db_name():
return "mango_test_" + uuid.uuid4().hex
def has_text_service():
- return os.path.isfile(os.getcwd() + "/../src/mango_cursor_text.erl")
+ return os.environ.get('MANGO_TEXT_INDEXES') == '1'
+
+def get_from_environment(key, default):
+ value = os.environ.get(key)
+ return value if value is not None else default
class Database(object):
- def __init__(self, host, port, dbname, auth=None):
- self.host = host
- self.port = port
+ def __init__(self, dbname,
+ host="127.0.0.1", port="15984",
+ user='testuser', password='testpass'):
+ root_url = get_from_environment('COUCH_HOST', "http://{}:{}".format(host, port))
+ auth_header = get_from_environment('COUCH_AUTH_HEADER', None)
+ user = get_from_environment('COUCH_USER', user)
+ password = get_from_environment('COUCH_PASSWORD', password)
+
+ self.root_url = root_url
self.dbname = dbname
self.sess = requests.session()
- self.sess.auth = ('testuser', 'testpass')
+
+ # allow explicit auth header to be set to enable testing
+ # against deployments where basic auth isn't available
+ if auth_header is not None:
+ self.sess.headers["Authorization"] = auth_header
+ else:
+ self.sess.auth = (user, password)
+
self.sess.headers["Content-Type"] = "application/json"
+
@property
def url(self):
- return "http://{}:{}/{}".format(self.host, self.port, self.dbname)
+ return "{}/{}".format(self.root_url, self.dbname)
def path(self, parts):
if isinstance(parts, ("".__class__, u"".__class__)):
parts = [parts]
return "/".join([self.url] + parts)
- def create(self, q=1, n=3):
+ def create(self, q=1, n=1):
r = self.sess.get(self.url)
if r.status_code == 404:
r = self.sess.put(self.url, params={"q":q, "n": n})
@@ -206,7 +224,7 @@ class UsersDbTests(unittest.TestCase):
@classmethod
def setUpClass(klass):
- klass.db = Database("127.0.0.1", "15984", "_users")
+ klass.db = Database("_users")
user_docs.setup_users(klass.db)
def setUp(self):
@@ -217,7 +235,7 @@ class DbPerClass(unittest.TestCase):
@classmethod
def setUpClass(klass):
- klass.db = Database("127.0.0.1", "15984", random_db_name())
+ klass.db = Database(random_db_name())
klass.db.create(q=1, n=3)
def setUp(self):