You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ro...@apache.org on 2015/02/03 16:13:08 UTC

[01/50] [abbrv] couchdb-mango git commit: Check `mango_crud:insert/3` return values

Repository: couchdb-mango
Updated Branches:
  refs/heads/master [created] 09e4b815d


Check `mango_crud:insert/3` return values

The wrapped `{ok, _}` tuple can be misleading on what's returned. This
checks the actual values more closely so that clients aren't confused
when an index creation fails.

BugzId: 31919


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/83fb0db3
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/83fb0db3
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/83fb0db3

Branch: refs/heads/master
Commit: 83fb0db3b99dabb7b82c412a3afe3265b4aedff4
Parents: 9e91d75
Author: Paul J. Davis <pa...@gmail.com>
Authored: Tue Jun 24 14:56:02 2014 -0500
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Tue Jun 24 14:56:02 2014 -0500

----------------------------------------------------------------------
 src/mango_error.erl | 19 +++++++++++++++++++
 src/mango_httpd.erl |  9 +++++++--
 2 files changed, 26 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/83fb0db3/src/mango_error.erl
----------------------------------------------------------------------
diff --git a/src/mango_error.erl b/src/mango_error.erl
index 2c4a53c..f34dac5 100644
--- a/src/mango_error.erl
+++ b/src/mango_error.erl
@@ -54,6 +54,25 @@ info(mango_fields, {invalid_field_json, BadField}) ->
         fmt("Invalid JSON for field spec: ~w", [BadField])
     };
 
+info(mango_httpd, error_saving_ddoc) ->
+    {
+        500,
+        <<"error_saving_ddoc">>,
+        <<"Unknown error while saving the design document.">>
+    };
+info(mango_httpd, {error_saving_ddoc, <<"conflict">>}) ->
+    {
+        500,
+        <<"error_saving_ddoc">>,
+        <<"Encountered a conflict while saving the design document.">>
+    };
+info(mango_httpd, {error_saving_ddoc, Reason}) ->
+    {
+        500,
+        <<"error_saving_ddoc">>,
+        fmt("Unknown error while saving the design document: ~s", [Reason])
+    };
+
 info(mango_idx, {invalid_index_type, BadType}) ->
     {
         400,

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/83fb0db3/src/mango_httpd.erl
----------------------------------------------------------------------
diff --git a/src/mango_httpd.erl b/src/mango_httpd.erl
index eb4f60e..36ebd91 100644
--- a/src/mango_httpd.erl
+++ b/src/mango_httpd.erl
@@ -50,8 +50,13 @@ handle_index_req(#httpd{method='POST', path_parts=[_, _]}=Req, Db) ->
             <<"exists">>;
         {ok, NewDDoc} ->
             case mango_crud:insert(Db, NewDDoc, [{w, "3"} | Opts]) of
-                {ok, _} ->
-                    <<"created">>;
+                {ok, [{RespProps}]} ->
+                    case lists:keyfind(error, 1, RespProps) of
+                        {error, Reason} ->
+                            ?MANGO_ERROR({error_saving_ddoc, Reason});
+                        _ ->
+                            <<"created">>
+                    end;
                 _ ->
                     ?MANGO_ERROR(error_saving_ddoc)
             end


[13/50] [abbrv] couchdb-mango git commit: Merge pull request #8 from cloudant/38248-escape-the-period-character

Posted by ro...@apache.org.
Merge pull request #8 from cloudant/38248-escape-the-period-character

Support escaped period character in query

Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/3bcd5140
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/3bcd5140
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/3bcd5140

Branch: refs/heads/master
Commit: 3bcd5140265294d804407010cb2fadbb4b47771e
Parents: 4bee6d1 80253c4
Author: Eric Avdey <ei...@eiri.ca>
Authored: Tue Nov 4 15:16:33 2014 -0400
Committer: Eric Avdey <ei...@eiri.ca>
Committed: Tue Nov 4 15:16:33 2014 -0400

----------------------------------------------------------------------
 .gitignore                 |  1 +
 README.md                  | 12 ++++++++---
 src/mango_doc.erl          | 44 ++++++++++++++++++++++++-----------------
 test/01-index-crud-test.py |  2 +-
 test/02-basic-find-test.py | 30 ++++++++++++++++++++++++++--
 test/user_docs.py          | 29 ++++++++++++++++++++++++++-
 6 files changed, 93 insertions(+), 25 deletions(-)
----------------------------------------------------------------------



[07/50] [abbrv] couchdb-mango git commit: Remove hardcoded w=3 for design doc updates

Posted by ro...@apache.org.
Remove hardcoded w=3 for design doc updates

This was left over from testing. Probably best to remove it for
production.


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/116227c5
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/116227c5
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/116227c5

Branch: refs/heads/master
Commit: 116227c5b3a41fd38e3642312c02bc8840921403
Parents: bbac0d2
Author: Paul J. Davis <pa...@gmail.com>
Authored: Tue Jun 24 15:30:33 2014 -0500
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Tue Jun 24 15:30:33 2014 -0500

----------------------------------------------------------------------
 src/mango_httpd.erl | 4 ++--
 src/mango_opts.erl  | 6 ++++++
 2 files changed, 8 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/116227c5/src/mango_httpd.erl
----------------------------------------------------------------------
diff --git a/src/mango_httpd.erl b/src/mango_httpd.erl
index 36ebd91..68bced0 100644
--- a/src/mango_httpd.erl
+++ b/src/mango_httpd.erl
@@ -49,7 +49,7 @@ handle_index_req(#httpd{method='POST', path_parts=[_, _]}=Req, Db) ->
         {ok, DDoc} ->
             <<"exists">>;
         {ok, NewDDoc} ->
-            case mango_crud:insert(Db, NewDDoc, [{w, "3"} | Opts]) of
+            case mango_crud:insert(Db, NewDDoc, Opts) of
                 {ok, [{RespProps}]} ->
                     case lists:keyfind(error, 1, RespProps) of
                         {error, Reason} ->
@@ -91,7 +91,7 @@ handle_index_req(#httpd{method='DELETE',
                 _ ->
                     NewDDoc
             end,
-            case mango_crud:insert(Db, FinalDDoc, [{w, "3"}]) of
+            case mango_crud:insert(Db, FinalDDoc, []) of
                 {ok, _} ->
                     chttpd:send_json(Req, {[{ok, true}]});
                 _ ->

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/116227c5/src/mango_opts.erl
----------------------------------------------------------------------
diff --git a/src/mango_opts.erl b/src/mango_opts.erl
index 44426db..9affbd0 100644
--- a/src/mango_opts.erl
+++ b/src/mango_opts.erl
@@ -46,6 +46,12 @@ validate_idx_create({Props}) ->
             {optional, true},
             {default, auto_name},
             {validator, fun validate_idx_name/1}
+        ]},
+        {<<"w">>, [
+            {tag, w},
+            {optional, true},
+            {default, 2},
+            {validator, fun is_non_neg_integer/1}
         ]}
     ],
     validate(Props, Opts).


[31/50] [abbrv] couchdb-mango git commit: Merge pull request #18 from cloudant/change-description

Posted by ro...@apache.org.
Merge pull request #18 from cloudant/change-description

change description

Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/9cfe0a95
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/9cfe0a95
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/9cfe0a95

Branch: refs/heads/master
Commit: 9cfe0a95f29456d6b9ebb1905051d221d89eab0d
Parents: 195d541 11343b2
Author: Robert Kowalski <ro...@kowalski.gd>
Authored: Fri Jan 9 17:57:56 2015 +0100
Committer: Robert Kowalski <ro...@kowalski.gd>
Committed: Fri Jan 9 17:57:56 2015 +0100

----------------------------------------------------------------------
 src/mango.app.src | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------



[14/50] [abbrv] couchdb-mango git commit: Add Array Support For $in Operator

Posted by ro...@apache.org.
Add Array Support For $in Operator

- Compare elements correctly when field value is an array.
- 38863-in-operator-arrays


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/2d0cd0cf
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/2d0cd0cf
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/2d0cd0cf

Branch: refs/heads/master
Commit: 2d0cd0cf36324739acbd93cd00e17f5c95e40521
Parents: 3bcd514
Author: Tony Sun <ll...@Tonys-MacBook-Pro.local>
Authored: Tue Nov 4 15:55:28 2014 -0800
Committer: Tony Sun <ll...@Tonys-MacBook-Pro.local>
Committed: Tue Nov 4 16:00:01 2014 -0800

----------------------------------------------------------------------
 src/mango_selector.erl | 5 +++++
 1 file changed, 5 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/2d0cd0cf/src/mango_selector.erl
----------------------------------------------------------------------
diff --git a/src/mango_selector.erl b/src/mango_selector.erl
index 06c3550..d392abe 100644
--- a/src/mango_selector.erl
+++ b/src/mango_selector.erl
@@ -651,6 +651,11 @@ match({[{<<"$gte">>, Arg}]}, Value, Cmp) ->
 match({[{<<"$gt">>, Arg}]}, Value, Cmp) ->
     Cmp(Value, Arg) > 0;
 
+match({[{<<"$in">>, Args}]}, Values, Cmp) when is_list(Values)->
+    Pred = fun(Arg) -> lists:foldl(fun(Value,Match) ->
+        (Cmp(Value, Arg) == 0) or Match end, false, Values)
+    end,
+    lists:any(Pred, Args);
 match({[{<<"$in">>, Args}]}, Value, Cmp) ->
     Pred = fun(Arg) -> Cmp(Value, Arg) == 0 end,
     lists:any(Pred, Args);


[34/50] [abbrv] couchdb-mango git commit: Allow cursors to add KVs to the find response

Posted by ro...@apache.org.
Allow cursors to add KVs to the find response

This change allows cursors to set top level key/value pairs in the
response to _find requests. This is ground work for Search based indexes
that will need to set a bookmark value for clients.

BugzId: 33294


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/9ed13044
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/9ed13044
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/9ed13044

Branch: refs/heads/master
Commit: 9ed13044727875534b2abd763a0ec67410b32034
Parents: d1d13e6
Author: Paul J. Davis <pa...@gmail.com>
Authored: Thu Jan 8 17:48:41 2015 -0600
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Fri Jan 16 13:32:49 2015 -0600

----------------------------------------------------------------------
 src/mango_httpd.erl | 23 ++++++++++++++++-------
 1 file changed, 16 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/9ed13044/src/mango_httpd.erl
----------------------------------------------------------------------
diff --git a/src/mango_httpd.erl b/src/mango_httpd.erl
index ef4cc5b..02e26ad 100644
--- a/src/mango_httpd.erl
+++ b/src/mango_httpd.erl
@@ -123,8 +123,8 @@ handle_find_req(#httpd{method='POST'}=Req, Db) ->
     {ok, Opts0} = mango_opts:validate_find(chttpd:json_body_obj(Req)),
     {value, {selector, Sel}, Opts} = lists:keytake(selector, 1, Opts0),
     {ok, Resp0} = start_find_resp(Req),
-    {ok, {Resp1, _}} = run_find(Resp0, Db, Sel, Opts),
-    end_find_resp(Resp1);
+    {ok, {Resp1, _, KVs}} = run_find(Resp0, Db, Sel, Opts),
+    end_find_resp(Resp1, KVs);
 
 handle_find_req(Req, _Db) ->
     chttpd:send_method_not_allowed(Req, "POST").
@@ -157,16 +157,25 @@ start_find_resp(Req) ->
     chttpd:start_delayed_json_response(Req, 200, [], "{\"docs\":[").
 
 
-end_find_resp(Resp0) ->
-    {ok, Resp1} = chttpd:send_delayed_chunk(Resp0, "\r\n]}"),
+end_find_resp(Resp0, KVs) ->
+    FinalAcc = lists:foldl(fun({K, V}, Acc) ->
+        JK = ?JSON_ENCODE(K),
+        JV = ?JSON_ENCODE(V),
+        [JV, ": ", JK, ",\r\n" | Acc]
+    end, ["\r\n]"], KVs),
+    Chunk = lists:reverse(FinalAcc, ["}\r\n"]),
+    {ok, Resp1} = chttpd:send_delayed_chunk(Resp0, Chunk),
     chttpd:end_delayed_json_response(Resp1).
 
 
 run_find(Resp, Db, Sel, Opts) ->
-    mango_crud:find(Db, Sel, fun handle_doc/2, {Resp, "\r\n"}, Opts).
+    mango_crud:find(Db, Sel, fun handle_doc/2, {Resp, "\r\n", []}, Opts).
 
 
-handle_doc({row, Doc}, {Resp0, Prepend}) ->
+handle_doc({add_key, Key, Value}, {Resp, Prepend, KVs}) ->
+    NewKVs = lists:keystore(Key, 1, KVs, {Key, Value}),
+    {ok, {Resp, Prepend, NewKVs}};
+handle_doc({row, Doc}, {Resp0, Prepend, KVs}) ->
     Chunk = [Prepend, ?JSON_ENCODE(Doc)],
     {ok, Resp1} = chttpd:send_delayed_chunk(Resp0, Chunk),
-    {ok, {Resp1, ",\r\n"}}.
+    {ok, {Resp1, ",\r\n", KVs}}.


[16/50] [abbrv] couchdb-mango git commit: Add Unit Test For In Operator Array

Posted by ro...@apache.org.
Add Unit Test For In Operator Array

38863-in-operatory-arrays


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/c09d8d4b
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/c09d8d4b
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/c09d8d4b

Branch: refs/heads/master
Commit: c09d8d4b2cfc12b6aac1f1e6ac8cd965811c3223
Parents: 52544a5
Author: Tony Sun <ll...@Tonys-MacBook-Pro.local>
Authored: Wed Nov 5 10:02:59 2014 -0800
Committer: Tony Sun <ll...@Tonys-MacBook-Pro.local>
Committed: Wed Nov 5 10:41:30 2014 -0800

----------------------------------------------------------------------
 src/mango_selector.erl   |  5 -----
 test/03-operator-test.py | 12 ++++++++++++
 2 files changed, 12 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/c09d8d4b/src/mango_selector.erl
----------------------------------------------------------------------
diff --git a/src/mango_selector.erl b/src/mango_selector.erl
index 97f4123..da6a8ba 100644
--- a/src/mango_selector.erl
+++ b/src/mango_selector.erl
@@ -658,11 +658,6 @@ match({[{<<"$in">>, Args}]}, Values, Cmp) when is_list(Values)->
         end, false, Values)
     end,
     lists:any(Pred, Args);
-match({[{<<"$in">>, Args}]}, Values, Cmp) when is_list(Values)->
-    Pred = fun(Arg) -> lists:foldl(fun(Value,Match) ->
-        (Cmp(Value, Arg) == 0) or Match end, false, Values)
-    end,
-    lists:any(Pred, Args);
 match({[{<<"$in">>, Args}]}, Value, Cmp) ->
     Pred = fun(Arg) -> Cmp(Value, Arg) == 0 end,
     lists:any(Pred, Args);

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/c09d8d4b/test/03-operator-test.py
----------------------------------------------------------------------
diff --git a/test/03-operator-test.py b/test/03-operator-test.py
index 6ee596a..4ef93ff 100644
--- a/test/03-operator-test.py
+++ b/test/03-operator-test.py
@@ -67,3 +67,15 @@ def test_regex():
     assert len(docs) == 2
     assert docs[0]["user_id"] == 2
     assert docs[1]["user_id"] == 10
+
+
+def test_in_operator_array():
+    db = user_docs.mkdb()
+
+    docs = db.find({
+            "manager": True,
+            "favorites": {"$in": ["Ruby", "Python"]}
+        })
+    assert len(docs) == 7
+    assert docs[0]["user_id"] == 2
+    assert docs[1]["user_id"] == 12


[08/50] [abbrv] couchdb-mango git commit: Allow the specification of w=3

Posted by ro...@apache.org.
Allow the specification of w=3

We removed the hardcoded w=3 but still want to use it for tests. This
enables us to specify w=3 for creating and deleting indexes and updates
the tests to use it.


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/c33efa32
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/c33efa32
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/c33efa32

Branch: refs/heads/master
Commit: c33efa3205b4405a3ac24bb4a3e8cc903d5ad399
Parents: 116227c
Author: Paul J. Davis <pa...@gmail.com>
Authored: Tue Jun 24 15:51:36 2014 -0500
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Tue Jun 24 15:51:36 2014 -0500

----------------------------------------------------------------------
 src/mango_httpd.erl | 25 +++++++++++++++++++++++--
 src/mango_opts.erl  |  2 +-
 test/mango.py       |  5 +++--
 3 files changed, 27 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/c33efa32/src/mango_httpd.erl
----------------------------------------------------------------------
diff --git a/src/mango_httpd.erl b/src/mango_httpd.erl
index 68bced0..fa6817f 100644
--- a/src/mango_httpd.erl
+++ b/src/mango_httpd.erl
@@ -49,7 +49,8 @@ handle_index_req(#httpd{method='POST', path_parts=[_, _]}=Req, Db) ->
         {ok, DDoc} ->
             <<"exists">>;
         {ok, NewDDoc} ->
-            case mango_crud:insert(Db, NewDDoc, Opts) of
+            CreateOpts = get_idx_create_opts(Opts),
+            case mango_crud:insert(Db, NewDDoc, CreateOpts) of
                 {ok, [{RespProps}]} ->
                     case lists:keyfind(error, 1, RespProps) of
                         {error, Reason} ->
@@ -91,7 +92,8 @@ handle_index_req(#httpd{method='DELETE',
                 _ ->
                     NewDDoc
             end,
-            case mango_crud:insert(Db, FinalDDoc, []) of
+            DelOpts = get_idx_del_opts(Req),
+            case mango_crud:insert(Db, FinalDDoc, DelOpts) of
                 {ok, _} ->
                     chttpd:send_json(Req, {[{ok, true}]});
                 _ ->
@@ -120,6 +122,25 @@ set_user_ctx(#httpd{user_ctx=Ctx}, Db) ->
     Db#db{user_ctx=Ctx}.
 
 
+get_idx_create_opts(Opts) ->
+    case lists:keyfind(w, 1, Opts) of
+        {w, N} when is_integer(N), N > 0 ->
+            [{w, integer_to_list(N)}];
+        _ ->
+            [{w, "2"}]
+    end.
+
+
+get_idx_del_opts(Req) ->
+    try
+        WStr = chttpd:qs_value(Req, "w", "2"),
+        _ = list_to_integer(WStr),
+        [{w, WStr}]
+    catch _:_ ->
+        [{w, "2"}]
+    end.
+
+
 start_find_resp(Req) ->
     chttpd:start_delayed_json_response(Req, 200, [], "{\"docs\":[").
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/c33efa32/src/mango_opts.erl
----------------------------------------------------------------------
diff --git a/src/mango_opts.erl b/src/mango_opts.erl
index 9affbd0..cb42011 100644
--- a/src/mango_opts.erl
+++ b/src/mango_opts.erl
@@ -51,7 +51,7 @@ validate_idx_create({Props}) ->
             {tag, w},
             {optional, true},
             {default, 2},
-            {validator, fun is_non_neg_integer/1}
+            {validator, fun is_pos_integer/1}
         ]}
     ],
     validate(Props, Opts).

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/c33efa32/test/mango.py
----------------------------------------------------------------------
diff --git a/test/mango.py b/test/mango.py
index 92c2a2c..fc201d6 100644
--- a/test/mango.py
+++ b/test/mango.py
@@ -65,7 +65,8 @@ class Database(object):
             "index": {
                 "fields": fields
             },
-            "type": idx_type
+            "type": idx_type,
+            "w": 3
         }
         if name is not None:
             body["name"] = name
@@ -83,7 +84,7 @@ class Database(object):
 
     def delete_index(self, ddocid, name, idx_type="json"):
         path = ["_index", ddocid, idx_type, name]
-        r = self.sess.delete(self.path(path))
+        r = self.sess.delete(self.path(path), params={"w":"3"})
         r.raise_for_status()
 
     def find(self, selector, limit=25, skip=0, sort=None, fields=None,


[36/50] [abbrv] couchdb-mango git commit: Move view specific logic to mango_view_idx

Posted by ro...@apache.org.
Move view specific logic to mango_view_idx

A lot of the logic in mango_selector is view specific so this moves it
to mango_idx_view to make that more apparent.

BugzId: 33294


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/7a9de344
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/7a9de344
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/7a9de344

Branch: refs/heads/master
Commit: 7a9de3447652d31bb517a6975b6cb5c622854681
Parents: 08c3320
Author: Paul J. Davis <pa...@gmail.com>
Authored: Fri Jan 9 12:03:29 2015 -0600
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Fri Jan 16 13:32:49 2015 -0600

----------------------------------------------------------------------
 src/mango_cursor.erl   |  19 +---
 src/mango_idx_view.erl | 272 +++++++++++++++++++++++++++++++++++++++++++-
 src/mango_selector.erl | 245 ---------------------------------------
 3 files changed, 273 insertions(+), 263 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/7a9de344/src/mango_cursor.erl
----------------------------------------------------------------------
diff --git a/src/mango_cursor.erl b/src/mango_cursor.erl
index 22970c7..8f780b7 100644
--- a/src/mango_cursor.erl
+++ b/src/mango_cursor.erl
@@ -29,8 +29,8 @@
 
 create(Db, Selector0, Opts) ->
     Selector = mango_selector:normalize(Selector0),
-    IndexFields = mango_selector:index_fields(Selector),    
-    FieldRanges = find_field_ranges(Selector, IndexFields),
+    IndexFields = mango_idx_view:indexable_fields(Selector),    
+    FieldRanges = mango_idx_view:field_ranges(Selector, IndexFields),
 
     if IndexFields /= [] -> ok; true ->
         ?MANGO_ERROR({no_usable_index, operator_unsupported})
@@ -123,21 +123,6 @@ limit_to_sort(ExistingIndexes, UsableIndexes, Sort) ->
     FinalIndexes.
 
 
-% For each field, return {Field, Range}
-find_field_ranges(Selector, Fields) ->
-    find_field_ranges(Selector, Fields, []).
-
-find_field_ranges(_Selector, [], Acc) ->
-    lists:reverse(Acc);
-find_field_ranges(Selector, [Field | Rest], Acc) ->
-    case mango_selector:range(Selector, Field) of
-        empty ->
-            [{Field, empty}];
-        Range ->
-            find_field_ranges(Selector, Rest, [{Field, Range} | Acc])
-    end.
-
-
 % Any of these indexes may be a composite index. For each
 % index find the most specific set of fields for each
 % index. Ie, if an index has columns a, b, c, d, then

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/7a9de344/src/mango_idx_view.erl
----------------------------------------------------------------------
diff --git a/src/mango_idx_view.erl b/src/mango_idx_view.erl
index 993574f..eaa4341 100644
--- a/src/mango_idx_view.erl
+++ b/src/mango_idx_view.erl
@@ -21,7 +21,11 @@
     to_json/1,
     columns/1,
     start_key/1,
-    end_key/1
+    end_key/1,
+
+    indexable_fields/1,
+    field_ranges/1,
+    field_ranges/2
 ]).
 
 
@@ -171,3 +175,269 @@ make_view(Idx) ->
         {<<"options">>, {Idx#idx.opts}}
     ]},
     {Idx#idx.name, View}.
+
+
+% This function returns a list of indexes that
+% can be used to restrict this query. This works by
+% searching the selector looking for field names that
+% can be "seen".
+%
+% Operators that can be seen through are '$and' and any of
+% the logical comparisons ('$lt', '$eq', etc). Things like
+% '$regex', '$in', '$nin', and '$or' can't be serviced by
+% a single index scan so we disallow them. In the future
+% we may become more clever and increase our ken such that
+% we will be able to see through these with crafty indexes
+% or new uses for existing indexes. For instance, I could
+% see an '$or' between comparisons on the same field becoming
+% the equivalent of a multi-query. But that's for another
+% day.
+
+% We can see through '$and' trivially
+indexable_fields({[{<<"$and">>, Args}]}) ->
+    lists:usort(lists:flatten([indexable_fields(A) || A <- Args]));
+
+% So far we can't see through any other operator
+indexable_fields({[{<<"$", _/binary>>, _}]}) ->
+    [];
+
+% If we have a field with a terminator that is locatable
+% using an index then the field is a possible index
+indexable_fields({[{Field, Cond}]}) ->
+    case indexable(Cond) of
+        true ->
+            [Field];
+        false ->
+            []
+    end;
+
+% An empty selector
+indexable_fields({[]}) ->
+    [].
+
+
+% Check if a condition is indexable. The logical
+% comparisons are mostly straight forward. We
+% currently don't understand '$in' which is
+% theoretically supportable. '$nin' and '$ne'
+% aren't currently supported because they require
+% multiple index scans.
+indexable({[{<<"$lt">>, _}]}) ->
+    true;
+indexable({[{<<"$lte">>, _}]}) ->
+    true;
+indexable({[{<<"$eq">>, _}]}) ->
+    true;
+indexable({[{<<"$gt">>, _}]}) ->
+    true;
+indexable({[{<<"$gte">>, _}]}) ->
+    true;
+
+% All other operators are currently not indexable.
+% This is also a subtle assertion that we don't
+% call indexable/1 on a field name.
+indexable({[{<<"$", _/binary>>, _}]}) ->
+    false.
+
+
+% For each field, return {Field, Range}
+field_ranges(Selector) ->
+    Fields = indexable_fields(Selector),
+    field_ranges(Selector, Fields).
+
+
+field_ranges(Selector, Fields) ->
+    field_ranges(Selector, Fields, []).
+
+
+field_ranges(_Selector, [], Acc) ->
+    lists:reverse(Acc);
+field_ranges(Selector, [Field | Rest], Acc) ->
+    case range(Selector, Field) of
+        empty ->
+            [{Field, empty}];
+        Range ->
+            field_ranges(Selector, Rest, [{Field, Range} | Acc])
+    end.
+
+
+% Find the complete range for a given index in this
+% selector. This works by AND'ing logical comparisons
+% together so that we can define the start and end
+% keys for a given index.
+%
+% Selector must have been normalized before calling
+% this function.
+range(Selector, Index) ->
+    range(Selector, Index, '$gt', mango_json:min(), '$lt', mango_json:max()).
+
+
+% Adjust Low and High based on values found for the
+% givend Index in Selector.
+range({[{<<"$and">>, Args}]}, Index, LCmp, Low, HCmp, High) ->
+    lists:foldl(fun
+        (Arg, {LC, L, HC, H}) ->
+            range(Arg, Index, LC, L, HC, H);
+        (_Arg, empty) ->
+            empty
+    end, {LCmp, Low, HCmp, High}, Args);
+
+% We can currently only traverse '$and' operators
+range({[{<<"$", _/binary>>}]}, _Index, LCmp, Low, HCmp, High) ->
+    {LCmp, Low, HCmp, High};
+
+% If the field name matches the index see if we can narrow
+% the acceptable range.
+range({[{Index, Cond}]}, Index, LCmp, Low, HCmp, High) ->
+    range(Cond, LCmp, Low, HCmp, High);
+
+% Else we have a field unrelated to this index so just
+% return the current values.
+range(_, _, LCmp, Low, HCmp, High) ->
+    {LCmp, Low, HCmp, High}.
+
+
+% The comments below are a bit cryptic at first but they show
+% where the Arg cand land in the current range.
+%
+% For instance, given:
+%
+%     {$lt: N}
+%     Low = 1
+%     High = 5
+%
+% Depending on the value of N we can have one of five locations
+% in regards to a given Low/High pair:
+%
+%     min low mid high max
+%
+%   That is:
+%       min = (N < Low)
+%       low = (N == Low)
+%       mid = (Low < N < High)
+%       high = (N == High)
+%       max = (High < N)
+%
+% If N < 1, (min) then the effective range is empty.
+%
+% If N == 1, (low) then we have to set the range to empty because
+% N < 1 && N >= 1 is an empty set. If the operator had been '$lte'
+% and LCmp was '$gte' or '$eq' then we could keep around the equality
+% check on Arg by setting LCmp == HCmp = '$eq' and Low == High == Arg.
+%
+% If 1 < N < 5 (mid), then we set High to Arg and Arg has just
+% narrowed our range. HCmp is set the the '$lt' operator that was
+% part of the input.
+%
+% If N == 5 (high), We just set HCmp to '$lt' since its guaranteed
+% to be equally or more restrictive than the current possible values
+% of '$lt' or '$lte'.
+%
+% If N > 5 (max), nothing changes as our current range is already
+% more narrow than the current condition.
+%
+% Obviously all of that logic gets tweaked for the other logical
+% operators but its all straight forward once you figure out how
+% we're basically just narrowing our logical ranges.
+
+range({[{<<"$lt">>, Arg}]}, LCmp, Low, HCmp, High) ->
+    case range_pos(Low, Arg, High) of
+        min ->
+            empty;
+        low ->
+            empty;
+        mid ->
+            {LCmp, Low, '$lt', Arg};
+        high ->
+            {LCmp, Low, '$lt', Arg};
+        max ->
+            {LCmp, Low, HCmp, High}
+    end;
+
+range({[{<<"$lte">>, Arg}]}, LCmp, Low, HCmp, High) ->
+    case range_pos(Low, Arg, High) of
+        min ->
+            empty;
+        low when LCmp == '$gte'; LCmp == '$eq' ->
+            {'$eq', Arg, '$eq', Arg};
+        low ->
+            empty;
+        mid ->
+            {LCmp, Low, '$lte', Arg};
+        high ->
+            {LCmp, Low, HCmp, High};
+        max ->
+            {LCmp, Low, HCmp, High}
+    end;
+
+range({[{<<"$eq">>, Arg}]}, LCmp, Low, HCmp, High) ->
+    case range_pos(Low, Arg, High) of
+        min ->
+            empty;
+        low when LCmp == '$gte'; LCmp == '$eq' ->
+            {'$eq', Arg, '$eq', Arg};
+        low ->
+            empty;
+        mid ->
+            {'$eq', Arg, '$eq', Arg};
+        high when HCmp == '$lte'; HCmp == '$eq' ->
+            {'$eq', Arg, '$eq', Arg};
+        high ->
+            empty;
+        max ->
+            empty
+    end;
+
+range({[{<<"$gte">>, Arg}]}, LCmp, Low, HCmp, High) ->
+    case range_pos(Low, Arg, High) of
+        min ->
+            {LCmp, Low, HCmp, High};
+        low ->
+            {LCmp, Low, HCmp, High};
+        mid ->
+            {'$gte', Arg, HCmp, High};
+        high when HCmp == '$lte'; HCmp == '$eq' ->
+            {'$eq', Arg, '$eq', Arg};
+        high ->
+            empty;
+        max ->
+            empty
+    end;
+
+range({[{<<"$gt">>, Arg}]}, LCmp, Low, HCmp, High) ->
+    case range_pos(Low, Arg, High) of
+        min ->
+            {LCmp, Low, HCmp, High};
+        low ->
+            {'$gt', Arg, HCmp, High};
+        mid ->
+            {'$gt', Arg, HCmp, High};
+        high ->
+            empty;
+        max ->
+            empty
+    end;
+
+% There's some other un-indexable restriction on the index
+% that will be applied as a post-filter. Ignore it and
+% carry on our merry way.
+range({[{<<"$", _/binary>>, _}]}, LCmp, Low, HCmp, High) ->
+    {LCmp, Low, HCmp, High}.
+
+
+% Returns the value min | low | mid | high | max depending
+% on how Arg compares to Low and High.
+range_pos(Low, Arg, High) ->
+    case mango_json:cmp(Arg, Low) of
+        N when N < 0 -> min;
+        N when N == 0 -> low;
+        _ ->
+            case mango_json:cmp(Arg, High) of
+                X when X < 0 ->
+                    mid;
+                X when X == 0 ->
+                    high;
+                _ ->
+                    max
+            end
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/7a9de344/src/mango_selector.erl
----------------------------------------------------------------------
diff --git a/src/mango_selector.erl b/src/mango_selector.erl
index d1c9898..dd16bf5 100644
--- a/src/mango_selector.erl
+++ b/src/mango_selector.erl
@@ -15,8 +15,6 @@
 
 -export([
     normalize/1,
-    index_fields/1,
-    range/2,
     match/2
 ]).
 
@@ -48,54 +46,6 @@ normalize(Selector) ->
     end,
     {NProps}.
 
-% This function returns a list of indexes that
-% can be used to restrict this query. This works by
-% searching the selector looking for field names that
-% can be "seen".
-%
-% Operators that can be seen through are '$and' and any of
-% the logical comparisons ('$lt', '$eq', etc). Things like
-% '$regex', '$in', '$nin', and '$or' can't be serviced by
-% a single index scan so we disallow them. In the future
-% we may become more clever and increase our ken such that
-% we will be able to see through these with crafty indexes
-% or new uses for existing indexes. For instance, I could
-% see an '$or' between comparisons on the same field becoming
-% the equivalent of a multi-query. But that's for another
-% day.
-
-% We can see through '$and' trivially
-index_fields({[{<<"$and">>, Args}]}) ->
-    lists:usort(lists:flatten([index_fields(A) || A <- Args]));
-
-% So far we can't see through any other operator
-index_fields({[{<<"$", _/binary>>, _}]}) ->
-    [];
-
-% If we have a field with a terminator that is locatable
-% using an index then the field is a possible index
-index_fields({[{Field, Cond}]}) ->
-    case indexable(Cond) of
-        true ->
-            [Field];
-        false ->
-            []
-    end;
-
-% An empty selector
-index_fields({[]}) ->
-    [].
-
-% Find the complete range for a given index in this
-% selector. This works by AND'ing logical comparisons
-% together so that we can define the start and end
-% keys for a given index.
-%
-% Selector must have been normalized before calling
-% this function.
-range(Selector, Index) ->
-    range(Selector, Index, '$gt', mango_json:min(), '$lt', mango_json:max()).
-
 
 % Match a selector against a #doc{} or EJSON value.
 % This assumes that the Selector has been normalized.
@@ -407,201 +357,6 @@ negate({[{Field, Cond}]}) ->
     {[{Field, negate(Cond)}]}.
 
 
-% Check if a condition is indexable. The logical
-% comparisons are mostly straight forward. We
-% currently don't understand '$in' which is
-% theoretically supportable. '$nin' and '$ne'
-% aren't currently supported because they require
-% multiple index scans.
-indexable({[{<<"$lt">>, _}]}) ->
-    true;
-indexable({[{<<"$lte">>, _}]}) ->
-    true;
-indexable({[{<<"$eq">>, _}]}) ->
-    true;
-indexable({[{<<"$gt">>, _}]}) ->
-    true;
-indexable({[{<<"$gte">>, _}]}) ->
-    true;
-
-% All other operators are currently not indexable.
-% This is also a subtle assertion that we don't
-% call indexable/1 on a field name.
-indexable({[{<<"$", _/binary>>, _}]}) ->
-    false.
-
-
-% Adjust Low and High based on values found for the
-% givend Index in Selector.
-range({[{<<"$and">>, Args}]}, Index, LCmp, Low, HCmp, High) ->
-    lists:foldl(fun
-        (Arg, {LC, L, HC, H}) ->
-            range(Arg, Index, LC, L, HC, H);
-        (_Arg, empty) ->
-            empty
-    end, {LCmp, Low, HCmp, High}, Args);
-
-% We can currently only traverse '$and' operators
-range({[{<<"$", _/binary>>}]}, _Index, LCmp, Low, HCmp, High) ->
-    {LCmp, Low, HCmp, High};
-
-% If the field name matches the index see if we can narrow
-% the acceptable range.
-range({[{Index, Cond}]}, Index, LCmp, Low, HCmp, High) ->
-    range(Cond, LCmp, Low, HCmp, High);
-
-% Else we have a field unrelated to this index so just
-% return the current values.
-range(_, _, LCmp, Low, HCmp, High) ->
-    {LCmp, Low, HCmp, High}.
-
-
-% The comments below are a bit cryptic at first but they show
-% where the Arg cand land in the current range.
-%
-% For instance, given:
-%
-%     {$lt: N}
-%     Low = 1
-%     High = 5
-%
-% Depending on the value of N we can have one of five locations
-% in regards to a given Low/High pair:
-%
-%     min low mid high max
-%
-%   That is:
-%       min = (N < Low)
-%       low = (N == Low)
-%       mid = (Low < N < High)
-%       high = (N == High)
-%       max = (High < N)
-%
-% If N < 1, (min) then the effective range is empty.
-%
-% If N == 1, (low) then we have to set the range to empty because
-% N < 1 && N >= 1 is an empty set. If the operator had been '$lte'
-% and LCmp was '$gte' or '$eq' then we could keep around the equality
-% check on Arg by setting LCmp == HCmp = '$eq' and Low == High == Arg.
-%
-% If 1 < N < 5 (mid), then we set High to Arg and Arg has just
-% narrowed our range. HCmp is set the the '$lt' operator that was
-% part of the input.
-%
-% If N == 5 (high), We just set HCmp to '$lt' since its guaranteed
-% to be equally or more restrictive than the current possible values
-% of '$lt' or '$lte'.
-%
-% If N > 5 (max), nothing changes as our current range is already
-% more narrow than the current condition.
-%
-% Obviously all of that logic gets tweaked for the other logical
-% operators but its all straight forward once you figure out how
-% we're basically just narrowing our logical ranges.
-
-range({[{<<"$lt">>, Arg}]}, LCmp, Low, HCmp, High) ->
-    case range_pos(Low, Arg, High) of
-        min ->
-            empty;
-        low ->
-            empty;
-        mid ->
-            {LCmp, Low, '$lt', Arg};
-        high ->
-            {LCmp, Low, '$lt', Arg};
-        max ->
-            {LCmp, Low, HCmp, High}
-    end;
-
-range({[{<<"$lte">>, Arg}]}, LCmp, Low, HCmp, High) ->
-    case range_pos(Low, Arg, High) of
-        min ->
-            empty;
-        low when LCmp == '$gte'; LCmp == '$eq' ->
-            {'$eq', Arg, '$eq', Arg};
-        low ->
-            empty;
-        mid ->
-            {LCmp, Low, '$lte', Arg};
-        high ->
-            {LCmp, Low, HCmp, High};
-        max ->
-            {LCmp, Low, HCmp, High}
-    end;
-
-range({[{<<"$eq">>, Arg}]}, LCmp, Low, HCmp, High) ->
-    case range_pos(Low, Arg, High) of
-        min ->
-            empty;
-        low when LCmp == '$gte'; LCmp == '$eq' ->
-            {'$eq', Arg, '$eq', Arg};
-        low ->
-            empty;
-        mid ->
-            {'$eq', Arg, '$eq', Arg};
-        high when HCmp == '$lte'; HCmp == '$eq' ->
-            {'$eq', Arg, '$eq', Arg};
-        high ->
-            empty;
-        max ->
-            empty
-    end;
-
-range({[{<<"$gte">>, Arg}]}, LCmp, Low, HCmp, High) ->
-    case range_pos(Low, Arg, High) of
-        min ->
-            {LCmp, Low, HCmp, High};
-        low ->
-            {LCmp, Low, HCmp, High};
-        mid ->
-            {'$gte', Arg, HCmp, High};
-        high when HCmp == '$lte'; HCmp == '$eq' ->
-            {'$eq', Arg, '$eq', Arg};
-        high ->
-            empty;
-        max ->
-            empty
-    end;
-
-range({[{<<"$gt">>, Arg}]}, LCmp, Low, HCmp, High) ->
-    case range_pos(Low, Arg, High) of
-        min ->
-            {LCmp, Low, HCmp, High};
-        low ->
-            {'$gt', Arg, HCmp, High};
-        mid ->
-            {'$gt', Arg, HCmp, High};
-        high ->
-            empty;
-        max ->
-            empty
-    end;
-
-% There's some other un-indexable restriction on the index
-% that will be applied as a post-filter. Ignore it and
-% carry on our merry way.
-range({[{<<"$", _/binary>>, _}]}, LCmp, Low, HCmp, High) ->
-    {LCmp, Low, HCmp, High}.
-
-
-% Returns the value min | low | mid | high | max depending
-% on how Arg compares to Low and High.
-range_pos(Low, Arg, High) ->
-    case mango_json:cmp(Arg, Low) of
-        N when N < 0 -> min;
-        N when N == 0 -> low;
-        _ ->
-            case mango_json:cmp(Arg, High) of
-                X when X < 0 ->
-                    mid;
-                X when X == 0 ->
-                    high;
-                _ ->
-                    max
-            end
-    end.
-
-
 match({[{<<"$and">>, Args}]}, Value, Cmp) ->
     Pred = fun(SubSel) -> match(SubSel, Value, Cmp) end,
     lists:all(Pred, Args);


[28/50] [abbrv] couchdb-mango git commit: Merge pull request #16 from cloudant/42707-always-use-dbname-cache-key

Posted by ro...@apache.org.
Merge pull request #16 from cloudant/42707-always-use-dbname-cache-key

Always use the DB name as the cache key

Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/195d5417
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/195d5417
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/195d5417

Branch: refs/heads/master
Commit: 195d5417bd5467da3bfd1b8dd618c81310ddb06d
Parents: 58fc27c 7bad11f
Author: Adam Kocoloski <ad...@cloudant.com>
Authored: Fri Dec 19 10:33:08 2014 -0500
Committer: Adam Kocoloski <ad...@cloudant.com>
Committed: Fri Dec 19 10:33:08 2014 -0500

----------------------------------------------------------------------
 src/mango_idx.erl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------



[25/50] [abbrv] couchdb-mango git commit: Parse each field only once

Posted by ro...@apache.org.
Parse each field only once

The get_field and set_field functions accepts parsed fields (i.e.,
Erlang arrays of path components). By exporting the parse_field function
we can halve the number of times we actually execute the parsing code.

BugzID: 42709


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/dd5a2d5a
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/dd5a2d5a
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/dd5a2d5a

Branch: refs/heads/master
Commit: dd5a2d5a326d720344a0c05c0203b289e1230637
Parents: ba40953
Author: Adam Kocoloski <ad...@cloudant.com>
Authored: Thu Dec 11 21:29:47 2014 -0500
Committer: Adam Kocoloski <ad...@cloudant.com>
Committed: Thu Dec 11 21:29:47 2014 -0500

----------------------------------------------------------------------
 src/mango_doc.erl    | 1 +
 src/mango_fields.erl | 5 +++--
 2 files changed, 4 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/dd5a2d5a/src/mango_doc.erl
----------------------------------------------------------------------
diff --git a/src/mango_doc.erl b/src/mango_doc.erl
index a2de4b2..0d5c731 100644
--- a/src/mango_doc.erl
+++ b/src/mango_doc.erl
@@ -10,6 +10,7 @@
 
     get_field/2,
     get_field/3,
+    parse_field/1,
     rem_field/2,
     set_field/3
 ]).

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/dd5a2d5a/src/mango_fields.erl
----------------------------------------------------------------------
diff --git a/src/mango_fields.erl b/src/mango_fields.erl
index 2e968b2..391a588 100644
--- a/src/mango_fields.erl
+++ b/src/mango_fields.erl
@@ -23,13 +23,14 @@ extract(Doc, all_fields) ->
     Doc;
 extract(Doc, Fields) ->
     lists:foldl(fun(F, NewDoc) ->
-        case mango_doc:get_field(Doc, F) of
+        {ok, Path} = mango_doc:parse_field(F),
+        case mango_doc:get_field(Doc, Path) of
             not_found ->
                 NewDoc;
             bad_path ->
                 NewDoc;
             Value ->
-                mango_doc:set_field(NewDoc, F, Value)
+                mango_doc:set_field(NewDoc, Path, Value)
         end
     end, {[]}, Fields).
 


[20/50] [abbrv] couchdb-mango git commit: Add ASF 2.0 license file

Posted by ro...@apache.org.
Add ASF 2.0 license file


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/910a6b6b
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/910a6b6b
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/910a6b6b

Branch: refs/heads/master
Commit: 910a6b6b35b45b9264ba584db7d423522b8a1d44
Parents: 77911e0
Author: Joan Touzet <wo...@apache.org>
Authored: Mon Nov 17 23:18:02 2014 +0100
Committer: Joan Touzet <wo...@apache.org>
Committed: Mon Nov 17 23:18:02 2014 +0100

----------------------------------------------------------------------
 LICENSE.txt | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 202 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/910a6b6b/LICENSE.txt
----------------------------------------------------------------------
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.


[33/50] [abbrv] couchdb-mango git commit: Fix trailing whitespace

Posted by ro...@apache.org.
Fix trailing whitespace


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/fc8d7d0f
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/fc8d7d0f
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/fc8d7d0f

Branch: refs/heads/master
Commit: fc8d7d0fd8886a91fc3223a0a9d01df33b1e132c
Parents: bb30744
Author: Paul J. Davis <pa...@gmail.com>
Authored: Tue Jan 13 17:11:35 2015 -0600
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Fri Jan 16 13:31:33 2015 -0600

----------------------------------------------------------------------
 src/mango_opts.erl | 2 +-
 src/mango_sort.erl | 3 ---
 2 files changed, 1 insertion(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/fc8d7d0f/src/mango_opts.erl
----------------------------------------------------------------------
diff --git a/src/mango_opts.erl b/src/mango_opts.erl
index 45784a9..43c2f31 100644
--- a/src/mango_opts.erl
+++ b/src/mango_opts.erl
@@ -25,7 +25,7 @@
     is_pos_integer/1,
     is_non_neg_integer/1,
     is_object/1,
-    
+
     validate_idx_name/1,
     validate_selector/1,
     validate_sort/1,

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/fc8d7d0f/src/mango_sort.erl
----------------------------------------------------------------------
diff --git a/src/mango_sort.erl b/src/mango_sort.erl
index 717099b..796ed33 100644
--- a/src/mango_sort.erl
+++ b/src/mango_sort.erl
@@ -71,6 +71,3 @@ validate({Props}) ->
         _ ->
             ?MANGO_ERROR({unsupported, mixed_sort})
     end.
-        
-    
-


[18/50] [abbrv] couchdb-mango git commit: Cloudant -> Apache CouchDB in README.md

Posted by ro...@apache.org.
Cloudant -> Apache CouchDB in README.md

Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/e9ced733
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/e9ced733
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/e9ced733

Branch: refs/heads/master
Commit: e9ced7331ae4c1b07f86aae8c7135e364c2af2bd
Parents: 18c6718
Author: Joan Touzet <wo...@users.noreply.github.com>
Authored: Mon Nov 17 09:50:56 2014 +0100
Committer: Joan Touzet <wo...@users.noreply.github.com>
Committed: Mon Nov 17 09:50:56 2014 +0100

----------------------------------------------------------------------
 README.md | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/e9ced733/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 9f7605a..d7dadf8 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,13 @@
 Mango
 =====
 
-A MongoDB inspired query language interface for Cloudant.
+A MongoDB inspired query language interface for Apache CouchDB.
 
 
 Motivation
 ----------
 
-Mango provides a single HTTP API endpoint that accepts JSON bodies via HTTP POST. These bodies provide a set of instructions that will be handled with the results being returned to the client in the same order as they were specified. The general principle of this API is to be simple to implement on the client side while providing users a more natural conversion to Cloudant/Apache CouchDB than would otherwise exist using the standard RESTful HTTP interface that already exists.
+Mango provides a single HTTP API endpoint that accepts JSON bodies via HTTP POST. These bodies provide a set of instructions that will be handled with the results being returned to the client in the same order as they were specified. The general principle of this API is to be simple to implement on the client side while providing users a more natural conversion to Apache CouchDB than would otherwise exist using the standard RESTful HTTP interface that already exists.
 
 
 Actions
@@ -20,10 +20,10 @@ have a string value indicating the action to be performed. For each action there
 
 For convenience, the HTTP API will accept a JSON body that is either a single JSON object which specifies a single action or a JSON array that specifies a list of actions that will then be invoked serially. While multiple commands can be batched into a single HTTP request, there are no guarantees about atomicity or isolation for a batch of commands.
 
-Activating Mango/Cloudant Query on a cluster
+Activating Query on a cluster
 --------------------------------------------
 
-Cloudant Query can be enabled by setting the following config:
+Query can be enabled by setting the following config:
 
 ```
 rpc:multicall(config, set, ["native_query_servers", "query", "{mango_native_proc, start_link, []}"]).
@@ -32,7 +32,7 @@ rpc:multicall(config, set, ["native_query_servers", "query", "{mango_native_proc
 HTTP API
 ========
 
-This API adds a single URI endpoint to the existing Cloudant HTTP API. Creating databases, authentication, Map/Reduce views, etc are all still supported exactly as currently document. No existing behavior is changed.
+This API adds a single URI endpoint to the existing CouchDB HTTP API. Creating databases, authentication, Map/Reduce views, etc are all still supported exactly as currently document. No existing behavior is changed.
 
 The endpoint added is for the URL pattern `/dbname/_query` and has the following characteristics:
 


[42/50] [abbrv] couchdb-mango git commit: Introduce the $text operator to mango_selector

Posted by ro...@apache.org.
Introduce the $text operator to mango_selector

This just allows us to handle the $text operator in a selector. There is
a bit of a subtlety in that we apply the $text operator against a field
called $default which looks like an operator. We do this becaus the
query syntax for $text doesn't include a field which confuses a bit of
our other logic which always expects a field name to be present.

BugzId: 33294


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/8f3b355d
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/8f3b355d
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/8f3b355d

Branch: refs/heads/master
Commit: 8f3b355dc5ffac849aa458ef69aecfa0287c43c4
Parents: 1b0426a
Author: Paul J. Davis <pa...@gmail.com>
Authored: Fri Jan 9 15:49:36 2015 -0600
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Fri Jan 16 13:32:50 2015 -0600

----------------------------------------------------------------------
 src/mango_selector.erl | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/8f3b355d/src/mango_selector.erl
----------------------------------------------------------------------
diff --git a/src/mango_selector.erl b/src/mango_selector.erl
index dd16bf5..4da394d 100644
--- a/src/mango_selector.erl
+++ b/src/mango_selector.erl
@@ -132,6 +132,19 @@ norm_ops({[{<<"$size">>, Arg}]}) when is_integer(Arg), Arg >= 0 ->
 norm_ops({[{<<"$size">>, Arg}]}) ->
     ?MANGO_ERROR({bad_arg, '$size', Arg});
 
+norm_ops({[{<<"$text">>, Arg}]}) when is_binary(Arg); is_number(Arg);
+        is_boolean(Arg) ->
+    {[{<<"$default">>, {[{<<"$text">>, Arg}]}}]};
+norm_ops({[{<<"$text">>, Arg}]}) ->
+    ?MANGO_ERROR({bad_arg, '$text', Arg});
+
+% Not technically an operator but we pass it through here
+% so that this function accepts its own output. This exists
+% so that $text can have a field name value which simplifies
+% logic elsewhere.
+norm_ops({[{<<"$default">>, _}]} = Selector) ->
+    Selector;
+
 % Terminals where we can't perform any validation
 % on the value because any value is acceptable.
 norm_ops({[{<<"$lt">>, _}]} = Cond) ->
@@ -224,6 +237,17 @@ norm_fields({[{<<"$elemMatch">>, Arg}]}, Path) ->
     Cond = {[{<<"$elemMatch">>, norm_fields(Arg)}]},
     {[{Path, Cond}]};
 
+
+% The text operator operates against the internal
+% $default field. This also asserts that the $default
+% field is at the root as well as that it only has
+% a $text operator applied.
+norm_fields({[{<<"$default">>, {[{<<"$text">>, _Arg}]}}]}=Sel, <<>>) ->
+    Sel;
+norm_fields({[{<<"$default">>, _}]} = Selector, _) ->
+    ?MANGO_ERROR({bad_field, Selector});
+
+
 % Any other operator is a terminal below which no
 % field names should exist. Set the path to this
 % terminal and return it.
@@ -461,6 +485,11 @@ match({[{<<"$size">>, Arg}]}, Values, _Cmp) when is_list(Values) ->
 match({[{<<"$size">>, _}]}, _Value, _Cmp) ->
     false;
 
+% We don't have any choice but to believe that the text
+% index returned valid matches
+match({[{<<"$default">>, _}]}, _Value, _Cmp) ->
+    true;
+
 % All other operators are internal assertion errors for
 % matching because we either should've removed them during
 % normalization or something else broke.


[15/50] [abbrv] couchdb-mango git commit: Format Condense Fold

Posted by ro...@apache.org.
Format Condense Fold

- Make foldl less condense and easier to read.
- 38863-in-operator-arrays


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/52544a5e
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/52544a5e
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/52544a5e

Branch: refs/heads/master
Commit: 52544a5e1db47178791f757f7b6e21707acaf00d
Parents: 2d0cd0c
Author: Tony Sun <ll...@Tonys-MacBook-Pro.local>
Authored: Wed Nov 5 09:58:04 2014 -0800
Committer: Tony Sun <ll...@Tonys-MacBook-Pro.local>
Committed: Wed Nov 5 09:58:04 2014 -0800

----------------------------------------------------------------------
 src/mango_selector.erl | 7 +++++++
 1 file changed, 7 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/52544a5e/src/mango_selector.erl
----------------------------------------------------------------------
diff --git a/src/mango_selector.erl b/src/mango_selector.erl
index d392abe..97f4123 100644
--- a/src/mango_selector.erl
+++ b/src/mango_selector.erl
@@ -652,6 +652,13 @@ match({[{<<"$gt">>, Arg}]}, Value, Cmp) ->
     Cmp(Value, Arg) > 0;
 
 match({[{<<"$in">>, Args}]}, Values, Cmp) when is_list(Values)->
+    Pred = fun(Arg) ->
+        lists:foldl(fun(Value,Match) ->
+            (Cmp(Value, Arg) == 0) or Match
+        end, false, Values)
+    end,
+    lists:any(Pred, Args);
+match({[{<<"$in">>, Args}]}, Values, Cmp) when is_list(Values)->
     Pred = fun(Arg) -> lists:foldl(fun(Value,Match) ->
         (Cmp(Value, Arg) == 0) or Match end, false, Values)
     end,


[26/50] [abbrv] couchdb-mango git commit: Merge pull request #13 from cloudant/42709-faster-field-filters

Posted by ro...@apache.org.
Merge pull request #13 from cloudant/42709-faster-field-filters

Optimize field filters

Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/58fc27ca
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/58fc27ca
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/58fc27ca

Branch: refs/heads/master
Commit: 58fc27cad3e5f06fb6023690f20861de0a06c739
Parents: 072d3f2 dd5a2d5
Author: Adam Kocoloski <ad...@cloudant.com>
Authored: Mon Dec 15 14:48:43 2014 -0500
Committer: Adam Kocoloski <ad...@cloudant.com>
Committed: Mon Dec 15 14:48:43 2014 -0500

----------------------------------------------------------------------
 src/mango_doc.erl    | 18 ++++++++++++++++++
 src/mango_fields.erl |  5 +++--
 2 files changed, 21 insertions(+), 2 deletions(-)
----------------------------------------------------------------------



[02/50] [abbrv] couchdb-mango git commit: Correctly set the ddoc language on recreation

Posted by ro...@apache.org.
Correctly set the ddoc language on recreation

If a design document was recreated it wasn't properly setting the
language elment which defaults to JavaScript. This lead to issues with
operations that appeared to succeed but actually failed behind the
scenes.

BugzId: 31919


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/2744ddfd
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/2744ddfd
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/2744ddfd

Branch: refs/heads/master
Commit: 2744ddfd9e72cb36d5fb463d3c3fd387d3c6bfb4
Parents: 83fb0db
Author: Paul J. Davis <pa...@gmail.com>
Authored: Tue Jun 24 14:58:17 2014 -0500
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Tue Jun 24 14:58:17 2014 -0500

----------------------------------------------------------------------
 src/mango_error.erl        |  7 +++++++
 src/mango_util.erl         | 16 +++++++++++++++-
 test/01-index-crud-test.py | 16 ++++++++++++++++
 3 files changed, 38 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/2744ddfd/src/mango_error.erl
----------------------------------------------------------------------
diff --git a/src/mango_error.erl b/src/mango_error.erl
index f34dac5..45ffe56 100644
--- a/src/mango_error.erl
+++ b/src/mango_error.erl
@@ -216,6 +216,13 @@ info(mango_sort, {unsupported, mixed_sort}) ->
         <<"Sorts currently only support a single direction for all fields.">>
     };
 
+info(mango_util, {invalid_ddoc_lang, Lang}) ->
+    {
+        400,
+        <<"invalid_ddoc_lang">>,
+        fmt("Existing design doc has an invalid language: ~w", [Lang])
+    };
+
 info(Module, Reason) ->
     {
         500,

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/2744ddfd/src/mango_util.erl
----------------------------------------------------------------------
diff --git a/src/mango_util.erl b/src/mango_util.erl
index d3dd279..ff7d654 100644
--- a/src/mango_util.erl
+++ b/src/mango_util.erl
@@ -49,7 +49,7 @@ open_ddocs(Db) ->
 load_ddoc(Db, DDocId) ->
     case mango_util:open_doc(Db, DDocId) of
         {ok, Doc} ->
-            {ok, Doc};
+            {ok, check_lang(Doc)};
         not_found ->
             Body = {[
                 {<<"language">>, <<"query">>}
@@ -135,6 +135,20 @@ assert_ejson_arr([Val | Rest]) ->
     end.
 
 
+check_lang(#doc{id = Id, deleted = true} = Doc) ->
+    Body = {[
+        {<<"language">>, <<"query">>}
+    ]},
+    #doc{id = Id, body = Body};
+check_lang(#doc{body = {Props}} = Doc) ->
+    case lists:keyfind(<<"language">>, 1, Props) of
+        {<<"language">>, <<"query">>} ->
+            Doc;
+        Else ->
+            ?MANGO_ERROR({invalid_ddoc_lang, Else})
+    end.
+
+
 to_lower(Key) when is_binary(Key) ->
     KStr = binary_to_list(Key),
     KLower = string:to_lower(KStr),

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/2744ddfd/test/01-index-crud-test.py
----------------------------------------------------------------------
diff --git a/test/01-index-crud-test.py b/test/01-index-crud-test.py
index 35b1301..b6ef786 100644
--- a/test/01-index-crud-test.py
+++ b/test/01-index-crud-test.py
@@ -170,6 +170,22 @@ def test_delete_idx_no_design():
     assert pre_indexes == post_indexes
 
 
+def test_recreate_index():
+    db = mkdb()
+    pre_indexes = db.list_indexes()
+    for i in range(5):
+        ret = db.create_index(["bing"], name="idx_recreate")
+        assert ret is True
+        for idx in db.list_indexes():
+            if idx["name"] != "idx_recreate":
+                continue
+            assert idx["def"]["fields"] == [{"bing": "asc"}]
+            db.delete_index(idx["ddoc"], idx["name"])
+            break
+        post_indexes = db.list_indexes()
+        assert pre_indexes == post_indexes
+
+
 def test_delete_misisng():
     db = mkdb()
 


[11/50] [abbrv] couchdb-mango git commit: Support escaped period character in query

Posted by ro...@apache.org.
Support escaped period character in query

If a document has a field with a period character in it, a query
issued with an optional parameter 'fields' will not return this
field because it'll clash with the selector's syntax.

This patch addresses it by allowing to escape a period character
in the request with a backslash.

BugzID: 38248


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/2db372bf
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/2db372bf
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/2db372bf

Branch: refs/heads/master
Commit: 2db372bf966c5fd239593de4e1cd1702dd562c30
Parents: 4bee6d1
Author: Eric Avdey <e....@cloudant.com>
Authored: Tue Nov 4 10:46:15 2014 -0400
Committer: Eric Avdey <e....@cloudant.com>
Committed: Tue Nov 4 14:58:49 2014 -0400

----------------------------------------------------------------------
 .gitignore                 |  1 +
 src/mango_doc.erl          | 44 ++++++++++++++++++++++++-----------------
 test/02-basic-find-test.py | 30 ++++++++++++++++++++++++++--
 test/user_docs.py          | 29 ++++++++++++++++++++++++++-
 4 files changed, 83 insertions(+), 21 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/2db372bf/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index 665827a..50d43d8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 ebin/
 test/*.pyc
 venv/
+.eunit

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/2db372bf/src/mango_doc.erl
----------------------------------------------------------------------
diff --git a/src/mango_doc.erl b/src/mango_doc.erl
index 97e8574..666ef9b 100644
--- a/src/mango_doc.erl
+++ b/src/mango_doc.erl
@@ -360,12 +360,7 @@ get_field(Props, Field) ->
 
 
 get_field(Props, Field, Validator) when is_binary(Field) ->
-    Path = re:split(Field, <<"\\.">>),
-    lists:foreach(fun(P) ->
-        if P /= <<>> -> ok; true ->
-            ?MANGO_ERROR({invalid_field_name, Field})
-        end
-    end, Path),
+    {ok, Path} = parse_field(Field),
     get_field(Props, Path, Validator);
 get_field(Props, [], no_validation) ->
     Props;
@@ -403,12 +398,7 @@ get_field(_, [_|_], _) ->
 
 
 rem_field(Props, Field) when is_binary(Field) ->
-    Path = re:split(Field, <<"\\.">>),
-    lists:foreach(fun(P) ->
-        if P /= <<>> -> ok; true ->
-            ?MANGO_ERROR({invalid_field_name, Field})
-        end
-    end, Path),
+    {ok, Path} = parse_field(Field),
     rem_field(Props, Path);
 rem_field({Props}, [Name]) ->
     case lists:keytake(Name, 1, Props) of
@@ -469,12 +459,7 @@ rem_field(_, [_|_]) ->
 
 
 set_field(Props, Field, Value) when is_binary(Field) ->
-    Path = re:split(Field, <<"\\.">>),
-    lists:foreach(fun(P) ->
-        if P /= <<>> -> ok; true ->
-            ?MANGO_ERROR({invalid_field_name, Field})
-        end
-    end, Path),
+    {ok, Path} = parse_field(Field),
     set_field(Props, Path, Value);
 set_field({Props}, [Name], Value) ->
     {lists:keystore(Name, 1, Props, {Name, Value})};
@@ -538,3 +523,26 @@ set_elem(1, [_ | Rest], Value) ->
     [Value | Rest];
 set_elem(I, [Item | Rest], Value) when I > 1 ->
     [Item | set_elem(I-1, Rest, Value)].
+
+parse_field(Field) ->
+    Path = lists:map(fun
+        (P) when P =:= <<>> ->
+            ?MANGO_ERROR({invalid_field_name, Field});
+        (P) ->
+            re:replace(P, <<"\\\\">>, <<>>, [global, {return, binary}])
+    end, re:split(Field, <<"(?<!\\\\)\\.">>)),
+    {ok, Path}.
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+parse_field_test() ->
+    ?assertEqual({ok, [<<"ab">>]}, parse_field(<<"ab">>)),
+    ?assertEqual({ok, [<<"a">>, <<"b">>]}, parse_field(<<"a.b">>)),
+    ?assertEqual({ok, [<<"a.b">>]}, parse_field(<<"a\\.b">>)),
+    ?assertEqual({ok, [<<"a">>, <<"b">>, <<"c">>]}, parse_field(<<"a.b.c">>)),
+    ?assertEqual({ok, [<<"a">>, <<"b.c">>]}, parse_field(<<"a.b\\.c">>)),
+    Exception = {mango_error, ?MODULE, {invalid_field_name, <<"a..b">>}},
+    ?assertThrow(Exception, parse_field(<<"a..b">>)).
+
+-endif.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/2db372bf/test/02-basic-find-test.py
----------------------------------------------------------------------
diff --git a/test/02-basic-find-test.py b/test/02-basic-find-test.py
index 4287138..f897ddb 100644
--- a/test/02-basic-find-test.py
+++ b/test/02-basic-find-test.py
@@ -1,7 +1,6 @@
-
+# -*- coding: latin-1 -*-
 import user_docs
 
-
 def setup():
     user_docs.create_db_and_indexes()
 
@@ -213,6 +212,33 @@ def test_missing_not_indexed():
     assert docs[2]["user_id"] == 0
     assert docs[3]["user_id"] == 13
 
+def test_dot_key():
+    db = user_docs.mkdb()
+    fields = ["title", "dot\\.key", "none.dot"]
+    docs = db.find({"type": "complex_key"}, fields = fields)
+    assert len(docs) == 4
+    assert docs[1].has_key("dot.key")
+    assert docs[1]["dot.key"] == "dot's value"
+    assert docs[1].has_key("none")
+    assert docs[1]["none"]["dot"] == "none dot's value"
+
+def test_peso_key():
+    db = user_docs.mkdb()
+    fields = ["title", "$key", "deep.$key"]
+    docs = db.find({"type": "complex_key"}, fields = fields)
+    assert len(docs) == 4
+    assert docs[2].has_key("$key")
+    assert docs[2]["$key"] == "peso"
+    assert docs[2].has_key("deep")
+    assert docs[2]["deep"]["$key"] == "deep peso"
+
+def test_unicode_key():
+    db = user_docs.mkdb()
+    docs = db.find({"type": "complex_key"}, fields = ["title", ""])
+    assert len(docs) == 4
+    # note:  == \uf8ff
+    assert docs[3].has_key(u'\uf8ff')
+    assert docs[3][u'\uf8ff'] == "apple"
 
 def test_limit():
     db = user_docs.mkdb()

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/2db372bf/test/user_docs.py
----------------------------------------------------------------------
diff --git a/test/user_docs.py b/test/user_docs.py
index 36f2d2d..d129f59 100644
--- a/test/user_docs.py
+++ b/test/user_docs.py
@@ -1,3 +1,4 @@
+# -*- coding: latin-1 -*-
 """
 Generated with http://www.json-generator.com/
 
@@ -65,7 +66,8 @@ def create_db_and_indexes():
         ["manager"],
         ["favorites"],
         ["favorites.3"],
-        ["twitter"]
+        ["twitter"],
+        ["type"]
     ]
     for idx in indexes:
         assert db.create_index(idx) is True
@@ -455,5 +457,30 @@ DOCS = [
             "Lisp",
             "Lisp"
         ]
+    },
+    {
+        "type": "complex_key",
+        "title": "normal key"
+    },
+    {
+        "type": "complex_key",
+        "title": "key with dot",
+        "dot.key": "dot's value",
+        "none": {
+            "dot": "none dot's value"
+        }
+    },
+    {
+        "type": "complex_key",
+        "title": "key with peso",
+        "$key": "peso",
+        "deep": {
+            "$key": "deep peso"
+        }
+    },
+    {
+        "type": "complex_key",
+        "title": "unicode key",
+        "": "apple"
     }
 ]


[40/50] [abbrv] couchdb-mango git commit: Implement the _explain endpoint

Posted by ro...@apache.org.
Implement the _explain endpoint

This returns a JSON blob describing a provided query. It accepts the
same exact POST body parameters as _find but instead of returning query
results it returns a description of the query that would be executed.
This is intended for debugging purposes.

BugzId: 33294


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/812cf6bd
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/812cf6bd
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/812cf6bd

Branch: refs/heads/master
Commit: 812cf6bd043f4c9f9b93f0f7eebca4a05df3b2ec
Parents: 06bf775
Author: Paul J. Davis <pa...@gmail.com>
Authored: Fri Jan 9 15:43:51 2015 -0600
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Fri Jan 16 13:32:50 2015 -0600

----------------------------------------------------------------------
 src/mango_crud.erl        | 10 +++++++++-
 src/mango_cursor.erl      | 23 +++++++++++++++++++++++
 src/mango_cursor_view.erl | 12 ++++++++++++
 src/mango_httpd.erl       | 12 ++++++++++++
 test/mango.py             | 13 ++++++++++---
 5 files changed, 66 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/812cf6bd/src/mango_crud.erl
----------------------------------------------------------------------
diff --git a/src/mango_crud.erl b/src/mango_crud.erl
index 159d5f7..68c9d6c 100644
--- a/src/mango_crud.erl
+++ b/src/mango_crud.erl
@@ -16,7 +16,8 @@
     insert/3,
     find/5,
     update/4,
-    delete/3
+    delete/3,
+    explain/3
 ]).
 
 -export([
@@ -98,6 +99,13 @@ delete(Db, Selector, Options) ->
     end.
 
 
+explain(Db, Selector, Opts0) ->
+    Opts1 = maybe_add_user_ctx(Db, Opts0),
+    Opts2 = maybe_int_to_str(r, Opts1),
+    {ok, Cursor} = mango_cursor:create(Db, Selector, Opts2),
+    mango_cursor:explain(Cursor).
+
+
 maybe_add_user_ctx(Db, Opts) ->
     case lists:keyfind(user_ctx, 1, Opts) of
         {user_ctx, _} ->

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/812cf6bd/src/mango_cursor.erl
----------------------------------------------------------------------
diff --git a/src/mango_cursor.erl b/src/mango_cursor.erl
index 5bfe383..d4d5523 100644
--- a/src/mango_cursor.erl
+++ b/src/mango_cursor.erl
@@ -15,6 +15,7 @@
 
 -export([
     create/3,
+    explain/1,
     execute/3
 ]).
 
@@ -49,6 +50,28 @@ create(Db, Selector0, Opts) ->
     create_cursor(Db, UsableIndexes, Selector, Opts).
 
 
+explain(#cursor{}=Cursor) ->
+    #cursor{
+        index = Idx,
+        selector = Selector,
+        opts = Opts0,
+        limit = Limit,
+        skip = Skip,
+        fields = Fields
+    } = Cursor,
+    Mod = mango_idx:cursor_mod(Idx),
+    Opts = lists:keydelete(user_ctx, 1, Opts0),
+    {[
+        {dbname, mango_idx:dbname(Idx)},
+        {index, mango_idx:to_json(Idx)},
+        {selector, Selector},
+        {opts, {Opts}},
+        {limit, Limit},
+        {skip, Skip},
+        {fields, Fields}
+    ] ++ Mod:explain(Cursor)}.
+
+
 execute(#cursor{index=Idx}=Cursor, UserFun, UserAcc) ->
     Mod = mango_idx:cursor_mod(Idx),
     Mod:execute(Cursor, UserFun, UserAcc).

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/812cf6bd/src/mango_cursor_view.erl
----------------------------------------------------------------------
diff --git a/src/mango_cursor_view.erl b/src/mango_cursor_view.erl
index 798b713..b32353d 100644
--- a/src/mango_cursor_view.erl
+++ b/src/mango_cursor_view.erl
@@ -14,6 +14,7 @@
 
 -export([
     create/4,
+    explain/1,
     execute/3
 ]).
 
@@ -47,6 +48,17 @@ create(Db, Indexes, Selector, Opts) ->
     }}.
 
 
+explain(Cursor) ->
+    #cursor{
+        index = Idx,
+        ranges = Ranges
+    } = Cursor,
+    [{range, {[
+        {start_key, mango_idx:start_key(Idx, Ranges)},
+        {end_key, mango_idx:end_key(Idx, Ranges)}
+    ]}}].
+
+
 execute(#cursor{db = Db, index = Idx} = Cursor0, UserFun, UserAcc) ->
     Cursor = Cursor0#cursor{
         user_fun = UserFun,

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/812cf6bd/src/mango_httpd.erl
----------------------------------------------------------------------
diff --git a/src/mango_httpd.erl b/src/mango_httpd.erl
index 02e26ad..ed1e9bc 100644
--- a/src/mango_httpd.erl
+++ b/src/mango_httpd.erl
@@ -41,6 +41,8 @@ handle_req(#httpd{} = Req, Db0) ->
 
 handle_req_int(#httpd{path_parts=[_, <<"_index">> | _]} = Req, Db) ->
     handle_index_req(Req, Db);
+handle_req_int(#httpd{path_parts=[_, <<"_explain">> | _]} = Req, Db) ->
+    handle_explain_req(Req, Db);
 handle_req_int(#httpd{path_parts=[_, <<"_find">> | _]} = Req, Db) ->
     handle_find_req(Req, Db);
 handle_req_int(_, _) ->
@@ -119,6 +121,16 @@ handle_index_req(Req, _Db) ->
     chttpd:send_method_not_allowed(Req, "GET,POST,DELETE").
 
 
+handle_explain_req(#httpd{method='POST'}=Req, Db) ->
+    {ok, Opts0} = mango_opts:validate_find(chttpd:json_body_obj(Req)),
+    {value, {selector, Sel}, Opts} = lists:keytake(selector, 1, Opts0),
+    Resp = mango_crud:explain(Db, Sel, Opts),
+    chttpd:send_json(Req, Resp);
+
+handle_explain_req(Req, _Db) ->
+    chttpd:send_method_not_allowed(Req, "POST").
+
+
 handle_find_req(#httpd{method='POST'}=Req, Db) ->
     {ok, Opts0} = mango_opts:validate_find(chttpd:json_body_obj(Req)),
     {value, {selector, Sel}, Opts} = lists:keytake(selector, 1, Opts0),

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/812cf6bd/test/mango.py
----------------------------------------------------------------------
diff --git a/test/mango.py b/test/mango.py
index c28f280..fac4e7e 100644
--- a/test/mango.py
+++ b/test/mango.py
@@ -107,7 +107,7 @@ class Database(object):
         r.raise_for_status()
 
     def find(self, selector, limit=25, skip=0, sort=None, fields=None,
-                r=1, conflicts=False):
+                r=1, conflicts=False, explain=False):
         body = {
             "selector": selector,
             "limit": limit,
@@ -120,9 +120,16 @@ class Database(object):
         if fields is not None:
             body["fields"] = fields
         body = json.dumps(body)
-        r = self.sess.post(self.path("_find"), data=body)
+        if explain:
+            path = self.path("_explain")
+        else:
+            path = self.path("_find")
+        r = self.sess.post(path, data=body)
         r.raise_for_status()
-        return r.json()["docs"]
+        if explain:
+            return r.json()
+        else:
+            return r.json()["docs"]
 
     def find_one(self, *args, **kwargs):
         results = self.find(*args, **kwargs)


[03/50] [abbrv] couchdb-mango git commit: Minor test case fixes

Posted by ro...@apache.org.
Minor test case fixes

A minor race condition in recreating the database would lead to
occasional 500's when cycling the test db. I also added a deep copy in
user_docs.py or else the _revs would exist on the second test which
generated conflicts.


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/29dc32c3
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/29dc32c3
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/29dc32c3

Branch: refs/heads/master
Commit: 29dc32c3fd522feda577ccad58723d487b0e9d06
Parents: 2744ddf
Author: Paul J. Davis <pa...@gmail.com>
Authored: Tue Jun 24 15:08:26 2014 -0500
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Tue Jun 24 15:08:26 2014 -0500

----------------------------------------------------------------------
 test/04-empty-selectors-test.py | 36 ++++++++++++++++++++++++++++++++++++
 test/04-empty-selectors.py      | 36 ------------------------------------
 test/mango.py                   |  3 +++
 test/user_docs.py               |  6 +++---
 4 files changed, 42 insertions(+), 39 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/29dc32c3/test/04-empty-selectors-test.py
----------------------------------------------------------------------
diff --git a/test/04-empty-selectors-test.py b/test/04-empty-selectors-test.py
new file mode 100644
index 0000000..ba05ea3
--- /dev/null
+++ b/test/04-empty-selectors-test.py
@@ -0,0 +1,36 @@
+
+import user_docs
+
+
+def setup():
+    user_docs.create_db_and_indexes()
+
+
+def test_empty():
+    db = user_docs.mkdb()
+    try:
+        db.find({})
+    except Exception, e:
+        assert e.response.status_code == 400
+    else:
+        raise AssertionError("bad find")
+
+
+def test_empty_subsel():
+    db = user_docs.mkdb()
+    docs = db.find({
+            "_id": {"$gt": None},
+            "location": {}
+        })
+    assert len(docs) == 0
+
+
+def test_empty_subsel_match():
+    db = user_docs.mkdb()
+    db.save_docs([{"user_id": "eo", "empty_obj": {}}])
+    docs = db.find({
+            "_id": {"$gt": None},
+            "empty_obj": {}
+        })
+    assert len(docs) == 1
+    assert docs[0]["user_id"] == "eo"

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/29dc32c3/test/04-empty-selectors.py
----------------------------------------------------------------------
diff --git a/test/04-empty-selectors.py b/test/04-empty-selectors.py
deleted file mode 100644
index ba05ea3..0000000
--- a/test/04-empty-selectors.py
+++ /dev/null
@@ -1,36 +0,0 @@
-
-import user_docs
-
-
-def setup():
-    user_docs.create_db_and_indexes()
-
-
-def test_empty():
-    db = user_docs.mkdb()
-    try:
-        db.find({})
-    except Exception, e:
-        assert e.response.status_code == 400
-    else:
-        raise AssertionError("bad find")
-
-
-def test_empty_subsel():
-    db = user_docs.mkdb()
-    docs = db.find({
-            "_id": {"$gt": None},
-            "location": {}
-        })
-    assert len(docs) == 0
-
-
-def test_empty_subsel_match():
-    db = user_docs.mkdb()
-    db.save_docs([{"user_id": "eo", "empty_obj": {}}])
-    docs = db.find({
-            "_id": {"$gt": None},
-            "empty_obj": {}
-        })
-    assert len(docs) == 1
-    assert docs[0]["user_id"] == "eo"

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/29dc32c3/test/mango.py
----------------------------------------------------------------------
diff --git a/test/mango.py b/test/mango.py
index 92e99d8..92c2a2c 100644
--- a/test/mango.py
+++ b/test/mango.py
@@ -1,5 +1,6 @@
 
 import json
+import time
 
 import requests
 
@@ -34,7 +35,9 @@ class Database(object):
 
     def recreate(self):
         self.delete()
+        time.sleep(1)
         self.create()
+        time.sleep(1)
 
     def save_doc(self, doc):
         self.save_docs([doc])

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/29dc32c3/test/user_docs.py
----------------------------------------------------------------------
diff --git a/test/user_docs.py b/test/user_docs.py
index a108bf2..36f2d2d 100644
--- a/test/user_docs.py
+++ b/test/user_docs.py
@@ -37,6 +37,7 @@ With this pattern:
 ]
 """
 
+import copy
 import time
 
 import mango
@@ -49,8 +50,7 @@ def mkdb():
 def create_db_and_indexes():
     db = mkdb()
     db.recreate()
-    time.sleep(1)
-    db.save_docs(DOCS)
+    db.save_docs(copy.deepcopy(DOCS))
     indexes = [
         ["user_id"],
         ["name.last", "name.first"],
@@ -456,4 +456,4 @@ DOCS = [
             "Lisp"
         ]
     }
-]
\ No newline at end of file
+]


[12/50] [abbrv] couchdb-mango git commit: Update README file

Posted by ro...@apache.org.
Update README file

Update README to include an information about added escape functionality.

Fix a typo in an old test's name


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/80253c45
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/80253c45
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/80253c45

Branch: refs/heads/master
Commit: 80253c45c6fe19035c011de5c7399126db175864
Parents: 2db372b
Author: Eric Avdey <e....@cloudant.com>
Authored: Tue Nov 4 12:25:46 2014 -0400
Committer: Eric Avdey <e....@cloudant.com>
Committed: Tue Nov 4 15:04:05 2014 -0400

----------------------------------------------------------------------
 README.md                  | 12 +++++++++---
 test/01-index-crud-test.py |  2 +-
 2 files changed, 10 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/80253c45/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 05bc128..9f7605a 100644
--- a/README.md
+++ b/README.md
@@ -221,6 +221,12 @@ There are two special syntax elements for the object keys in a selector. The fir
     {"location": {"city": "Omaha"}}
     {"location.city": "Omaha"}
 
+If the object's key contains the period it could be escaped with backslash, i.e.
+
+    {"location\\.city": "Omaha"}
+
+Note that the double backslash here is necessary to encode an actual single backslash.
+
 The second important syntax element is the use of a dollar sign (`$`) prefix to denote operators. For example:
 
     {"age": {"$gt": 21}}
@@ -229,7 +235,7 @@ In this example, we have created the boolean expression `age > 21`.
 
 There are two core types of operators in the selector syntax: combination operators and condition operators. In general, combination operators contain groups of condition operators. We'll describe the list of each below.
 
-=== Implicit Operators ===
+### Implicit Operators
 
 For the most part every operator must be of the form `{"$operator": argument}`. Though there are two implicit operators for selectors.
 
@@ -253,7 +259,7 @@ Although, the previous example would actually be normalized internally to this:
     {"foo.bar": {"$eq": "baz"}}
 
 
-=== Combination Operators ===
+### Combination Operators
 
 These operators are responsible for combining groups of condition operators. Most familiar are the standard boolean operators plus a few extra for working with JSON arrays.
 
@@ -268,7 +274,7 @@ The list of combining characters:
 * "$all" - array argument (special operator for array values)
 * "$elemMatch" - single argument (special operator for array values)
 
-=== Condition Operators ===
+### Condition Operators
 
 Condition operators are specified on a per field basis and apply to the value indexed for that field. For instance, the basic "$eq" operator matches when the indexed field is equal to its argument. There is currently support for the basic equality and inequality operators as well as a number of meta operators. Some of these operators will accept any JSON argument while some require a specific JSON formatted argument. Each is noted below.
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/80253c45/test/01-index-crud-test.py
----------------------------------------------------------------------
diff --git a/test/01-index-crud-test.py b/test/01-index-crud-test.py
index b6ef786..1f8b8bf 100644
--- a/test/01-index-crud-test.py
+++ b/test/01-index-crud-test.py
@@ -186,7 +186,7 @@ def test_recreate_index():
         assert pre_indexes == post_indexes
 
 
-def test_delete_misisng():
+def test_delete_missing():
     db = mkdb()
 
     # Missing design doc


[38/50] [abbrv] couchdb-mango git commit: Move view specific logic to mango_cursor_view

Posted by ro...@apache.org.
Move view specific logic to mango_cursor_view

A good portion of mango_cursor turned out to be specific to view based
indexes. This moves the view related logic to mango_cursor_view and
updates mango_cursor to be more generic in preparation for the text
based indexing.

BugzId: 33294


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/06bf7757
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/06bf7757
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/06bf7757

Branch: refs/heads/master
Commit: 06bf7757b9f98c7cf5d5c597713021f6cb4ae27b
Parents: 7a9de34
Author: Paul J. Davis <pa...@gmail.com>
Authored: Fri Jan 9 14:21:12 2015 -0600
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Fri Jan 16 13:32:49 2015 -0600

----------------------------------------------------------------------
 src/mango_cursor.erl      | 160 +++++++++--------------------------------
 src/mango_cursor_view.erl |  71 ++++++++++++++++++
 src/mango_error.erl       |   4 +-
 src/mango_idx.erl         |  36 ++++++++++
 src/mango_idx_special.erl |   6 ++
 src/mango_idx_view.erl    |   9 +++
 6 files changed, 156 insertions(+), 130 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/06bf7757/src/mango_cursor.erl
----------------------------------------------------------------------
diff --git a/src/mango_cursor.erl b/src/mango_cursor.erl
index 8f780b7..5bfe383 100644
--- a/src/mango_cursor.erl
+++ b/src/mango_cursor.erl
@@ -29,34 +29,24 @@
 
 create(Db, Selector0, Opts) ->
     Selector = mango_selector:normalize(Selector0),
-    IndexFields = mango_idx_view:indexable_fields(Selector),    
-    FieldRanges = mango_idx_view:field_ranges(Selector, IndexFields),
-
-    if IndexFields /= [] -> ok; true ->
-        ?MANGO_ERROR({no_usable_index, operator_unsupported})
-    end,
 
     ExistingIndexes = mango_idx:list(Db),
-    UsableIndexes = find_usable_indexes(IndexFields, ExistingIndexes),
-    SortIndexes = get_sort_indexes(ExistingIndexes, UsableIndexes, Opts),
+    if ExistingIndexes /= [] -> ok; true ->
+        ?MANGO_ERROR({no_usable_index, no_indexes_defined})
+    end,
 
-    Composited = composite_indexes(SortIndexes, FieldRanges),
-    {Index, Ranges} = choose_best_index(Db, Composited),
+    SortIndexes = mango_idx:for_sort(ExistingIndexes, Opts),
+    if SortIndexes /= [] -> ok; true ->
+        ?MANGO_ERROR({no_usable_index, missing_sort_index})
+    end,
 
-    Limit = couch_util:get_value(limit, Opts, 10000000000),
-    Skip = couch_util:get_value(skip, Opts, 0),
-    Fields = couch_util:get_value(fields, Opts, all_fields),
+    UsableFilter = fun(I) -> mango_idx:is_usable(I, Selector) end,
+    UsableIndexes = lists:filter(UsableFilter, SortIndexes),
+    if UsableIndexes /= [] -> ok; true ->
+        ?MANGO_ERROR({no_usable_index, selector_unsupported})
+    end,
 
-    {ok, #cursor{
-        db = Db,
-        index = Index,
-        ranges = Ranges,
-        selector = Selector,
-        opts = Opts,
-        limit = Limit,
-        skip = Skip,
-        fields = Fields
-    }}.
+    create_cursor(Db, UsableIndexes, Selector, Opts).
 
 
 execute(#cursor{index=Idx}=Cursor, UserFun, UserAcc) ->
@@ -64,109 +54,23 @@ execute(#cursor{index=Idx}=Cursor, UserFun, UserAcc) ->
     Mod:execute(Cursor, UserFun, UserAcc).
 
 
-% Find the intersection between the Possible and Existing
-% indexes.
-find_usable_indexes([], _) ->
-    ?MANGO_ERROR({no_usable_index, query_unsupported});
-find_usable_indexes(Possible, []) ->
-    ?MANGO_ERROR({no_usable_index, {fields, Possible}});
-find_usable_indexes(Possible, Existing) ->
-    Usable = lists:foldl(fun(Idx, Acc) ->
-        [Col0 | _] = mango_idx:columns(Idx),
-        case lists:member(Col0, Possible) of
-            true ->
-                [Idx | Acc];
-            false ->
-                Acc
+create_cursor(Db, Indexes, Selector, Opts) ->
+    [{CursorMod, CursorModIndexes} | _] = group_indexes_by_type(Indexes),
+    CursorMod:create(Db, CursorModIndexes, Selector, Opts).
+
+
+group_indexes_by_type(Indexes) ->
+    IdxDict = lists:foldl(fun(I, D) ->
+        dict:append(mango_idx:cursor_mod(I), I, D)
+    end, dict:new(), Indexes),
+    CursorModules = [
+        mango_cursor_view
+    ],
+    lists:flatmap(fun(CMod) ->
+        case dict:find(CMod, IdxDict) of
+            {ok, CModIndexes} ->
+                [{CMod, CModIndexes}];
+            error ->
+                []
         end
-    end, [], Existing),
-    if length(Usable) > 0 -> ok; true ->
-        ?MANGO_ERROR({no_usable_index, {fields, Possible}})
-    end,
-    Usable.
-
-
-get_sort_indexes(ExistingIndexes, UsableIndexes, Opts) ->
-    % If a sort was specified we have to find an index that
-    % can satisfy the request.
-    case lists:keyfind(sort, 1, Opts) of
-        {sort, {[_ | _]} = Sort} ->
-            limit_to_sort(ExistingIndexes, UsableIndexes, Sort);
-        _ ->
-            UsableIndexes
-    end.
-
-
-limit_to_sort(ExistingIndexes, UsableIndexes, Sort) ->
-    Fields = mango_sort:fields(Sort),
-
-    % First make sure that we have an index that could
-    % answer this sort. We split like this so that the
-    % user error is more obvious.
-    SortFilt = fun(Idx) ->
-        Cols = mango_idx:columns(Idx),
-        lists:prefix(Fields, Cols)
-    end,
-    SortIndexes = lists:filter(SortFilt, ExistingIndexes),
-    if SortIndexes /= [] -> ok; true ->
-        ?MANGO_ERROR({no_usable_index, {sort, Fields}})
-    end,
-
-    % And then check if one or more of our SortIndexes
-    % is usable.
-    UsableFilt = fun(Idx) -> lists:member(Idx, UsableIndexes) end,
-    FinalIndexes = lists:filter(UsableFilt, SortIndexes),
-    if FinalIndexes /= [] -> ok; true ->
-        ?MANGO_ERROR({no_usable_index, sort_field})
-    end,
-
-    FinalIndexes.
-
-
-% Any of these indexes may be a composite index. For each
-% index find the most specific set of fields for each
-% index. Ie, if an index has columns a, b, c, d, then
-% check FieldRanges for a, b, c, and d and return
-% the longest prefix of columns found.
-composite_indexes(Indexes, FieldRanges) ->
-    lists:foldl(fun(Idx, Acc) ->
-        Cols = mango_idx:columns(Idx),
-        Prefix = composite_prefix(Cols, FieldRanges),
-        [{Idx, Prefix} | Acc]
-    end, [], Indexes).
-
-
-composite_prefix([], _) ->
-    [];
-composite_prefix([Col | Rest], Ranges) ->
-    case lists:keyfind(Col, 1, Ranges) of
-        {Col, Range} ->
-            [Range | composite_prefix(Rest, Ranges)];
-        false ->
-            []
-    end.
-
-
-% Low and behold our query planner. Or something.
-% So stupid, but we can fix this up later. First
-% pass: Sort the IndexRanges by (num_columns, idx_name)
-% and return the first element. Yes. Its going to
-% be that dumb for now.
-%
-% In the future we can look into doing a cached parallel
-% reduce view read on each index with the ranges to find
-% the one that has the fewest number of rows or something.
-choose_best_index(_DbName, IndexRanges) ->
-    Cmp = fun({A1, A2}, {B1, B2}) ->
-        case length(A2) - length(B2) of
-            N when N < 0 -> true;
-            N when N == 0 ->
-                % This is a really bad sort and will end
-                % up preferring indices based on the
-                % (dbname, ddocid, view_name) triple
-                A1 =< B1;
-            _ ->
-                false
-        end
-    end,
-    hd(lists:sort(Cmp, IndexRanges)).
+    end, CursorModules).

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/06bf7757/src/mango_cursor_view.erl
----------------------------------------------------------------------
diff --git a/src/mango_cursor_view.erl b/src/mango_cursor_view.erl
index a9f66b0..798b713 100644
--- a/src/mango_cursor_view.erl
+++ b/src/mango_cursor_view.erl
@@ -13,6 +13,7 @@
 -module(mango_cursor_view).
 
 -export([
+    create/4,
     execute/3
 ]).
 
@@ -25,6 +26,27 @@
 -include("mango_cursor.hrl").
 
 
+create(Db, Indexes, Selector, Opts) ->
+    FieldRanges = mango_idx_view:field_ranges(Selector),
+    Composited = composite_indexes(Indexes, FieldRanges),
+    {Index, IndexRanges} = choose_best_index(Db, Composited),
+
+    Limit = couch_util:get_value(limit, Opts, 10000000000),
+    Skip = couch_util:get_value(skip, Opts, 0),
+    Fields = couch_util:get_value(fields, Opts, all_fields),
+
+    {ok, #cursor{
+        db = Db,
+        index = Index,
+        ranges = IndexRanges,
+        selector = Selector,
+        opts = Opts,
+        limit = Limit,
+        skip = Skip,
+        fields = Fields
+    }}.
+
+
 execute(#cursor{db = Db, index = Idx} = Cursor0, UserFun, UserAcc) ->
     Cursor = Cursor0#cursor{
         user_fun = UserFun,
@@ -52,6 +74,55 @@ execute(#cursor{db = Db, index = Idx} = Cursor0, UserFun, UserAcc) ->
     {ok, LastCursor#cursor.user_acc}.
 
 
+% Any of these indexes may be a composite index. For each
+% index find the most specific set of fields for each
+% index. Ie, if an index has columns a, b, c, d, then
+% check FieldRanges for a, b, c, and d and return
+% the longest prefix of columns found.
+composite_indexes(Indexes, FieldRanges) ->
+    lists:foldl(fun(Idx, Acc) ->
+        Cols = mango_idx:columns(Idx),
+        Prefix = composite_prefix(Cols, FieldRanges),
+        [{Idx, Prefix} | Acc]
+    end, [], Indexes).
+
+
+composite_prefix([], _) ->
+    [];
+composite_prefix([Col | Rest], Ranges) ->
+    case lists:keyfind(Col, 1, Ranges) of
+        {Col, Range} ->
+            [Range | composite_prefix(Rest, Ranges)];
+        false ->
+            []
+    end.
+
+
+% Low and behold our query planner. Or something.
+% So stupid, but we can fix this up later. First
+% pass: Sort the IndexRanges by (num_columns, idx_name)
+% and return the first element. Yes. Its going to
+% be that dumb for now.
+%
+% In the future we can look into doing a cached parallel
+% reduce view read on each index with the ranges to find
+% the one that has the fewest number of rows or something.
+choose_best_index(_DbName, IndexRanges) ->
+    Cmp = fun({A1, A2}, {B1, B2}) ->
+        case length(A2) - length(B2) of
+            N when N < 0 -> true;
+            N when N == 0 ->
+                % This is a really bad sort and will end
+                % up preferring indices based on the
+                % (dbname, ddocid, view_name) triple
+                A1 =< B1;
+            _ ->
+                false
+        end
+    end,
+    hd(lists:sort(Cmp, IndexRanges)).
+
+
 handle_message({total_and_offset, _, _} = _TO, Cursor) ->
     %twig:log(err, "TOTAL AND OFFSET: ~p", [_TO]),
     {ok, Cursor};

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/06bf7757/src/mango_error.erl
----------------------------------------------------------------------
diff --git a/src/mango_error.erl b/src/mango_error.erl
index 973dd43..778df2d 100644
--- a/src/mango_error.erl
+++ b/src/mango_error.erl
@@ -24,11 +24,11 @@ info(mango_cursor, {no_usable_index, operator_unsupported}) ->
         <<"no_usable_index">>,
         <<"There is no operator in this selector can used with an index.">>
     };
-info(mango_cursor, {no_usable_index, query_unsupported}) ->
+info(mango_cursor, {no_usable_index, selector_unsupported}) ->
     {
         400,
         <<"no_usable_index">>,
-        <<"Query unsupported because it would require multiple indices.">>
+        <<"There is no index available for this selector.">>
     };
 info(mango_cursor, {no_usable_index, sort_field}) ->
     {

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/06bf7757/src/mango_idx.erl
----------------------------------------------------------------------
diff --git a/src/mango_idx.erl b/src/mango_idx.erl
index c0a07a8..902fb75 100644
--- a/src/mango_idx.erl
+++ b/src/mango_idx.erl
@@ -20,6 +20,7 @@
 -export([
     list/1,
     recover/1,
+    for_sort/2,
 
     new/2,
     validate/1,
@@ -35,6 +36,7 @@
     def/1,
     opts/1,
     columns/1,
+    is_usable/2,
     start_key/2,
     end_key/2,
     cursor_mod/1,
@@ -67,6 +69,35 @@ recover(Db) ->
     end, DDocs)}.
 
 
+for_sort(Indexes, Opts) ->
+    % If a sort was specified we have to find an index that
+    % can satisfy the request.
+    case lists:keyfind(sort, 1, Opts) of
+        {sort, {SProps}} when is_list(SProps) ->
+            for_sort_int(Indexes, {SProps});
+        _ ->
+            Indexes
+    end.
+
+
+for_sort_int(Indexes, Sort) ->
+    Fields = mango_sort:fields(Sort),
+    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));
+            {<<"json">>, _} ->
+                lists:prefix(Fields, Cols);
+            {<<"special">>, _} ->
+                lists:prefix(Fields, Cols)
+        end
+    end,
+    lists:filter(FilterFun, Indexes).
+
+
 new(Db, Opts) ->
     Def = get_idx_def(Opts),
     Type = get_idx_type(Opts),
@@ -169,6 +200,11 @@ columns(#idx{}=Idx) ->
     Mod:columns(Idx).
 
 
+is_usable(#idx{}=Idx, Selector) ->
+    Mod = idx_mod(Idx),
+    Mod:is_usable(Idx, Selector).
+
+
 start_key(#idx{}=Idx, Ranges) ->
     Mod = idx_mod(Idx),
     Mod:start_key(Ranges).

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/06bf7757/src/mango_idx_special.erl
----------------------------------------------------------------------
diff --git a/src/mango_idx_special.erl b/src/mango_idx_special.erl
index 0234dff..a8f9400 100644
--- a/src/mango_idx_special.erl
+++ b/src/mango_idx_special.erl
@@ -20,6 +20,7 @@
     from_ddoc/1,
     to_json/1,
     columns/1,
+    is_usable/2,
     start_key/1,
     end_key/1
 ]).
@@ -62,6 +63,11 @@ columns(#idx{def=all_docs}) ->
     [<<"_id">>].
 
 
+is_usable(#idx{def=all_docs}, Selector) ->
+    Fields = mango_idx_view:indexable_fields(Selector),
+    lists:member(<<"_id">>, Fields).
+
+
 start_key([{'$gt', Key, _, _}]) ->
     case mango_json:special(Key) of
         true ->

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/06bf7757/src/mango_idx_view.erl
----------------------------------------------------------------------
diff --git a/src/mango_idx_view.erl b/src/mango_idx_view.erl
index eaa4341..ce0b206 100644
--- a/src/mango_idx_view.erl
+++ b/src/mango_idx_view.erl
@@ -19,6 +19,7 @@
     remove/2,
     from_ddoc/1,
     to_json/1,
+    is_usable/2,
     columns/1,
     start_key/1,
     end_key/1,
@@ -106,6 +107,14 @@ columns(Idx) ->
     [Key || {Key, _} <- Fields].
 
 
+is_usable(Idx, Selector) ->
+    % This index is usable if at least the first column is
+    % a member of the indexable fields of the selector.
+    Columns = columns(Idx),
+    Fields = indexable_fields(Selector),
+    lists:member(hd(Columns), Fields).
+
+
 start_key([]) ->
     [];
 start_key([{'$gt', Key, _, _} | Rest]) ->


[41/50] [abbrv] couchdb-mango git commit: Implement the use_index query parameter

Posted by ro...@apache.org.
Implement the use_index query parameter

This adds the ability for users to specify a specific index to use when
responding to a query. There are a few places where its helpful to force
the index selection to a specific index. For instance choosing a more
sparse single column index over an available multi-column index.

BugzId: 33294


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/1b0426aa
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/1b0426aa
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/1b0426aa

Branch: refs/heads/master
Commit: 1b0426aa2839f34d747e329ffb8bbaee1c42aaa8
Parents: 812cf6b
Author: Paul J. Davis <pa...@gmail.com>
Authored: Fri Jan 9 15:32:40 2015 -0600
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Fri Jan 16 13:32:50 2015 -0600

----------------------------------------------------------------------
 src/mango_cursor.erl            | 35 ++++++++++++++++++++++++++++++++++-
 src/mango_opts.erl              | 33 +++++++++++++++++++++++++++++++++
 test/05-index-selection-test.py | 34 ++++++++++++++++++++++++++++++++++
 test/mango.py                   |  3 ++-
 4 files changed, 103 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/1b0426aa/src/mango_cursor.erl
----------------------------------------------------------------------
diff --git a/src/mango_cursor.erl b/src/mango_cursor.erl
index d4d5523..72ee8bb 100644
--- a/src/mango_cursor.erl
+++ b/src/mango_cursor.erl
@@ -36,7 +36,12 @@ create(Db, Selector0, Opts) ->
         ?MANGO_ERROR({no_usable_index, no_indexes_defined})
     end,
 
-    SortIndexes = mango_idx:for_sort(ExistingIndexes, Opts),
+    FilteredIndexes = maybe_filter_indexes(ExistingIndexes, Opts),
+    if FilteredIndexes /= [] -> ok; true ->
+        ?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,
@@ -77,6 +82,34 @@ execute(#cursor{index=Idx}=Cursor, UserFun, UserAcc) ->
     Mod:execute(Cursor, UserFun, UserAcc).
 
 
+maybe_filter_indexes(Indexes, Opts) ->
+    case lists:keyfind(use_index, 1, Opts) of
+        {use_index, []} ->
+            Indexes;
+        {use_index, [DesignId]} ->
+            filter_indexes(Indexes, DesignId);
+        {use_index, [DesignId, ViewName]} ->
+            filter_indexes(Indexes, DesignId, ViewName)
+    end.
+
+
+filter_indexes(Indexes, DesignId0) ->
+    DesignId = case DesignId0 of
+        <<"_design/", _/binary>> ->
+            DesignId0;
+        Else ->
+            <<"_design/", Else/binary>>
+    end,
+    FiltFun = fun(I) -> mango_idx:ddoc(I) == DesignId end,
+    lists:filter(FiltFun, Indexes).
+
+
+filter_indexes(Indexes0, DesignId, ViewName) ->
+    Indexes = filter_indexes(Indexes0, DesignId),
+    FiltFun = fun(I) -> mango_idx:name(I) == ViewName end,
+    lists:filter(FiltFun, Indexes).
+
+
 create_cursor(Db, Indexes, Selector, Opts) ->
     [{CursorMod, CursorModIndexes} | _] = group_indexes_by_type(Indexes),
     CursorMod:create(Db, CursorModIndexes, Selector, Opts).

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/1b0426aa/src/mango_opts.erl
----------------------------------------------------------------------
diff --git a/src/mango_opts.erl b/src/mango_opts.erl
index 43c2f31..e15a446 100644
--- a/src/mango_opts.erl
+++ b/src/mango_opts.erl
@@ -28,6 +28,7 @@
 
     validate_idx_name/1,
     validate_selector/1,
+    validate_use_index/1,
     validate_sort/1,
     validate_fields/1
 ]).
@@ -75,6 +76,12 @@ validate_find({Props}) ->
             {tag, selector},
             {validator, fun validate_selector/1}
         ]},
+        {<<"use_index">>, [
+            {tag, use_index},
+            {optional, true},
+            {default, []},
+            {validator, fun validate_use_index/1}
+        ]},
         {<<"limit">>, [
             {tag, limit},
             {optional, true},
@@ -178,6 +185,32 @@ validate_selector(Else) ->
     ?MANGO_ERROR({invalid_selector_json, Else}).
 
 
+validate_use_index(IndexName) when is_binary(IndexName) ->
+    case binary:split(IndexName, <<"/">>) of
+        [DesignId] ->
+            {ok, [DesignId]};
+        [<<"_design">>, DesignId] ->
+            {ok, [DesignId]};
+        [DesignId, ViewName] ->
+            {ok, [DesignId, ViewName]};
+        [<<"_design">>, DesignId, ViewName] ->
+            {ok, [DesignId, ViewName]};
+        _ ->
+            ?MANGO_ERROR({invalid_index_name, IndexName})
+    end;
+validate_use_index(null) ->
+    {ok, []};
+validate_use_index([]) ->
+    {ok, []};
+validate_use_index([DesignId]) when is_binary(DesignId) ->
+    {ok, [DesignId]};
+validate_use_index([DesignId, ViewName])
+        when is_binary(DesignId), is_binary(ViewName) ->
+    {ok, [DesignId, ViewName]};
+validate_use_index(Else) ->
+    ?MANGO_ERROR({invalid_index_name, Else}).
+
+
 validate_sort(Value) ->
     mango_sort:new(Value).
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/1b0426aa/test/05-index-selection-test.py
----------------------------------------------------------------------
diff --git a/test/05-index-selection-test.py b/test/05-index-selection-test.py
new file mode 100644
index 0000000..5aa86c6
--- /dev/null
+++ b/test/05-index-selection-test.py
@@ -0,0 +1,34 @@
+
+import mango
+import user_docs
+
+
+class IndexSelectionTests(mango.UserDocsTests):
+
+    def test_basic(self):
+        resp = self.db.find({"name.last": "A last name"}, explain=True)
+        assert resp["index"]["type"] == "json"
+
+    def test_with_and(self):
+        resp = self.db.find({
+                "name.first": "Stephanie",
+                "name.last": "This doesn't have to match anything."
+            }, explain=True)
+        assert resp["index"]["type"] == "json"
+
+    def test_use_most_columns(self):
+        # ddoc id for the age index
+        ddocid = "_design/ad3d537c03cd7c6a43cf8dff66ef70ea54c2b40f"
+        resp = self.db.find({
+                "name.first": "Stephanie",
+                "name.last": "Something or other",
+                "age": {"$gt": 1}
+            }, explain=True)
+        assert resp["index"]["ddoc"] != "_design/" + ddocid
+
+        resp = self.db.find({
+                "name.first": "Stephanie",
+                "name.last": "Something or other",
+                "age": {"$gt": 1}
+            }, use_index=ddocid, explain=True)
+        assert resp["index"]["ddoc"] == ddocid

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/1b0426aa/test/mango.py
----------------------------------------------------------------------
diff --git a/test/mango.py b/test/mango.py
index fac4e7e..5b022b8 100644
--- a/test/mango.py
+++ b/test/mango.py
@@ -107,9 +107,10 @@ class Database(object):
         r.raise_for_status()
 
     def find(self, selector, limit=25, skip=0, sort=None, fields=None,
-                r=1, conflicts=False, explain=False):
+                r=1, conflicts=False, explain=False, use_index=None):
         body = {
             "selector": selector,
+            "use_index": use_index,
             "limit": limit,
             "skip": skip,
             "r": r,


[49/50] [abbrv] couchdb-mango git commit: Add tests to test for field names with period

Posted by ro...@apache.org.
Add tests to test for field names with period

BugzId:43621


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/a7c88e58
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/a7c88e58
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/a7c88e58

Branch: refs/heads/master
Commit: a7c88e589ff6d261cbc6ab9f59edb613df034626
Parents: 8af882b
Author: Tony Sun <to...@cloudant.com>
Authored: Wed Jan 21 18:21:19 2015 -0800
Committer: Tony Sun <to...@cloudant.com>
Committed: Fri Jan 23 15:39:17 2015 -0800

----------------------------------------------------------------------
 src/mango_native_proc.erl              | 43 ++++++-----------------------
 src/mango_selector_text.erl            | 24 ++++++----------
 src/mango_util.erl                     |  7 +++++
 test/04-key-tests.py                   | 30 ++++++++++++++++++--
 test/07-text-custom-field-list-test.py | 17 +++++++++++-
 test/user_docs.py                      |  3 +-
 6 files changed, 71 insertions(+), 53 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/a7c88e58/src/mango_native_proc.erl
----------------------------------------------------------------------
diff --git a/src/mango_native_proc.erl b/src/mango_native_proc.erl
index a55a3ac..9481ca4 100644
--- a/src/mango_native_proc.erl
+++ b/src/mango_native_proc.erl
@@ -168,8 +168,7 @@ get_text_field_values(Values, TAcc) when is_list(Values) ->
     % We bypass make_text_field and directly call make_text_field_name
     % because the length field name is not part of the path.
     LengthFieldName = make_text_field_name(NewTAcc#tacc.path, <<"length">>),
-    LengthField = [{escape_name_parts(LengthFieldName), <<"length">>,
-        length(Values)}],
+    LengthField = [{LengthFieldName, <<"length">>, length(Values)}],
     get_text_field_values_arr(Values, NewTAcc, LengthField);
 
 get_text_field_values(Bin, TAcc) when is_binary(Bin) ->
@@ -258,7 +257,8 @@ should_index(Selector, Doc) ->
 get_text_field_list(IdxProps) ->
     case couch_util:get_value(<<"fields">>, IdxProps) of
         Fields when is_list(Fields) ->
-            lists:flatmap(fun get_text_field_info/1, Fields);
+            RawList = lists:flatmap(fun get_text_field_info/1, Fields),
+            [mango_util:lucene_escape_user(Field) || Field <- RawList];
         _ ->
             all_fields
     end.
@@ -283,41 +283,16 @@ get_text_field_type(_) ->
 
 make_text_field(TAcc, Type, Value) ->
     FieldName = make_text_field_name(TAcc#tacc.path, Type),
-    Fields = case TAcc#tacc.fields of
-        Fields0 when is_list(Fields0) ->
-            % for field names such as "name\\.first"
-            PFields = [mango_doc:parse_field(F) || F <- Fields0],
-            [iolist_to_binary(mango_util:join(".", P)) || {ok, P} <- PFields];
-        _ ->
-            all_fields
-    end,
-    % need to convert the fieldname to binary but not escape it in order
-    % to compare with the user field names.
-    BName = iolist_to_binary(FieldName),
-    case Fields == all_fields orelse lists:member(BName, Fields) of
+    Fields = TAcc#tacc.fields,
+    case Fields == all_fields orelse lists:member(FieldName, Fields) of
         true ->
-            [{escape_name_parts(FieldName), Type, Value}];
+            [{FieldName, Type, Value}];
         false ->
             []
     end.
 
 
 make_text_field_name([P | Rest], Type) ->
-    make_text_field_name0(Rest, [P, ":", Type]).
-
-make_text_field_name0([], Name) ->
-    Name;
-make_text_field_name0([P | Rest], Name) ->
-    make_text_field_name0(Rest, [P, "." | Name]).
-
-
-escape_name_parts(Name) ->
-    EscapedName = lists:map(fun(N) ->
-        case N of
-            "." ->
-                ".";
-            Else ->
-                mango_util:lucene_escape_field(Else)
-        end
-    end, Name),
-    iolist_to_binary(EscapedName).
+    Parts = lists:reverse(Rest, [iolist_to_binary([P, ":", Type])]),
+    Escaped = [mango_util:lucene_escape_field(N) || N <- Parts],
+    iolist_to_binary(mango_util:join(".", Escaped)).

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/a7c88e58/src/mango_selector_text.erl
----------------------------------------------------------------------
diff --git a/src/mango_selector_text.erl b/src/mango_selector_text.erl
index 9a69853..bdd82bf 100644
--- a/src/mango_selector_text.erl
+++ b/src/mango_selector_text.erl
@@ -171,9 +171,8 @@ convert(_Path, {Props} = Sel) when length(Props) > 1 ->
 
 
 to_query({op_and, Args}) when is_list(Args) ->
-    Res = ["(", mango_util:join(<<" AND ">>, lists:map(fun to_query/1, Args)),
-        ")"],
-    Res;
+    QueryArgs = lists:map(fun to_query/1, Args),
+    ["(", mango_util:join(<<" AND ">>, QueryArgs), ")"];
 
 to_query({op_or, Args}) when is_list(Args) ->
     ["(", mango_util:join(" OR ", lists:map(fun to_query/1, Args)), ")"];
@@ -192,27 +191,21 @@ to_query({op_insert, Arg}) when is_binary(Arg) ->
 %% This needs to be resolved.
 to_query({op_field, {Name, Value}}) ->
     NameBin = iolist_to_binary(Name),
-    ["(", escape_name_parts(NameBin), ":", Value, ")"];
+    ["(", mango_util:lucene_escape_user(NameBin), ":", Value, ")"];
 
 %% This is for indexable_fields
 to_query({op_null, {Name, Value}}) ->
     NameBin = iolist_to_binary(Name),
-    ["(", escape_name_parts(NameBin), ":", Value, ")"];
+    ["(", mango_util:lucene_escape_user(NameBin), ":", Value, ")"];
 
 to_query({op_fieldname, {Name, Wildcard}}) ->
     NameBin = iolist_to_binary(Name),
-    ["($fieldnames:", escape_name_parts(NameBin), Wildcard, ")"];
+    ["($fieldnames:", mango_util:lucene_escape_user(NameBin), Wildcard, ")"];
 
 to_query({op_default, Value}) ->
     ["($default:", Value, ")"].
 
 
-escape_name_parts(Name) ->
-    {ok, ParsedNames} = mango_doc:parse_field(Name),
-    EncodedNames = [mango_util:lucene_escape_field(N) || N <- ParsedNames],
-    iolist_to_binary(mango_util:join(".", EncodedNames)).
-
-
 %% We match on fieldname and fieldname.[]
 convert_in(Path, Args) ->
     Path0 = [<<"[]">> | Path],
@@ -256,9 +249,10 @@ field_exists_query(Path) ->
     % match a path foo.name against foo.name_first (if were to just
     % appened * isntead).
     Parts = [
+        % We need to remove the period from the path list to indicate that it is
+        % a path separator. We escape the colon because it is not used as a
+        % separator and we escape colons in field names.
         {op_fieldname, {[path_str(Path), ":"], "*"}},
-        % need to extract out the period because mango_doc:parse_field/1
-        % will not accept "name.", also we don't want to escape the .
         {op_fieldname, {[path_str(Path)], ".*"}}
     ],
     {op_or, Parts}.
@@ -307,7 +301,7 @@ value_str(null) ->
 
 
 append_sort_type(RawSortField, Selector) ->
-    EncodeField = escape_name_parts(RawSortField),
+    EncodeField = mango_util:lucene_escape_user(RawSortField),
     String = mango_util:has_suffix(EncodeField, <<"_3astring">>),
     Number = mango_util:has_suffix(EncodeField, <<"_3anumber">>),
     case {String, Number} of

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/a7c88e58/src/mango_util.erl
----------------------------------------------------------------------
diff --git a/src/mango_util.erl b/src/mango_util.erl
index b1d30fb..f350710 100644
--- a/src/mango_util.erl
+++ b/src/mango_util.erl
@@ -33,6 +33,7 @@
 
     lucene_escape_field/1,
     lucene_escape_query_value/1,
+    lucene_escape_user/1,
 
     has_suffix/2,
 
@@ -283,6 +284,12 @@ lucene_escape_qv(<<C, Rest/binary>>) ->
     Out ++ lucene_escape_qv(Rest).
 
 
+lucene_escape_user(Field) ->
+    {ok, Path} = mango_doc:parse_field(Field),
+    Escaped = [mango_util:lucene_escape_field(P) || P <- Path],
+    iolist_to_binary(join(".", Escaped)).
+
+
 has_suffix(Bin, Suffix) when is_binary(Bin), is_binary(Suffix) ->
     SBin = size(Bin),
     SSuffix = size(Suffix),

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/a7c88e58/test/04-key-tests.py
----------------------------------------------------------------------
diff --git a/test/04-key-tests.py b/test/04-key-tests.py
index c673cf2..0109737 100644
--- a/test/04-key-tests.py
+++ b/test/04-key-tests.py
@@ -26,7 +26,8 @@ TEST_DOCS = [
         "dot.key": "dot's value",
         "none": {
             "dot": "none dot's value"
-        }
+        },
+        "name.first" : "Kvothe"
     },
     {
         "type": "complex_key",
@@ -34,7 +35,8 @@ TEST_DOCS = [
         "$key": "peso",
         "deep": {
             "$key": "deep peso"
-        }
+        },
+        "name": {"first" : "Master Elodin"}
     },
     {
         "type": "complex_key",
@@ -121,3 +123,27 @@ class KeyTests(mango.DbPerClass):
             assert docs[0]["title"] == "internal_fields_format"
         for query in queries:
             self.run_check(query, check, indexes=["text"])
+
+    def test_escape_period(self):
+        query = {"name\\.first" : "Kvothe"}
+        def check(docs):
+            assert len(docs) == 1
+            assert docs[0]["name.first"] == "Kvothe"
+        self.run_check(query, check, indexes=["text"])
+
+        query = {"name.first" : "Kvothe"}
+        def check_empty(docs):
+            assert len(docs) == 0
+        self.run_check(query, check_empty, indexes=["text"])
+
+    def test_object_period(self):
+        query = {"name.first" : "Master Elodin"}
+        def check(docs):
+            assert len(docs) == 1
+            assert docs[0]["title"] == "key with peso"
+        self.run_check(query, check, indexes=["text"])
+
+        query = {"name\\.first" : "Master Elodin"}
+        def check_empty(docs):
+            assert len(docs) == 0
+        self.run_check(query, check_empty, indexes=["text"])

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/a7c88e58/test/07-text-custom-field-list-test.py
----------------------------------------------------------------------
diff --git a/test/07-text-custom-field-list-test.py b/test/07-text-custom-field-list-test.py
index 5e5f7cc..a019ea2 100644
--- a/test/07-text-custom-field-list-test.py
+++ b/test/07-text-custom-field-list-test.py
@@ -26,7 +26,8 @@ class CustomFieldsTest(mango.UserDocsTextTests):
         {
             "name": "location.address.street",
             "type": "string"
-        }
+        },
+        {"name": "name\\.first", "type": "string"}
     ]
 
     def test_basic(self):
@@ -60,3 +61,17 @@ class CustomFieldsTest(mango.UserDocsTextTests):
         docs = self.db.find({"location.state": "New Hampshire"})
         assert len(docs) == 1
         assert docs[0]["user_id"] == 10
+    
+    # Since our FIELDS list only includes "name\\.first", we should
+    # get an error when we try to search for "name.first", since the index
+    # for that field does not exist.
+    def test_escaped_field(self):
+        docs = self.db.find({"name\\.first": "name dot first"})
+        assert len(docs) == 1
+        assert docs[0]["name.first"] == "name dot first"
+
+        try:
+            self.db.find({"name.first": "name dot first"})
+            raise Exception("Should have thrown an HTTPError")
+        except:
+            return

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/a7c88e58/test/user_docs.py
----------------------------------------------------------------------
diff --git a/test/user_docs.py b/test/user_docs.py
index 05eabbb..baf83f7 100644
--- a/test/user_docs.py
+++ b/test/user_docs.py
@@ -224,7 +224,8 @@ DOCS = [
             "C",
             "Ruby",
             "Ruby"
-        ]
+        ],
+        "name.first" : "name dot first"
     },
     {
         "_id": "a33d5457-741a-4dce-a217-3eab28b24e3e",


[32/50] [abbrv] couchdb-mango git commit: Merge pull request #17 from cloudant/license-headers

Posted by ro...@apache.org.
Merge pull request #17 from cloudant/license-headers

add license headers to source files

Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/bb30744f
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/bb30744f
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/bb30744f

Branch: refs/heads/master
Commit: bb30744f2335a81375751b42fec47601a44d0df0
Parents: 9cfe0a9 440856a
Author: Robert Kowalski <ro...@kowalski.gd>
Authored: Fri Jan 9 18:22:57 2015 +0100
Committer: Robert Kowalski <ro...@kowalski.gd>
Committed: Fri Jan 9 18:22:57 2015 +0100

----------------------------------------------------------------------
 src/mango.app.src               | 12 ++++++++++++
 src/mango.hrl                   | 11 +++++++++++
 src/mango_crud.erl              | 11 +++++++++++
 src/mango_cursor.erl            | 12 ++++++++++++
 src/mango_cursor.hrl            | 11 +++++++++++
 src/mango_cursor_view.erl       | 12 ++++++++++++
 src/mango_doc.erl               | 12 ++++++++++++
 src/mango_error.erl             | 12 ++++++++++++
 src/mango_fields.erl            | 12 ++++++++++++
 src/mango_httpd.erl             | 12 ++++++++++++
 src/mango_idx.erl               | 12 ++++++++++++
 src/mango_idx.hrl               | 12 ++++++++++++
 src/mango_idx_special.erl       | 12 ++++++++++++
 src/mango_idx_view.erl          | 12 ++++++++++++
 src/mango_json.erl              | 12 ++++++++++++
 src/mango_native_proc.erl       | 12 ++++++++++++
 src/mango_opts.erl              | 12 ++++++++++++
 src/mango_selector.erl          | 12 ++++++++++++
 src/mango_sort.erl              | 12 ++++++++++++
 src/mango_util.erl              | 12 ++++++++++++
 test/01-index-crud-test.py      | 11 +++++++++++
 test/02-basic-find-test.py      | 12 ++++++++++++
 test/03-operator-test.py        | 11 +++++++++++
 test/04-empty-selectors-test.py | 11 +++++++++++
 test/mango.py                   | 11 +++++++++++
 test/user_docs.py               | 12 ++++++++++++
 26 files changed, 305 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/bb30744f/src/mango.app.src
----------------------------------------------------------------------
diff --cc src/mango.app.src
index 52a4fb2,4fda6c0..7bd5d74
--- a/src/mango.app.src
+++ b/src/mango.app.src
@@@ -1,5 -1,17 +1,17 @@@
+ % 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.
+ 
  {application, mango, [
 -    {description, "MongoDB API compatibility layer for Cloudant"},
 +    {description, "MongoDB API compatibility layer for CouchDB"},
      {vsn, git},
      {registered, []},
      {applications, [


[46/50] [abbrv] couchdb-mango git commit: Merge pull request #20 from cloudant/43531-mango-text-sort-desc

Posted by ro...@apache.org.
Merge pull request #20 from cloudant/43531-mango-text-sort-desc

Fix text sort desc

Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/bdf9cf7a
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/bdf9cf7a
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/bdf9cf7a

Branch: refs/heads/master
Commit: bdf9cf7a8fd6169e1d97ab2f9b1ed672bcedb6cd
Parents: 801857d e57dcc5
Author: Tony Sun <to...@cloudant.com>
Authored: Wed Jan 21 11:56:47 2015 -0800
Committer: Tony Sun <to...@cloudant.com>
Committed: Wed Jan 21 11:56:47 2015 -0800

----------------------------------------------------------------------
 src/mango_cursor_text.erl | 16 +++++++++++-----
 test/09-text-sort-test.py | 11 +++++++++++
 2 files changed, 22 insertions(+), 5 deletions(-)
----------------------------------------------------------------------



[10/50] [abbrv] couchdb-mango git commit: Merge pull request #6 from cloudant/readme_activation_instructions

Posted by ro...@apache.org.
Merge pull request #6 from cloudant/readme_activation_instructions

Bump readme with config setting to turn on Mango/CQ

Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/4bee6d10
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/4bee6d10
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/4bee6d10

Branch: refs/heads/master
Commit: 4bee6d108ec03147888db13dd601674a438dbc9b
Parents: c33efa3 45717b0
Author: Kyle Snavely <ky...@cloudant.com>
Authored: Tue Aug 26 15:55:15 2014 -0400
Committer: Kyle Snavely <ky...@cloudant.com>
Committed: Tue Aug 26 15:55:15 2014 -0400

----------------------------------------------------------------------
 README.md | 8 ++++++++
 1 file changed, 8 insertions(+)
----------------------------------------------------------------------



[27/50] [abbrv] couchdb-mango git commit: Always use the DB name as the cache key

Posted by ro...@apache.org.
Always use the DB name as the cache key

mango_idx:list/1 accepts a #db{} in addition to a database name. When
the former is used it's often a stub record with a randomly generated
UUID in the #db_header. This really kills the cache hit rate ;)

BugzID: 42707


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/7bad11f4
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/7bad11f4
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/7bad11f4

Branch: refs/heads/master
Commit: 7bad11f4a7add4a599f0f459627e8cf077b5c797
Parents: 58fc27c
Author: Adam Kocoloski <ad...@cloudant.com>
Authored: Wed Dec 17 14:58:29 2014 -0500
Committer: Adam Kocoloski <ad...@cloudant.com>
Committed: Wed Dec 17 14:58:29 2014 -0500

----------------------------------------------------------------------
 src/mango_idx.erl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/7bad11f4/src/mango_idx.erl
----------------------------------------------------------------------
diff --git a/src/mango_idx.erl b/src/mango_idx.erl
index c1bbe4c..0498e07 100644
--- a/src/mango_idx.erl
+++ b/src/mango_idx.erl
@@ -37,7 +37,7 @@
 
 
 list(Db) ->
-    {ok, Indexes} = ddoc_cache:open(Db, ?MODULE),
+    {ok, Indexes} = ddoc_cache:open(db_to_name(Db), ?MODULE),
     Indexes.
 
 recover(Db) ->


[48/50] [abbrv] couchdb-mango git commit: Escape field name with period correctly

Posted by ro...@apache.org.
Escape field name with period correctly

Change period escaping mechanism during field indexing
and query building. Rather than escaping the whole field name
at the end, we separate the field name by periods and escape
each component.

BugzId:43621


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/8af882b7
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/8af882b7
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/8af882b7

Branch: refs/heads/master
Commit: 8af882b7f79054e417e5c251cb5f940217a9ee6f
Parents: bdf9cf7
Author: Tony Sun <to...@cloudant.com>
Authored: Wed Jan 21 18:18:01 2015 -0800
Committer: Tony Sun <to...@cloudant.com>
Committed: Wed Jan 21 18:18:01 2015 -0800

----------------------------------------------------------------------
 src/mango_native_proc.erl   | 35 ++++++++++++++++++++++++++++-------
 src/mango_selector_text.erl | 25 ++++++++++++++-----------
 src/mango_util.erl          | 10 +++++++++-
 3 files changed, 51 insertions(+), 19 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/8af882b7/src/mango_native_proc.erl
----------------------------------------------------------------------
diff --git a/src/mango_native_proc.erl b/src/mango_native_proc.erl
index 3e189bd..a55a3ac 100644
--- a/src/mango_native_proc.erl
+++ b/src/mango_native_proc.erl
@@ -168,8 +168,8 @@ get_text_field_values(Values, TAcc) when is_list(Values) ->
     % We bypass make_text_field and directly call make_text_field_name
     % because the length field name is not part of the path.
     LengthFieldName = make_text_field_name(NewTAcc#tacc.path, <<"length">>),
-    EncLFN = mango_util:lucene_escape_field(LengthFieldName),
-    LengthField = [{EncLFN, <<"length">>, length(Values)}],
+    LengthField = [{escape_name_parts(LengthFieldName), <<"length">>,
+        length(Values)}],
     get_text_field_values_arr(Values, NewTAcc, LengthField);
 
 get_text_field_values(Bin, TAcc) when is_binary(Bin) ->
@@ -283,11 +283,20 @@ get_text_field_type(_) ->
 
 make_text_field(TAcc, Type, Value) ->
     FieldName = make_text_field_name(TAcc#tacc.path, Type),
-    Fields = TAcc#tacc.fields,
-    case Fields == all_fields orelse lists:member(FieldName, Fields) of
+    Fields = case TAcc#tacc.fields of
+        Fields0 when is_list(Fields0) ->
+            % for field names such as "name\\.first"
+            PFields = [mango_doc:parse_field(F) || F <- Fields0],
+            [iolist_to_binary(mango_util:join(".", P)) || {ok, P} <- PFields];
+        _ ->
+            all_fields
+    end,
+    % need to convert the fieldname to binary but not escape it in order
+    % to compare with the user field names.
+    BName = iolist_to_binary(FieldName),
+    case Fields == all_fields orelse lists:member(BName, Fields) of
         true ->
-            [{mango_util:lucene_escape_field(FieldName), Type,
-            Value}];
+            [{escape_name_parts(FieldName), Type, Value}];
         false ->
             []
     end.
@@ -297,6 +306,18 @@ make_text_field_name([P | Rest], Type) ->
     make_text_field_name0(Rest, [P, ":", Type]).
 
 make_text_field_name0([], Name) ->
-    iolist_to_binary(Name);
+    Name;
 make_text_field_name0([P | Rest], Name) ->
     make_text_field_name0(Rest, [P, "." | Name]).
+
+
+escape_name_parts(Name) ->
+    EscapedName = lists:map(fun(N) ->
+        case N of
+            "." ->
+                ".";
+            Else ->
+                mango_util:lucene_escape_field(Else)
+        end
+    end, Name),
+    iolist_to_binary(EscapedName).

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/8af882b7/src/mango_selector_text.erl
----------------------------------------------------------------------
diff --git a/src/mango_selector_text.erl b/src/mango_selector_text.erl
index 35a0d4c..9a69853 100644
--- a/src/mango_selector_text.erl
+++ b/src/mango_selector_text.erl
@@ -171,11 +171,12 @@ convert(_Path, {Props} = Sel) when length(Props) > 1 ->
 
 
 to_query({op_and, Args}) when is_list(Args) ->
-    Res = ["(", join(<<" AND ">>, lists:map(fun to_query/1, Args)), ")"],
+    Res = ["(", mango_util:join(<<" AND ">>, lists:map(fun to_query/1, Args)),
+        ")"],
     Res;
 
 to_query({op_or, Args}) when is_list(Args) ->
-    ["(", join(" OR ", lists:map(fun to_query/1, Args)), ")"];
+    ["(", mango_util:join(" OR ", lists:map(fun to_query/1, Args)), ")"];
 
 to_query({op_not, {ExistsQuery, Arg}}) when is_tuple(Arg) ->
     ["(", to_query(ExistsQuery), " AND NOT (", to_query(Arg), "))"];
@@ -191,25 +192,25 @@ to_query({op_insert, Arg}) when is_binary(Arg) ->
 %% This needs to be resolved.
 to_query({op_field, {Name, Value}}) ->
     NameBin = iolist_to_binary(Name),
-    ["(", mango_util:lucene_escape_field(NameBin), ":", Value, ")"];
+    ["(", escape_name_parts(NameBin), ":", Value, ")"];
 
 %% This is for indexable_fields
 to_query({op_null, {Name, Value}}) ->
     NameBin = iolist_to_binary(Name),
-    ["(", mango_util:lucene_escape_field(NameBin), ":", Value, ")"];
+    ["(", escape_name_parts(NameBin), ":", Value, ")"];
 
 to_query({op_fieldname, {Name, Wildcard}}) ->
     NameBin = iolist_to_binary(Name),
-    ["($fieldnames:", mango_util:lucene_escape_field(NameBin), Wildcard, ")"];
+    ["($fieldnames:", escape_name_parts(NameBin), Wildcard, ")"];
 
 to_query({op_default, Value}) ->
     ["($default:", Value, ")"].
 
 
-join(_Sep, [Item]) ->
-    [Item];
-join(Sep, [Item | Rest]) ->
-    [Item, Sep | join(Sep, Rest)].
+escape_name_parts(Name) ->
+    {ok, ParsedNames} = mango_doc:parse_field(Name),
+    EncodedNames = [mango_util:lucene_escape_field(N) || N <- ParsedNames],
+    iolist_to_binary(mango_util:join(".", EncodedNames)).
 
 
 %% We match on fieldname and fieldname.[]
@@ -256,7 +257,9 @@ field_exists_query(Path) ->
     % appened * isntead).
     Parts = [
         {op_fieldname, {[path_str(Path), ":"], "*"}},
-        {op_fieldname, {[path_str(Path), "."], "*"}}
+        % need to extract out the period because mango_doc:parse_field/1
+        % will not accept "name.", also we don't want to escape the .
+        {op_fieldname, {[path_str(Path)], ".*"}}
     ],
     {op_or, Parts}.
 
@@ -304,7 +307,7 @@ value_str(null) ->
 
 
 append_sort_type(RawSortField, Selector) ->
-    EncodeField = mango_util:lucene_escape_field(RawSortField),
+    EncodeField = escape_name_parts(RawSortField),
     String = mango_util:has_suffix(EncodeField, <<"_3astring">>),
     Number = mango_util:has_suffix(EncodeField, <<"_3anumber">>),
     case {String, Number} of

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/8af882b7/src/mango_util.erl
----------------------------------------------------------------------
diff --git a/src/mango_util.erl b/src/mango_util.erl
index f3b60b2..b1d30fb 100644
--- a/src/mango_util.erl
+++ b/src/mango_util.erl
@@ -34,7 +34,9 @@
     lucene_escape_field/1,
     lucene_escape_query_value/1,
 
-    has_suffix/2
+    has_suffix/2,
+
+    join/2
 ]).
 
 
@@ -293,3 +295,9 @@ has_suffix(Bin, Suffix) when is_binary(Bin), is_binary(Suffix) ->
                 false
         end
     end.
+
+
+join(_Sep, [Item]) ->
+    [Item];
+join(Sep, [Item | Rest]) ->
+    [Item, Sep | join(Sep, Rest)].


[04/50] [abbrv] couchdb-mango git commit: Change the default limit to effectively unlimited

Posted by ro...@apache.org.
Change the default limit to effectively unlimited

This just does the same hack as views by setting the default limit value
extremely high to mimic unlimited.


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/b5206e5d
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/b5206e5d
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/b5206e5d

Branch: refs/heads/master
Commit: b5206e5def84972c1fed7ac3fe1785b9c743dae9
Parents: 29dc32c
Author: Paul J. Davis <pa...@gmail.com>
Authored: Tue Jun 24 15:13:08 2014 -0500
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Tue Jun 24 15:13:08 2014 -0500

----------------------------------------------------------------------
 src/mango_opts.erl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/b5206e5d/src/mango_opts.erl
----------------------------------------------------------------------
diff --git a/src/mango_opts.erl b/src/mango_opts.erl
index 84ead8d..44426db 100644
--- a/src/mango_opts.erl
+++ b/src/mango_opts.erl
@@ -60,7 +60,7 @@ validate_find({Props}) ->
         {<<"limit">>, [
             {tag, limit},
             {optional, true},
-            {default, 25},
+            {default, 10000000000},
             {validator, fun is_non_neg_integer/1}
         ]},
         {<<"skip">>, [


[21/50] [abbrv] couchdb-mango git commit: Set correct copyright info

Posted by ro...@apache.org.
Set correct copyright info


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/6f661f1a
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/6f661f1a
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/6f661f1a

Branch: refs/heads/master
Commit: 6f661f1a6d8bdc3b67fdde33f17c2061c9ea72cd
Parents: 910a6b6
Author: Joan Touzet <wo...@apache.org>
Authored: Mon Nov 17 23:27:09 2014 +0100
Committer: Joan Touzet <wo...@apache.org>
Committed: Mon Nov 17 23:27:09 2014 +0100

----------------------------------------------------------------------
 LICENSE.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/6f661f1a/LICENSE.txt
----------------------------------------------------------------------
diff --git a/LICENSE.txt b/LICENSE.txt
index d645695..b47557a 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -187,7 +187,7 @@
       same "printed page" as the copyright notice for easier
       identification within third-party archives.
 
-   Copyright [yyyy] [name of copyright owner]
+   Copyright 2014 IBM Corporation
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.


[19/50] [abbrv] couchdb-mango git commit: Update README.md with finalised HTTP API endpoints

Posted by ro...@apache.org.
Update README.md with finalised HTTP API endpoints

Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/77911e0b
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/77911e0b
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/77911e0b

Branch: refs/heads/master
Commit: 77911e0b4602c52f56fd32247221b2cb0c25b7f8
Parents: e9ced73
Author: Joan Touzet <wo...@users.noreply.github.com>
Authored: Mon Nov 17 09:53:35 2014 +0100
Committer: Joan Touzet <wo...@users.noreply.github.com>
Committed: Mon Nov 17 09:53:35 2014 +0100

----------------------------------------------------------------------
 README.md | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/77911e0b/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index d7dadf8..382681f 100644
--- a/README.md
+++ b/README.md
@@ -334,12 +334,12 @@ A trivial example:
     ["foo", "bar", "baz"]
 
 
-Alternative HTTP API
-====================
+HTTP API
+========
 
-This is quick off the top of my head from discussions today. I'm not sure if we should include the \_mango component or use \_find and \_index at the top level.  These are trivial to change in the future though as long as we agree on the method and request/response bodies.
+Short summary until the full documentation can be brought over.
 
-POST /dbname/\_mango/find
+POST /dbname/\_find
 -------------------------
 
 Issue a query.
@@ -348,21 +348,21 @@ Request body is a JSON object that has the selector and the various options like
 
 Response is streamed out like a view. 
 
-POST /dbname/\_mango/index
+POST /dbname/\_index
 --------------------------
 
 Request body contains the index definition.
 
 Response body is empty and the result is returned as the status code (200 OK -> created, 3something for exists).
 
-GET /dbname/\_mango/index
+GET /dbname/\_index
 -------------------------
 
 Request body is empty.
 
 Response body is all of the indexes that are available for use by find.
 
-DELETE /dbname/\_mango/index/ddocid/viewname
+DELETE /dbname/\_index/ddocid/viewname
 --------------------------------------------
 
 Remove the specified index.


[09/50] [abbrv] couchdb-mango git commit: Bump readme with config setting to turn on Mango/CQ

Posted by ro...@apache.org.
Bump readme with config setting to turn on Mango/CQ


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/45717b07
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/45717b07
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/45717b07

Branch: refs/heads/master
Commit: 45717b07761477e98b00d9ea5b6f066b4184d006
Parents: c33efa3
Author: Kyle Snavely <ks...@cloudant.com>
Authored: Tue Aug 26 15:40:31 2014 -0400
Committer: Kyle Snavely <ks...@cloudant.com>
Committed: Tue Aug 26 15:40:31 2014 -0400

----------------------------------------------------------------------
 README.md | 8 ++++++++
 1 file changed, 8 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/45717b07/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index aa6ab16..05bc128 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,14 @@ have a string value indicating the action to be performed. For each action there
 
 For convenience, the HTTP API will accept a JSON body that is either a single JSON object which specifies a single action or a JSON array that specifies a list of actions that will then be invoked serially. While multiple commands can be batched into a single HTTP request, there are no guarantees about atomicity or isolation for a batch of commands.
 
+Activating Mango/Cloudant Query on a cluster
+--------------------------------------------
+
+Cloudant Query can be enabled by setting the following config:
+
+```
+rpc:multicall(config, set, ["native_query_servers", "query", "{mango_native_proc, start_link, []}"]).
+```
 
 HTTP API
 ========


[05/50] [abbrv] couchdb-mango git commit: Merge pull request #4 from cloudant/31919-fix-index-creation-failures

Posted by ro...@apache.org.
Merge pull request #4 from cloudant/31919-fix-index-creation-failures

31919 fix index creation failures

Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/87344438
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/87344438
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/87344438

Branch: refs/heads/master
Commit: 873444386b5f8229f75fa72613930f548365117e
Parents: 9e91d75 b5206e5
Author: Paul J. Davis <pa...@gmail.com>
Authored: Tue Jun 24 15:15:59 2014 -0500
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Tue Jun 24 15:15:59 2014 -0500

----------------------------------------------------------------------
 src/mango_error.erl             | 26 ++++++++++++++++++++++++++
 src/mango_httpd.erl             |  9 +++++++--
 src/mango_opts.erl              |  2 +-
 src/mango_util.erl              | 16 +++++++++++++++-
 test/01-index-crud-test.py      | 16 ++++++++++++++++
 test/04-empty-selectors-test.py | 36 ++++++++++++++++++++++++++++++++++++
 test/04-empty-selectors.py      | 36 ------------------------------------
 test/mango.py                   |  3 +++
 test/user_docs.py               |  6 +++---
 9 files changed, 107 insertions(+), 43 deletions(-)
----------------------------------------------------------------------



[24/50] [abbrv] couchdb-mango git commit: Avoid regex in the common case

Posted by ro...@apache.org.
Avoid regex in the common case

Escaped characters in field names are highly unusual. We can quickly
test for their presence and if none are found do the path splitting
using the more efficient binary module instead of regular expressions. A
microbenchmark shows this approach to be a little more than twice as
fast as the original.


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/ba40953a
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/ba40953a
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/ba40953a

Branch: refs/heads/master
Commit: ba40953a2d3053d4dbd92320da6105b3d4f8f1d8
Parents: 072d3f2
Author: Adam Kocoloski <ad...@cloudant.com>
Authored: Thu Dec 11 21:09:33 2014 -0500
Committer: Adam Kocoloski <ad...@cloudant.com>
Committed: Thu Dec 11 21:09:33 2014 -0500

----------------------------------------------------------------------
 src/mango_doc.erl | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/ba40953a/src/mango_doc.erl
----------------------------------------------------------------------
diff --git a/src/mango_doc.erl b/src/mango_doc.erl
index 666ef9b..a2de4b2 100644
--- a/src/mango_doc.erl
+++ b/src/mango_doc.erl
@@ -525,6 +525,15 @@ set_elem(I, [Item | Rest], Value) when I > 1 ->
     [Item | set_elem(I-1, Rest, Value)].
 
 parse_field(Field) ->
+    case binary:match(Field, <<"\\">>, []) of
+        nomatch ->
+            % Fast path, no regex required
+            {ok, check_non_empty(Field, binary:split(Field, <<".">>, [global]))};
+        _ ->
+            parse_field_slow(Field)
+    end.
+
+parse_field_slow(Field) ->
     Path = lists:map(fun
         (P) when P =:= <<>> ->
             ?MANGO_ERROR({invalid_field_name, Field});
@@ -533,6 +542,14 @@ parse_field(Field) ->
     end, re:split(Field, <<"(?<!\\\\)\\.">>)),
     {ok, Path}.
 
+check_non_empty(Field, Parts) ->
+    case lists:member(<<>>, Parts) of
+        true ->
+            ?MANGO_ERROR({invalid_field_name, Field});
+        false ->
+            Parts
+    end.
+
 -ifdef(TEST).
 -include_lib("eunit/include/eunit.hrl").
 


[22/50] [abbrv] couchdb-mango git commit: Attempt to retrieve indexes from cache

Posted by ro...@apache.org.
Attempt to retrieve indexes from cache

BugzID: 42707


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/c61576c3
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/c61576c3
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/c61576c3

Branch: refs/heads/master
Commit: c61576c3cb44ee43a68764dbde7837edce821be7
Parents: 6f661f1
Author: Adam Kocoloski <ad...@cloudant.com>
Authored: Thu Dec 11 14:32:48 2014 -0500
Committer: Adam Kocoloski <ad...@cloudant.com>
Committed: Thu Dec 11 16:19:44 2014 -0500

----------------------------------------------------------------------
 src/mango_idx.erl | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/c61576c3/src/mango_idx.erl
----------------------------------------------------------------------
diff --git a/src/mango_idx.erl b/src/mango_idx.erl
index 4a3cf83..c1bbe4c 100644
--- a/src/mango_idx.erl
+++ b/src/mango_idx.erl
@@ -7,6 +7,7 @@
 
 -export([
     list/1,
+    recover/1,
 
     new/2,
     validate/1,
@@ -36,6 +37,10 @@
 
 
 list(Db) ->
+    {ok, Indexes} = ddoc_cache:open(Db, ?MODULE),
+    Indexes.
+
+recover(Db) ->
     {ok, DDocs0} = mango_util:open_ddocs(Db),
     Pred = fun({Props}) ->
         case proplists:get_value(<<"language">>, Props) of
@@ -45,9 +50,9 @@ list(Db) ->
     end,
     DDocs = lists:filter(Pred, DDocs0),
     Special = special(Db),
-    Special ++ lists:flatmap(fun(Doc) ->
+    {ok, Special ++ lists:flatmap(fun(Doc) ->
         from_ddoc(Db, Doc)
-    end, DDocs).
+    end, DDocs)}.
 
 
 new(Db, Opts) ->


[06/50] [abbrv] couchdb-mango git commit: Silence compiler warning

Posted by ro...@apache.org.
Silence compiler warning


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/bbac0d29
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/bbac0d29
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/bbac0d29

Branch: refs/heads/master
Commit: bbac0d2968c6bedd42046cc8ac1d8d813d37aebd
Parents: 8734443
Author: Paul J. Davis <pa...@gmail.com>
Authored: Tue Jun 24 15:29:03 2014 -0500
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Tue Jun 24 15:29:03 2014 -0500

----------------------------------------------------------------------
 src/mango_util.erl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/bbac0d29/src/mango_util.erl
----------------------------------------------------------------------
diff --git a/src/mango_util.erl b/src/mango_util.erl
index ff7d654..a60eb50 100644
--- a/src/mango_util.erl
+++ b/src/mango_util.erl
@@ -135,7 +135,7 @@ assert_ejson_arr([Val | Rest]) ->
     end.
 
 
-check_lang(#doc{id = Id, deleted = true} = Doc) ->
+check_lang(#doc{id = Id, deleted = true}) ->
     Body = {[
         {<<"language">>, <<"query">>}
     ]},


[37/50] [abbrv] couchdb-mango git commit: Refactor the test suite

Posted by ro...@apache.org.
Refactor the test suite

This moves the test suite to using unittest classes so that we can
control the database creation more directly. This also creates randomly
named databases so that we have fewer race conditions during test runs.

BugzId: 33294


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/d1d13e6f
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/d1d13e6f
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/d1d13e6f

Branch: refs/heads/master
Commit: d1d13e6fdc42b81a7b333d213c3392dbe80bc825
Parents: fc8d7d0
Author: Paul J. Davis <pa...@gmail.com>
Authored: Thu Jan 8 17:12:20 2015 -0600
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Fri Jan 16 13:32:49 2015 -0600

----------------------------------------------------------------------
 test/01-index-crud-test.py      | 362 +++++++++++------------
 test/02-basic-find-test.py      | 539 +++++++++++++++++------------------
 test/03-operator-test.py        | 132 ++++-----
 test/04-empty-selectors-test.py |  47 ---
 test/mango.py                   |  59 +++-
 test/user_docs.py               |  79 +++--
 6 files changed, 593 insertions(+), 625 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/d1d13e6f/test/01-index-crud-test.py
----------------------------------------------------------------------
diff --git a/test/01-index-crud-test.py b/test/01-index-crud-test.py
index be13bdd..459566b 100644
--- a/test/01-index-crud-test.py
+++ b/test/01-index-crud-test.py
@@ -11,218 +11,184 @@
 # the License.
 
 import random
-import time
 
 import mango
 
 
-def mkdb():
-    return mango.Database("127.0.0.1", "5984", "mango_test")
+class IndexCrudTests(mango.DbPerClass):
+    def test_bad_fields(self):
+        bad_fields = [
+            None,
+            True,
+            False,
+            "bing",
+            2.0,
+            {"foo": "bar"},
+            [{"foo": 2}],
+            [{"foo": "asc", "bar": "desc"}],
+            [{"foo": "asc"}, {"bar": "desc"}]
+        ]
+        for fields in bad_fields:
+            try:
+                self.db.create_index(fields)
+            except Exception, e:
+                assert e.response.status_code == 400
+            else:
+                raise AssertionError("bad create index")
+
+    def test_bad_types(self):
+        bad_types = [
+            None,
+            True,
+            False,
+            1.5,
+            "foo", # Future support
+            "geo", # Future support
+            {"foo": "bar"},
+            ["baz", 3.0]
+        ]
+        for bt in bad_types:
+            try:
+                self.db.create_index(["foo"], idx_type=bt)
+            except Exception, e:
+                assert e.response.status_code == 400, (bt, e.response.status_code)
+            else:
+                raise AssertionError("bad create index")
+
+    def test_bad_names(self):
+        bad_names = [
+            True,
+            False,
+            1.5,
+            {"foo": "bar"},
+            [None, False]
+        ]
+        for bn in bad_names:
+            try:
+                self.db.create_index(["foo"], name=bn)
+            except Exception, e:
+                assert e.response.status_code == 400
+            else:
+                raise AssertionError("bad create index")
+            try:
+                self.db.create_index(["foo"], ddoc=bn)
+            except Exception, e:
+                assert e.response.status_code == 400
+            else:
+                raise AssertionError("bad create index")
+
+    def test_create_idx_01(self):
+        fields = ["foo", "bar"]
+        ret = self.db.create_index(fields, name="idx_01")
+        assert ret is True
+        for idx in self.db.list_indexes():
+            if idx["name"] != "idx_01":
+                continue
+            assert idx["def"]["fields"] == [{"foo": "asc"}, {"bar": "asc"}]
+            return
+        raise AssertionError("index not created")
+
+    def test_create_idx_01_exists(self):
+        fields = ["foo", "bar"]
+        ret = self.db.create_index(fields, name="idx_01")
+        assert ret is False
+
+    def test_create_idx_02(self):
+        fields = ["baz", "foo"]
+        ret = self.db.create_index(fields, name="idx_02")
+        assert ret is True
+        for idx in self.db.list_indexes():
+            if idx["name"] != "idx_02":
+                continue
+            assert idx["def"]["fields"] == [{"baz": "asc"}, {"foo": "asc"}]
+            return
+        raise AssertionError("index not created")
 
+    def test_read_idx_doc(self):
+        for idx in self.db.list_indexes():
+            if idx["type"] == "special":
+                continue
+            ddocid = idx["ddoc"]
+            doc = self.db.open_doc(ddocid)
+            assert doc["_id"] == ddocid
+            info = self.db.ddoc_info(ddocid)
+            assert info["name"] == ddocid
+
+    def test_delete_idx_escaped(self):
+        pre_indexes = self.db.list_indexes()
+        ret = self.db.create_index(["bing"], name="idx_del_1")
+        assert ret is True
+        for idx in self.db.list_indexes():
+            if idx["name"] != "idx_del_1":
+                continue
+            assert 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
 
-def setup():
-    db = mkdb()
-    db.recreate()
-    time.sleep(1)
+    def test_delete_idx_unescaped(self):
+        pre_indexes = self.db.list_indexes()
+        ret = self.db.create_index(["bing"], name="idx_del_2")
+        assert ret is True
+        for idx in self.db.list_indexes():
+            if idx["name"] != "idx_del_2":
+                continue
+            assert idx["def"]["fields"] == [{"bing": "asc"}]
+            self.db.delete_index(idx["ddoc"], idx["name"])
+        post_indexes = self.db.list_indexes()
+        assert pre_indexes == post_indexes
 
+    def test_delete_idx_no_design(self):
+        pre_indexes = self.db.list_indexes()
+        ret = self.db.create_index(["bing"], name="idx_del_3")
+        assert ret is True
+        for idx in self.db.list_indexes():
+            if idx["name"] != "idx_del_3":
+                continue
+            assert 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
 
-def test_bad_fields():
-    db = mkdb()
-    bad_fields = [
-        None,
-        True,
-        False,
-        "bing",
-        2.0,
-        {"foo": "bar"},
-        [{"foo": 2}],
-        [{"foo": "asc", "bar": "desc"}],
-        [{"foo": "asc"}, {"bar": "desc"}]
-    ]
-    for fields in bad_fields:
+    def test_recreate_index(self):
+        pre_indexes = self.db.list_indexes()
+        for i in range(5):
+            ret = self.db.create_index(["bing"], name="idx_recreate")
+            assert ret is True
+            for idx in self.db.list_indexes():
+                if idx["name"] != "idx_recreate":
+                    continue
+                assert 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
+
+    def test_delete_misisng(self):
+        # Missing design doc
         try:
-            db.create_index(fields)
+            self.db.delete_index("this_is_not_a_design_doc_id", "foo")
         except Exception, e:
-            assert e.response.status_code == 400
+            assert e.response.status_code == 404
         else:
-            raise AssertionError("bad create index")
-
-
-def test_bad_types():
-    db = mkdb()
-    bad_types = [
-        None,
-        True,
-        False,
-        1.5,
-        "foo",
-        "text", # Future support
-        "geo", # Future support
-        {"foo": "bar"},
-        ["baz", 3.0]
-    ]
-    for bt in bad_types:
-        try:
-            db.create_index(["foo"], idx_type=bt)
-        except Exception, e:
-            assert e.response.status_code == 400, (bt, e.response.status_code)
-        else:
-            raise AssertionError("bad create index")
-
-
-def test_bad_names():
-    db = mkdb()
-    bad_names = [
-        True,
-        False,
-        1.5,
-        {"foo": "bar"},
-        [None, False]
-    ]
-    for bn in bad_names:
+            raise AssertionError("bad index delete")
+
+        # Missing view name
+        indexes = self.db.list_indexes()
+        not_special = [idx for idx in indexes if idx["type"] != "special"]
+        idx = random.choice(not_special)
+        ddocid = idx["ddoc"].split("/")[-1]
         try:
-            db.create_index(["foo"], name=bn)
+            self.db.delete_index(ddocid, "this_is_not_an_index_name")
         except Exception, e:
-            assert e.response.status_code == 400
+            assert e.response.status_code == 404
         else:
-            raise AssertionError("bad create index")
+            raise AssertionError("bad index delete")
+
+        # Bad view type
         try:
-            db.create_index(["foo"], ddoc=bn)
+            self.db.delete_index(ddocid, idx["name"], idx_type="not_a_real_type")
         except Exception, e:
-            assert e.response.status_code == 400
+            assert e.response.status_code == 404
         else:
-            raise AssertionError("bad create index")
-
-
-def test_create_idx_01():
-    db = mkdb()
-    fields = ["foo", "bar"]
-    ret = db.create_index(fields, name="idx_01")
-    assert ret is True
-    for idx in db.list_indexes():
-        if idx["name"] != "idx_01":
-            continue
-        assert idx["def"]["fields"] == [{"foo": "asc"}, {"bar": "asc"}]
-        return
-    raise AssertionError("index not created")
-
-
-def test_create_idx_01_exists():
-    db = mkdb()
-    fields = ["foo", "bar"]
-    ret = db.create_index(fields, name="idx_01")
-    assert ret is False
-
-
-def test_create_idx_02():
-    db = mkdb()
-    fields = ["baz", "foo"]
-    ret = db.create_index(fields, name="idx_02")
-    assert ret is True
-    for idx in db.list_indexes():
-        if idx["name"] != "idx_02":
-            continue
-        assert idx["def"]["fields"] == [{"baz": "asc"}, {"foo": "asc"}]
-        return
-    raise AssertionError("index not created")
-
-
-def test_read_idx_doc():
-    db = mkdb()
-    for idx in db.list_indexes():
-        if idx["type"] == "special":
-            continue
-        ddocid = idx["ddoc"]
-        doc = db.open_doc(ddocid)
-        assert doc["_id"] == ddocid
-        info = db.ddoc_info(ddocid)
-        assert info["name"] == ddocid
-
-
-def test_delete_idx_escaped():
-    db = mkdb()
-    pre_indexes = db.list_indexes()
-    ret = db.create_index(["bing"], name="idx_del_1")
-    assert ret is True
-    for idx in db.list_indexes():
-        if idx["name"] != "idx_del_1":
-            continue
-        assert idx["def"]["fields"] == [{"bing": "asc"}]
-        db.delete_index(idx["ddoc"].replace("/", "%2F"), idx["name"])
-    post_indexes = db.list_indexes()
-    assert pre_indexes == post_indexes
-
-
-def test_delete_idx_unescaped():
-    db = mkdb()
-    pre_indexes = db.list_indexes()
-    ret = db.create_index(["bing"], name="idx_del_2")
-    assert ret is True
-    for idx in db.list_indexes():
-        if idx["name"] != "idx_del_2":
-            continue
-        assert idx["def"]["fields"] == [{"bing": "asc"}]
-        db.delete_index(idx["ddoc"], idx["name"])
-    post_indexes = db.list_indexes()
-    assert pre_indexes == post_indexes
-
-
-def test_delete_idx_no_design():
-    db = mkdb()
-    pre_indexes = db.list_indexes()
-    ret = db.create_index(["bing"], name="idx_del_3")
-    assert ret is True
-    for idx in db.list_indexes():
-        if idx["name"] != "idx_del_3":
-            continue
-        assert idx["def"]["fields"] == [{"bing": "asc"}]
-        db.delete_index(idx["ddoc"].split("/")[-1], idx["name"])
-    post_indexes = db.list_indexes()
-    assert pre_indexes == post_indexes
-
-
-def test_recreate_index():
-    db = mkdb()
-    pre_indexes = db.list_indexes()
-    for i in range(5):
-        ret = db.create_index(["bing"], name="idx_recreate")
-        assert ret is True
-        for idx in db.list_indexes():
-            if idx["name"] != "idx_recreate":
-                continue
-            assert idx["def"]["fields"] == [{"bing": "asc"}]
-            db.delete_index(idx["ddoc"], idx["name"])
-            break
-        post_indexes = db.list_indexes()
-        assert pre_indexes == post_indexes
-
-
-def test_delete_missing():
-    db = mkdb()
-
-    # Missing design doc
-    try:
-        db.delete_index("this_is_not_a_design_doc_id", "foo")
-    except Exception, e:
-        assert e.response.status_code == 404
-    else:
-        raise AssertionError("bad index delete")
-
-    # Missing view name
-    indexes = db.list_indexes()
-    idx = random.choice([idx for idx in indexes if idx["type"] != "special"])
-    ddocid = idx["ddoc"].split("/")[-1]
-    try:
-        db.delete_index(ddocid, "this_is_not_an_index_name")
-    except Exception, e:
-        assert e.response.status_code == 404
-    else:
-        raise AssertionError("bad index delete")
-
-    # Bad view type
-    try:
-        db.delete_index(ddocid, idx["name"], idx_type="not_a_real_type")
-    except Exception, e:
-        assert e.response.status_code == 404
-    else:
-        raise AssertionError("bad index delete")
+            raise AssertionError("bad index delete")

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/d1d13e6f/test/02-basic-find-test.py
----------------------------------------------------------------------
diff --git a/test/02-basic-find-test.py b/test/02-basic-find-test.py
index f6b0610..58029ed 100644
--- a/test/02-basic-find-test.py
+++ b/test/02-basic-find-test.py
@@ -11,286 +11,275 @@
 # the License.
 
 # -*- coding: latin-1 -*-
-import user_docs
-
-def setup():
-    user_docs.create_db_and_indexes()
-
-
-def test_bad_selector():
-    db = user_docs.mkdb()
-    bad_selectors = [
-        None,
-        True,
-        False,
-        1.0,
-        "foobarbaz",
-        {"foo":{"$not_an_op": 2}},
-        {"$gt":2},
-        [None, "bing"]
-    ]
-    for bs in bad_selectors:
-        try:
-            db.find(bs)
-        except Exception, e:
-            assert e.response.status_code == 400
-        else:
-            raise AssertionError("bad find")
-
-
-def test_bad_limit():
-    db = user_docs.mkdb()
-    bad_limits = [
-        None,
-        True,
-        False,
-        -1,
-        1.2,
-        "no limit!",
-        {"foo": "bar"},
-        [2]
-    ],
-    for bl in bad_limits:
-        try:
-            db.find({"int":{"$gt":2}}, limit=bl)
-        except Exception, e:
-            assert e.response.status_code == 400
-        else:
-            raise AssertionError("bad find")
-
-
-def test_bad_skip():
-    db = user_docs.mkdb()
-    bad_skips = [
-        None,
-        True,
-        False,
-        -3,
-        1.2,
-        "no limit!",
-        {"foo": "bar"},
-        [2]
-    ],
-    for bs in bad_skips:
-        try:
-            db.find({"int":{"$gt":2}}, skip=bs)
-        except Exception, e:
-            assert e.response.status_code == 400
-        else:
-            raise AssertionError("bad find")
-
-
-def test_bad_sort():
-    db = user_docs.mkdb()
-    bad_sorts = [
-        None,
-        True,
-        False,
-        1.2,
-        "no limit!",
-        {"foo": "bar"},
-        [2],
-        [{"foo":"asc", "bar": "asc"}],
-        [{"foo":"asc"}, {"bar":"desc"}],
-    ],
-    for bs in bad_sorts:
-        try:
-            db.find({"int":{"$gt":2}}, sort=bs)
-        except Exception, e:
-            assert e.response.status_code == 400
-        else:
-            raise AssertionError("bad find")
-
-
-def test_bad_fields():
-    db = user_docs.mkdb()
-    bad_fields = [
-        None,
-        True,
-        False,
-        1.2,
-        "no limit!",
-        {"foo": "bar"},
-        [2],
-        [[]],
-        ["foo", 2.0],
-    ],
-    for bf in bad_fields:
-        try:
-            db.find({"int":{"$gt":2}}, fields=bf)
-        except Exception, e:
-            assert e.response.status_code == 400
-        else:
-            raise AssertionError("bad find")
-
-
-def test_bad_r():
-    db = user_docs.mkdb()
-    bad_rs = [
-        None,
-        True,
-        False,
-        1.2,
-        "no limit!",
-        {"foo": "bar"},
-        [2],
-    ],
-    for br in bad_rs:
-        try:
-            db.find({"int":{"$gt":2}}, r=br)
-        except Exception, e:
-            assert e.response.status_code == 400
-        else:
-            raise AssertionError("bad find")
 
+import mango
+
+
+class BasicFindTests(mango.UserDocsTests):
+
+    def test_bad_selector(self):
+        bad_selectors = [
+            None,
+            True,
+            False,
+            1.0,
+            "foobarbaz",
+            {"foo":{"$not_an_op": 2}},
+            {"$gt":2},
+            [None, "bing"]
+        ]
+        for bs in bad_selectors:
+            try:
+                self.db.find(bs)
+            except Exception, e:
+                assert e.response.status_code == 400
+            else:
+                raise AssertionError("bad find")
+
+    def test_bad_limit(self):
+        bad_limits = [
+            None,
+            True,
+            False,
+            -1,
+            1.2,
+            "no limit!",
+            {"foo": "bar"},
+            [2]
+        ],
+        for bl in bad_limits:
+            try:
+                self.db.find({"int":{"$gt":2}}, limit=bl)
+            except Exception, e:
+                assert e.response.status_code == 400
+            else:
+                raise AssertionError("bad find")
+
+    def test_bad_skip(self):
+        bad_skips = [
+            None,
+            True,
+            False,
+            -3,
+            1.2,
+            "no limit!",
+            {"foo": "bar"},
+            [2]
+        ],
+        for bs in bad_skips:
+            try:
+                self.db.find({"int":{"$gt":2}}, skip=bs)
+            except Exception, e:
+                assert e.response.status_code == 400
+            else:
+                raise AssertionError("bad find")
+
+    def test_bad_sort(self):
+        bad_sorts = [
+            None,
+            True,
+            False,
+            1.2,
+            "no limit!",
+            {"foo": "bar"},
+            [2],
+            [{"foo":"asc", "bar": "asc"}],
+            [{"foo":"asc"}, {"bar":"desc"}],
+        ],
+        for bs in bad_sorts:
+            try:
+                self.db.find({"int":{"$gt":2}}, sort=bs)
+            except Exception, e:
+                assert e.response.status_code == 400
+            else:
+                raise AssertionError("bad find")
+
+    def test_bad_fields(self):
+        bad_fields = [
+            None,
+            True,
+            False,
+            1.2,
+            "no limit!",
+            {"foo": "bar"},
+            [2],
+            [[]],
+            ["foo", 2.0],
+        ],
+        for bf in bad_fields:
+            try:
+                self.db.find({"int":{"$gt":2}}, fields=bf)
+            except Exception, e:
+                assert e.response.status_code == 400
+            else:
+                raise AssertionError("bad find")
+
+    def test_bad_r(self):
+        bad_rs = [
+            None,
+            True,
+            False,
+            1.2,
+            "no limit!",
+            {"foo": "bar"},
+            [2],
+        ],
+        for br in bad_rs:
+            try:
+                self.db.find({"int":{"$gt":2}}, r=br)
+            except Exception, e:
+                assert e.response.status_code == 400
+            else:
+                raise AssertionError("bad find")
+
+    def test_bad_conflicts(self):
+        bad_conflicts = [
+            None,
+            1.2,
+            "no limit!",
+            {"foo": "bar"},
+            [2],
+        ],
+        for bc in bad_conflicts:
+            try:
+                self.db.find({"int":{"$gt":2}}, conflicts=bc)
+            except Exception, e:
+                assert e.response.status_code == 400
+            else:
+                raise AssertionError("bad find")
+
+    def test_simple_find(self):
+        docs = self.db.find({"age": {"$lt": 35}})
+        assert len(docs) == 3
+        assert docs[0]["user_id"] == 9
+        assert docs[1]["user_id"] == 1
+        assert docs[2]["user_id"] == 7
+
+    def test_multi_cond_and(self):
+        docs = self.db.find({"manager": True, "location.city": "Longbranch"})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 7
+
+    def test_multi_cond_or(self):
+        docs = self.db.find({
+                "$and":[
+                    {"age":{"$gte": 75}},
+                    {"$or": [
+                        {"name.first": "Mathis"},
+                        {"name.first": "Whitley"}
+                    ]}
+                ]
+            })
+        assert len(docs) == 2
+        assert docs[0]["user_id"] == 11
+        assert docs[1]["user_id"] == 13
+
+    def test_multi_col_idx(self):
+        docs = self.db.find({
+            "location.state": {"$and": [
+                {"$gt": "Hawaii"},
+                {"$lt": "Maine"}
+            ]},
+            "location.city": {"$lt": "Longbranch"}
+        })
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 6
+
+    def test_missing_not_indexed(self):
+        docs = self.db.find({"favorites.3": "C"})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 6
+
+        docs = self.db.find({"favorites.3": None})
+        assert len(docs) == 0
+
+        docs = self.db.find({"twitter": {"$gt": None}})
+        assert len(docs) == 4
+        assert docs[0]["user_id"] == 1
+        assert docs[1]["user_id"] == 4
+        assert docs[2]["user_id"] == 0
+        assert docs[3]["user_id"] == 13
+
+    def test_limit(self):
+        docs = self.db.find({"age": {"$gt": 0}})
+        assert len(docs) == 15
+        for l in [0, 1, 5, 14]:
+            docs = self.db.find({"age": {"$gt": 0}}, limit=l)
+            assert len(docs) == l
 
-def test_bad_conflicts():
-    db = user_docs.mkdb()
-    bad_conflicts = [
-        None,
-        1.2,
-        "no limit!",
-        {"foo": "bar"},
-        [2],
-    ],
-    for bc in bad_conflicts:
+    def test_skip(self):
+        docs = self.db.find({"age": {"$gt": 0}})
+        assert len(docs) == 15
+        for s in [0, 1, 5, 14]:
+            docs = self.db.find({"age": {"$gt": 0}}, skip=s)
+            assert len(docs) == (15 - s)
+
+    def test_sort(self):
+        docs1 = self.db.find({"age": {"$gt": 0}}, sort=[{"age":"asc"}])
+        docs2 = list(sorted(docs1, key=lambda d: d["age"]))
+        assert docs1 is not docs2 and docs1 == docs2
+
+        docs1 = self.db.find({"age": {"$gt": 0}}, sort=[{"age":"desc"}])
+        docs2 = list(reversed(sorted(docs1, key=lambda d: d["age"])))
+        assert docs1 is not docs2 and docs1 == docs2
+
+    def test_fields(self):
+        selector = {"age": {"$gt": 0}}
+        docs = self.db.find(selector, fields=["user_id", "location.address"])
+        for d in docs:
+            assert sorted(d.keys()) == ["location", "user_id"]
+            assert sorted(d["location"].keys()) == ["address"]
+
+    def test_r(self):
+        for r in [1, 2, 3]:
+            docs = self.db.find({"age": {"$gt": 0}}, r=r)
+            assert len(docs) == 15
+
+    def test_dot_key(self):
+        fields = ["title", "dot\\.key", "none.dot"]
+        docs = self.db.find({"type": "complex_key"}, fields = fields)
+        assert len(docs) == 4
+        assert docs[1].has_key("dot.key")
+        assert docs[1]["dot.key"] == "dot's value"
+        assert docs[1].has_key("none")
+        assert docs[1]["none"]["dot"] == "none dot's value"
+
+    def test_peso_key(self):
+        fields = ["title", "$key", "deep.$key"]
+        docs = self.db.find({"type": "complex_key"}, fields = fields)
+        assert len(docs) == 4
+        assert docs[2].has_key("$key")
+        assert docs[2]["$key"] == "peso"
+        assert docs[2].has_key("deep")
+        assert docs[2]["deep"]["$key"] == "deep peso"
+
+    def test_unicode_key(self):
+        docs = self.db.find({"type": "complex_key"}, fields = ["title", ""])
+        assert len(docs) == 4
+        # note:  == \uf8ff
+        assert docs[3].has_key(u'\uf8ff')
+        assert docs[3][u'\uf8ff'] == "apple"
+
+    def test_empty(self):
         try:
-            db.find({"int":{"$gt":2}}, conflicts=bc)
+            self.db.find({})
         except Exception, e:
             assert e.response.status_code == 400
         else:
             raise AssertionError("bad find")
 
-
-def test_simple_find():
-    db = user_docs.mkdb()
-    docs = db.find({"age": {"$lt": 35}})
-    assert len(docs) == 3
-    assert docs[0]["user_id"] == 9
-    assert docs[1]["user_id"] == 1
-    assert docs[2]["user_id"] == 7
-
-
-def test_multi_cond_and():
-    db = user_docs.mkdb()
-    docs = db.find({"manager": True, "location.city": "Longbranch"})
-    assert len(docs) == 1
-    assert docs[0]["user_id"] == 7
-
-
-def test_multi_cond_or():
-    db = user_docs.mkdb()
-    docs = db.find({
-            "$and":[
-                {"age":{"$gte": 75}},
-                {"$or": [
-                    {"name.first": "Mathis"},
-                    {"name.first": "Whitley"}
-                ]}
-            ]
-        })
-    assert len(docs) == 2
-    assert docs[0]["user_id"] == 11
-    assert docs[1]["user_id"] == 13
-
-
-def test_multi_col_idx():
-    db = user_docs.mkdb()
-    docs = db.find({
-        "location.state": {"$and": [
-            {"$gt": "Hawaii"},
-            {"$lt": "Maine"}
-        ]},
-        "location.city": {"$lt": "Longbranch"}
-    })
-    assert len(docs) == 1
-    assert docs[0]["user_id"] == 6
-
-
-def test_missing_not_indexed():
-    db = user_docs.mkdb()
-    docs = db.find({"favorites.3": "C"})
-    assert len(docs) == 2
-    assert docs[0]["user_id"] == 8
-    assert docs[1]["user_id"] == 6
-
-    docs = db.find({"favorites.3": None})
-    assert len(docs) == 0
-
-    docs = db.find({"twitter": {"$gt": None}})
-    assert len(docs) == 4
-    assert docs[0]["user_id"] == 1
-    assert docs[1]["user_id"] == 4
-    assert docs[2]["user_id"] == 0
-    assert docs[3]["user_id"] == 13
-
-def test_dot_key():
-    db = user_docs.mkdb()
-    fields = ["title", "dot\\.key", "none.dot"]
-    docs = db.find({"type": "complex_key"}, fields = fields)
-    assert len(docs) == 4
-    assert docs[1].has_key("dot.key")
-    assert docs[1]["dot.key"] == "dot's value"
-    assert docs[1].has_key("none")
-    assert docs[1]["none"]["dot"] == "none dot's value"
-
-def test_peso_key():
-    db = user_docs.mkdb()
-    fields = ["title", "$key", "deep.$key"]
-    docs = db.find({"type": "complex_key"}, fields = fields)
-    assert len(docs) == 4
-    assert docs[2].has_key("$key")
-    assert docs[2]["$key"] == "peso"
-    assert docs[2].has_key("deep")
-    assert docs[2]["deep"]["$key"] == "deep peso"
-
-def test_unicode_key():
-    db = user_docs.mkdb()
-    docs = db.find({"type": "complex_key"}, fields = ["title", ""])
-    assert len(docs) == 4
-    # note:  == \uf8ff
-    assert docs[3].has_key(u'\uf8ff')
-    assert docs[3][u'\uf8ff'] == "apple"
-
-def test_limit():
-    db = user_docs.mkdb()
-    docs = db.find({"age": {"$gt": 0}})
-    assert len(docs) == 15
-    for l in [0, 1, 5, 14]:
-        docs = db.find({"age": {"$gt": 0}}, limit=l)
-        assert len(docs) == l
-
-def test_skip():
-    db = user_docs.mkdb()
-    docs = db.find({"age": {"$gt": 0}})
-    assert len(docs) == 15
-    for s in [0, 1, 5, 14]:
-        docs = db.find({"age": {"$gt": 0}}, skip=s)
-        assert len(docs) == (15 - s)
-
-
-def test_sort():
-    db = user_docs.mkdb()
-
-    docs1 = db.find({"age": {"$gt": 0}}, sort=[{"age":"asc"}])
-    docs2 = list(sorted(docs1, key=lambda d: d["age"]))
-    assert docs1 is not docs2 and docs1 == docs2
-
-    docs1 = db.find({"age": {"$gt": 0}}, sort=[{"age":"desc"}])
-    docs2 = list(reversed(sorted(docs1, key=lambda d: d["age"])))
-    assert docs1 is not docs2 and docs1 == docs2
-
-
-def test_fields():
-    db = user_docs.mkdb()
-    docs = db.find({"age": {"$gt": 0}}, fields=["user_id", "location.address"])
-    for d in docs:
-        assert sorted(d.keys()) == ["location", "user_id"]
-        assert sorted(d["location"].keys()) == ["address"]
-
-
-def test_r():
-    db = user_docs.mkdb()
-    for r in [1, 2, 3]:
-        docs = db.find({"age": {"$gt": 0}}, r=r)
-        assert len(docs) == 15
+    def test_empty_subsel(self):
+        docs = self.db.find({
+                "_id": {"$gt": None},
+                "location": {}
+            })
+        assert len(docs) == 0
+
+    def test_empty_subsel_match(self):
+        self.db.save_docs([{"user_id": "eo", "empty_obj": {}}])
+        docs = self.db.find({
+                "_id": {"$gt": None},
+                "empty_obj": {}
+            })
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == "eo"

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/d1d13e6f/test/03-operator-test.py
----------------------------------------------------------------------
diff --git a/test/03-operator-test.py b/test/03-operator-test.py
index bd0500f..27418a6 100644
--- a/test/03-operator-test.py
+++ b/test/03-operator-test.py
@@ -10,83 +10,73 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-import user_docs
+import mango
 
 
-def setup():
-    user_docs.create_db_and_indexes()
+class OperatorTests(mango.UserDocsTests):
 
+    def test_all(self):
+        docs = self.db.find({
+                "manager": True,
+                "favorites": {"$all": ["Lisp", "Python"]}
+            })
+        print docs
+        assert len(docs) == 4
+        assert docs[0]["user_id"] == 2
+        assert docs[1]["user_id"] == 12
+        assert docs[2]["user_id"] == 9
+        assert docs[3]["user_id"] == 14
 
-def test_all():
-    db = user_docs.mkdb()
-    docs = db.find({
-            "manager": True,
-            "favorites": {"$all": ["Lisp", "Python"]}
-        })
-    assert len(docs) == 3
-    assert docs[0]["user_id"] == 2
-    assert docs[1]["user_id"] == 12
-    assert docs[2]["user_id"] == 9
-
-
-def test_all_non_array():
-    db = user_docs.mkdb()
-    docs = db.find({
-            "manager": True,
-            "location": {"$all": ["Ohai"]}
-        })
-    assert len(docs) == 0
-
+    def test_all_non_array(self):
+        docs = self.db.find({
+                "manager": True,
+                "location": {"$all": ["Ohai"]}
+            })
+        assert len(docs) == 0
 
-def test_elem_match():
-    db = user_docs.mkdb()
-    emdocs = [
-        {
-            "user_id": "a",
-            "bang": [{
-                "foo": 1,
-                "bar": 2
-            }]
-        },
-        {
-            "user_id": "b",
-            "bang": [{
-                "foo": 2,
+    def test_elem_match(self):
+        emdocs = [
+            {
+                "user_id": "a",
+                "bang": [{
+                    "foo": 1,
+                    "bar": 2
+                }]
+            },
+            {
+                "user_id": "b",
+                "bang": [{
+                    "foo": 2,
+                    "bam": True
+                }]
+            }
+        ]
+        self.db.save_docs(emdocs, w=3)
+        docs = self.db.find({
+            "_id": {"$gt": None},
+            "bang": {"$elemMatch": {
+                "foo": {"$gte": 1},
                 "bam": True
-            }]
-        }
-    ]
-    db.save_docs(emdocs)
-    docs = db.find({
-        "_id": {"$gt": None},
-        "bang": {"$elemMatch": {
-            "foo": {"$gte": 1},
-            "bam": True
-        }}
-    })
-    assert len(docs) == 1
-    assert docs[0]["user_id"] == "b"
-
-
-def test_regex():
-    db = user_docs.mkdb()
-
-    docs = db.find({
-            "age": {"$gt": 40},
-            "location.state": {"$regex": "(?i)new.*"}
+            }}
         })
-    assert len(docs) == 2
-    assert docs[0]["user_id"] == 2
-    assert docs[1]["user_id"] == 10
+        print docs
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == "b"
 
+    def test_in_operator_array(self):
+        docs = self.db.find({
+                "manager": True,
+                "favorites": {"$in": ["Ruby", "Python"]}
+            })
+        assert len(docs) == 7
+        assert docs[0]["user_id"] == 2
+        assert docs[1]["user_id"] == 12
 
-def test_in_operator_array():
-    db = user_docs.mkdb()
-
-    docs = db.find({
-            "manager": True,
-            "favorites": {"$in": ["Ruby", "Python"]}
-        })
-    assert len(docs) == 7
-    assert docs[0]["user_id"] == 2
-    assert docs[1]["user_id"] == 12
+    def test_regex(self):
+        docs = self.db.find({
+                "age": {"$gt": 40},
+                "location.state": {"$regex": "(?i)new.*"}
+            })
+        assert len(docs) == 2
+        assert docs[0]["user_id"] == 2
+        assert docs[1]["user_id"] == 10

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/d1d13e6f/test/04-empty-selectors-test.py
----------------------------------------------------------------------
diff --git a/test/04-empty-selectors-test.py b/test/04-empty-selectors-test.py
deleted file mode 100644
index 08bb03e..0000000
--- a/test/04-empty-selectors-test.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# 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 user_docs
-
-
-def setup():
-    user_docs.create_db_and_indexes()
-
-
-def test_empty():
-    db = user_docs.mkdb()
-    try:
-        db.find({})
-    except Exception, e:
-        assert e.response.status_code == 400
-    else:
-        raise AssertionError("bad find")
-
-
-def test_empty_subsel():
-    db = user_docs.mkdb()
-    docs = db.find({
-            "_id": {"$gt": None},
-            "location": {}
-        })
-    assert len(docs) == 0
-
-
-def test_empty_subsel_match():
-    db = user_docs.mkdb()
-    db.save_docs([{"user_id": "eo", "empty_obj": {}}])
-    docs = db.find({
-            "_id": {"$gt": None},
-            "empty_obj": {}
-        })
-    assert len(docs) == 1
-    assert docs[0]["user_id"] == "eo"

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/d1d13e6f/test/mango.py
----------------------------------------------------------------------
diff --git a/test/mango.py b/test/mango.py
index 3163cf5..c28f280 100644
--- a/test/mango.py
+++ b/test/mango.py
@@ -12,9 +12,17 @@
 
 import json
 import time
+import unittest
+import uuid
 
 import requests
 
+import user_docs
+
+
+def random_db_name():
+    return "mango_test_" + uuid.uuid4().hex
+
 
 class Database(object):
     def __init__(self, host, port, dbname, auth=None):
@@ -35,10 +43,10 @@ class Database(object):
             parts = [parts]
         return "/".join([self.url] + parts)
 
-    def create(self):
+    def create(self, q=1, n=3):
         r = self.sess.get(self.url)
         if r.status_code == 404:
-            r = self.sess.put(self.url)
+            r = self.sess.put(self.url, params={"q":q, "n": n})
             r.raise_for_status()
 
     def delete(self):
@@ -53,9 +61,9 @@ class Database(object):
     def save_doc(self, doc):
         self.save_docs([doc])
 
-    def save_docs(self, docs):
+    def save_docs(self, docs, **kwargs):
         body = json.dumps({"docs": docs})
-        r = self.sess.post(self.path("_bulk_docs"), data=body)
+        r = self.sess.post(self.path("_bulk_docs"), data=body, params=kwargs)
         r.raise_for_status()
         for doc, result in zip(docs, r.json()):
             doc["_id"] = result["id"]
@@ -124,3 +132,46 @@ class Database(object):
             return results[0]
         else:
             return None
+
+
+class DbPerClass(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(klass):
+        klass.db = Database("127.0.0.1", "5984", random_db_name())
+        klass.db.create(q=1, n=3)
+
+    def setUp(self):
+        self.db = self.__class__.db
+
+
+class UserDocsTests(DbPerClass):
+
+    @classmethod
+    def setUpClass(klass):
+        super(UserDocsTests, klass).setUpClass()
+        user_docs.setup(klass.db)
+
+
+class UserDocsTextTests(DbPerClass):
+
+    DEFAULT_FIELD = None
+    FIELDS = None
+
+    @classmethod
+    def setUpClass(klass):
+        super(UserDocsTextTests, klass).setUpClass()
+        user_docs.setup(
+                klass.db,
+                index_type="text",
+                default_field=klass.DEFAULT_FIELD,
+                fields=klass.FIELDS
+            )
+
+
+class FriendDocsTextTests(DbPerClass):
+
+    @classmethod
+    def setUpClass(klass):
+        super(FriendDocsTextTests, klass).setUpClass()
+        friend_docs.setup(klass.db, index_type="text")

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/d1d13e6f/test/user_docs.py
----------------------------------------------------------------------
diff --git a/test/user_docs.py b/test/user_docs.py
index 1cccb58..465370a 100644
--- a/test/user_docs.py
+++ b/test/user_docs.py
@@ -9,8 +9,8 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-
-# -*- coding: latin-1 -*-
+#
+# -*- coding: utf-8 -*-
 """
 Generated with http://www.json-generator.com/
 
@@ -50,20 +50,20 @@ With this pattern:
 ]
 """
 
-import copy
-import time
-
-import mango
-
 
-def mkdb():
-    return mango.Database("127.0.0.1", "5984", "mango_test")
+import copy
 
 
-def create_db_and_indexes():
-    db = mkdb()
+def setup(db, index_type="view", **kwargs):
     db.recreate()
     db.save_docs(copy.deepcopy(DOCS))
+    if index_type == "view":
+        add_view_indexes(db, kwargs)
+    elif index_type == "text":
+        add_text_indexes(db, kwargs)
+
+
+def add_view_indexes(db, kwargs):
     indexes = [
         ["user_id"],
         ["name.last", "name.first"],
@@ -85,6 +85,10 @@ def create_db_and_indexes():
         assert db.create_index(idx) is True
 
 
+def add_text_indexes(db, kwargs):
+    db.create_text_index(**kwargs)
+
+
 DOCS = [
     {
         "_id": "71562648-6acb-42bc-a182-df6b1f005b09",
@@ -110,7 +114,8 @@ DOCS = [
             "Ruby",
             "C",
             "Python"
-        ]
+        ],
+        "test" : [{"a":1}, {"b":2}]
     },
     {
         "_id": "12a2800c-4fe2-45a8-8d78-c084f4e242a9",
@@ -136,8 +141,9 @@ DOCS = [
             "Ruby",
             "Python",
             "C",
-            "Python"
-        ]
+            {"Versions": {"Alpha": "Beta"}}
+        ],
+        "test" : [{"a":1, "b":2}]
     },
     {
         "_id": "48ca0455-8bd0-473f-9ae2-459e42e3edd1",
@@ -162,7 +168,8 @@ DOCS = [
             "Lisp",
             "Python",
             "Erlang"
-        ]
+        ],
+        "test_in": {"val1" : 1, "val2": "val2"}
     },
     {
         "_id": "0461444c-e60a-457d-a4bb-b8d811853f21",
@@ -183,7 +190,11 @@ DOCS = [
         "company": "Tasmania",
         "email": "madelynsoto@tasmania.com",
         "manager": True,
-        "favorites": [
+        "favorites": [[
+                "Lisp",
+                "Erlang",
+                "Python"
+            ],
             "Erlang",
             "C",
             "Erlang"
@@ -291,7 +302,9 @@ DOCS = [
             "Ruby",
             "Ruby",
             "Erlang"
-        ]
+        ],
+        "exists_field" : "should_exist1"
+
     },
     {
         "_id": "6c0afcf1-e57e-421d-a03d-0c0717ebf843",
@@ -312,12 +325,8 @@ DOCS = [
         "company": "Globoil",
         "email": "jamesmcdaniel@globoil.com",
         "manager": True,
-        "favorites": [
-            "Lisp",
-            "C",
-            "Ruby",
-            "C"
-        ]
+        "favorites": None,
+        "exists_field" : "should_exist2"
     },
     {
         "_id": "954272af-d5ed-4039-a5eb-8ed57e9def01",
@@ -342,7 +351,8 @@ DOCS = [
             "Lisp",
             "Erlang",
             "Python"
-        ]
+        ],
+        "exists_array" : ["should", "exist", "array1"]
     },
     {
         "_id": "e900001d-bc48-48a6-9b1a-ac9a1f5d1a03",
@@ -366,7 +376,8 @@ DOCS = [
         "favorites": [
             "Erlang",
             "Erlang"
-        ]
+        ],
+        "exists_array" : ["should", "exist", "array2"]
     },
     {
         "_id": "b06aadcf-cd0f-4ca6-9f7e-2c993e48d4c4",
@@ -393,7 +404,8 @@ DOCS = [
             "C",
             "C++",
             "C++"
-        ]
+        ],
+        "exists_object" : {"should": "object"}
     },
     {
         "_id": "5b61abc1-a3d3-4092-b9d7-ced90e675536",
@@ -418,7 +430,8 @@ DOCS = [
             "C",
             "Python",
             "Lisp"
-        ]
+        ],
+        "exists_object" : {"another": "object"}
     },
     {
         "_id": "b1e70402-8add-4068-af8f-b4f3d0feb049",
@@ -436,7 +449,7 @@ DOCS = [
                 "number": 8766
             }
         },
-        "company": "Fangold",
+        "company": None,
         "email": "whitleyharvey@fangold.com",
         "manager": False,
         "twitter": "@whitleyharvey",
@@ -444,11 +457,16 @@ DOCS = [
             "C",
             "Ruby",
             "Ruby"
-        ]
+        ],
+        "utf8-1[]:string" : "string",
+        "utf8-2[]:boolean[]" : True,
+        "utf8-3[]:number" : 9,
+        "utf8-3[]:null" : None
     },
     {
         "_id": "c78c529f-0b07-4947-90a6-d6b7ca81da62",
         "user_id": 14,
+        "«ταБЬℓσ»" : "utf-8",
         "name": {
             "first": "Faith",
             "last": "Hess"
@@ -466,7 +484,8 @@ DOCS = [
         "email": "faithhess@pharmex.com",
         "manager": True,
         "favorites": [
-            "Lisp",
+            "Erlang",
+            "Python",
             "Lisp"
         ]
     },


[23/50] [abbrv] couchdb-mango git commit: Merge pull request #12 from cloudant/42707-cache-ddocs

Posted by ro...@apache.org.
Merge pull request #12 from cloudant/42707-cache-ddocs

Attempt to retrieve indexes from cache

BugzID: 42707

Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/072d3f25
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/072d3f25
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/072d3f25

Branch: refs/heads/master
Commit: 072d3f25d962dc75a722910483c30be89cccc43b
Parents: 6f661f1 c61576c
Author: Adam Kocoloski <ad...@cloudant.com>
Authored: Thu Dec 11 20:07:48 2014 -0500
Committer: Adam Kocoloski <ad...@cloudant.com>
Committed: Thu Dec 11 20:07:48 2014 -0500

----------------------------------------------------------------------
 src/mango_idx.erl | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)
----------------------------------------------------------------------



[44/50] [abbrv] couchdb-mango git commit: Support Text Index Creation

Posted by ro...@apache.org.
Support Text Index Creation

Add support for new index type based on Lucene text indexes. This feature
allows users to perform full text search and also improves our ability to
answer complex queries that were preivously not possible.

33294-query-text-search


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/aa4edf42
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/aa4edf42
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/aa4edf42

Branch: refs/heads/master
Commit: aa4edf42b736bb1a1ffa86248f0895d4ea028259
Parents: bb91429
Author: Tony Sun <ll...@Tonys-MacBook-Pro.local>
Authored: Thu Aug 7 13:11:09 2014 -0700
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Fri Jan 16 13:41:29 2015 -0600

----------------------------------------------------------------------
 src/mango_cursor.erl                   |   7 +-
 src/mango_cursor_text.erl              | 301 +++++++++++++++
 src/mango_error.erl                    | 122 +++++-
 src/mango_fields.erl                   |   2 +
 src/mango_idx.erl                      |  12 +-
 src/mango_idx_text.erl                 | 256 +++++++++++++
 src/mango_native_proc.erl              | 182 +++++++++
 src/mango_opts.erl                     |  17 +
 src/mango_selector.erl                 |   5 +-
 src/mango_selector_text.erl            | 347 +++++++++++++++++
 src/mango_util.erl                     |  68 +++-
 test/02-basic-find-test.py             |   2 +-
 test/04-key-tests.py                   |  98 ++++-
 test/05-index-selection-test.py        |  50 +++
 test/06-basic-text-test.py             | 488 ++++++++++++++++++++++++
 test/06-text-default-field-test.py     |  70 ++++
 test/07-text-custom-field-list-test.py |  62 +++
 test/08-text-limit-test.py             | 134 +++++++
 test/09-text-sort-test.py              |  89 +++++
 test/friend_docs.py                    | 568 ++++++++++++++++++++++++++++
 test/limit_docs.py                     | 408 ++++++++++++++++++++
 test/mango.py                          |  41 +-
 test/user_docs.py                      |  14 +-
 23 files changed, 3286 insertions(+), 57 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/src/mango_cursor.erl
----------------------------------------------------------------------
diff --git a/src/mango_cursor.erl b/src/mango_cursor.erl
index 72ee8bb..545a863 100644
--- a/src/mango_cursor.erl
+++ b/src/mango_cursor.erl
@@ -119,8 +119,13 @@ group_indexes_by_type(Indexes) ->
     IdxDict = lists:foldl(fun(I, D) ->
         dict:append(mango_idx:cursor_mod(I), I, D)
     end, dict:new(), Indexes),
+    % The first cursor module that has indexes will be
+    % used to service this query. This is so that we
+    % don't suddenly switch indexes for existing client
+    % queries.
     CursorModules = [
-        mango_cursor_view
+        mango_cursor_view,
+        mango_cursor_text
     ],
     lists:flatmap(fun(CMod) ->
         case dict:find(CMod, IdxDict) of

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/src/mango_cursor_text.erl
----------------------------------------------------------------------
diff --git a/src/mango_cursor_text.erl b/src/mango_cursor_text.erl
new file mode 100644
index 0000000..7c1b992
--- /dev/null
+++ b/src/mango_cursor_text.erl
@@ -0,0 +1,301 @@
+% 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.
+
+-module(mango_cursor_text).
+
+-export([
+    create/4,
+    explain/1,
+    execute/3
+]).
+
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("dreyfus/include/dreyfus.hrl").
+-include("mango_cursor.hrl").
+-include("mango.hrl").
+
+
+-record(cacc, {
+    selector,
+    dbname,
+    ddocid,
+    idx_name,
+    query_args,
+    bookmark,
+    limit,
+    skip,
+    user_fun,
+    user_acc
+}).
+
+
+create(Db, Indexes, Selector, Opts0) ->
+    Index = case Indexes of
+        [Index0] ->
+            Index0;
+        _ ->
+            ?MANGO_ERROR(multiple_text_indexes)
+    end,
+
+    Opts = unpack_bookmark(Db#db.name, Opts0),
+
+    % Limit the result set size to 50 for Clouseau's
+    % sake. We may want to revisit this.
+    Limit0 = couch_util:get_value(limit, Opts, 50),
+    Limit = if Limit0 < 50 -> Limit0; true -> 50 end,
+    Skip = couch_util:get_value(skip, Opts, 0),
+    Fields = couch_util:get_value(fields, Opts, all_fields),
+
+    {ok, #cursor{
+        db = Db,
+        index = Index,
+        ranges = null,
+        selector = Selector,
+        opts = Opts,
+        limit = Limit,
+        skip = Skip,
+        fields = Fields
+    }}.
+
+
+explain(Cursor) ->
+    #cursor{
+        selector = Selector,
+        opts = Opts
+    } = Cursor,
+    [
+        {'query', mango_selector_text:convert(Selector)},
+        {sort, sort_query(Opts, Selector)}
+    ].
+
+
+execute(Cursor, UserFun, UserAcc) ->
+    #cursor{
+        db = Db,
+        index = Idx,
+        limit = Limit,
+        skip = Skip,
+        selector = Selector,
+        opts = Opts
+    } = Cursor,
+    QueryArgs = #index_query_args{
+        q = mango_selector_text:convert(Selector),
+        sort = sort_query(Opts, Selector),
+        raw_bookmark = true
+    },
+    CAcc = #cacc{
+        selector = Selector,
+        dbname = Db#db.name,
+        ddocid = ddocid(Idx),
+        idx_name = mango_idx:name(Idx),
+        bookmark = get_bookmark(Opts),
+        limit = Limit,
+        skip = Skip,
+        query_args = QueryArgs,
+        user_fun = UserFun,
+        user_acc = UserAcc
+    },
+    try
+        execute(CAcc)
+    catch
+        throw:{stop, FinalCAcc} ->
+            #cacc{
+                bookmark = FinalBM,
+                user_fun = UserFun,
+                user_acc = LastUserAcc
+            } = FinalCAcc,
+            JsonBM = dreyfus_bookmark:pack(FinalBM),
+            Arg = {add_key, bookmark, JsonBM},
+            {_Go, FinalUserAcc} = UserFun(Arg, LastUserAcc),
+            {ok, FinalUserAcc}
+    end.
+
+
+execute(CAcc) ->
+    case search_docs(CAcc) of
+        {ok, Bookmark, []} ->
+            % If we don't have any results from the
+            % query it means the request has paged through
+            % all possible results and the request is over.
+            NewCAcc = CAcc#cacc{bookmark = Bookmark},
+            throw({stop, NewCAcc});
+        {ok, Bookmark, Hits} ->
+            NewCAcc = CAcc#cacc{bookmark = Bookmark},
+            HitDocs = get_json_docs(CAcc#cacc.dbname, Hits),
+            {ok, FinalCAcc} = handle_hits(NewCAcc, HitDocs),
+            execute(FinalCAcc)
+    end.
+
+
+search_docs(CAcc) ->
+    #cacc{
+        dbname = DbName,
+        ddocid = DDocId,
+        idx_name = IdxName
+    } = CAcc,
+    QueryArgs = update_query_args(CAcc),
+    case dreyfus_fabric_search:go(DbName, DDocId, IdxName, QueryArgs) of
+        {ok, Bookmark, _, Hits, _, _} ->
+            {ok, Bookmark, Hits};
+        {error, Reason} ->
+            ?MANGO_ERROR({text_search_error, {error, Reason}})
+    end.
+
+
+handle_hits(CAcc, []) ->
+    {ok, CAcc};
+
+handle_hits(CAcc0, [{Sort, Doc} | Rest]) ->
+    CAcc1 = handle_hit(CAcc0, Sort, Doc),
+    handle_hits(CAcc1, Rest).
+
+
+handle_hit(CAcc0, Sort, Doc) ->
+    #cacc{
+        limit = Limit,
+        skip = Skip
+    } = CAcc0,
+    CAcc1 = update_bookmark(CAcc0, Sort),
+    case mango_selector:match(CAcc1#cacc.selector, Doc) of
+        true when Skip > 0 ->
+            CAcc1#cacc{skip = Skip - 1};
+        true when Limit == 0 ->
+            % We hit this case if the user spcified with a
+            % zero limit. Notice that in this case we need
+            % to return the bookmark from before this match
+            throw({stop, CAcc0});
+        true when Limit == 1 ->
+            NewCAcc = apply_user_fun(CAcc1, Doc),
+            throw({stop, NewCAcc});
+        true when Limit > 1 ->
+            NewCAcc = apply_user_fun(CAcc1, Doc),
+            NewCAcc#cacc{limit = Limit - 1};
+        false ->
+            CAcc1
+    end.
+
+
+apply_user_fun(CAcc, Doc) ->
+    #cacc{
+        user_fun = UserFun,
+        user_acc = UserAcc
+    } = CAcc,
+    case UserFun({row, Doc}, UserAcc) of
+        {ok, NewUserAcc} ->
+            CAcc#cacc{user_acc = NewUserAcc};
+        {stop, NewUserAcc} ->
+            throw({stop, CAcc#cacc{user_acc = NewUserAcc}})
+    end.
+
+
+%% Convert Query to Dreyfus sort specifications
+%% Covert <<"Field">>, <<"desc">> to <<"-Field">>
+%% and append to the dreyfus query
+sort_query(Opts, Selector) ->
+    {sort, {Sort}} = lists:keyfind(sort, 1, Opts),
+    SortList = lists:map(fun(SortField) ->
+        RawSortField = case SortField of
+            {Field, <<"asc">>} -> Field;
+            {Field, <<"desc">>} -> <<"-", Field/binary>>;
+            Field when is_binary(Field) -> Field
+        end,
+        mango_selector_text:append_sort_type(RawSortField, Selector)
+    end, Sort),
+    case SortList of
+        [] -> relevance;
+        _ -> SortList
+    end.
+
+
+get_bookmark(Opts) ->
+    case lists:keyfind(bookmark, 1, Opts) of
+        {_, BM} when is_list(BM), BM /= [] ->
+            BM;
+        _ ->
+            nil
+    end.
+
+
+update_bookmark(CAcc, Sortable) ->
+    BM = CAcc#cacc.bookmark,
+    QueryArgs = CAcc#cacc.query_args,
+    Sort = QueryArgs#index_query_args.sort,
+    NewBM = dreyfus_bookmark:update(Sort, BM, [Sortable]),
+    CAcc#cacc{bookmark = NewBM}.
+
+
+pack_bookmark(Bookmark) ->
+    case dreyfus_bookmark:pack(Bookmark) of
+        null -> nil;
+        Enc -> Enc
+    end.
+
+
+unpack_bookmark(DbName, Opts) ->
+    NewBM = case lists:keyfind(bookmark, 1, Opts) of
+        {_, nil} ->
+            [];
+        {_, Bin} ->
+            try
+                dreyfus_bookmark:unpack(DbName, Bin)
+            catch _:_ ->
+                ?MANGO_ERROR({invalid_bookmark, Bin})
+            end
+    end,
+    lists:keystore(bookmark, 1, Opts, {bookmark, NewBM}).
+
+
+ddocid(Idx) ->
+    case mango_idx:ddoc(Idx) of
+        <<"_design/", Rest/binary>> ->
+            Rest;
+        Else ->
+            Else
+    end.
+
+
+update_query_args(CAcc) ->
+    #cacc{
+        bookmark = Bookmark,
+        query_args = QueryArgs
+    } = CAcc,
+    QueryArgs#index_query_args{
+        bookmark = pack_bookmark(Bookmark),
+        limit = get_limit(CAcc)
+    }.
+
+
+get_limit(CAcc) ->
+    Total = CAcc#cacc.limit + CAcc#cacc.skip,
+    if
+        Total < 25 -> 25;
+        Total > 100 -> 100;
+        true -> Total
+    end.
+
+
+get_json_docs(DbName, Hits) ->
+    Ids = lists:map(fun(#sortable{item = Item}) ->
+        couch_util:get_value(<<"_id">>, Item#hit.fields)
+    end, Hits),
+    {ok, IdDocs} = dreyfus_fabric:get_json_docs(DbName, Ids),
+    lists:map(fun(#sortable{item = Item} = Sort) ->
+        Id = couch_util:get_value(<<"_id">>, Item#hit.fields),
+        case lists:keyfind(Id, 1, IdDocs) of
+            {Id, {doc, Doc}} ->
+                {Sort, Doc};
+            false ->
+                {Sort, not_found}
+        end
+    end, Hits).
+

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/src/mango_error.erl
----------------------------------------------------------------------
diff --git a/src/mango_error.erl b/src/mango_error.erl
index 778df2d..8aebfb9 100644
--- a/src/mango_error.erl
+++ b/src/mango_error.erl
@@ -13,44 +13,63 @@
 -module(mango_error).
 
 
+-include_lib("couch/include/couch_db.hrl").
+
+
 -export([
     info/2
 ]).
 
 
-info(mango_cursor, {no_usable_index, operator_unsupported}) ->
+info(mango_cursor, {no_usable_index, no_indexes_defined}) ->
     {
         400,
         <<"no_usable_index">>,
-        <<"There is no operator in this selector can used with an index.">>
+        <<"There are no indexes defined in this database.">>
     };
-info(mango_cursor, {no_usable_index, selector_unsupported}) ->
+info(mango_cursor, {no_usable_index, no_index_matching_name}) ->
     {
         400,
         <<"no_usable_index">>,
-        <<"There is no index available for this selector.">>
+        <<"No index matches the index specified with \"use_index\"">>
     };
-info(mango_cursor, {no_usable_index, sort_field}) ->
+info(mango_cursor, {no_usable_index, missing_sort_index}) ->
     {
         400,
         <<"no_usable_index">>,
-        <<"No index can satisfy both the selector and sort specified.">>
+        <<"No index exists for this sort, try indexing by the sort fields.">>
     };
-info(mango_cursor, {no_usable_index, {sort, Fields}}) ->
-    S0 = [binary_to_list(F) || F <- Fields],
-    S1 = string:join(S0, ", "),
+info(mango_cursor, {no_usable_index, selector_unsupported}) ->
     {
         400,
         <<"no_usable_index">>,
-        fmt("No index exists for this sort, try indexing: ~s", [S1])
+        <<"There is no index available for this selector.">>
+    };
+
+info(mango_cursor_text, {invalid_bookmark, BadBookmark}) ->
+    {
+        400,
+        <<"invalid_bookmark">>,
+        fmt("Invalid boomkark value: ~s", [?JSON_ENCODE(BadBookmark)])
     };
-info(mango_cursor, {no_usable_index, {fields, Possible}}) ->
-    S0 = [binary_to_list(P) || P <- Possible],
-    S1 = string:join(S0, ", "),
+info(mango_cursor_text, multiple_text_indexes) ->
     {
         400,
-        <<"no_usable_index">>,
-        fmt("No index exists for this selector, try indexing one of: ~s", [S1])
+        <<"multiple_text_indexes">>,
+        <<"You must specify an index with the `use_index` parameter.">>
+    };
+info(mango_cursor_text, {text_search_error, {error, {bad_request, Msg}}}) 
+        when is_binary(Msg) ->
+    {
+        400,
+        <<"text_search_error">>,
+        Msg
+    };
+info(mango_cursor_text, {text_search_error, {error, Error}}) ->
+    {
+        400,
+        <<"text_search_error">>,
+        fmt("Error performing text search: ~p", [Error])
     };
 
 info(mango_fields, {invalid_fields_json, BadFields}) ->
@@ -88,23 +107,48 @@ info(mango_httpd, {error_saving_ddoc, Reason}) ->
 info(mango_idx, {invalid_index_type, BadType}) ->
     {
         400,
-        <<"invalid_index_type">>,
+        <<"invalid_index">>,
         fmt("Invalid type for index: ~s", [BadType])
     };
+info(mango_idx, invalid_query_ddoc_language) ->
+    {
+        400,
+        <<"invalid_index">>,
+        <<"Invalid design document query language.">>
+    };
+info(mango_idx, no_index_definition) ->
+    {
+        400,
+        <<"invalid_index">>,
+        <<"Index is missing its definition.">>
+    };
 
 info(mango_idx_view, {invalid_index_json, BadIdx}) ->
     {
         400,
-        <<"invalid_index_json">>,
+        <<"invalid_index">>,
         fmt("JSON indexes must be an object, not: ~w", [BadIdx])
     };
 info(mango_idx_view, {index_not_found, BadIdx}) ->
     {
         404,
-        <<"index_not_found">>,
+        <<"invalid_index">>,
         fmt("JSON index ~s not found in this design doc.", [BadIdx])
     };
 
+info(mango_idx_text, {invalid_index_text, BadIdx}) ->
+    {
+        400,
+        <<"invalid_index">>,
+        fmt("Text indexes must be an object, not: ~w", [BadIdx])
+    };
+info(mango_idx_text, {index_not_found, BadIdx}) ->
+    {
+        404,
+        <<"index_not_found">>,
+        fmt("Text index ~s not found in this design doc.", [BadIdx])
+    };
+
 info(mango_opts, {invalid_ejson, Val}) ->
     {
         400,
@@ -171,6 +215,20 @@ info(mango_opts, {invalid_selector_json, BadSel}) ->
         <<"invalid_selector_json">>,
         fmt("Selector must be a JSON object, not: ~w", [BadSel])
     };
+info(mango_opts, {invalid_index_name, BadName}) ->
+    {
+        400,
+        <<"invalid_index_name">>,
+        fmt("Invalid index name: ~w", [BadName])
+    };
+
+info(mango_opts, {multiple_text_operator, {invalid_selector, BadSel}}) ->
+    {
+        400,
+        <<"multiple_text_selector">>,
+        fmt("Selector cannot contain more than one $text operator: ~w",
+            [BadSel])
+    };
 
 info(mango_selector, {invalid_selector, missing_field_name}) ->
     {
@@ -203,6 +261,22 @@ info(mango_selector, {bad_field, BadSel}) ->
         fmt("Invalid field normalization on selector: ~w", [BadSel])
     };
 
+info(mango_selector_text, {invalid_operator, Op}) ->
+    {
+        400,
+        <<"invalid_operator">>,
+        fmt("Invalid text operator: ~s", [Op])
+    };
+info(mango_selector_text, {text_sort_error, Field}) ->
+    S = binary_to_list(Field),
+    Msg = "Unspecified or ambiguous sort type. Try appending :number or"
+        " :string to the sort field. ~s",
+    {
+        400,
+        <<"text_sort_error">>,
+        fmt(Msg, [S])
+    };
+
 info(mango_sort, {invalid_sort_json, BadSort}) ->
     {
         400,
@@ -228,6 +302,18 @@ info(mango_sort, {unsupported, mixed_sort}) ->
         <<"Sorts currently only support a single direction for all fields.">>
     };
 
+info(mango_util, {error_loading_doc, DocId}) ->
+    {
+        500,
+        <<"internal_error">>,
+        fmt("Error loading doc: ~s", [DocId])
+    };
+info(mango_util, error_loading_ddocs) ->
+    {
+        500,
+        <<"internal_error">>,
+        <<"Error loading design documents">>
+    };
 info(mango_util, {invalid_ddoc_lang, Lang}) ->
     {
         400,

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/src/mango_fields.erl
----------------------------------------------------------------------
diff --git a/src/mango_fields.erl b/src/mango_fields.erl
index 46049af..8b6a00b 100644
--- a/src/mango_fields.erl
+++ b/src/mango_fields.erl
@@ -49,5 +49,7 @@ extract(Doc, Fields) ->
 
 field(Val) when is_binary(Val) ->
     Val;
+field({Val}) when is_list(Val) ->
+    {Val};
 field(Else) ->
     ?MANGO_ERROR({invalid_field_json, Else}).

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/src/mango_idx.erl
----------------------------------------------------------------------
diff --git a/src/mango_idx.erl b/src/mango_idx.erl
index 902fb75..8e644c3 100644
--- a/src/mango_idx.erl
+++ b/src/mango_idx.erl
@@ -144,7 +144,7 @@ from_ddoc(Db, {Props}) ->
             ?MANGO_ERROR(invalid_query_ddoc_language)
     end,
 
-    IdxMods = [mango_idx_view],
+    IdxMods = [mango_idx_view, mango_idx_text],
     Idxs = lists:flatmap(fun(Mod) -> Mod:from_ddoc({Props}) end, IdxMods),
     lists:map(fun(Idx) ->
         Idx#idx{
@@ -218,13 +218,17 @@ end_key(#idx{}=Idx, Ranges) ->
 cursor_mod(#idx{type = <<"json">>}) ->
     mango_cursor_view;
 cursor_mod(#idx{def = all_docs, type= <<"special">>}) ->
-    mango_cursor_view.
+    mango_cursor_view;
+cursor_mod(#idx{type = <<"text">>}) ->
+    mango_cursor_text.
 
 
 idx_mod(#idx{type = <<"json">>}) ->
     mango_idx_view;
 idx_mod(#idx{type = <<"special">>}) ->
-    mango_idx_special.
+    mango_idx_special;
+idx_mod(#idx{type = <<"text">>}) ->
+    mango_idx_text.
 
 
 db_to_name(#db{name=Name}) ->
@@ -247,7 +251,7 @@ get_idx_def(Opts) ->
 get_idx_type(Opts) ->
     case proplists:get_value(type, Opts) of
         <<"json">> -> <<"json">>;
-        %<<"text">> -> <<"text">>;
+        <<"text">> -> <<"text">>;
         %<<"geo">> -> <<"geo">>;
         undefined -> <<"json">>;
         BadType ->

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/src/mango_idx_text.erl
----------------------------------------------------------------------
diff --git a/src/mango_idx_text.erl b/src/mango_idx_text.erl
new file mode 100644
index 0000000..507e0c2
--- /dev/null
+++ b/src/mango_idx_text.erl
@@ -0,0 +1,256 @@
+% 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.
+
+-module(mango_idx_text).
+
+
+-export([
+    validate/1,
+    add/2,
+    remove/2,
+    from_ddoc/1,
+    to_json/1,
+    columns/1,
+    is_usable/2,
+    get_default_field_options/1
+]).
+
+
+-include_lib("couch/include/couch_db.hrl").
+-include("mango.hrl").
+-include("mango_idx.hrl").
+
+
+validate(#idx{}=Idx) ->
+    {ok, Def} = do_validate(Idx#idx.def),
+    {ok, Idx#idx{def=Def}}.
+
+
+add(#doc{body={Props0}}=DDoc, Idx) ->
+    Texts1 = case proplists:get_value(<<"indexes">>, Props0) of
+        {Texts0} -> Texts0;
+        _ -> []
+    end,
+    NewText = make_text(Idx),
+    Texts2 = lists:keystore(element(1, NewText), 1, Texts1, NewText),
+    Props1 = lists:keystore(<<"indexes">>, 1, Props0, {<<"indexes">>,
+        {Texts2}}),
+    {ok, DDoc#doc{body={Props1}}}.
+
+
+remove(#doc{body={Props0}}=DDoc, Idx) ->
+    Texts1 = case proplists:get_value(<<"indexes">>, Props0) of
+        {Texts0} ->
+            Texts0;
+        _ ->
+            ?MANGO_ERROR({index_not_found, Idx#idx.name})
+    end,
+    Texts2 = lists:keydelete(Idx#idx.name, 1, Texts1),
+    if Texts2 /= Texts1 -> ok; true ->
+        ?MANGO_ERROR({index_not_found, Idx#idx.name})
+    end,
+    Props1 = case Texts2 of
+        [] ->
+            lists:keydelete(<<"indexes">>, 1, Props0);
+        _ ->
+            lists:keystore(<<"indexes">>, 1, Props0, {<<"indexes">>, {Texts2}})
+    end,
+    {ok, DDoc#doc{body={Props1}}}.
+
+
+from_ddoc({Props}) ->
+    case lists:keyfind(<<"indexes">>, 1, Props) of
+        {<<"indexes">>, {Texts}} when is_list(Texts) ->
+            lists:flatmap(fun({Name, {VProps}}) ->
+                Def = proplists:get_value(<<"index">>, VProps),
+                I = #idx{
+                    type = <<"text">>,
+                    name = Name,
+                    def = Def
+                },
+                % TODO: Validate the index definition
+                [I]
+            end, Texts);
+        _ ->
+            []
+    end.
+
+
+to_json(Idx) ->
+    {[
+        {ddoc, Idx#idx.ddoc},
+        {name, Idx#idx.name},
+        {type, Idx#idx.type},
+        {def, {def_to_json(Idx#idx.def)}}
+    ]}.
+
+
+columns(Idx) ->
+    {Props} = Idx#idx.def,
+    {<<"fields">>, Fields} = lists:keyfind(<<"fields">>, 1, Props),
+    case Fields of
+        <<"all_fields">> ->
+            all_fields;
+        _ ->
+            {DFProps} = couch_util:get_value(<<"default_field">>, Props, {[]}),
+            Enabled = couch_util:get_value(<<"enabled">>, DFProps, true),
+            Default = case Enabled of
+                true -> [<<"$default">>];
+                false -> []
+            end,
+            Default ++ lists:map(fun({FProps}) ->
+                {_, Name} = lists:keyfind(<<"name">>, 1, FProps),
+                {_, Type} = lists:keyfind(<<"type">>, 1, FProps),
+                iolist_to_binary([Name, ":", Type])
+            end, Fields)
+    end.
+
+
+is_usable(Idx, Selector) ->
+    case columns(Idx) of
+        all_fields ->
+            true;
+        Cols ->
+            Fields = indexable_fields(Selector),
+            sets:is_subset(sets:from_list(Fields), sets:from_list(Cols))
+    end.
+
+
+do_validate({Props}) ->
+    {ok, Opts} = mango_opts:validate(Props, opts()),
+    {ok, {Opts}};
+do_validate(Else) ->
+    ?MANGO_ERROR({invalid_index_text, Else}).
+
+
+def_to_json({Props}) ->
+    def_to_json(Props);
+def_to_json([]) ->
+    [];
+def_to_json([{<<"fields">>, <<"all_fields">>} | Rest]) ->
+    [{<<"fields">>, []} | def_to_json(Rest)];
+def_to_json([{fields, Fields} | Rest]) ->
+    [{<<"fields">>, mango_sort:to_json(Fields)} | def_to_json(Rest)];
+def_to_json([{<<"fields">>, Fields} | Rest]) ->
+    [{<<"fields">>, mango_sort:to_json(Fields)} | def_to_json(Rest)];
+def_to_json([{Key, Value} | Rest]) ->
+    [{Key, Value} | def_to_json(Rest)].
+
+
+opts() ->
+    [
+        {<<"default_analyzer">>, [
+            {tag, default_analyzer},
+            {optional, true},
+            {default, <<"keyword">>}
+        ]},
+        {<<"default_field">>, [
+            {tag, default_field},
+            {optional, true},
+            {default, {[]}}
+        ]},
+         {<<"selector">>, [
+            {tag, selector},
+            {optional, true},
+            {default, {[]}},
+            {validator, fun mango_opts:validate_selector/1}
+        ]},
+        {<<"fields">>, [
+            {tag, fields},
+            {optional, true},
+            {default, []},
+            {validator, fun mango_opts:validate_fields/1}
+        ]}
+    ].
+
+
+make_text(Idx) ->
+    Text= {[
+        {<<"index">>, Idx#idx.def},
+        {<<"analyzer">>, construct_analyzer(Idx#idx.def)}
+    ]},
+    {Idx#idx.name, Text}.
+
+
+get_default_field_options(Props) ->
+    Default = couch_util:get_value(default_field, Props, {[]}),
+    case Default of
+        Bool when is_boolean(Bool) ->
+            {Bool, <<"standard">>};
+        {[]} ->
+            {true, <<"standard">>};
+        {Opts}->
+            Enabled = couch_util:get_value(<<"enabled">>, Opts, true),
+            Analyzer = couch_util:get_value(<<"analyzer">>, Opts,
+                <<"standard">>),
+            {Enabled, Analyzer}
+    end.
+
+
+construct_analyzer({Props}) ->
+    DefaultAnalyzer = couch_util:get_value(default_analyzer, Props,
+        <<"keyword">>),
+    {DefaultField, DefaultFieldAnalyzer} = get_default_field_options(Props),
+    DefaultAnalyzerDef = case DefaultField of
+        true ->
+            [{<<"$default">>, DefaultFieldAnalyzer}];
+        _ ->
+            []
+    end,
+    case DefaultAnalyzerDef of
+        [] ->
+            <<"keyword">>;
+        _ ->
+            {[
+                {<<"name">>, <<"perfield">>},
+                {<<"default">>, DefaultAnalyzer},
+                {<<"fields">>, {DefaultAnalyzerDef}}
+            ]}
+    end.
+
+
+indexable_fields(Selector) ->
+    TupleTree = mango_selector_text:convert([], Selector),
+    indexable_fields([], TupleTree).
+
+
+indexable_fields(Fields, {op_and, Args}) when is_list(Args) ->
+    lists:foldl(fun(Arg, Fields0) -> indexable_fields(Fields0, Arg) end,
+        Fields, Args);
+
+indexable_fields(Fields, {op_or, Args}) when is_list(Args) ->
+    lists:foldl(fun(Arg, Fields0) -> indexable_fields(Fields0, Arg) end,
+        Fields, Args);
+
+indexable_fields(Fields, {op_not, {ExistsQuery, Arg}}) when is_tuple(Arg) ->
+    Fields0 = indexable_fields(Fields, ExistsQuery),
+    indexable_fields(Fields0, Arg);
+
+indexable_fields(Fields, {op_insert, Arg}) when is_binary(Arg) ->
+    Fields;
+
+indexable_fields(Fields, {op_field, {Name, _}}) ->
+    [iolist_to_binary(Name) | Fields];
+
+%% In this particular case, the lucene index is doing a field_exists query
+%% so it is looking at all sorts of combinations of field:* and field.*
+%% We don't add the field because we cannot pre-determine what field will exist.
+%% Hence we just return Fields and make it less restrictive.
+indexable_fields(Fields, {op_fieldname, {_, _}}) ->
+    Fields;
+
+%% Similar idea to op_fieldname but with fieldname:null
+indexable_fields(Fields, {op_null, {_, _}}) ->
+    Fields;
+
+indexable_fields(Fields, {op_default, _}) ->
+    [<<"$default">> | Fields].

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/src/mango_native_proc.erl
----------------------------------------------------------------------
diff --git a/src/mango_native_proc.erl b/src/mango_native_proc.erl
index 636da5c..3e189bd 100644
--- a/src/mango_native_proc.erl
+++ b/src/mango_native_proc.erl
@@ -36,6 +36,12 @@
 }).
 
 
+-record(tacc, {
+    fields = all_fields,
+    path = []
+}).
+
+
 start_link() ->
     gen_server:start_link(?MODULE, [], []).
 
@@ -79,6 +85,9 @@ handle_call({prompt, [<<"reduce">>, _, _]}, _From, St) ->
 handle_call({prompt, [<<"rereduce">>, _, _]}, _From, St) ->
     {reply, null, St};
 
+handle_call({prompt, [<<"index_doc">>, Doc]}, _From, St) ->
+    {reply, index_doc(St, mango_json:to_binary(Doc)), St};
+
 handle_call(Msg, _From, St) ->
     {stop, {invalid_call, Msg}, {invalid_call, Msg}, St}.
 
@@ -103,6 +112,10 @@ map_doc(#st{indexes=Indexes}, Doc) ->
     lists:map(fun(Idx) -> get_index_entries(Idx, Doc) end, Indexes).
 
 
+index_doc(#st{indexes=Indexes}, Doc) ->
+    lists:map(fun(Idx) -> get_text_entries(Idx, Doc) end, Indexes).
+
+
 get_index_entries({IdxProps}, Doc) ->
     {Fields} = couch_util:get_value(<<"fields">>, IdxProps),
     Values = lists:map(fun({Field, _Dir}) ->
@@ -118,3 +131,172 @@ get_index_entries({IdxProps}, Doc) ->
         false ->
             [[Values, null]]
     end.
+
+
+get_text_entries({IdxProps}, Doc) ->
+    Selector = case couch_util:get_value(<<"selector">>, IdxProps) of
+        [] -> {[]};
+        Else -> Else
+    end,
+    case should_index(Selector, Doc) of
+        true ->
+            get_text_entries0(IdxProps, Doc);
+        false ->
+            []
+    end.
+
+
+get_text_entries0(IdxProps, Doc) ->
+    DefaultEnabled = get_default_enabled(IdxProps),
+    FieldsList = get_text_field_list(IdxProps),
+    TAcc = #tacc{fields = FieldsList},
+    Fields0 = get_text_field_values(Doc, TAcc),
+    Fields = if not DefaultEnabled -> Fields0; true ->
+        add_default_text_field(Fields0)
+    end,
+    FieldNames = get_field_names(Fields, []),
+    Converted = convert_text_fields(Fields),
+    FieldNames ++ Converted.
+
+
+get_text_field_values({Props}, TAcc) when is_list(Props) ->
+    get_text_field_values_obj(Props, TAcc, []);
+
+get_text_field_values(Values, TAcc) when is_list(Values) ->
+    NewPath = ["[]" | TAcc#tacc.path],
+    NewTAcc = TAcc#tacc{path = NewPath},
+    % We bypass make_text_field and directly call make_text_field_name
+    % because the length field name is not part of the path.
+    LengthFieldName = make_text_field_name(NewTAcc#tacc.path, <<"length">>),
+    EncLFN = mango_util:lucene_escape_field(LengthFieldName),
+    LengthField = [{EncLFN, <<"length">>, length(Values)}],
+    get_text_field_values_arr(Values, NewTAcc, LengthField);
+
+get_text_field_values(Bin, TAcc) when is_binary(Bin) ->
+    make_text_field(TAcc, <<"string">>, Bin);
+
+get_text_field_values(Num, TAcc) when is_number(Num) ->
+    make_text_field(TAcc, <<"number">>, Num);
+
+get_text_field_values(Bool, TAcc) when is_boolean(Bool) ->
+    make_text_field(TAcc, <<"boolean">>, Bool);
+
+get_text_field_values(null, TAcc) ->
+    make_text_field(TAcc, <<"null">>, true).
+
+
+get_text_field_values_obj([], _, FAcc) ->
+    FAcc;
+get_text_field_values_obj([{Key, Val} | Rest], TAcc, FAcc) ->
+    NewPath = [Key | TAcc#tacc.path],
+    NewTAcc = TAcc#tacc{path = NewPath},
+    Fields = get_text_field_values(Val, NewTAcc),
+    get_text_field_values_obj(Rest, TAcc, Fields ++ FAcc).
+
+
+get_text_field_values_arr([], _, FAcc) ->
+    FAcc;
+get_text_field_values_arr([Value | Rest], TAcc, FAcc) ->
+    Fields = get_text_field_values(Value, TAcc),
+    get_text_field_values_arr(Rest, TAcc, Fields ++ FAcc).
+
+
+get_default_enabled(Props) ->
+    case couch_util:get_value(<<"default_field">>, Props, {[]}) of
+        Bool when is_boolean(Bool) ->
+            Bool;
+        {[]} ->
+            true;
+        {Opts}->
+            couch_util:get_value(<<"enabled">>, Opts, true)
+    end.
+
+
+add_default_text_field(Fields) ->
+    DefaultFields = add_default_text_field(Fields, []),
+    DefaultFields ++ Fields.
+
+
+add_default_text_field([], Acc) ->
+    Acc;
+add_default_text_field([{_Name, <<"string">>, Value} | Rest], Acc) ->
+    NewAcc = [{<<"$default">>, <<"string">>, Value} | Acc],
+    add_default_text_field(Rest, NewAcc);
+add_default_text_field([_ | Rest], Acc) ->
+    add_default_text_field(Rest, Acc).
+
+
+%% index of all field names
+get_field_names([], FAcc) ->
+    FAcc;
+get_field_names([{Name, _Type, _Value} | Rest], FAcc) ->
+    case lists:member([<<"$fieldnames">>, Name, []], FAcc) of
+        true ->
+            get_field_names(Rest, FAcc);
+        false ->
+            get_field_names(Rest, [[<<"$fieldnames">>, Name, []] | FAcc])
+    end.
+
+
+convert_text_fields([]) ->
+    [];
+convert_text_fields([{Name, _Type, Value} | Rest]) ->
+    [[Name, Value, []] | convert_text_fields(Rest)].
+
+
+should_index(Selector, Doc) ->
+    % We should do this
+    NormSelector = mango_selector:normalize(Selector),
+    Matches = mango_selector:match(NormSelector, Doc),
+    IsDesign = case mango_doc:get_field(Doc, <<"_id">>) of
+        <<"_design/", _/binary>> -> true;
+        _ -> false
+    end,
+    Matches and not IsDesign.
+
+
+get_text_field_list(IdxProps) ->
+    case couch_util:get_value(<<"fields">>, IdxProps) of
+        Fields when is_list(Fields) ->
+            lists:flatmap(fun get_text_field_info/1, Fields);
+        _ ->
+            all_fields
+    end.
+
+
+get_text_field_info({Props}) ->
+    Name = couch_util:get_value(<<"name">>, Props),
+    Type0 = couch_util:get_value(<<"type">>, Props),
+    if not is_binary(Name) -> []; true ->
+        Type = get_text_field_type(Type0),
+        [iolist_to_binary([Name, ":", Type])]
+    end.
+
+
+get_text_field_type(<<"number">>) ->
+    <<"number">>;
+get_text_field_type(<<"boolean">>) ->
+    <<"boolean">>;
+get_text_field_type(_) ->
+    <<"string">>.
+
+
+make_text_field(TAcc, Type, Value) ->
+    FieldName = make_text_field_name(TAcc#tacc.path, Type),
+    Fields = TAcc#tacc.fields,
+    case Fields == all_fields orelse lists:member(FieldName, Fields) of
+        true ->
+            [{mango_util:lucene_escape_field(FieldName), Type,
+            Value}];
+        false ->
+            []
+    end.
+
+
+make_text_field_name([P | Rest], Type) ->
+    make_text_field_name0(Rest, [P, ":", Type]).
+
+make_text_field_name0([], Name) ->
+    iolist_to_binary(Name);
+make_text_field_name0([P | Rest], Name) ->
+    make_text_field_name0(Rest, [P, "." | Name]).

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/src/mango_opts.erl
----------------------------------------------------------------------
diff --git a/src/mango_opts.erl b/src/mango_opts.erl
index e15a446..f7874a6 100644
--- a/src/mango_opts.erl
+++ b/src/mango_opts.erl
@@ -29,6 +29,7 @@
     validate_idx_name/1,
     validate_selector/1,
     validate_use_index/1,
+    validate_bookmark/1,
     validate_sort/1,
     validate_fields/1
 ]).
@@ -82,6 +83,12 @@ validate_find({Props}) ->
             {default, []},
             {validator, fun validate_use_index/1}
         ]},
+        {<<"bookmark">>, [
+            {tag, bookmark},
+            {optional, true},
+            {default, <<>>},
+            {validator, fun validate_bookmark/1}
+        ]},
         {<<"limit">>, [
             {tag, limit},
             {optional, true},
@@ -211,6 +218,16 @@ validate_use_index(Else) ->
     ?MANGO_ERROR({invalid_index_name, Else}).
 
 
+validate_bookmark(null) ->
+    {ok, nil};
+validate_bookmark(<<>>) ->
+    {ok, nil};
+validate_bookmark(Bin) when is_binary(Bin) ->
+    {ok, Bin};
+validate_bookmark(Else) ->
+    ?MANGO_ERROR({invalid_bookmark, Else}).
+
+
 validate_sort(Value) ->
     mango_sort:new(Value).
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/src/mango_selector.erl
----------------------------------------------------------------------
diff --git a/src/mango_selector.erl b/src/mango_selector.erl
index 56f2072..c008a4c 100644
--- a/src/mango_selector.erl
+++ b/src/mango_selector.erl
@@ -348,6 +348,9 @@ negate({[{<<"$and">>, Args}]}) ->
 negate({[{<<"$or">>, Args}]}) ->
     {[{<<"$and">>, [negate(A) || A <- Args]}]};
 
+negate({[{<<"$default">>, _}]} = Arg) ->
+    ?MANGO_ERROR({bad_arg, '$not', Arg});
+
 % Negating comparison operators is straight forward
 negate({[{<<"$lt">>, Arg}]}) ->
     {[{<<"$gte">>, Arg}]};
@@ -514,4 +517,4 @@ match({[{Field, Cond}]}, Value, Cmp) ->
     end;
 
 match({Props} = Sel, _Value, _Cmp) when length(Props) > 1 ->
-    erlang:error({unnormalized_selector, Sel}).
\ No newline at end of file
+    erlang:error({unnormalized_selector, Sel}).

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/src/mango_selector_text.erl
----------------------------------------------------------------------
diff --git a/src/mango_selector_text.erl b/src/mango_selector_text.erl
new file mode 100644
index 0000000..35a0d4c
--- /dev/null
+++ b/src/mango_selector_text.erl
@@ -0,0 +1,347 @@
+% 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.
+
+-module(mango_selector_text).
+
+
+-export([
+    convert/1,
+    convert/2,
+
+    append_sort_type/2
+]).
+
+
+-include_lib("couch/include/couch_db.hrl").
+-include("mango.hrl").
+
+
+convert(Object) ->
+    TupleTree = convert([], Object),
+    iolist_to_binary(to_query(TupleTree)).
+
+
+convert(Path, {[{<<"$and">>, Args}]}) ->
+    Parts = [convert(Path, Arg) || Arg <- Args],
+    {op_and, Parts};
+convert(Path, {[{<<"$or">>, Args}]}) ->
+    Parts = [convert(Path, Arg) || Arg <- Args],
+    {op_or, Parts};
+convert(Path, {[{<<"$not">>, Arg}]}) ->
+    {op_not, {field_exists_query(Path), convert(Path, Arg)}};
+convert(Path, {[{<<"$default">>, Arg}]}) ->
+    {op_field, {_, Query}} = convert(Path, Arg),
+    {op_default, Query};
+
+% The $text operator specifies a Lucene syntax query
+% so we just pull it in directly.
+convert(Path, {[{<<"$text">>, Query}]}) when is_binary(Query) ->
+    {op_field, {make_field(Path, Query), value_str(Query)}};
+
+% The MongoDB docs for $all are super confusing and read more
+% like they screwed up the implementation of this operator
+% and then just documented it as a feature.
+%
+% This implementation will match the behavior as closely as
+% possible based on the available docs but we'll need to have
+% the testing team validate how MongoDB handles edge conditions
+convert(Path, {[{<<"$all">>, Args}]}) ->
+    case Args of
+        [Values] when is_list(Values) ->
+            % If Args is a single element array then we have to
+            % either match if Path is that array or if it contains
+            % the array as an element of an array (which isn't at all
+            % confusing). For Lucene to return us all possible matches
+            % that means we just need to search for each value in
+            % Path.[] and Path.[].[] and rely on our filtering to limit
+            % the results properly.
+            Fields1 = convert(Path, {[{<<"$eq">> , Values}]}),
+            Fields2 = convert([<<"[]">>| Path], {[{<<"$eq">> , Values}]}),
+            {op_or, [Fields1, Fields2]};
+        _ ->
+            % Otherwise the $all operator is equivalent to an $and
+            % operator so we treat it as such.
+            convert(Path, {[{<<"$eq">> , Args}]})
+    end;
+
+% The $elemMatch Lucene query is not an exact translation
+% as we can't enforce that the matches are all for the same
+% item in an array. We just rely on the final selector match
+% to filter out anything that doesn't match. The only trick
+% is that we have to add the `[]` path element since the docs
+% say this has to match against an array.
+convert(Path, {[{<<"$elemMatch">>, Arg}]}) ->
+    convert([<<"[]">> | Path], Arg);
+
+% Our comparison operators are fairly straight forward
+convert(Path, {[{<<"$lt">>, Arg}]}) when is_list(Arg); is_tuple(Arg);
+        Arg =:= null ->
+    field_exists_query(Path);
+convert(Path, {[{<<"$lt">>, Arg}]}) ->
+    {op_field, {make_field(Path, Arg), range(lt, Arg)}};
+convert(Path, {[{<<"$lte">>, Arg}]}) when is_list(Arg); is_tuple(Arg);
+        Arg =:= null->
+    field_exists_query(Path);
+convert(Path, {[{<<"$lte">>, Arg}]}) ->
+    {op_field, {make_field(Path, Arg), range(lte, Arg)}};
+%% This is for indexable_fields
+convert(Path, {[{<<"$eq">>, Arg}]}) when Arg =:= null ->
+    {op_null, {make_field(Path, Arg), value_str(Arg)}};
+convert(Path, {[{<<"$eq">>, Args}]}) when is_list(Args) ->
+    Path0 = [<<"[]">> | Path],
+    LPart = {op_field, {make_field(Path0, length), value_str(length(Args))}},
+    Parts0 = [convert(Path0, {[{<<"$eq">>, Arg}]}) || Arg <- Args],
+    Parts = [LPart | Parts0],
+    {op_and, Parts};
+convert(Path, {[{<<"$eq">>, {_} = Arg}]}) ->
+    convert(Path, Arg);
+convert(Path, {[{<<"$eq">>, Arg}]}) ->
+    {op_field, {make_field(Path, Arg), value_str(Arg)}};
+convert(Path, {[{<<"$ne">>, Arg}]}) ->
+    {op_not, {field_exists_query(Path), convert(Path, {[{<<"$eq">>, Arg}]})}};
+convert(Path, {[{<<"$gte">>, Arg}]}) when is_list(Arg); is_tuple(Arg);
+        Arg =:= null ->
+    field_exists_query(Path);
+convert(Path, {[{<<"$gte">>, Arg}]}) ->
+    {op_field, {make_field(Path, Arg), range(gte, Arg)}};
+convert(Path, {[{<<"$gt">>, Arg}]}) when is_list(Arg); is_tuple(Arg);
+        Arg =:= null->
+    field_exists_query(Path);
+convert(Path, {[{<<"$gt">>, Arg}]}) ->
+    {op_field, {make_field(Path, Arg), range(gt, Arg)}};
+
+convert(Path, {[{<<"$in">>, Args}]}) ->
+    {op_or, convert_in(Path, Args)};
+
+convert(Path, {[{<<"$nin">>, Args}]}) ->
+    {op_not, {field_exists_query(Path), convert(Path, {[{<<"$in">>, Args}]})}};
+
+convert(Path, {[{<<"$exists">>, ShouldExist}]}) ->
+    FieldExists = field_exists_query(Path),
+    case ShouldExist of
+        true -> FieldExists;
+        false -> {op_not, {FieldExists, false}}
+    end;
+
+% We're not checking the actual type here, just looking for
+% anything that has a possibility of matching by checking
+% for the field name. We use the same logic for $exists on
+% the actual query.
+convert(Path, {[{<<"$type">>, _}]}) ->
+    field_exists_query(Path);
+
+convert(Path, {[{<<"$mod">>, _}]}) ->
+    field_exists_query(Path, "number");
+
+convert(Path, {[{<<"$regex">>, _}]}) ->
+    field_exists_query(Path, "string");
+
+convert(Path, {[{<<"$size">>, Arg}]}) ->
+    {op_field, {make_field(Path, length), value_str(Arg)}};
+
+% All other operators are internal assertion errors for
+% matching because we either should've removed them during
+% normalization or something else broke.
+convert(_Path, {[{<<"$", _/binary>>=Op, _}]}) ->
+    ?MANGO_ERROR({invalid_operator, Op});
+
+% We've hit a field name specifier. We need to break the name
+% into path parts and continue our conversion.
+convert(Path, {[{Field, Cond}]}) ->
+    NewPathParts = re:split(Field, <<"\\.">>),
+    NewPath = lists:reverse(NewPathParts) ++ Path,
+    convert(NewPath, Cond);
+
+%% For $in
+convert(Path, Val) when is_binary(Val); is_number(Val); is_boolean(Val) ->
+    {op_field, {make_field(Path, Val), value_str(Val)}};
+
+% Anything else is a bad selector.
+convert(_Path, {Props} = Sel) when length(Props) > 1 ->
+    erlang:error({unnormalized_selector, Sel}).
+
+
+to_query({op_and, Args}) when is_list(Args) ->
+    Res = ["(", join(<<" AND ">>, lists:map(fun to_query/1, Args)), ")"],
+    Res;
+
+to_query({op_or, Args}) when is_list(Args) ->
+    ["(", join(" OR ", lists:map(fun to_query/1, Args)), ")"];
+
+to_query({op_not, {ExistsQuery, Arg}}) when is_tuple(Arg) ->
+    ["(", to_query(ExistsQuery), " AND NOT (", to_query(Arg), "))"];
+
+%% For $exists:false
+to_query({op_not, {ExistsQuery, false}}) ->
+    ["($fieldnames:/.*/ ", " AND NOT (", to_query(ExistsQuery), "))"];
+
+to_query({op_insert, Arg}) when is_binary(Arg) ->
+    ["(", Arg, ")"];
+
+%% We escape : and / for now for values and all lucene chars for fieldnames
+%% This needs to be resolved.
+to_query({op_field, {Name, Value}}) ->
+    NameBin = iolist_to_binary(Name),
+    ["(", mango_util:lucene_escape_field(NameBin), ":", Value, ")"];
+
+%% This is for indexable_fields
+to_query({op_null, {Name, Value}}) ->
+    NameBin = iolist_to_binary(Name),
+    ["(", mango_util:lucene_escape_field(NameBin), ":", Value, ")"];
+
+to_query({op_fieldname, {Name, Wildcard}}) ->
+    NameBin = iolist_to_binary(Name),
+    ["($fieldnames:", mango_util:lucene_escape_field(NameBin), Wildcard, ")"];
+
+to_query({op_default, Value}) ->
+    ["($default:", Value, ")"].
+
+
+join(_Sep, [Item]) ->
+    [Item];
+join(Sep, [Item | Rest]) ->
+    [Item, Sep | join(Sep, Rest)].
+
+
+%% We match on fieldname and fieldname.[]
+convert_in(Path, Args) ->
+    Path0 = [<<"[]">> | Path],
+    lists:map(fun(Arg) ->
+        case Arg of
+            {Object} ->
+                Parts = lists:map(fun (SubObject) ->
+                    Fields1 = convert(Path, {[SubObject]}),
+                    Fields2 = convert(Path0, {[SubObject]}),
+                    {op_or, [Fields1, Fields2]}
+                end, Object),
+                {op_or, Parts};
+            SingleVal ->
+                Fields1 = {op_field, {make_field(Path, SingleVal),
+                value_str(SingleVal)}},
+                Fields2 = {op_field, {make_field(Path0, SingleVal),
+                value_str(SingleVal)}},
+                {op_or, [Fields1, Fields2]}
+        end
+    end, Args).
+
+
+make_field(Path, length) ->
+    [path_str(Path), <<":length">>];
+make_field(Path, Arg) ->
+    [path_str(Path), <<":">>, type_str(Arg)].
+
+
+range(lt, Arg) ->
+    [<<"[-Infinity TO ">>, value_str(Arg), <<"}">>];
+range(lte, Arg) ->
+    [<<"[-Infinity TO ">>, value_str(Arg), <<"]">>];
+range(gte, Arg) ->
+    [<<"[">>, value_str(Arg), <<" TO Infinity]">>];
+range(gt, Arg) ->
+    [<<"{">>, value_str(Arg), <<" TO Infinity]">>].
+
+
+field_exists_query(Path) ->
+    % We specify two here for :* and .* so that we don't incorrectly
+    % match a path foo.name against foo.name_first (if were to just
+    % appened * isntead).
+    Parts = [
+        {op_fieldname, {[path_str(Path), ":"], "*"}},
+        {op_fieldname, {[path_str(Path), "."], "*"}}
+    ],
+    {op_or, Parts}.
+
+
+field_exists_query(Path, Type) ->
+    {op_fieldname, [path_str(Path), ":", Type]}.
+
+
+path_str(Path) ->
+    path_str(Path, []).
+
+
+path_str([], Acc) ->
+    Acc;
+path_str([Part], Acc) ->
+    % No reverse because Path is backwards
+    % during recursion of convert.
+    [Part | Acc];
+path_str([Part | Rest], Acc) ->
+    path_str(Rest, [<<".">>, Part | Acc]).
+
+
+type_str(Value) when is_number(Value) ->
+    <<"number">>;
+type_str(Value) when is_boolean(Value) ->
+    <<"boolean">>;
+type_str(Value) when is_binary(Value) ->
+    <<"string">>;
+type_str(null) ->
+    <<"null">>.
+
+
+value_str(Value) when is_binary(Value) ->
+    mango_util:lucene_escape_query_value(Value);
+value_str(Value) when is_integer(Value) ->
+    list_to_binary(integer_to_list(Value));
+value_str(Value) when is_float(Value) ->
+    list_to_binary(float_to_list(Value));
+value_str(true) ->
+    <<"true">>;
+value_str(false) ->
+    <<"false">>;
+value_str(null) ->
+    <<"true">>.
+
+
+append_sort_type(RawSortField, Selector) ->
+    EncodeField = mango_util:lucene_escape_field(RawSortField),
+    String = mango_util:has_suffix(EncodeField, <<"_3astring">>),
+    Number = mango_util:has_suffix(EncodeField, <<"_3anumber">>),
+    case {String, Number} of
+        {true, _} ->
+            <<EncodeField/binary, "<string>">>;
+        {_, true} ->
+            <<EncodeField/binary, "<number>">>;
+        _ ->
+            Type = get_sort_type(RawSortField, Selector),
+            <<EncodeField/binary, Type/binary>>
+    end.
+
+
+get_sort_type(Field, Selector) ->
+    Types = get_sort_types(Field, Selector, []),
+    case lists:usort(Types) of
+        [str] -> <<"_3astring<string>">>;
+        [num] -> <<"_3anumber<number>">>;
+        _ -> ?MANGO_ERROR({text_sort_error, Field})
+    end.
+
+
+get_sort_types(Field, {[{Field, {[{<<"$", _/binary>>, Cond}]}}]}, Acc)
+        when is_binary(Cond) ->
+    [str | Acc];
+
+get_sort_types(Field, {[{Field, {[{<<"$", _/binary>>, Cond}]}}]}, Acc)
+        when is_number(Cond) ->
+    [num | Acc];
+
+get_sort_types(Field, {[{_, Cond}]}, Acc) when is_list(Cond) ->
+    lists:foldl(fun(Arg, InnerAcc) ->
+        get_sort_types(Field, Arg, InnerAcc)
+    end, Acc, Cond);
+
+get_sort_types(Field, {[{_, Cond}]}, Acc)  when is_tuple(Cond)->
+    get_sort_types(Field, Cond, Acc);
+
+get_sort_types(_Field, _, Acc)  ->
+    Acc.

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/src/mango_util.erl
----------------------------------------------------------------------
diff --git a/src/mango_util.erl b/src/mango_util.erl
index b0767dc..f3b60b2 100644
--- a/src/mango_util.erl
+++ b/src/mango_util.erl
@@ -29,7 +29,12 @@
     dec_dbname/1,
 
     enc_hex/1,
-    dec_hex/1
+    dec_hex/1,
+
+    lucene_escape_field/1,
+    lucene_escape_query_value/1,
+
+    has_suffix/2
 ]).
 
 
@@ -227,3 +232,64 @@ dec_hex_byte(N) when N >= $A, N =< $F -> (N - $A) + 10;
 dec_hex_byte(N) -> throw({invalid_hex_character, N}).
 
 
+
+lucene_escape_field(Bin) when is_binary(Bin) ->
+    Str = binary_to_list(Bin),
+    Enc = lucene_escape_field(Str),
+    iolist_to_binary(Enc);
+lucene_escape_field([H | T]) when is_number(H), H >= 0, H =< 255 ->
+    if
+        H >= $a, $z >= H ->
+            [H | lucene_escape_field(T)];
+        H >= $A, $Z >= H ->
+            [H | lucene_escape_field(T)];
+        H >= $0, $9 >= H ->
+            [H | lucene_escape_field(T)];
+        true ->
+            Hi = enc_hex_byte(H div 16),
+            Lo = enc_hex_byte(H rem 16),
+            [$_, Hi, Lo | lucene_escape_field(T)]
+        end;
+lucene_escape_field([]) ->
+    [].
+
+
+lucene_escape_query_value(IoList) when is_list(IoList) ->
+    lucene_escape_query_value(iolist_to_binary(IoList));
+lucene_escape_query_value(Bin) when is_binary(Bin) ->
+    IoList = lucene_escape_qv(Bin),
+    iolist_to_binary(IoList).
+
+
+% This escapes the special Lucene query characters
+% listed below as well as any whitespace.
+%
+%   + - && || ! ( ) { } [ ] ^ ~ * ? : \ " /
+%
+
+lucene_escape_qv(<<>>) -> [];
+lucene_escape_qv(<<"&&", Rest/binary>>) ->
+    ["\\&&" | lucene_escape_qv(Rest)];
+lucene_escape_qv(<<"||", Rest/binary>>) ->
+    ["\\||" | lucene_escape_qv(Rest)];
+lucene_escape_qv(<<C, Rest/binary>>) ->
+    NeedsEscape = "+-(){}[]!^~*?:/\\\" \t\r\n",
+    Out = case lists:member(C, NeedsEscape) of
+        true -> ["\\", C];
+        false -> [C]
+    end,
+    Out ++ lucene_escape_qv(Rest).
+
+
+has_suffix(Bin, Suffix) when is_binary(Bin), is_binary(Suffix) ->
+    SBin = size(Bin),
+    SSuffix = size(Suffix),
+    if SBin < SSuffix -> false; true ->
+        PSize = SBin - SSuffix,
+        case Bin of
+            <<_:PSize/binary, Suffix/binary>> ->
+                true;
+            _ ->
+                false
+        end
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/test/02-basic-find-test.py
----------------------------------------------------------------------
diff --git a/test/02-basic-find-test.py b/test/02-basic-find-test.py
index 4e3fc29..8113b21 100644
--- a/test/02-basic-find-test.py
+++ b/test/02-basic-find-test.py
@@ -1,3 +1,4 @@
+# -*- coding: latin-1 -*-
 # 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
@@ -10,7 +11,6 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-# -*- coding: latin-1 -*-
 
 import mango
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/test/04-key-tests.py
----------------------------------------------------------------------
diff --git a/test/04-key-tests.py b/test/04-key-tests.py
index 4a5e904..c673cf2 100644
--- a/test/04-key-tests.py
+++ b/test/04-key-tests.py
@@ -1,4 +1,16 @@
-# -*- coding: utf-8 -*-
+# -*- coding: latin-1 -*-
+# 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 mango
 
@@ -28,6 +40,13 @@ TEST_DOCS = [
         "type": "complex_key",
         "title": "unicode key",
         "": "apple"
+    },
+    {
+        "title": "internal_fields_format",
+        "utf8-1[]:string" : "string",
+        "utf8-2[]:boolean[]" : True,
+        "utf8-3[]:number" : 9,
+        "utf8-3[]:null" : None
     }
 ]
 
@@ -37,29 +56,68 @@ class KeyTests(mango.DbPerClass):
     def setUpClass(klass):
         super(KeyTests, klass).setUpClass()
         klass.db.save_docs(TEST_DOCS, w=3)
-        klass.db.create_index(["type"])
+        klass.db.create_index(["type"], ddoc="view")
+        klass.db.create_text_index(ddoc="text")
+
+    def run_check(self, query, check, fields=None, indexes=None):
+        if indexes is None:
+            indexes = ["view", "text"]
+        for idx in indexes:
+            docs = self.db.find(query, fields=fields, use_index=idx)
+            check(docs)
 
     def test_dot_key(self):
+        query = {"type": "complex_key"}
         fields = ["title", "dot\\.key", "none.dot"]
-        docs = self.db.find({"type": "complex_key"}, fields = fields)
-        assert len(docs) == 4
-        assert docs[1].has_key("dot.key")
-        assert docs[1]["dot.key"] == "dot's value"
-        assert docs[1].has_key("none")
-        assert docs[1]["none"]["dot"] == "none dot's value"
+        def check(docs):
+            assert len(docs) == 4
+            assert docs[1].has_key("dot.key")
+            assert docs[1]["dot.key"] == "dot's value"
+            assert docs[1].has_key("none")
+            assert docs[1]["none"]["dot"] == "none dot's value"
+        self.run_check(query, check, fields=fields)
 
     def test_peso_key(self):
+        query = {"type": "complex_key"}
         fields = ["title", "$key", "deep.$key"]
-        docs = self.db.find({"type": "complex_key"}, fields = fields)
-        assert len(docs) == 4
-        assert docs[2].has_key("$key")
-        assert docs[2]["$key"] == "peso"
-        assert docs[2].has_key("deep")
-        assert docs[2]["deep"]["$key"] == "deep peso"
+        def check(docs):
+            assert len(docs) == 4
+            assert docs[2].has_key("$key")
+            assert docs[2]["$key"] == "peso"
+            assert docs[2].has_key("deep")
+            assert docs[2]["deep"]["$key"] == "deep peso"
+        self.run_check(query, check, fields=fields)
+
+    def test_unicode_in_fieldname(self):
+        query = {"type": "complex_key"}
+        fields = ["title", ""]
+        def check(docs):
+            assert len(docs) == 4
+            # note:  == \uf8ff
+            assert docs[3].has_key(u'\uf8ff')
+            assert docs[3][u'\uf8ff'] == "apple"
+        self.run_check(query, check, fields=fields)
+
+    # The rest of these tests are only run against the text
+    # indexes because view indexes don't have to worry about
+    # field *name* escaping in the index.
+
+    def test_unicode_in_selector_field(self):
+        query = {"" : "apple"}
+        def check(docs):
+            assert len(docs) == 1
+            assert docs[0][u"\uf8ff"] == "apple"
+        self.run_check(query, check, indexes=["text"])
 
-    def test_unicode_key(self):
-        docs = self.db.find({"type": "complex_key"}, fields = ["title", ""])
-        assert len(docs) == 4
-        # note:  == \uf8ff
-        assert docs[3].has_key(u'\uf8ff')
-        assert docs[3][u'\uf8ff'] == "apple"
+    def test_internal_field_tests(self):
+        queries = [
+            {"utf8-1[]:string" : "string"},
+            {"utf8-2[]:boolean[]" : True},
+            {"utf8-3[]:number" : 9},
+            {"utf8-3[]:null" : None}
+        ]
+        def check(docs):
+            assert len(docs) == 1
+            assert docs[0]["title"] == "internal_fields_format"
+        for query in queries:
+            self.run_check(query, check, indexes=["text"])

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/test/05-index-selection-test.py
----------------------------------------------------------------------
diff --git a/test/05-index-selection-test.py b/test/05-index-selection-test.py
index 5aa86c6..8c2c018 100644
--- a/test/05-index-selection-test.py
+++ b/test/05-index-selection-test.py
@@ -1,9 +1,24 @@
+# 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 mango
 import user_docs
 
 
 class IndexSelectionTests(mango.UserDocsTests):
+    @classmethod
+    def setUpClass(klass):
+        super(IndexSelectionTests, klass).setUpClass()
+        user_docs.add_text_indexes(klass.db, {})
 
     def test_basic(self):
         resp = self.db.find({"name.last": "A last name"}, explain=True)
@@ -16,6 +31,19 @@ class IndexSelectionTests(mango.UserDocsTests):
             }, explain=True)
         assert resp["index"]["type"] == "json"
 
+    def test_no_view_index(self):
+        resp = self.db.find({"name.first": "Ohai!"}, explain=True)
+        assert 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)
+        assert resp["index"]["type"] == "text"
+
     def test_use_most_columns(self):
         # ddoc id for the age index
         ddocid = "_design/ad3d537c03cd7c6a43cf8dff66ef70ea54c2b40f"
@@ -32,3 +60,25 @@ class IndexSelectionTests(mango.UserDocsTests):
                 "age": {"$gt": 1}
             }, use_index=ddocid, explain=True)
         assert resp["index"]["ddoc"] == ddocid
+
+
+class MultiTextIndexSelectionTests(mango.UserDocsTests):
+    @classmethod
+    def setUpClass(klass):
+        super(MultiTextIndexSelectionTests, klass).setUpClass()
+        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)
+        assert resp["index"]["type"] == "json"
+
+    def test_multi_text_index_is_error(self):
+        try:
+            self.db.find({"$text": "a query"}, explain=True)
+        except Exception, e:
+            assert e.response.status_code == 400
+
+    def test_use_index_works(self):
+        resp = self.db.find({"$text": "a query"}, use_index="foo", explain=True)
+        assert resp["index"]["ddoc"] == "_design/foo"

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/test/06-basic-text-test.py
----------------------------------------------------------------------
diff --git a/test/06-basic-text-test.py b/test/06-basic-text-test.py
new file mode 100644
index 0000000..71eeb70
--- /dev/null
+++ b/test/06-basic-text-test.py
@@ -0,0 +1,488 @@
+# 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 mango
+import user_docs
+
+
+class BasicTextTests(mango.UserDocsTextTests):
+    def test_simple(self):
+        docs = self.db.find({"$text": "Stephanie"})
+        assert len(docs) == 1
+        assert docs[0]["name"]["first"] == "Stephanie"
+
+    def test_with_integer(self):
+        docs = self.db.find({"name.first": "Stephanie", "age": 48})
+        assert len(docs) == 1
+        assert docs[0]["name"]["first"] == "Stephanie"
+        assert docs[0]["age"] == 48
+
+    def test_with_boolean(self):
+        docs = self.db.find({"name.first": "Stephanie", "manager": False})
+        assert len(docs) == 1
+        assert docs[0]["name"]["first"] == "Stephanie"
+        assert docs[0]["manager"] == False
+
+    def test_with_array(self):
+        faves = ["Ruby", "C", "Python"]
+        docs = self.db.find({"name.first": "Stephanie", "favorites": faves})
+        assert docs[0]["name"]["first"] == "Stephanie"
+        assert docs[0]["favorites"] == faves
+
+    def test_lt(self):
+        docs = self.db.find({"age": {"$lt": 22}})
+        assert len(docs) == 0
+
+        docs = self.db.find({"age": {"$lt": 23}})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+        docs = self.db.find({"age": {"$lt": 33}})
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (1, 9)
+
+        docs = self.db.find({"age": {"$lt": 34}})
+        assert len(docs) == 3
+        for d in docs:
+            assert d["user_id"] in (1, 7, 9)
+
+    def test_lte(self):
+        docs = self.db.find({"age": {"$lte": 21}})
+        assert len(docs) == 0
+
+        docs = self.db.find({"age": {"$lte": 22}})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+        docs = self.db.find({"age": {"$lte": 33}})
+        assert len(docs) == 3
+        for d in docs:
+            assert d["user_id"] in (1, 7, 9)
+
+    def test_eq(self):
+        docs = self.db.find({"age": 21})
+        assert len(docs) == 0
+
+        docs = self.db.find({"age": 22})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+        docs = self.db.find({"age": {"$eq": 22}})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+        docs = self.db.find({"age": 33})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 7
+
+    def test_ne(self):
+        docs = self.db.find({"age": {"$ne": 22}})
+        assert len(docs) == len(user_docs.DOCS) - 1
+        for d in docs:
+            assert d["age"] != 22
+
+        docs = self.db.find({"$not": {"age": 22}})
+        assert len(docs) == len(user_docs.DOCS) - 1
+        for d in docs:
+            assert d["age"] != 22
+
+    def test_gt(self):
+        docs = self.db.find({"age": {"$gt": 77}})
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (3, 13)
+
+        docs = self.db.find({"age": {"$gt": 78}})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 3
+
+        docs = self.db.find({"age": {"$gt": 79}})
+        assert len(docs) == 0
+
+    def test_gte(self):
+        docs = self.db.find({"age": {"$gte": 77}})
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (3, 13)
+
+        docs = self.db.find({"age": {"$gte": 78}})
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (3, 13)
+
+        docs = self.db.find({"age": {"$gte": 79}})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 3
+
+        docs = self.db.find({"age": {"$gte": 80}})
+        assert len(docs) == 0
+
+    def test_and(self):
+        docs = self.db.find({"age": 22, "manager": True})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+        docs = self.db.find({"age": 22, "manager": False})
+        assert len(docs) == 0
+
+        docs = self.db.find({"$and": [{"age": 22}, {"manager": True}]})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+        docs = self.db.find({"$and": [{"age": 22}, {"manager": False}]})
+        assert len(docs) == 0
+
+        docs = self.db.find({"$text": "Ramona", "age": 22})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+        docs = self.db.find({"$and": [{"$text": "Ramona"}, {"age": 22}]})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+        docs = self.db.find({"$and": [{"$text": "Ramona"}, {"$text": "Floyd"}]})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+    def test_or(self):
+        docs = self.db.find({"$or": [{"age": 22}, {"age": 33}]})
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (7, 9)
+
+        q = {"$or": [{"$text": "Ramona"}, {"$text": "Stephanie"}]}
+        docs = self.db.find(q)
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (0, 9)
+
+        q = {"$or": [{"$text": "Ramona"}, {"age": 22}]}
+        docs = self.db.find(q)
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+    def test_and_or(self):
+        q = {
+            "age": 22,
+            "$or": [
+                {"manager": False},
+                {"location.state": "Missouri"}
+            ]
+        }
+        docs = self.db.find(q)
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+        q = {
+            "$or": [
+                {"age": 22},
+                {"age": 43, "manager": True}
+            ]
+        }
+        docs = self.db.find(q)
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (9, 10)
+
+        q = {
+            "$or": [
+                {"$text": "Ramona"},
+                {"age": 43, "manager": True}
+            ]
+        }
+        docs = self.db.find(q)
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (9, 10)
+
+    def test_nor(self):
+        docs = self.db.find({"$nor": [{"age": 22}, {"age": 33}]})
+        assert len(docs) == 13
+        for d in docs:
+            assert d["user_id"] not in (7, 9)
+
+    def test_in_with_value(self):
+        docs = self.db.find({"age": {"$in": [1, 5]}})
+        assert len(docs) == 0
+
+        docs = self.db.find({"age": {"$in": [1, 5, 22]}})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+        docs = self.db.find({"age": {"$in": [1, 5, 22, 31]}})
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (1, 9)
+
+        docs = self.db.find({"age": {"$in": [22, 31]}})
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (1, 9)
+
+        # Limits on boolean clauses?
+        docs = self.db.find({"age": {"$in": range(1000)}})
+        assert len(docs) == 15
+
+    def test_in_with_array(self):
+        vals = ["Random Garbage", 52, {"Versions": {"Alpha": "Beta"}}]
+        docs = self.db.find({"favorites": {"$in": vals}})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 1
+
+        vals = ["Lisp", "Python"]
+        docs = self.db.find({"favorites": {"$in": vals}})
+        assert len(docs) == 10
+
+        vals = [{"val1": 1, "val2": "val2"}]
+        docs = self.db.find({"test_in": {"$in": vals}})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 2
+
+    def test_nin_with_value(self):
+        docs = self.db.find({"age": {"$nin": [1, 5]}})
+        assert len(docs) == len(user_docs.DOCS)
+
+        docs = self.db.find({"age": {"$nin": [1, 5, 22]}})
+        assert len(docs) == len(user_docs.DOCS) - 1
+        for d in docs:
+            assert d["user_id"] != 9
+
+        docs = self.db.find({"age": {"$nin": [1, 5, 22, 31]}})
+        assert len(docs) == len(user_docs.DOCS) - 2
+        for d in docs:
+            assert d["user_id"] not in (1, 9)
+
+        docs = self.db.find({"age": {"$nin": [22, 31]}})
+        assert len(docs) == len(user_docs.DOCS) - 2
+        for d in docs:
+            assert d["user_id"] not in (1, 9)
+
+        # Limits on boolean clauses?
+        docs = self.db.find({"age": {"$nin": range(1000)}})
+        assert len(docs) == 0
+
+    def test_nin_with_array(self):
+        vals = ["Random Garbage", 52, {"Versions": {"Alpha": "Beta"}}]
+        docs = self.db.find({"favorites": {"$nin": vals}})
+        assert len(docs) == len(user_docs.DOCS) - 1
+        for d in docs:
+            assert d["user_id"] != 1
+
+        vals = ["Lisp", "Python"]
+        docs = self.db.find({"favorites": {"$nin": vals}})
+        assert len(docs) == 5
+
+        vals = [{"val1": 1, "val2": "val2"}]
+        docs = self.db.find({"test_in": {"$nin": vals}})
+        assert len(docs) == 0
+
+    def test_all(self):
+        vals = ["Ruby", "C", "Python", {"Versions": {"Alpha": "Beta"}}]
+        docs = self.db.find({"favorites": {"$all": vals}})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 1
+
+        # This matches where favorites either contains
+        # the nested array, or is the nested array. This is
+        # notably different than the non-nested array in that
+        # it does not match a re-ordered version of the array.
+        # The fact that user_id 14 isn't included demonstrates
+        # this behavior.
+        vals = [["Lisp", "Erlang", "Python"]]
+        docs = self.db.find({"favorites": {"$all": vals}})
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (3, 9)
+
+    def test_exists_field(self):
+        docs = self.db.find({"exists_field": {"$exists": True}})
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (7, 8)
+
+        docs = self.db.find({"exists_field": {"$exists": False}})
+        assert len(docs) == len(user_docs.DOCS) - 2
+        for d in docs:
+            assert d["user_id"] not in (7, 8)
+
+    def test_exists_array(self):
+        docs = self.db.find({"exists_array": {"$exists": True}})
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (9, 10)
+
+        docs = self.db.find({"exists_array": {"$exists": False}})
+        assert len(docs) == len(user_docs.DOCS) - 2
+        for d in docs:
+            assert d["user_id"] not in (9, 10)
+
+    def test_exists_object(self):
+        docs = self.db.find({"exists_object": {"$exists": True}})
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (11, 12)
+
+        docs = self.db.find({"exists_object": {"$exists": False}})
+        assert len(docs) == len(user_docs.DOCS) - 2
+        for d in docs:
+            assert d["user_id"] not in (11, 12)
+
+    def test_exists_object_member(self):
+        docs = self.db.find({"exists_object.should": {"$exists": True}})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 11
+
+        docs = self.db.find({"exists_object.should": {"$exists": False}})
+        assert len(docs) == len(user_docs.DOCS) - 1
+        for d in docs:
+            assert d["user_id"] != 11
+
+    def test_exists_and(self):
+        q = {"$and": [
+            {"manager": {"$exists": True}},
+            {"exists_object.should": {"$exists": True}}
+        ]}
+        docs = self.db.find(q)
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 11
+
+        q = {"$and": [
+            {"manager": {"$exists": False}},
+            {"exists_object.should": {"$exists": True}}
+        ]}
+        docs = self.db.find(q)
+        assert len(docs) == 0
+
+        # Translates to manager exists or exists_object.should doesn't
+        # exist, which will match all docs
+        q = {"$not": q}
+        docs = self.db.find(q)
+        assert len(docs) == len(user_docs.DOCS)
+
+    def test_value_chars(self):
+        q = {"complex_field_value": "+-(){}[]^~&&*||\"\\/?:!"}
+        docs = self.db.find(q)
+        assert len(docs) == 1
+
+    # test lucene syntax in $text
+
+
+class ElemMatchTests(mango.FriendDocsTextTests):
+    def test_elem_match(self):
+        q = {"friends": {
+                "$elemMatch":
+                    {"name.first": "Vargas"}
+            }
+        }
+        docs = self.db.find(q)
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (0, 1)
+
+        q = {
+            "friends": {
+                "$elemMatch": {
+                    "name.first": "Ochoa",
+                    "name.last": "Burch"
+                }
+            }
+        }
+        docs = self.db.find(q)
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 4
+
+
+        # Check that we can do logic in elemMatch
+        q = {
+            "friends": {"$elemMatch": {
+                "name.first": "Ochoa", "type": "work"
+            }}
+        }
+        docs = self.db.find(q)
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 1
+
+        q = {
+            "friends": {
+                "$elemMatch": {
+                    "name.first": "Ochoa",
+                    "$or": [
+                        {"type": "work"},
+                        {"type": "personal"}
+                    ]
+                }
+            }
+        }
+        docs = self.db.find(q)
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (1, 4)
+
+        # Same as last, but using $in
+        q = {
+            "friends": {
+                "$elemMatch": {
+                    "name.first": "Ochoa",
+                    "type": {"$in": ["work", "personal"]}
+                }
+            }
+        }
+        docs = self.db.find(q)
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (1, 4)
+
+        q = {
+            "$and": [{
+                "friends": {
+                    "$elemMatch": {
+                        "id": 0,
+                        "name": {
+                            "$exists": True
+                            }
+                        }
+                    }
+                },
+                {
+                "friends": {
+                    "$elemMatch": {
+                        "$or": [
+                            {
+                            "name": {
+                                "first": "Campos",
+                                "last": "Freeman"
+                                }
+                            },
+                            {
+                            "name": {
+                                "$in": [{
+                                    "first": "Gibbs",
+                                    "last": "Mccarty"
+                                    },
+                                    {
+                                    "first": "Wilkins",
+                                    "last": "Chang"
+                                     }
+                                    ]
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+            ]
+        }
+        docs = self.db.find(q)
+        assert len(docs) == 3
+        for d in docs:
+            assert d["user_id"] in (10, 11,12)

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/test/06-text-default-field-test.py
----------------------------------------------------------------------
diff --git a/test/06-text-default-field-test.py b/test/06-text-default-field-test.py
new file mode 100644
index 0000000..691a885
--- /dev/null
+++ b/test/06-text-default-field-test.py
@@ -0,0 +1,70 @@
+# 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 mango
+
+
+
+class NoDefaultFieldTest(mango.UserDocsTextTests):
+
+    DEFAULT_FIELD = False
+
+    def test_basic(self):
+        docs = self.db.find({"$text": "Ramona"})
+        # Or should this throw an error?
+        assert len(docs) == 0
+
+    def test_other_fields_exist(self):
+        docs = self.db.find({"age": 22})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+
+class NoDefaultFieldWithAnalyzer(mango.UserDocsTextTests):
+
+    DEFAULT_FIELD = {
+        "enabled": False,
+        "analyzer": "keyword"
+    }
+
+    def test_basic(self):
+        docs = self.db.find({"$text": "Ramona"})
+        assert len(docs) == 0
+
+    def test_other_fields_exist(self):
+        docs = self.db.find({"age": 22})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+
+class DefaultFieldWithCustomAnalyzer(mango.UserDocsTextTests):
+
+    DEFAULT_FIELD = {
+        "enabled": True,
+        "analyzer": "keyword"
+    }
+
+    def test_basic(self):
+        docs = self.db.find({"$text": "Ramona"})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+    def test_not_analyzed(self):
+        docs = self.db.find({"$text": "Lott Place"})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+        docs = self.db.find({"$text": "Lott"})
+        assert len(docs) == 0
+
+        docs = self.db.find({"$text": "Place"})
+        assert len(docs) == 0

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/test/07-text-custom-field-list-test.py
----------------------------------------------------------------------
diff --git a/test/07-text-custom-field-list-test.py b/test/07-text-custom-field-list-test.py
new file mode 100644
index 0000000..5e5f7cc
--- /dev/null
+++ b/test/07-text-custom-field-list-test.py
@@ -0,0 +1,62 @@
+# 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 mango
+
+
+
+class CustomFieldsTest(mango.UserDocsTextTests):
+
+    FIELDS = [
+        {"name": "favorites.[]", "type": "string"},
+        {"name": "manager", "type": "boolean"},
+        {"name": "age", "type": "number"},
+        # These two are to test the default analyzer for
+        # each field.
+        {"name": "location.state", "type": "string"},
+        {
+            "name": "location.address.street",
+            "type": "string"
+        }
+    ]
+
+    def test_basic(self):
+        docs = self.db.find({"age": 22})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+    def test_multi_field(self):
+        docs = self.db.find({"age": 22, "manager": True})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 9
+
+        docs = self.db.find({"age": 22, "manager": False})
+        assert len(docs) == 0
+
+    def test_missing(self):
+        self.db.find({"location.state": "Nevada"})
+
+    def test_missing_type(self):
+        # Raises an exception
+        try:
+            self.db.find({"age": "foo"})
+            raise Exception("Should have thrown an HTTPError")
+        except:
+            return
+
+    def test_field_analyzer_is_keyword(self):
+        docs = self.db.find({"location.state": "New"})
+        assert len(docs) == 0
+
+        docs = self.db.find({"location.state": "New Hampshire"})
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 10

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/test/08-text-limit-test.py
----------------------------------------------------------------------
diff --git a/test/08-text-limit-test.py b/test/08-text-limit-test.py
new file mode 100644
index 0000000..72c87b5
--- /dev/null
+++ b/test/08-text-limit-test.py
@@ -0,0 +1,134 @@
+# 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 mango
+import limit_docs
+
+class LimitTests(mango.LimitDocsTextTests):
+
+    def test_limit_field(self):
+        q = {"$or": [{"user_id" : {"$lt" : 10}}, {"filtered_array.[]": 1}]}
+        docs = self.db.find(q, limit=10)
+        assert len(docs) == 8
+        for d in docs:
+            assert d["user_id"] < 10
+
+    def test_limit_field2(self):
+        q = {"$or": [{"user_id" : {"$lt" : 20}}, {"filtered_array.[]": 1}]}
+        docs = self.db.find(q, limit=10)
+        assert len(docs) == 10
+        for d in docs:
+            assert d["user_id"] < 20
+
+    def test_limit_field3(self):
+        q = {"$or": [{"user_id" : {"$lt" : 100}}, {"filtered_array.[]": 1}]}
+        docs = self.db.find(q, limit=1)
+        assert len(docs) == 1
+        for d in docs:
+            assert d["user_id"] < 100
+
+    def test_limit_field4(self):
+        q = {"$or": [{"user_id" : {"$lt" : 0}}, {"filtered_array.[]": 1}]}
+        docs = self.db.find(q, limit=35)
+        assert len(docs) == 0
+
+    # We reach our cap here of 50
+    def test_limit_field5(self):
+        q = {"$or": [{"user_id" : {"$lt" : 100}}, {"filtered_array.[]": 1}]}
+        docs = self.db.find(q, limit=55)
+        assert len(docs) == 50
+        for d in docs:
+            assert d["user_id"] < 100
+
+    def test_limit_skip_field1(self):
+        q = {"$or": [{"user_id" : {"$lt" : 100}}, {"filtered_array.[]": 1}]}
+        docs = self.db.find(q, limit=10, skip=20)
+        assert len(docs) == 10
+        for d in docs:
+            assert d["user_id"] > 20
+
+    def test_limit_skip_field2(self):
+        q = {"$or": [{"user_id" : {"$lt" : 100}}, {"filtered_array.[]": 1}]}
+        docs = self.db.find(q, limit=100, skip=100)
+        assert len(docs) == 0
+
+    def test_limit_skip_field3(self):
+        q = {"$or": [{"user_id" : {"$lt" : 20}}, {"filtered_array.[]": 1}]}
+        docs = self.db.find(q, limit=1, skip=30)
+        assert len(docs) == 0
+
+    def test_limit_skip_field4(self):
+        q = {"$or": [{"user_id" : {"$lt" : 100}}, {"filtered_array.[]": 1}]}
+        docs = self.db.find(q, limit=0, skip=0)
+        assert len(docs) == 0
+
+    def test_limit_skip_field5(self):
+        q = {"$or": [{"user_id" : {"$lt" : 100}}, {"filtered_array.[]": 1}]}
+        try:
+            self.db.find(q, limit=-1)
+        except Exception, e:
+            assert e.response.status_code == 400
+        else:
+            raise AssertionError("Should have thrown error for negative limit")
+
+    def test_limit_skip_field6(self):
+        q = {"$or": [{"user_id" : {"$lt" : 100}}, {"filtered_array.[]": 1}]}
+        try:
+            self.db.find(q, skip=-1)
+        except Exception, e:
+            assert e.response.status_code == 400
+        else:
+            raise AssertionError("Should have thrown error for negative skip")
+
+    # Basic test to ensure we can iterate through documents with a bookmark
+    def test_limit_bookmark(self):
+        for i in range(1, len(limit_docs.DOCS), 5):
+            self.run_bookmark_check(i)
+
+        for i in range(1, len(limit_docs.DOCS), 5):
+            self.run_bookmark_sort_check(i)
+
+
+    def run_bookmark_check(self, size):
+        print size
+        q = {"age": {"$gt": 0}}
+        seen_docs = set()
+        bm = None
+        while True:
+            json = self.db.find(q, limit=size, bookmark=bm, return_raw=True)
+            for doc in json["docs"]:
+                assert doc["_id"] not in seen_docs
+                seen_docs.add(doc["_id"])
+            if not len(json["docs"]):
+                break
+            assert json["bookmark"] != bm
+            bm = json["bookmark"]
+        assert len(seen_docs) == len(limit_docs.DOCS)
+
+    def run_bookmark_sort_check(self, size):
+        q = {"age": {"$gt": 0}}
+        seen_docs = set()
+        bm = None
+        age = 0
+        while True:
+            json = self.db.find(q, limit=size, bookmark=bm, sort=["age"],
+                return_raw=True)
+            for doc in json["docs"]:
+                assert doc["_id"] not in seen_docs
+                assert doc["age"] >= age
+                age = doc["age"]
+                seen_docs.add(doc["_id"])
+            if not len(json["docs"]):
+                break
+            assert json["bookmark"] != bm
+            bm = json["bookmark"]
+        assert len(seen_docs) == len(limit_docs.DOCS)


[45/50] [abbrv] couchdb-mango git commit: Merge pull request #19 from cloudant/33294-query-text-search

Posted by ro...@apache.org.
Merge pull request #19 from cloudant/33294-query-text-search

Text Search In Mango

Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/801857d7
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/801857d7
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/801857d7

Branch: refs/heads/master
Commit: 801857d73a45d9b45a64296f68983b003f4c88db
Parents: bb30744 aa4edf4
Author: Tony Sun <to...@cloudant.com>
Authored: Fri Jan 16 11:43:18 2015 -0800
Committer: Tony Sun <to...@cloudant.com>
Committed: Fri Jan 16 11:43:18 2015 -0800

----------------------------------------------------------------------
 src/mango_crud.erl                     |  10 +-
 src/mango_cursor.erl                   | 218 ++++-------
 src/mango_cursor_text.erl              | 301 +++++++++++++++
 src/mango_cursor_view.erl              |  83 ++++
 src/mango_error.erl                    | 122 +++++-
 src/mango_fields.erl                   |   2 +
 src/mango_httpd.erl                    |  35 +-
 src/mango_idx.erl                      |  48 ++-
 src/mango_idx_special.erl              |   6 +
 src/mango_idx_text.erl                 | 256 +++++++++++++
 src/mango_idx_view.erl                 | 281 +++++++++++++-
 src/mango_native_proc.erl              | 182 +++++++++
 src/mango_opts.erl                     |  52 ++-
 src/mango_selector.erl                 | 281 ++------------
 src/mango_selector_text.erl            | 347 +++++++++++++++++
 src/mango_sort.erl                     |   3 -
 src/mango_util.erl                     |  68 +++-
 test/01-index-crud-test.py             | 362 ++++++++----------
 test/02-basic-find-test.py             | 516 ++++++++++++-------------
 test/03-operator-test.py               | 140 +++----
 test/04-empty-selectors-test.py        |  47 ---
 test/04-key-tests.py                   | 123 ++++++
 test/05-index-selection-test.py        |  84 ++++
 test/06-basic-text-test.py             | 488 ++++++++++++++++++++++++
 test/06-text-default-field-test.py     |  70 ++++
 test/07-text-custom-field-list-test.py |  62 +++
 test/08-text-limit-test.py             | 134 +++++++
 test/09-text-sort-test.py              |  89 +++++
 test/friend_docs.py                    | 568 ++++++++++++++++++++++++++++
 test/limit_docs.py                     | 408 ++++++++++++++++++++
 test/mango.py                          | 110 +++++-
 test/user_docs.py                      |  99 +++--
 32 files changed, 4526 insertions(+), 1069 deletions(-)
----------------------------------------------------------------------



[29/50] [abbrv] couchdb-mango git commit: add license headers to source files

Posted by ro...@apache.org.
add license headers to source files


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/440856a3
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/440856a3
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/440856a3

Branch: refs/heads/master
Commit: 440856a33ef8af81acad101ca10451f509f9309a
Parents: 195d541
Author: Robert Kowalski <ro...@apache.org>
Authored: Fri Jan 9 14:29:03 2015 +0100
Committer: Robert Kowalski <ro...@apache.org>
Committed: Fri Jan 9 14:29:03 2015 +0100

----------------------------------------------------------------------
 src/mango.app.src               | 12 ++++++++++++
 src/mango.hrl                   | 11 +++++++++++
 src/mango_crud.erl              | 11 +++++++++++
 src/mango_cursor.erl            | 12 ++++++++++++
 src/mango_cursor.hrl            | 11 +++++++++++
 src/mango_cursor_view.erl       | 12 ++++++++++++
 src/mango_doc.erl               | 12 ++++++++++++
 src/mango_error.erl             | 12 ++++++++++++
 src/mango_fields.erl            | 12 ++++++++++++
 src/mango_httpd.erl             | 12 ++++++++++++
 src/mango_idx.erl               | 12 ++++++++++++
 src/mango_idx.hrl               | 12 ++++++++++++
 src/mango_idx_special.erl       | 12 ++++++++++++
 src/mango_idx_view.erl          | 12 ++++++++++++
 src/mango_json.erl              | 12 ++++++++++++
 src/mango_native_proc.erl       | 12 ++++++++++++
 src/mango_opts.erl              | 12 ++++++++++++
 src/mango_selector.erl          | 12 ++++++++++++
 src/mango_sort.erl              | 12 ++++++++++++
 src/mango_util.erl              | 12 ++++++++++++
 test/01-index-crud-test.py      | 11 +++++++++++
 test/02-basic-find-test.py      | 12 ++++++++++++
 test/03-operator-test.py        | 11 +++++++++++
 test/04-empty-selectors-test.py | 11 +++++++++++
 test/mango.py                   | 11 +++++++++++
 test/user_docs.py               | 12 ++++++++++++
 26 files changed, 305 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango.app.src
----------------------------------------------------------------------
diff --git a/src/mango.app.src b/src/mango.app.src
index 67e7234..4fda6c0 100644
--- a/src/mango.app.src
+++ b/src/mango.app.src
@@ -1,3 +1,15 @@
+% 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.
+
 {application, mango, [
     {description, "MongoDB API compatibility layer for Cloudant"},
     {vsn, git},

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango.hrl
----------------------------------------------------------------------
diff --git a/src/mango.hrl b/src/mango.hrl
index f265864..26a9d43 100644
--- a/src/mango.hrl
+++ b/src/mango.hrl
@@ -1,2 +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(MANGO_ERROR(R), throw({mango_error, ?MODULE, R})).

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_crud.erl
----------------------------------------------------------------------
diff --git a/src/mango_crud.erl b/src/mango_crud.erl
index 9fcbbc0..159d5f7 100644
--- a/src/mango_crud.erl
+++ b/src/mango_crud.erl
@@ -1,3 +1,14 @@
+% 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.
 
 -module(mango_crud).
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_cursor.erl
----------------------------------------------------------------------
diff --git a/src/mango_cursor.erl b/src/mango_cursor.erl
index f30084f..22970c7 100644
--- a/src/mango_cursor.erl
+++ b/src/mango_cursor.erl
@@ -1,3 +1,15 @@
+% 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.
+
 -module(mango_cursor).
 
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_cursor.hrl
----------------------------------------------------------------------
diff --git a/src/mango_cursor.hrl b/src/mango_cursor.hrl
index b7c08b1..f80c987 100644
--- a/src/mango_cursor.hrl
+++ b/src/mango_cursor.hrl
@@ -1,3 +1,14 @@
+% 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.
 
 -record(cursor, {
     db,

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_cursor_view.erl
----------------------------------------------------------------------
diff --git a/src/mango_cursor_view.erl b/src/mango_cursor_view.erl
index d220f54..a9f66b0 100644
--- a/src/mango_cursor_view.erl
+++ b/src/mango_cursor_view.erl
@@ -1,3 +1,15 @@
+% 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.
+
 -module(mango_cursor_view).
 
 -export([

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_doc.erl
----------------------------------------------------------------------
diff --git a/src/mango_doc.erl b/src/mango_doc.erl
index 0d5c731..479a8ad 100644
--- a/src/mango_doc.erl
+++ b/src/mango_doc.erl
@@ -1,3 +1,15 @@
+% 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.
+
 -module(mango_doc).
 
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_error.erl
----------------------------------------------------------------------
diff --git a/src/mango_error.erl b/src/mango_error.erl
index 45ffe56..973dd43 100644
--- a/src/mango_error.erl
+++ b/src/mango_error.erl
@@ -1,3 +1,15 @@
+% 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.
+
 -module(mango_error).
 
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_fields.erl
----------------------------------------------------------------------
diff --git a/src/mango_fields.erl b/src/mango_fields.erl
index 391a588..46049af 100644
--- a/src/mango_fields.erl
+++ b/src/mango_fields.erl
@@ -1,3 +1,15 @@
+% 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.
+
 -module(mango_fields).
 
 -export([

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_httpd.erl
----------------------------------------------------------------------
diff --git a/src/mango_httpd.erl b/src/mango_httpd.erl
index fa6817f..ef4cc5b 100644
--- a/src/mango_httpd.erl
+++ b/src/mango_httpd.erl
@@ -1,3 +1,15 @@
+% 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.
+
 -module(mango_httpd).
 
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_idx.erl
----------------------------------------------------------------------
diff --git a/src/mango_idx.erl b/src/mango_idx.erl
index 0498e07..c0a07a8 100644
--- a/src/mango_idx.erl
+++ b/src/mango_idx.erl
@@ -1,3 +1,15 @@
+% 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.
+
 % This module is for the "index object" as in, the data structure
 % representing an index. Not to be confused with mango_index which
 % contains APIs for managing indexes.

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_idx.hrl
----------------------------------------------------------------------
diff --git a/src/mango_idx.hrl b/src/mango_idx.hrl
index 52bc010..712031b 100644
--- a/src/mango_idx.hrl
+++ b/src/mango_idx.hrl
@@ -1,3 +1,15 @@
+% 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.
+
 -record(idx, {
     dbname,
     ddoc,

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_idx_special.erl
----------------------------------------------------------------------
diff --git a/src/mango_idx_special.erl b/src/mango_idx_special.erl
index dcff480..0234dff 100644
--- a/src/mango_idx_special.erl
+++ b/src/mango_idx_special.erl
@@ -1,3 +1,15 @@
+% 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.
+
 -module(mango_idx_special).
 
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_idx_view.erl
----------------------------------------------------------------------
diff --git a/src/mango_idx_view.erl b/src/mango_idx_view.erl
index 552acf5..993574f 100644
--- a/src/mango_idx_view.erl
+++ b/src/mango_idx_view.erl
@@ -1,3 +1,15 @@
+% 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.
+
 -module(mango_idx_view).
 
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_json.erl
----------------------------------------------------------------------
diff --git a/src/mango_json.erl b/src/mango_json.erl
index 9e6a78c..1a52003 100644
--- a/src/mango_json.erl
+++ b/src/mango_json.erl
@@ -1,3 +1,15 @@
+% 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.
+
 -module(mango_json).
 
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_native_proc.erl
----------------------------------------------------------------------
diff --git a/src/mango_native_proc.erl b/src/mango_native_proc.erl
index aaa00e2..636da5c 100644
--- a/src/mango_native_proc.erl
+++ b/src/mango_native_proc.erl
@@ -1,3 +1,15 @@
+% 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.
+
 -module(mango_native_proc).
 -behavior(gen_server).
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_opts.erl
----------------------------------------------------------------------
diff --git a/src/mango_opts.erl b/src/mango_opts.erl
index cb42011..45784a9 100644
--- a/src/mango_opts.erl
+++ b/src/mango_opts.erl
@@ -1,3 +1,15 @@
+% 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.
+
 -module(mango_opts).
 
 -export([

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_selector.erl
----------------------------------------------------------------------
diff --git a/src/mango_selector.erl b/src/mango_selector.erl
index da6a8ba..d1c9898 100644
--- a/src/mango_selector.erl
+++ b/src/mango_selector.erl
@@ -1,3 +1,15 @@
+% 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.
+
 -module(mango_selector).
 
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_sort.erl
----------------------------------------------------------------------
diff --git a/src/mango_sort.erl b/src/mango_sort.erl
index 75cb3c2..717099b 100644
--- a/src/mango_sort.erl
+++ b/src/mango_sort.erl
@@ -1,3 +1,15 @@
+% 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.
+
 -module(mango_sort).
 
 -export([

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/src/mango_util.erl
----------------------------------------------------------------------
diff --git a/src/mango_util.erl b/src/mango_util.erl
index a60eb50..b0767dc 100644
--- a/src/mango_util.erl
+++ b/src/mango_util.erl
@@ -1,3 +1,15 @@
+% 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.
+
 -module(mango_util).
 
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/test/01-index-crud-test.py
----------------------------------------------------------------------
diff --git a/test/01-index-crud-test.py b/test/01-index-crud-test.py
index 1f8b8bf..be13bdd 100644
--- a/test/01-index-crud-test.py
+++ b/test/01-index-crud-test.py
@@ -1,3 +1,14 @@
+# 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 random
 import time

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/test/02-basic-find-test.py
----------------------------------------------------------------------
diff --git a/test/02-basic-find-test.py b/test/02-basic-find-test.py
index f897ddb..f6b0610 100644
--- a/test/02-basic-find-test.py
+++ b/test/02-basic-find-test.py
@@ -1,3 +1,15 @@
+# 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.
+
 # -*- coding: latin-1 -*-
 import user_docs
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/test/03-operator-test.py
----------------------------------------------------------------------
diff --git a/test/03-operator-test.py b/test/03-operator-test.py
index 4ef93ff..bd0500f 100644
--- a/test/03-operator-test.py
+++ b/test/03-operator-test.py
@@ -1,3 +1,14 @@
+# 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 user_docs
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/test/04-empty-selectors-test.py
----------------------------------------------------------------------
diff --git a/test/04-empty-selectors-test.py b/test/04-empty-selectors-test.py
index ba05ea3..08bb03e 100644
--- a/test/04-empty-selectors-test.py
+++ b/test/04-empty-selectors-test.py
@@ -1,3 +1,14 @@
+# 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 user_docs
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/test/mango.py
----------------------------------------------------------------------
diff --git a/test/mango.py b/test/mango.py
index fc201d6..3163cf5 100644
--- a/test/mango.py
+++ b/test/mango.py
@@ -1,3 +1,14 @@
+# 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 json
 import time

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/440856a3/test/user_docs.py
----------------------------------------------------------------------
diff --git a/test/user_docs.py b/test/user_docs.py
index d129f59..1cccb58 100644
--- a/test/user_docs.py
+++ b/test/user_docs.py
@@ -1,3 +1,15 @@
+# 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.
+
 # -*- coding: latin-1 -*-
 """
 Generated with http://www.json-generator.com/


[50/50] [abbrv] couchdb-mango git commit: Merge pull request #21 from cloudant/43621-mango-escape-period

Posted by ro...@apache.org.
Merge pull request #21 from cloudant/43621-mango-escape-period

43621 mango escape period

Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/09e4b815
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/09e4b815
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/09e4b815

Branch: refs/heads/master
Commit: 09e4b815dd8a8777ea1fac51923216cedd8ad066
Parents: bdf9cf7 a7c88e5
Author: Tony Sun <to...@cloudant.com>
Authored: Fri Jan 23 15:43:42 2015 -0800
Committer: Tony Sun <to...@cloudant.com>
Committed: Fri Jan 23 15:43:42 2015 -0800

----------------------------------------------------------------------
 src/mango_native_proc.erl              | 18 +++++++----------
 src/mango_selector_text.erl            | 25 +++++++++++-------------
 src/mango_util.erl                     | 17 +++++++++++++++-
 test/04-key-tests.py                   | 30 +++++++++++++++++++++++++++--
 test/07-text-custom-field-list-test.py | 17 +++++++++++++++-
 test/user_docs.py                      |  3 ++-
 6 files changed, 80 insertions(+), 30 deletions(-)
----------------------------------------------------------------------



[43/50] [abbrv] couchdb-mango git commit: Support Text Index Creation

Posted by ro...@apache.org.
http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/test/09-text-sort-test.py
----------------------------------------------------------------------
diff --git a/test/09-text-sort-test.py b/test/09-text-sort-test.py
new file mode 100644
index 0000000..d2bd454
--- /dev/null
+++ b/test/09-text-sort-test.py
@@ -0,0 +1,89 @@
+# 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 mango
+import user_docs
+
+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
+
+    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"
+
+    def test_notype_sort(self):
+        q = {"email": {"$gt": None}}
+        try:
+            self.db.find(q, sort=["email"])
+        except Exception, e:
+            assert 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
+
+    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
+
+    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
+
+    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
+
+    def test_ambiguous_type_sort(self):
+        q = {"$or": [{"age":{"$gt": 0}}, {"email": {"$gt": None}},
+            {"age": "34"}]}
+        try:
+            self.db.find(q, sort=["age"])
+        except Exception, e:
+            assert e.response.status_code == 400
+        else:
+            raise AssertionError("Should have thrown error for sort")
+
+    def test_guess_multi_sort(self):
+        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
+
+    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

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/test/friend_docs.py
----------------------------------------------------------------------
diff --git a/test/friend_docs.py b/test/friend_docs.py
new file mode 100644
index 0000000..8bba58d
--- /dev/null
+++ b/test/friend_docs.py
@@ -0,0 +1,568 @@
+# 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.
+
+"""
+Generated with http://www.json-generator.com/
+
+With this pattern:
+
+[
+  '{{repeat(15)}}',
+  {
+    _id: '{{index()}}',
+    name: {
+      first: '{{firstName()}}',
+      last: '{{surname()}}'
+    },
+    friends: [
+      '{{repeat(3)}}',
+      {
+        id: '{{index()}}',
+        name: {
+          first: '{{firstName()}}',
+          last: '{{surname()}}'
+        },
+        type: '{{random("personal", "work")}}'
+      }
+    ]
+  }
+]
+"""
+
+import copy
+
+
+def setup(db, index_type="view"):
+    db.recreate()
+    db.save_docs(copy.deepcopy(DOCS))
+    if index_type == "view":
+        add_view_indexes(db)
+    elif index_type == "text":
+        add_text_indexes(db)
+
+
+def add_text_indexes(db):
+    db.create_text_index()
+
+
+DOCS =  [
+    {
+        "_id": "54a43171d37ae5e81bff5ae0",
+        "user_id": 0,
+        "name": {
+            "first": "Ochoa",
+            "last": "Fox"
+        },
+        "friends": [
+            {
+                "id": 0,
+                "name": {
+                    "first": "Sherman",
+                    "last": "Davidson"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 1,
+                "name": {
+                    "first": "Vargas",
+                    "last": "Mendez"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 2,
+                "name": {
+                    "first": "Sheppard",
+                    "last": "Cotton"
+                },
+                "type": "work"
+            }
+        ]
+    },
+    {
+        "_id": "54a43171958485dc32917c50",
+        "user_id": 1,
+        "name": {
+            "first": "Sheppard",
+            "last": "Cotton"
+        },
+        "friends": [
+            {
+                "id": 0,
+                "name": {
+                    "first": "Ochoa",
+                    "last": "Fox"
+                },
+                "type": "work"
+            },
+            {
+                "id": 1,
+                "name": {
+                    "first": "Vargas",
+                    "last": "Mendez"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 2,
+                "name": {
+                    "first": "Kendra",
+                    "last": "Burns"
+                },
+                "type": "work"
+            }
+        ]
+    },
+    {
+        "_id": "54a431711cf025ba74bea899",
+        "user_id": 2,
+        "name": {
+            "first": "Hunter",
+            "last": "Wells"
+        },
+        "friends": [
+            {
+                "id": 0,
+                "name": {
+                    "first": "Estes",
+                    "last": "Fischer"
+                },
+                "type": "work"
+            },
+            {
+                "id": 1,
+                "name": {
+                    "first": "Farrell",
+                    "last": "Maddox"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 2,
+                "name": {
+                    "first": "Kendra",
+                    "last": "Burns"
+                },
+                "type": "work"
+            }
+        ]
+    },
+    {
+        "_id": "54a4317151a70a9881ac28a4",
+        "user_id": 3,
+        "name": {
+            "first": "Millicent",
+            "last": "Guy"
+        },
+        "friends": [
+            {
+                "id": 0,
+                "name": {
+                    "first": "Luella",
+                    "last": "Mendoza"
+                },
+                "type": "work"
+            },
+            {
+                "id": 1,
+                "name": {
+                    "first": "Melanie",
+                    "last": "Foster"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 2,
+                "name": {
+                    "first": "Hopkins",
+                    "last": "Scott"
+                },
+                "type": "work"
+            }
+        ]
+    },
+    {
+        "_id": "54a43171d946b78703a0e076",
+        "user_id": 4,
+        "name": {
+            "first": "Elisabeth",
+            "last": "Brady"
+        },
+        "friends": [
+            {
+                "id": 0,
+                "name": {
+                    "first": "Sofia",
+                    "last": "Workman"
+                },
+                "type": "work"
+            },
+            {
+                "id": 1,
+                "name": {
+                    "first": "Alisha",
+                    "last": "Reilly"
+                },
+                "type": "work"
+            },
+            {
+                "id": 2,
+                "name": {
+                    "first": "Ochoa",
+                    "last": "Burch"
+                },
+                "type": "personal"
+            }
+        ]
+    },
+    {
+        "_id": "54a4317118abd7f1992464ee",
+        "user_id": 5,
+        "name": {
+            "first": "Pollard",
+            "last": "French"
+        },
+        "friends": [
+            {
+                "id": 0,
+                "name": {
+                    "first": "Hollie",
+                    "last": "Juarez"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 1,
+                "name": {
+                    "first": "Nelda",
+                    "last": "Newton"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 2,
+                "name": {
+                    "first": "Yang",
+                    "last": "Pace"
+                },
+                "type": "personal"
+            }
+        ]
+    },
+    {
+        "_id": "54a43171f139e63d6579121e",
+        "user_id": 6,
+        "name": {
+            "first": "Acevedo",
+            "last": "Morales"
+        },
+        "friends": [
+            {
+                "id": 0,
+                "name": {
+                    "first": "Payne",
+                    "last": "Berry"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 1,
+                "name": {
+                    "first": "Rene",
+                    "last": "Valenzuela"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 2,
+                "name": {
+                    "first": "Dora",
+                    "last": "Gallegos"
+                },
+                "type": "work"
+            }
+        ]
+    },
+    {
+        "_id": "54a431719783cef80876dde8",
+        "user_id": 7,
+        "name": {
+            "first": "Cervantes",
+            "last": "Marquez"
+        },
+        "friends": [
+            {
+                "id": 0,
+                "name": {
+                    "first": "Maxwell",
+                    "last": "Norman"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 1,
+                "name": {
+                    "first": "Shields",
+                    "last": "Bass"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 2,
+                "name": {
+                    "first": "Luz",
+                    "last": "Jacobson"
+                },
+                "type": "work"
+            }
+        ]
+    },
+    {
+        "_id": "54a43171ecc7540d1f7aceae",
+        "user_id": 8,
+        "name": {
+            "first": "West",
+            "last": "Morrow"
+        },
+        "friends": [
+            {
+                "id": 0,
+                "name": {
+                    "first": "Townsend",
+                    "last": "Dixon"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 1,
+                "name": {
+                    "first": "Callahan",
+                    "last": "Buck"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 2,
+                "name": {
+                    "first": "Rachel",
+                    "last": "Fletcher"
+                },
+                "type": "personal"
+            }
+        ]
+    },
+    {
+        "_id": "54a4317113e831f4af041a0a",
+        "user_id": 9,
+        "name": {
+            "first": "Cotton",
+            "last": "House"
+        },
+        "friends": [
+            {
+                "id": 0,
+                "name": {
+                    "first": "Mckenzie",
+                    "last": "Medina"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 1,
+                "name": {
+                    "first": "Cecilia",
+                    "last": "Miles"
+                },
+                "type": "work"
+            },
+            {
+                "id": 2,
+                "name": {
+                    "first": "Guerra",
+                    "last": "Cervantes"
+                },
+                "type": "work"
+            }
+        ]
+    },
+    {
+        "_id": "54a43171686eb1f48ebcbe01",
+        "user_id": 10,
+        "name": {
+            "first": "Wright",
+            "last": "Rivas"
+        },
+        "friends": [
+            {
+                "id": 0,
+                "name": {
+                    "first": "Campos",
+                    "last": "Freeman"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 1,
+                "name": {
+                    "first": "Christian",
+                    "last": "Ferguson"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 2,
+                "name": {
+                    "first": "Doreen",
+                    "last": "Wilder"
+                },
+                "type": "work"
+            }
+        ]
+    },
+    {
+        "_id": "54a43171a4f3d5638c162f4f",
+        "user_id": 11,
+        "name": {
+            "first": "Lorene",
+            "last": "Dorsey"
+        },
+        "friends": [
+            {
+                "id": 0,
+                "name": {
+                    "first": "Gibbs",
+                    "last": "Mccarty"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 1,
+                "name": {
+                    "first": "Neal",
+                    "last": "Franklin"
+                },
+                "type": "work"
+            },
+            {
+                "id": 2,
+                "name": {
+                    "first": "Kristy",
+                    "last": "Head"
+                },
+                "type": "personal"
+            }
+        ]
+    },
+    {
+        "_id": "54a431719faa420a5b4fbeb0",
+        "user_id": 12,
+        "name": {
+            "first": "Juanita",
+            "last": "Cook"
+        },
+        "friends": [
+            {
+                "id": 0,
+                "name": {
+                    "first": "Wilkins",
+                    "last": "Chang"
+                },
+                "type": "work"
+            },
+            {
+                "id": 1,
+                "name": {
+                    "first": "Haney",
+                    "last": "Rivera"
+                },
+                "type": "work"
+            },
+            {
+                "id": 2,
+                "name": {
+                    "first": "Lauren",
+                    "last": "Manning"
+                },
+                "type": "work"
+            }
+        ]
+    },
+    {
+        "_id": "54a43171e65d35f9ee8c53c0",
+        "user_id": 13,
+        "name": {
+            "first": "Levy",
+            "last": "Osborn"
+        },
+        "friends": [
+            {
+                "id": 0,
+                "name": {
+                    "first": "Vinson",
+                    "last": "Vargas"
+                },
+                "type": "work"
+            },
+            {
+                "id": 1,
+                "name": {
+                    "first": "Felicia",
+                    "last": "Beach"
+                },
+                "type": "work"
+            },
+            {
+                "id": 2,
+                "name": {
+                    "first": "Nadine",
+                    "last": "Kemp"
+                },
+                "type": "work"
+            }
+        ]
+    },
+    {
+        "_id": "54a4317132f2c81561833259",
+        "user_id": 14,
+        "name": {
+            "first": "Christina",
+            "last": "Raymond"
+        },
+        "friends": [
+            {
+                "id": 0,
+                "name": {
+                    "first": "Herrera",
+                    "last": "Walton"
+                },
+                "type": "work"
+            },
+            {
+                "id": 1,
+                "name": {
+                    "first": "Hahn",
+                    "last": "Rutledge"
+                },
+                "type": "work"
+            },
+            {
+                "id": 2,
+                "name": {
+                    "first": "Stacie",
+                    "last": "Harding"
+                },
+                "type": "work"
+            }
+        ]
+    }
+]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/test/limit_docs.py
----------------------------------------------------------------------
diff --git a/test/limit_docs.py b/test/limit_docs.py
new file mode 100644
index 0000000..53ab523
--- /dev/null
+++ b/test/limit_docs.py
@@ -0,0 +1,408 @@
+# 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
+
+
+def setup(db, index_type="view"):
+    db.recreate()
+    db.save_docs(copy.deepcopy(DOCS))
+    if index_type == "view":
+        add_view_indexes(db)
+    elif index_type == "text":
+        add_text_indexes(db)
+
+
+def add_text_indexes(db):
+    db.create_text_index()
+
+
+DOCS =  [
+  {
+    "_id": "54af50626de419f5109c962f",
+    "user_id": 0,
+    "age": 10
+  },
+  {
+    "_id": "54af50622071121b25402dc3",
+    "user_id": 1,
+    "age": 11
+
+  },
+  {
+    "_id": "54af50623809e19159a3cdd0",
+    "user_id": 2,
+    "age": 12
+  },
+  {
+    "_id": "54af50629f45a0f49a441d01",
+    "user_id": 3,
+    "age": 13
+
+  },
+  {
+    "_id": "54af50620f1755c22359a362",
+    "user_id": 4,
+    "age": 14
+  },
+  {
+    "_id": "54af5062dd6f6c689ad2ca23",
+    "user_id": 5,
+    "age": 15
+  },
+  {
+    "_id": "54af50623e89b432be1187b8",
+    "user_id": 6,
+    "age": 16
+  },
+  {
+    "_id": "54af5062932a00270a3b5ab0",
+    "user_id": 7,
+    "age": 17
+
+  },
+  {
+    "_id": "54af5062df773d69174e3345",
+    "filtered_array" : [1, 2, 3],
+    "age": 18
+  },
+  {
+    "_id": "54af50629c1153b9e21e346d",
+    "filtered_array" : [1, 2, 3],
+    "age": 19
+  },
+  {
+    "_id": "54af5062dabb7cc4b60e0c95",
+    "user_id": 10,
+    "age": 20
+  },
+  {
+    "_id": "54af5062204996970a4439a2",
+    "user_id": 11,
+    "age": 21
+  },
+  {
+    "_id": "54af50629cea39e8ea52bfac",
+    "user_id": 12,
+    "age": 22
+  },
+  {
+    "_id": "54af50620597c094f75db2a1",
+    "user_id": 13,
+    "age": 23
+  },
+  {
+    "_id": "54af50628d4048de0010723c",
+    "user_id": 14,
+    "age": 24
+  },
+  {
+    "_id": "54af5062f339b6f44f52faf6",
+    "user_id": 15,
+    "age": 25
+  },
+  {
+    "_id": "54af5062a893f17ea4402031",
+    "user_id": 16,
+    "age": 26
+  },
+  {
+    "_id": "54af5062323dbc7077deb60a",
+    "user_id": 17,
+    "age": 27
+  },
+  {
+    "_id": "54af506224db85bd7fcd0243",
+    "filtered_array" : [1, 2, 3],
+    "age": 28
+  },
+  {
+    "_id": "54af506255bb551c9cc251bf",
+    "filtered_array" : [1, 2, 3],
+    "age": 29
+  },
+  {
+    "_id": "54af50625a97394e07d718a1",
+    "filtered_array" : [1, 2, 3],
+    "age": 30
+  },
+  {
+    "_id": "54af506223f51d586b4ef529",
+    "user_id": 21,
+    "age": 31
+  },
+  {
+    "_id": "54af50622740dede7d6117b7",
+    "user_id": 22,
+    "age": 32
+  },
+  {
+    "_id": "54af50624efc87684a52e8fb",
+    "user_id": 23,
+    "age": 33
+  },
+  {
+    "_id": "54af5062f40932760347799c",
+    "user_id": 24,
+    "age": 34
+  },
+  {
+    "_id": "54af5062d9f7361951ac645d",
+    "user_id": 25,
+    "age": 35
+  },
+  {
+    "_id": "54af5062f89aef302b37c3bc",
+    "filtered_array" : [1, 2, 3],
+    "age": 36
+  },
+  {
+    "_id": "54af5062498ec905dcb351f8",
+    "filtered_array" : [1, 2, 3],
+    "age": 37
+  },
+  {
+    "_id": "54af5062b1d2f2c5a85bdd7e",
+    "user_id": 28,
+    "age": 38
+  },
+  {
+    "_id": "54af50625061029c0dd942b5",
+    "filtered_array" : [1, 2, 3],
+    "age": 39
+  },
+  {
+    "_id": "54af50628b0d08a1d23c030a",
+    "user_id": 30,
+    "age": 40
+  },
+  {
+    "_id": "54af506271b6e3119eb31d46",
+    "filtered_array" : [1, 2, 3],
+    "age": 41
+  },
+  {
+    "_id": "54af5062b69f46424dfcf3e5",
+    "user_id": 32,
+    "age": 42
+  },
+  {
+    "_id": "54af5062ed00c7dbe4d1bdcf",
+    "user_id": 33,
+    "age": 43
+  },
+  {
+    "_id": "54af5062fb64e45180c9a90d",
+    "user_id": 34,
+    "age": 44
+  },
+  {
+    "_id": "54af5062241c72b067127b09",
+    "user_id": 35,
+    "age": 45
+  },
+  {
+    "_id": "54af50626a467d8b781a6d06",
+    "user_id": 36,
+    "age": 46
+  },
+  {
+    "_id": "54af50620e992d60af03bf86",
+    "filtered_array" : [1, 2, 3],
+    "age": 47
+  },
+  {
+    "_id": "54af506254f992aa3c51532f",
+    "user_id": 38,
+    "age": 48
+  },
+  {
+    "_id": "54af5062e99b20f301de39b9",
+    "user_id": 39,
+    "age": 49
+  },
+  {
+    "_id": "54af50624fbade6b11505b5d",
+    "user_id": 40,
+    "age": 50
+  },
+  {
+    "_id": "54af506278ad79b21e807ae4",
+    "user_id": 41,
+    "age": 51
+  },
+  {
+    "_id": "54af5062fc7a1dcb33f31d08",
+    "user_id": 42,
+    "age": 52
+  },
+  {
+    "_id": "54af5062ea2c954c650009cf",
+    "user_id": 43,
+    "age": 53
+  },
+  {
+    "_id": "54af506213576c2f09858266",
+    "user_id": 44,
+    "age": 54
+  },
+  {
+    "_id": "54af50624a05ac34c994b1c0",
+    "user_id": 45,
+    "age": 55
+  },
+  {
+    "_id": "54af50625a624983edf2087e",
+    "user_id": 46,
+    "age": 56
+  },
+  {
+    "_id": "54af50623de488c49d064355",
+    "user_id": 47,
+    "age": 57
+  },
+  {
+    "_id": "54af5062628b5df08661a9d5",
+    "user_id": 48,
+    "age": 58
+  },
+  {
+    "_id": "54af50620c706fc23032ae62",
+    "user_id": 49,
+    "age": 59
+  },
+  {
+    "_id": "54af5062509f1e2371fe1da4",
+    "user_id": 50,
+    "age": 60
+  },
+  {
+    "_id": "54af50625e96b22436791653",
+    "user_id": 51,
+    "age": 61
+  },
+  {
+    "_id": "54af5062a9cb71463bb9577f",
+    "user_id": 52,
+    "age": 62
+  },
+  {
+    "_id": "54af50624fea77a4221a4baf",
+    "user_id": 53,
+    "age": 63
+  },
+  {
+    "_id": "54af5062c63df0a147d2417e",
+    "user_id": 54,
+    "age": 64
+  },
+  {
+    "_id": "54af50623c56d78029316c9f",
+    "user_id": 55,
+    "age": 65
+  },
+  {
+    "_id": "54af5062167f6e13aa0dd014",
+    "user_id": 56,
+    "age": 66
+  },
+  {
+    "_id": "54af50621558abe77797d137",
+    "filtered_array" : [1, 2, 3],
+    "age": 67
+  },
+  {
+    "_id": "54af50624d5b36aa7cb5fa77",
+    "user_id": 58,
+    "age": 68
+  },
+  {
+    "_id": "54af50620d79118184ae66bd",
+    "user_id": 59,
+    "age": 69
+  },
+  {
+    "_id": "54af5062d18aafa5c4ca4935",
+    "user_id": 60,
+    "age": 71
+  },
+  {
+    "_id": "54af5062fd22a409649962f4",
+    "filtered_array" : [1, 2, 3],
+    "age": 72
+  },
+  {
+    "_id": "54af5062e31045a1908e89f9",
+    "user_id": 62,
+    "age": 73
+  },
+  {
+    "_id": "54af50624c062fcb4c59398b",
+    "user_id": 63,
+    "age": 74
+  },
+  {
+    "_id": "54af506241ec83430a15957f",
+    "user_id": 64,
+    "age": 75
+  },
+  {
+    "_id": "54af506224d0f888ae411101",
+    "user_id": 65,
+    "age": 76
+  },
+  {
+    "_id": "54af506272a971c6cf3ab6b8",
+    "user_id": 66,
+    "age": 77
+  },
+  {
+    "_id": "54af506221e25b485c95355b",
+    "user_id": 67,
+    "age": 78
+  },
+  {
+    "_id": "54af5062800f7f2ca73e9623",
+    "user_id": 68,
+    "age": 79
+  },
+  {
+    "_id": "54af5062bc962da30740534a",
+    "user_id": 69,
+    "age": 80
+  },
+  {
+    "_id": "54af50625102d6e210fc2efd",
+    "filtered_array" : [1, 2, 3],
+    "age": 81
+  },
+  {
+    "_id": "54af5062e014b9d039f02c5e",
+    "user_id": 71,
+    "age": 82
+  },
+  {
+    "_id": "54af5062fbd5e801dd217515",
+    "user_id": 72,
+    "age": 83
+  },
+  {
+    "_id": "54af50629971992b658fcb88",
+    "user_id": 73,
+    "age": 84
+  },
+  {
+    "_id": "54af5062607d53416c30bafd",
+    "filtered_array" : [1, 2, 3],
+    "age": 85
+  }
+]

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/test/mango.py
----------------------------------------------------------------------
diff --git a/test/mango.py b/test/mango.py
index 5b022b8..79545e1 100644
--- a/test/mango.py
+++ b/test/mango.py
@@ -17,7 +17,9 @@ import uuid
 
 import requests
 
+import friend_docs
 import user_docs
+import limit_docs
 
 
 def random_db_name():
@@ -96,6 +98,31 @@ class Database(object):
         r.raise_for_status()
         return r.json()["result"] == "created"
 
+    def create_text_index(self, analyzer=None, selector=None, idx_type="text",
+        default_field=None, fields=None, name=None, ddoc=None):
+        body = {
+            "index": {
+            },
+            "type": idx_type,
+            "w": 3,
+        }
+        if name is not None:
+            body["name"] = name
+        if analyzer is not None:
+            body["index"]["default_analyzer"] = analyzer
+        if default_field is not None:
+            body["index"]["default_field"] = default_field
+        if selector is not None:
+            body["selector"] = selector
+        if fields is not None:
+            body["index"]["fields"] = fields
+        if ddoc is not None:
+            body["ddoc"] = ddoc
+        body = json.dumps(body)
+        r = self.sess.post(self.path("_index"), data=body)
+        r.raise_for_status()
+        return r.json()["result"] == "created"
+
     def list_indexes(self):
         r = self.sess.get(self.path("_index"))
         r.raise_for_status()
@@ -107,7 +134,8 @@ class Database(object):
         r.raise_for_status()
 
     def find(self, selector, limit=25, skip=0, sort=None, fields=None,
-                r=1, conflicts=False, explain=False, use_index=None):
+                r=1, conflicts=False, use_index=None, explain=False,
+                bookmark=None, return_raw=False):
         body = {
             "selector": selector,
             "use_index": use_index,
@@ -120,6 +148,8 @@ class Database(object):
             body["sort"] = sort
         if fields is not None:
             body["fields"] = fields
+        if bookmark is not None:
+            body["bookmark"] = bookmark
         body = json.dumps(body)
         if explain:
             path = self.path("_explain")
@@ -127,7 +157,7 @@ class Database(object):
             path = self.path("_find")
         r = self.sess.post(path, data=body)
         r.raise_for_status()
-        if explain:
+        if explain or return_raw:
             return r.json()
         else:
             return r.json()["docs"]
@@ -183,3 +213,10 @@ class FriendDocsTextTests(DbPerClass):
     def setUpClass(klass):
         super(FriendDocsTextTests, klass).setUpClass()
         friend_docs.setup(klass.db, index_type="text")
+
+class LimitDocsTextTests(DbPerClass):
+
+    @classmethod
+    def setUpClass(klass):
+        super(LimitDocsTextTests, klass).setUpClass()
+        limit_docs.setup(klass.db, index_type="text")

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/aa4edf42/test/user_docs.py
----------------------------------------------------------------------
diff --git a/test/user_docs.py b/test/user_docs.py
index 972eafc..05eabbb 100644
--- a/test/user_docs.py
+++ b/test/user_docs.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 # 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
@@ -9,8 +10,7 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-#
-# -*- coding: utf-8 -*-
+
 """
 Generated with http://www.json-generator.com/
 
@@ -351,7 +351,8 @@ DOCS = [
             "Erlang",
             "Python"
         ],
-        "exists_array" : ["should", "exist", "array1"]
+        "exists_array" : ["should", "exist", "array1"],
+        "complex_field_value" : "+-(){}[]^~&&*||\"\\/?:!"
     },
     {
         "_id": "e900001d-bc48-48a6-9b1a-ac9a1f5d1a03",
@@ -456,16 +457,11 @@ DOCS = [
             "C",
             "Ruby",
             "Ruby"
-        ],
-        "utf8-1[]:string" : "string",
-        "utf8-2[]:boolean[]" : True,
-        "utf8-3[]:number" : 9,
-        "utf8-3[]:null" : None
+        ]
     },
     {
         "_id": "c78c529f-0b07-4947-90a6-d6b7ca81da62",
         "user_id": 14,
-        "«ταБЬℓσ»" : "utf-8",
         "name": {
             "first": "Faith",
             "last": "Hess"


[35/50] [abbrv] couchdb-mango git commit: Move key tests to their own test suite

Posted by ro...@apache.org.
Move key tests to their own test suite


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/08c33202
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/08c33202
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/08c33202

Branch: refs/heads/master
Commit: 08c33202ff848814ab4bab11d18284417ccd5bd2
Parents: 9ed1304
Author: Paul J. Davis <pa...@gmail.com>
Authored: Fri Jan 9 16:06:17 2015 -0600
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Fri Jan 16 13:32:49 2015 -0600

----------------------------------------------------------------------
 test/02-basic-find-test.py | 25 ----------------
 test/04-key-tests.py       | 65 +++++++++++++++++++++++++++++++++++++++++
 test/user_docs.py          | 28 +-----------------
 3 files changed, 66 insertions(+), 52 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/08c33202/test/02-basic-find-test.py
----------------------------------------------------------------------
diff --git a/test/02-basic-find-test.py b/test/02-basic-find-test.py
index 58029ed..4e3fc29 100644
--- a/test/02-basic-find-test.py
+++ b/test/02-basic-find-test.py
@@ -235,31 +235,6 @@ class BasicFindTests(mango.UserDocsTests):
             docs = self.db.find({"age": {"$gt": 0}}, r=r)
             assert len(docs) == 15
 
-    def test_dot_key(self):
-        fields = ["title", "dot\\.key", "none.dot"]
-        docs = self.db.find({"type": "complex_key"}, fields = fields)
-        assert len(docs) == 4
-        assert docs[1].has_key("dot.key")
-        assert docs[1]["dot.key"] == "dot's value"
-        assert docs[1].has_key("none")
-        assert docs[1]["none"]["dot"] == "none dot's value"
-
-    def test_peso_key(self):
-        fields = ["title", "$key", "deep.$key"]
-        docs = self.db.find({"type": "complex_key"}, fields = fields)
-        assert len(docs) == 4
-        assert docs[2].has_key("$key")
-        assert docs[2]["$key"] == "peso"
-        assert docs[2].has_key("deep")
-        assert docs[2]["deep"]["$key"] == "deep peso"
-
-    def test_unicode_key(self):
-        docs = self.db.find({"type": "complex_key"}, fields = ["title", ""])
-        assert len(docs) == 4
-        # note:  == \uf8ff
-        assert docs[3].has_key(u'\uf8ff')
-        assert docs[3][u'\uf8ff'] == "apple"
-
     def test_empty(self):
         try:
             self.db.find({})

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/08c33202/test/04-key-tests.py
----------------------------------------------------------------------
diff --git a/test/04-key-tests.py b/test/04-key-tests.py
new file mode 100644
index 0000000..4a5e904
--- /dev/null
+++ b/test/04-key-tests.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+import mango
+
+
+TEST_DOCS = [
+    {
+        "type": "complex_key",
+        "title": "normal key"
+    },
+    {
+        "type": "complex_key",
+        "title": "key with dot",
+        "dot.key": "dot's value",
+        "none": {
+            "dot": "none dot's value"
+        }
+    },
+    {
+        "type": "complex_key",
+        "title": "key with peso",
+        "$key": "peso",
+        "deep": {
+            "$key": "deep peso"
+        }
+    },
+    {
+        "type": "complex_key",
+        "title": "unicode key",
+        "": "apple"
+    }
+]
+
+
+class KeyTests(mango.DbPerClass):
+    @classmethod
+    def setUpClass(klass):
+        super(KeyTests, klass).setUpClass()
+        klass.db.save_docs(TEST_DOCS, w=3)
+        klass.db.create_index(["type"])
+
+    def test_dot_key(self):
+        fields = ["title", "dot\\.key", "none.dot"]
+        docs = self.db.find({"type": "complex_key"}, fields = fields)
+        assert len(docs) == 4
+        assert docs[1].has_key("dot.key")
+        assert docs[1]["dot.key"] == "dot's value"
+        assert docs[1].has_key("none")
+        assert docs[1]["none"]["dot"] == "none dot's value"
+
+    def test_peso_key(self):
+        fields = ["title", "$key", "deep.$key"]
+        docs = self.db.find({"type": "complex_key"}, fields = fields)
+        assert len(docs) == 4
+        assert docs[2].has_key("$key")
+        assert docs[2]["$key"] == "peso"
+        assert docs[2].has_key("deep")
+        assert docs[2]["deep"]["$key"] == "deep peso"
+
+    def test_unicode_key(self):
+        docs = self.db.find({"type": "complex_key"}, fields = ["title", ""])
+        assert len(docs) == 4
+        # note:  == \uf8ff
+        assert docs[3].has_key(u'\uf8ff')
+        assert docs[3][u'\uf8ff'] == "apple"

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/08c33202/test/user_docs.py
----------------------------------------------------------------------
diff --git a/test/user_docs.py b/test/user_docs.py
index 465370a..972eafc 100644
--- a/test/user_docs.py
+++ b/test/user_docs.py
@@ -78,8 +78,7 @@ def add_view_indexes(db, kwargs):
         ["manager"],
         ["favorites"],
         ["favorites.3"],
-        ["twitter"],
-        ["type"]
+        ["twitter"]
     ]
     for idx in indexes:
         assert db.create_index(idx) is True
@@ -488,30 +487,5 @@ DOCS = [
             "Python",
             "Lisp"
         ]
-    },
-    {
-        "type": "complex_key",
-        "title": "normal key"
-    },
-    {
-        "type": "complex_key",
-        "title": "key with dot",
-        "dot.key": "dot's value",
-        "none": {
-            "dot": "none dot's value"
-        }
-    },
-    {
-        "type": "complex_key",
-        "title": "key with peso",
-        "$key": "peso",
-        "deep": {
-            "$key": "deep peso"
-        }
-    },
-    {
-        "type": "complex_key",
-        "title": "unicode key",
-        "": "apple"
     }
 ]


[39/50] [abbrv] couchdb-mango git commit: Fix queries that include {"$exists": false}

Posted by ro...@apache.org.
Fix queries that include {"$exists": false}

We were incorrectly stripping documents for which a field didn't exist.

BugzId: 33294


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/bb91429c
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/bb91429c
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/bb91429c

Branch: refs/heads/master
Commit: bb91429c143d79e7c9a053233852ec2600ea23a0
Parents: 8f3b355
Author: Paul J. Davis <pa...@gmail.com>
Authored: Fri Jan 9 16:08:58 2015 -0600
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Fri Jan 16 13:32:50 2015 -0600

----------------------------------------------------------------------
 src/mango_selector.erl   |  2 ++
 test/03-operator-test.py | 10 ++++++++++
 2 files changed, 12 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/bb91429c/src/mango_selector.erl
----------------------------------------------------------------------
diff --git a/src/mango_selector.erl b/src/mango_selector.erl
index 4da394d..56f2072 100644
--- a/src/mango_selector.erl
+++ b/src/mango_selector.erl
@@ -501,6 +501,8 @@ match({[{<<"$", _/binary>>=Op, _}]}, _, _) ->
 % bad_path in which case matching fails.
 match({[{Field, Cond}]}, Value, Cmp) ->
     case mango_doc:get_field(Value, Field) of
+        not_found when Cond == {[{<<"$exists">>, false}]} ->
+            true;
         not_found ->
             false;
         bad_path ->

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/bb91429c/test/03-operator-test.py
----------------------------------------------------------------------
diff --git a/test/03-operator-test.py b/test/03-operator-test.py
index 27418a6..d8a7c29 100644
--- a/test/03-operator-test.py
+++ b/test/03-operator-test.py
@@ -80,3 +80,13 @@ class OperatorTests(mango.UserDocsTests):
         assert len(docs) == 2
         assert docs[0]["user_id"] == 2
         assert docs[1]["user_id"] == 10
+
+    def test_exists_false(self):
+        docs = self.db.find({
+                "age": {"$gt": 0},
+                "twitter": {"$exists": False}
+            })
+        user_ids = [2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 14]
+        assert len(docs) == len(user_ids)
+        for doc in docs:
+            assert doc["user_id"] in user_ids


[17/50] [abbrv] couchdb-mango git commit: Merge pull request #9 from cloudant/38863-in-operator-arrays

Posted by ro...@apache.org.
Merge pull request #9 from cloudant/38863-in-operator-arrays

Add Array Support For $in Operator

Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/18c67185
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/18c67185
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/18c67185

Branch: refs/heads/master
Commit: 18c671855e65170af0fc17cda0e8114e4974dd63
Parents: 3bcd514 c09d8d4
Author: Tony Sun <to...@cloudant.com>
Authored: Wed Nov 5 10:46:31 2014 -0800
Committer: Tony Sun <to...@cloudant.com>
Committed: Wed Nov 5 10:46:31 2014 -0800

----------------------------------------------------------------------
 src/mango_selector.erl   |  7 +++++++
 test/03-operator-test.py | 12 ++++++++++++
 2 files changed, 19 insertions(+)
----------------------------------------------------------------------



[47/50] [abbrv] couchdb-mango git commit: Fix text sort desc

Posted by ro...@apache.org.
Fix text sort desc

Call append_sort_type on field name before appending "-" when the sort
is descending.

BugzId: 43531


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/e57dcc5e
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/e57dcc5e
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/e57dcc5e

Branch: refs/heads/master
Commit: e57dcc5e1de9f1e52cc1ab5693419a4fa6721eb0
Parents: 801857d
Author: Tony Sun <to...@cloudant.com>
Authored: Mon Jan 19 13:14:26 2015 -0800
Committer: Tony Sun <to...@cloudant.com>
Committed: Wed Jan 21 11:59:12 2015 -0800

----------------------------------------------------------------------
 src/mango_cursor_text.erl | 16 +++++++++++-----
 test/09-text-sort-test.py | 11 +++++++++++
 2 files changed, 22 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/e57dcc5e/src/mango_cursor_text.erl
----------------------------------------------------------------------
diff --git a/src/mango_cursor_text.erl b/src/mango_cursor_text.erl
index 7c1b992..c774c82 100644
--- a/src/mango_cursor_text.erl
+++ b/src/mango_cursor_text.erl
@@ -204,12 +204,18 @@ apply_user_fun(CAcc, Doc) ->
 sort_query(Opts, Selector) ->
     {sort, {Sort}} = lists:keyfind(sort, 1, Opts),
     SortList = lists:map(fun(SortField) ->
-        RawSortField = case SortField of
-            {Field, <<"asc">>} -> Field;
-            {Field, <<"desc">>} -> <<"-", Field/binary>>;
-            Field when is_binary(Field) -> Field
+        {Dir, RawSortField}  = case SortField of
+            {Field, <<"asc">>} -> {asc, Field};
+            {Field, <<"desc">>} -> {desc, Field};
+            Field when is_binary(Field) -> {asc, Field}
         end,
-        mango_selector_text:append_sort_type(RawSortField, Selector)
+        SField = mango_selector_text:append_sort_type(RawSortField, Selector),
+        case Dir of
+            asc ->
+                SField;
+            desc ->
+                <<"-", SField/binary>>
+        end
     end, Sort),
     case SortList of
         [] -> relevance;

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/e57dcc5e/test/09-text-sort-test.py
----------------------------------------------------------------------
diff --git a/test/09-text-sort-test.py b/test/09-text-sort-test.py
index d2bd454..b77ca9a 100644
--- a/test/09-text-sort-test.py
+++ b/test/09-text-sort-test.py
@@ -21,6 +21,17 @@ class SortTests(mango.UserDocsTextTests):
         assert len(docs) == 15
         assert 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
+
+        q = {"manager": True}
+        docs = self.db.find(q, sort=[{"age:number": "desc"}])
+        assert len(docs) == 11
+        assert docs[0]["age"] == 79
+
     def test_string_sort(self):
         q = {"email": {"$gt": None}}
         docs = self.db.find(q, sort=["email:string"])


[30/50] [abbrv] couchdb-mango git commit: change description

Posted by ro...@apache.org.
change description

this will also be used in CouchDB after the ip-clearance


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/11343b2a
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/11343b2a
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/11343b2a

Branch: refs/heads/master
Commit: 11343b2a64cd85e4a4b49bc7de5cb7e346016885
Parents: 195d541
Author: Robert Kowalski <ro...@apache.org>
Authored: Fri Jan 9 14:34:39 2015 +0100
Committer: Robert Kowalski <ro...@apache.org>
Committed: Fri Jan 9 14:35:03 2015 +0100

----------------------------------------------------------------------
 src/mango.app.src | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/11343b2a/src/mango.app.src
----------------------------------------------------------------------
diff --git a/src/mango.app.src b/src/mango.app.src
index 67e7234..52a4fb2 100644
--- a/src/mango.app.src
+++ b/src/mango.app.src
@@ -1,5 +1,5 @@
 {application, mango, [
-    {description, "MongoDB API compatibility layer for Cloudant"},
+    {description, "MongoDB API compatibility layer for CouchDB"},
     {vsn, git},
     {registered, []},
     {applications, [