You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ga...@apache.org on 2020/01/28 06:18:58 UTC

[couchdb] branch fdb-mango-indexes updated (42b79f4 -> 020de0c)

This is an automated email from the ASF dual-hosted git repository.

garren pushed a change to branch fdb-mango-indexes
in repository https://gitbox.apache.org/repos/asf/couchdb.git.


 discard 42b79f4  temp commit
 discard 73e88ef  very rough indexing and return docs
 discard 1223bb9  mango crud index definitions
 discard e88fc7f  change mango test auth to match elixir
 discard 332c983  add crude mango hook and indexer setup
     add 963f84b  Add `external`  tag to opentrace events
     add 6b1da76  Merge pull request #2451 from cloudant/tracing-external
     add ecaf215  Change map indexes to be stored in one row
     new a03c704  add crude mango hook and indexer setup
     new 8446f15  change mango test auth to match elixir
     new 703a9c0  mango crud index definitions
     new c1c9cba  very rough indexing and return docs
     new 020de0c  initial creation of fdb startkey/endkey

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (42b79f4)
            \
             N -- N -- N   refs/heads/fdb-mango-indexes (020de0c)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/chttpd/src/chttpd.erl                  |  12 +--
 src/couch_views/include/couch_views.hrl    |   3 -
 src/couch_views/src/couch_views_fdb.erl    | 126 ++++-------------------------
 src/couch_views/src/couch_views_reader.erl |   2 +-
 src/ctrace/README.md                       |   3 +
 src/mango/src/mango_cursor_view.erl        |  13 +--
 src/mango/src/mango_fdb.erl                |  34 +++++---
 src/mango/src/mango_idx_view.erl           |   3 +-
 src/mango/src/mango_idx_view.hrl           |   3 +-
 src/mango/test/mango.py                    |   1 -
 10 files changed, 60 insertions(+), 140 deletions(-)


[couchdb] 03/05: mango crud index definitions

Posted by ga...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

garren pushed a commit to branch fdb-mango-indexes
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 703a9c0b5ec33f7d7e238411b18d44bed958a4c4
Author: Garren Smith <ga...@gmail.com>
AuthorDate: Tue Jan 21 13:45:21 2020 +0200

    mango crud index definitions
---
 src/mango/src/mango_crud.erl         |  6 +++--
 src/mango/src/mango_httpd.erl        |  8 +------
 src/mango/src/mango_idx.erl          | 44 +++++++++++++++++++++++++++++++-----
 src/mango/src/mango_util.erl         | 20 ++++++++--------
 src/mango/test/01-index-crud-test.py | 15 ++++++++++++
 5 files changed, 69 insertions(+), 24 deletions(-)

diff --git a/src/mango/src/mango_crud.erl b/src/mango/src/mango_crud.erl
index 41a4d14..735531d 100644
--- a/src/mango/src/mango_crud.erl
+++ b/src/mango/src/mango_crud.erl
@@ -35,8 +35,9 @@ insert(Db, {_}=Doc, Opts) ->
     insert(Db, [Doc], Opts);
 insert(Db, Docs, Opts0) when is_list(Docs) ->
     Opts1 = maybe_add_user_ctx(Db, Opts0),
+    % Todo: I dont think we need to support w = 3?
     Opts2 = maybe_int_to_str(w, Opts1),
-    case fabric:update_docs(Db, Docs, Opts2) of
+    case fabric2_db:update_docs(Db, Docs, Opts2) of
         {ok, Results0} ->
             {ok, lists:zipwith(fun result_to_json/2, Docs, Results0)};
         {accepted, Results0} ->
@@ -111,7 +112,8 @@ maybe_add_user_ctx(Db, Opts) ->
         {user_ctx, _} ->
             Opts;
         false ->
-            [{user_ctx, couch_db:get_user_ctx(Db)} | Opts]
+            UserCtx = maps:get(user_ctx, Db),
+            [{user_ctx, UserCtx} | Opts]
     end.
 
 
diff --git a/src/mango/src/mango_httpd.erl b/src/mango/src/mango_httpd.erl
index 379d2e1..b046229 100644
--- a/src/mango/src/mango_httpd.erl
+++ b/src/mango/src/mango_httpd.erl
@@ -32,9 +32,8 @@
     threshold = 1490
 }).
 
-handle_req(#httpd{} = Req, Db0) ->
+handle_req(#httpd{} = Req, Db) ->
     try
-        Db = set_user_ctx(Req, Db0),
         handle_req_int(Req, Db)
     catch
         throw:{mango_error, Module, Reason} ->
@@ -198,11 +197,6 @@ handle_find_req(Req, _Db) ->
     chttpd:send_method_not_allowed(Req, "POST").
 
 
-set_user_ctx(#httpd{user_ctx=Ctx}, Db) ->
-    {ok, NewDb} = couch_db:set_user_ctx(Db, Ctx),
-    NewDb.
-
-
 get_idx_w_opts(Opts) ->
     case lists:keyfind(w, 1, Opts) of
         {w, N} when is_integer(N), N > 0 ->
diff --git a/src/mango/src/mango_idx.erl b/src/mango/src/mango_idx.erl
index 5d06a8f..7997057 100644
--- a/src/mango/src/mango_idx.erl
+++ b/src/mango/src/mango_idx.erl
@@ -52,10 +52,40 @@
 -include("mango.hrl").
 -include("mango_idx.hrl").
 
-
 list(Db) ->
-    {ok, Indexes} = ddoc_cache:open(db_to_name(Db), ?MODULE),
-    Indexes.
+    Acc0 = #{
+        db => Db,
+        rows => []
+    },
+    {ok, Indexes} = fabric2_db:fold_design_docs(Db, fun ddoc_fold_cb/2, Acc0, []),
+    io:format("INDEXES ~p ~n", [Indexes]),
+    Indexes ++ special(Db).
+
+
+% Todo this should all be in fabric2_db
+ddoc_fold_cb({meta, _}, Acc) ->
+    {ok, Acc};
+
+ddoc_fold_cb(complete, Acc) ->
+    #{rows := Rows} = Acc,
+    {ok, Rows};
+
+ddoc_fold_cb({row, Row}, Acc) ->
+    #{
+        db := Db,
+        rows := Rows
+    } = Acc,
+    {_, Id} = lists:keyfind(id, 1, Row),
+    {ok, Doc} = fabric2_db:open_doc(Db, Id),
+    JSONDoc = couch_doc:to_json_obj(Doc, []),
+    try
+        Idx = from_ddoc(Db, JSONDoc),
+        {ok, Acc#{rows:= Rows ++ Idx}}
+    catch
+       throw:{mango_error, _, invalid_query_ddoc_language} ->
+           io:format("ERROR ~p ~n", [JSONDoc]),
+           {ok, Acc}
+    end.
 
 
 get_usable_indexes(Db, Selector, Opts) ->
@@ -294,7 +324,7 @@ db_to_name(Name) when is_binary(Name) ->
 db_to_name(Name) when is_list(Name) ->
     iolist_to_binary(Name);
 db_to_name(Db) ->
-    couch_db:name(Db).
+    maps:get(name, Db).
 
 
 get_idx_def(Opts) ->
@@ -407,8 +437,10 @@ set_ddoc_partitioned_option(DDoc, Partitioned) ->
     DDoc#doc{body = {NewProps}}.
 
 
-get_idx_partitioned(Db, DDocProps) ->
-    Default = fabric_util:is_partitioned(Db),
+get_idx_partitioned(_Db, DDocProps) ->
+    % TODO: Add in partition support
+%%    Default = fabric_util:is_partitioned(Db),
+    Default = false,
     case couch_util:get_value(<<"options">>, DDocProps) of
         {DesignOpts} ->
             case couch_util:get_value(<<"partitioned">>, DesignOpts) of
diff --git a/src/mango/src/mango_util.erl b/src/mango/src/mango_util.erl
index a734717..50fa79a 100644
--- a/src/mango/src/mango_util.erl
+++ b/src/mango/src/mango_util.erl
@@ -85,14 +85,16 @@ open_doc(Db, DocId) ->
 
 
 open_doc(Db, DocId, Options) ->
-    case mango_util:defer(fabric, open_doc, [Db, DocId, Options]) of
-        {ok, Doc} ->
-            {ok, Doc};
-        {not_found, _} ->
-            not_found;
-        _ ->
-            ?MANGO_ERROR({error_loading_doc, DocId})
-    end.
+    fabric2_db:open_doc(Db, DocId, Options).
+    % TODO: is this defer still required?
+%%    case mango_util:defer(fabric, open_doc, [Db, DocId, Options]) of
+%%        {ok, Doc} ->
+%%            {ok, Doc};
+%%        {not_found, _} ->
+%%            not_found;
+%%        _ ->
+%%            ?MANGO_ERROR({error_loading_doc, DocId})
+%%    end.
 
 
 open_ddocs(Db) ->
@@ -111,7 +113,7 @@ load_ddoc(Db, DDocId, DbOpts) ->
     case open_doc(Db, DDocId, DbOpts) of
         {ok, Doc} ->
             {ok, check_lang(Doc)};
-        not_found ->
+        {not_found, missing} ->
             Body = {[
                 {<<"language">>, <<"query">>}
             ]},
diff --git a/src/mango/test/01-index-crud-test.py b/src/mango/test/01-index-crud-test.py
index b602399..dd9ab1a 100644
--- a/src/mango/test/01-index-crud-test.py
+++ b/src/mango/test/01-index-crud-test.py
@@ -113,6 +113,21 @@ class IndexCrudTests(mango.DbPerClass):
             return
         raise AssertionError("index not created")
 
+    def test_ignore_design_docs(self):
+        fields = ["baz", "foo"]
+        ret = self.db.create_index(fields, name="idx_02")
+        assert ret is True
+        self.db.save_doc({
+            "_id": "_design/ignore",
+            "views": {
+                "view1": {
+                    "map": "function (doc) { emit(doc._id, 1)}"
+                }
+            }
+        })
+        Indexes = self.db.list_indexes()
+        self.assertEqual(len(Indexes), 2)
+
     def test_read_idx_doc(self):
         self.db.create_index(["foo", "bar"], name="idx_01")
         self.db.create_index(["hello", "bar"])


[couchdb] 04/05: very rough indexing and return docs

Posted by ga...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

garren pushed a commit to branch fdb-mango-indexes
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit c1c9cba4b15c2be0010c8bb06782313ea224965d
Author: Garren Smith <ga...@gmail.com>
AuthorDate: Thu Jan 23 17:05:26 2020 +0200

    very rough indexing and return docs
---
 src/fabric/include/fabric2.hrl               |   1 +
 src/mango/src/mango.hrl                      |   3 +
 src/mango/src/mango_cursor_view.erl          | 205 ++++----
 src/mango/src/mango_fdb.erl                  |  78 ++-
 src/mango/src/mango_idx.erl                  |  27 +-
 src/mango/src/mango_indexer.erl              |  89 +++-
 src/mango/test/01-index-crud-test.py         | 710 +++++++++++++--------------
 src/mango/test/02-basic-find-test.py         | 565 ++++++++++-----------
 src/mango/test/exunit/mango_indexer_test.exs | 141 ++++--
 src/mango/test/exunit/test_helper.exs        |   2 +-
 src/mango/test/mango.py                      |   2 +
 src/mango/test/user_docs.py                  |  38 +-
 12 files changed, 1006 insertions(+), 855 deletions(-)

diff --git a/src/fabric/include/fabric2.hrl b/src/fabric/include/fabric2.hrl
index b4dd084..453fc90 100644
--- a/src/fabric/include/fabric2.hrl
+++ b/src/fabric/include/fabric2.hrl
@@ -38,6 +38,7 @@
 -define(DB_VIEWS, 24).
 -define(DB_LOCAL_DOC_BODIES, 25).
 -define(DB_ATT_NAMES, 26).
+-define(DB_MANGO, 27).
 
 
 % Versions
diff --git a/src/mango/src/mango.hrl b/src/mango/src/mango.hrl
index 26a9d43..d3445a8 100644
--- a/src/mango/src/mango.hrl
+++ b/src/mango/src/mango.hrl
@@ -11,3 +11,6 @@
 % the License.
 
 -define(MANGO_ERROR(R), throw({mango_error, ?MODULE, R})).
+
+-define(MANGO_IDX_BUILD_STATUS, 0).
+-define(MANGO_IDX_RANGE, 1).
diff --git a/src/mango/src/mango_cursor_view.erl b/src/mango/src/mango_cursor_view.erl
index 1c4b342..7b47a40 100644
--- a/src/mango/src/mango_cursor_view.erl
+++ b/src/mango/src/mango_cursor_view.erl
@@ -136,7 +136,7 @@ execute(#cursor{db = Db, index = Idx, execution_stats = Stats} = Cursor0, UserFu
                     % Normal view
                     DDoc = ddocid(Idx),
                     Name = mango_idx:name(Idx),
-                    fabric:query_view(Db, DbOpts, DDoc, Name, CB, Cursor, Args)
+                    mango_fdb:query(Db, CB, Cursor, Args)
             end,
             case Result of
                 {ok, LastCursor} ->
@@ -217,65 +217,65 @@ choose_best_index(_DbName, IndexRanges) ->
     {SelectedIndex, SelectedIndexRanges}.
 
 
-view_cb({meta, Meta}, Acc) ->
-    % Map function starting
-    put(mango_docs_examined, 0),
-    set_mango_msg_timestamp(),
-    ok = rexi:stream2({meta, Meta}),
-    {ok, Acc};
-view_cb({row, Row}, #mrargs{extra = Options} = Acc) ->
-    ViewRow =  #view_row{
-        id = couch_util:get_value(id, Row),
-        key = couch_util:get_value(key, Row),
-        doc = couch_util:get_value(doc, Row)
-    },
-    case ViewRow#view_row.doc of
-        null ->
-            put(mango_docs_examined, get(mango_docs_examined) + 1),
-            maybe_send_mango_ping();
-        undefined ->
-            ViewRow2 = ViewRow#view_row{
-                value = couch_util:get_value(value, Row)
-            },
-            ok = rexi:stream2(ViewRow2),
-            put(mango_docs_examined, 0),
-            set_mango_msg_timestamp();
-        Doc ->
-            Selector = couch_util:get_value(selector, Options),
-            case mango_selector:match(Selector, Doc) of
-                true ->
-                    ViewRow2 = ViewRow#view_row{
-                        value = get(mango_docs_examined) + 1
-                    },
-                    ok = rexi:stream2(ViewRow2),
-                    put(mango_docs_examined, 0),
-                    set_mango_msg_timestamp();
-                false ->
-                    put(mango_docs_examined, get(mango_docs_examined) + 1),
-                    maybe_send_mango_ping()
-            end
-        end,
-    {ok, Acc};
-view_cb(complete, Acc) ->
-    % Finish view output
-    ok = rexi:stream_last(complete),
-    {ok, Acc};
-view_cb(ok, ddoc_updated) ->
-    rexi:reply({ok, ddoc_updated}).
-
-
-maybe_send_mango_ping() ->
-    Current = os:timestamp(),
-    LastPing = get(mango_last_msg_timestamp),
-    % Fabric will timeout if it has not heard a response from a worker node
-    % after 5 seconds. Send a ping every 4 seconds so the timeout doesn't happen.
-    case timer:now_diff(Current, LastPing) > ?HEARTBEAT_INTERVAL_IN_USEC of
-        false ->
-            ok;
-        true ->
-            rexi:ping(),
-            set_mango_msg_timestamp()
-    end.
+%%view_cb({meta, Meta}, Acc) ->
+%%    % Map function starting
+%%    put(mango_docs_examined, 0),
+%%    set_mango_msg_timestamp(),
+%%    ok = rexi:stream2({meta, Meta}),
+%%    {ok, Acc};
+%%view_cb({row, Row}, #mrargs{extra = Options} = Acc) ->
+%%    ViewRow =  #view_row{
+%%        id = couch_util:get_value(id, Row),
+%%        key = couch_util:get_value(key, Row),
+%%        doc = couch_util:get_value(doc, Row)
+%%    },
+%%    case ViewRow#view_row.doc of
+%%        null ->
+%%            put(mango_docs_examined, get(mango_docs_examined) + 1),
+%%            maybe_send_mango_ping();
+%%        undefined ->
+%%            ViewRow2 = ViewRow#view_row{
+%%                value = couch_util:get_value(value, Row)
+%%            },
+%%            ok = rexi:stream2(ViewRow2),
+%%            put(mango_docs_examined, 0),
+%%            set_mango_msg_timestamp();
+%%        Doc ->
+%%            Selector = couch_util:get_value(selector, Options),
+%%            case mango_selector:match(Selector, Doc) of
+%%                true ->
+%%                    ViewRow2 = ViewRow#view_row{
+%%                        value = get(mango_docs_examined) + 1
+%%                    },
+%%                    ok = rexi:stream2(ViewRow2),
+%%                    put(mango_docs_examined, 0),
+%%                    set_mango_msg_timestamp();
+%%                false ->
+%%                    put(mango_docs_examined, get(mango_docs_examined) + 1),
+%%                    maybe_send_mango_ping()
+%%            end
+%%        end,
+%%    {ok, Acc};
+%%view_cb(complete, Acc) ->
+%%    % Finish view output
+%%    ok = rexi:stream_last(complete),
+%%    {ok, Acc};
+%%view_cb(ok, ddoc_updated) ->
+%%    rexi:reply({ok, ddoc_updated}).
+
+
+%%maybe_send_mango_ping() ->
+%%    Current = os:timestamp(),
+%%    LastPing = get(mango_last_msg_timestamp),
+%%    % Fabric will timeout if it has not heard a response from a worker node
+%%    % after 5 seconds. Send a ping every 4 seconds so the timeout doesn't happen.
+%%    case timer:now_diff(Current, LastPing) > ?HEARTBEAT_INTERVAL_IN_USEC of
+%%        false ->
+%%            ok;
+%%        true ->
+%%            rexi:ping(),
+%%            set_mango_msg_timestamp()
+%%    end.
 
 
 set_mango_msg_timestamp() ->
@@ -284,14 +284,16 @@ set_mango_msg_timestamp() ->
 
 handle_message({meta, _}, Cursor) ->
     {ok, Cursor};
-handle_message({row, Props}, Cursor) ->
-    case doc_member(Cursor, Props) of
-        {ok, Doc, {execution_stats, ExecutionStats1}} ->
+handle_message(Doc, Cursor) ->
+    JSONDoc = couch_doc:to_json_obj(Doc, []),
+    case doc_member(Cursor, JSONDoc) of
+        {ok, JSONDoc, {execution_stats, ExecutionStats1}} ->
             Cursor1 = Cursor#cursor {
                 execution_stats = ExecutionStats1
             },
+            {Props} = JSONDoc,
             Cursor2 = update_bookmark_keys(Cursor1, Props),
-            FinalDoc = mango_fields:extract(Doc, Cursor2#cursor.fields),
+            FinalDoc = mango_fields:extract(JSONDoc, Cursor2#cursor.fields),
             handle_doc(Cursor2, FinalDoc);
         {no_match, _, {execution_stats, ExecutionStats1}} ->
             Cursor1 = Cursor#cursor {
@@ -409,47 +411,50 @@ apply_opts([{_, _} | Rest], Args) ->
     apply_opts(Rest, Args).
 
 
-doc_member(Cursor, RowProps) ->
-    Db = Cursor#cursor.db, 
-    Opts = Cursor#cursor.opts,
+doc_member(Cursor, DocProps) ->
+%%    Db = Cursor#cursor.db,
+%%    Opts = Cursor#cursor.opts,
     ExecutionStats = Cursor#cursor.execution_stats,
     Selector = Cursor#cursor.selector,
-    {Matched, Incr} = case couch_util:get_value(value, RowProps) of
-        N when is_integer(N) -> {true, N};
-        _ -> {false, 1}
-    end,
-    case couch_util:get_value(doc, RowProps) of
-        {DocProps} ->
-            ExecutionStats1 = mango_execution_stats:incr_docs_examined(ExecutionStats, Incr),
-            case Matched of
-                true ->
-                    {ok, {DocProps}, {execution_stats, ExecutionStats1}};
-                false ->
-                    match_doc(Selector, {DocProps}, ExecutionStats1)
-                end;
-        undefined ->
-            ExecutionStats1 = mango_execution_stats:incr_quorum_docs_examined(ExecutionStats),
-            Id = couch_util:get_value(id, RowProps),
-            case mango_util:defer(fabric, open_doc, [Db, Id, Opts]) of
-                {ok, #doc{}=DocProps} ->
-                    Doc = couch_doc:to_json_obj(DocProps, []),
-                    match_doc(Selector, Doc, ExecutionStats1);
-                Else ->
-                    Else
-            end;
-        null ->
-            ExecutionStats1 = mango_execution_stats:incr_docs_examined(ExecutionStats),
-            {no_match, null, {execution_stats, ExecutionStats1}}
-    end.
+    ExecutionStats1 = mango_execution_stats:incr_docs_examined(ExecutionStats, 1),
+    match_doc(Selector, DocProps, ExecutionStats1).
+    %%    {Matched, Incr} = case couch_util:get_value(value, RowProps) of
+%%        N when is_integer(N) -> {true, N};
+%%        _ -> {false, 1}
+%%    end,
+%%    case couch_util:get_value(doc, RowProps) of
+%%        {DocProps} ->
+%%            ExecutionStats1 = mango_execution_stats:incr_docs_examined(ExecutionStats, Incr),
+%%            case Matched of
+%%                true ->
+%%                    {ok, {DocProps}, {execution_stats, ExecutionStats1}};
+%%                false ->
+%%                    match_doc(Selector, {DocProps}, ExecutionStats1)
+%%                end
+%%        undefined ->
+%%            ExecutionStats1 = mango_execution_stats:incr_quorum_docs_examined(ExecutionStats),
+%%            Id = couch_util:get_value(id, RowProps),
+%%            case mango_util:defer(fabric, open_doc, [Db, Id, Opts]) of
+%%                {ok, #doc{}=DocProps} ->
+%%                    Doc = couch_doc:to_json_obj(DocProps, []),
+%%                    match_doc(Selector, Doc, ExecutionStats1);
+%%                Else ->
+%%                    Else
+%%            end;
+%%        null ->
+%%            ExecutionStats1 = mango_execution_stats:incr_docs_examined(ExecutionStats),
+%%            {no_match, null, {execution_stats, ExecutionStats1}}
+%%    end.
 
 
 match_doc(Selector, Doc, ExecutionStats) ->
-    case mango_selector:match(Selector, Doc) of
-        true ->
-            {ok, Doc, {execution_stats, ExecutionStats}};
-        false ->
-            {no_match, Doc, {execution_stats, ExecutionStats}}
-    end.
+    {ok, Doc, {execution_stats, ExecutionStats}}.
+%%    case mango_selector:match(Selector, Doc) of
+%%        true ->
+%%            {ok, Doc, {execution_stats, ExecutionStats}};
+%%        false ->
+%%            {no_match, Doc, {execution_stats, ExecutionStats}}
+%%    end.
 
 
 is_design_doc(RowProps) ->
diff --git a/src/mango/src/mango_fdb.erl b/src/mango/src/mango_fdb.erl
index c29ae8f..36cb400 100644
--- a/src/mango/src/mango_fdb.erl
+++ b/src/mango/src/mango_fdb.erl
@@ -14,22 +14,80 @@
 -module(mango_fdb).
 
 
+-include_lib("fabric/include/fabric2.hrl").
+-include("mango.hrl").
+-include("mango_idx.hrl").
+-include("mango_cursor.hrl").
+
+
 -export([
-    write_doc/4
+    write_doc/3,
+    query/4
 ]).
 
 
-write_doc(Db, Doc, Indexes, Results) ->
-    lists:foreach(fun (Index) ->
-        MangoIdxPrefix = mango_idx_prefix(Db, Index),
-        ok
-        end, Indexes).
+query(Db, CallBack, Cursor, Args) ->
+    #cursor{
+        index = Idx
+    } = Cursor,
+    MangoIdxPrefix = mango_idx_prefix(Db, Idx#idx.ddoc),
+    fabric2_fdb:transactional(Db, fun (TxDb) ->
+        Acc0 = #{
+            cursor => Cursor,
+            prefix => MangoIdxPrefix,
+            db => TxDb,
+            callback => CallBack
+        },
+        io:format("DB ~p ~n", [TxDb]),
+        Acc1 = fabric2_fdb:fold_range(TxDb, MangoIdxPrefix, fun fold_cb/2, Acc0, []),
+        #{
+            cursor := Cursor1
+        } = Acc1,
+        {ok, Cursor1}
+    end).
 
 
-mango_idx_prefix(Db, Index) ->
+fold_cb({Key, _}, Acc) ->
+    #{
+        prefix := MangoIdxPrefix,
+        db := Db,
+        callback := Callback,
+        cursor := Cursor
+
+    } = Acc,
+    {_, DocId} = erlfdb_tuple:unpack(Key, MangoIdxPrefix),
+    {ok, Doc} = fabric2_db:open_doc(Db, DocId),
+    io:format("PRINT ~p ~p ~n", [DocId, Doc]),
+    {ok, Cursor1} = Callback(Doc, Cursor),
+    Acc#{
+        cursor := Cursor1
+    }.
+
+
+write_doc(TxDb, DocId, IdxResults) ->
+    lists:foreach(fun (IdxResult) ->
+        #{
+            ddoc_id := DDocId,
+            results := Results
+        } = IdxResult,
+        MangoIdxPrefix = mango_idx_prefix(TxDb, DDocId),
+        add_key(TxDb, MangoIdxPrefix, Results, DocId)
+        end, IdxResults).
+
+
+mango_idx_prefix(TxDb, Id) ->
     #{
         db_prefix := DbPrefix
-    } = Db,
-    io:format("INDEX ~p ~n", [Index]),
-    ok.
+    } = TxDb,
+    Key = {?DB_MANGO, Id, ?MANGO_IDX_RANGE},
+    erlfdb_tuple:pack(Key, DbPrefix).
+
+
+add_key(TxDb, MangoIdxPrefix, Results, DocId) ->
+    #{
+        tx := Tx
+    } = TxDb,
+    EncodedResults = couch_views_encoding:encode(Results, key),
+    Key = erlfdb_tuple:pack({EncodedResults, DocId}, MangoIdxPrefix),
+    erlfdb:set(Tx, Key, <<0>>).
 
diff --git a/src/mango/src/mango_idx.erl b/src/mango/src/mango_idx.erl
index 7997057..b9ce640 100644
--- a/src/mango/src/mango_idx.erl
+++ b/src/mango/src/mango_idx.erl
@@ -78,13 +78,13 @@ ddoc_fold_cb({row, Row}, Acc) ->
     {_, Id} = lists:keyfind(id, 1, Row),
     {ok, Doc} = fabric2_db:open_doc(Db, Id),
     JSONDoc = couch_doc:to_json_obj(Doc, []),
-    try
-        Idx = from_ddoc(Db, JSONDoc),
-        {ok, Acc#{rows:= Rows ++ Idx}}
-    catch
-       throw:{mango_error, _, invalid_query_ddoc_language} ->
-           io:format("ERROR ~p ~n", [JSONDoc]),
-           {ok, Acc}
+    {Props} = JSONDoc,
+    case proplists:get_value(<<"language">>, Props) of
+        <<"query">> ->
+            Idx = from_ddoc(Db, JSONDoc),
+            {ok, Acc#{rows:= Rows ++ Idx}};
+        _ ->
+            {ok, Acc}
     end.
 
 
@@ -212,12 +212,13 @@ from_ddoc(Db, {Props}) ->
         _ ->
             ?MANGO_ERROR(invalid_query_ddoc_language)
     end,
-    IdxMods = case clouseau_rpc:connected() of
-        true ->
-            [mango_idx_view, mango_idx_text];
-        false ->
-            [mango_idx_view]
-    end,
+    IdxMods = [mango_idx_view],
+%%    IdxMods = case clouseau_rpc:connected() of
+%%        true ->
+%%            [mango_idx_view, mango_idx_text];
+%%        false ->
+%%            [mango_idx_view]
+%%    end,
     Idxs = lists:flatmap(fun(Mod) -> Mod:from_ddoc({Props}) end, IdxMods),
     lists:map(fun(Idx) ->
         Idx#idx{
diff --git a/src/mango/src/mango_indexer.erl b/src/mango/src/mango_indexer.erl
index b217ce1..2040059 100644
--- a/src/mango/src/mango_indexer.erl
+++ b/src/mango/src/mango_indexer.erl
@@ -19,59 +19,96 @@
 ]).
 
 
-update(Db, deleted, _, OldDoc) ->
+-include_lib("couch/include/couch_db.hrl").
+-include("mango_idx.hrl").
+
+% Design doc
+% Todo: Check if design doc is mango index and kick off background worker
+% to build new index
+update(Db, Change, #doc{id = <<?DESIGN_DOC_PREFIX, _/binary>>} = Doc, OldDoc) ->
+    ok;
+
+update(Db, deleted, _, OldDoc)  ->
     ok;
+
 update(Db, updated, Doc, OldDoc) ->
     ok;
+
 update(Db, created, Doc, _) ->
-%%    Indexes = mango_idx:list(Db),
-%%    Fun = fun (DDoc, Acc) ->
-%%        io:format("DESIGN DOC ~p ~n", [DDoc]),
-%%        Acc
-%%    end,
-%%    fabric2_db:fold_design_docs(Db, Fun, [], []),
-%%    % maybe validate indexes here
-%%    JSONDoc = mango_json:to_binary(couch_doc:to_json_obj(Doc, [])),
-%%    io:format("Update ~p ~n, ~p ~n", [Doc, JSONDoc]),
-%%    Results = index_doc(Indexes, JSONDoc),
-    ok.
+    #doc{id = DocId} = Doc,
+    Indexes = mango_idx:list(Db),
+    Indexes1 = filter_and_to_json(Indexes),
+    io:format("UPDATE INDEXES ~p ~n filtered ~p ~n", [Indexes, Indexes1]),
+    JSONDoc = mango_json:to_binary(couch_doc:to_json_obj(Doc, [])),
+    io:format("DOC ~p ~n", [Doc]),
+    Results = index_doc(Indexes1, JSONDoc),
+    io:format("Update ~p ~n, ~p ~n Results ~p ~n", [Doc, JSONDoc, Results]),
+    mango_fdb:write_doc(Db, DocId, Results).
+
+
+filter_and_to_json(Indexes) ->
+    lists:filter(fun (Idx) ->
+        case Idx#idx.type == <<"special">> of
+            true -> false;
+            false -> true
+        end
+    end, Indexes).
 
 
 index_doc(Indexes, Doc) ->
-    lists:map(fun(Idx) -> get_index_entries(Idx, Doc) end, Indexes).
+    lists:foldl(fun(Idx, Acc) ->
+        io:format("II ~p ~n", [Idx]),
+        {IdxDef} = mango_idx:def(Idx),
+        Results = get_index_entries(IdxDef, Doc),
+        case lists:member(not_found, Results) of
+            true ->
+                Acc;
+            false ->
+                IdxResult = #{
+                    name => mango_idx:name(Idx),
+                    ddoc_id => mango_idx:ddoc(Idx),
+                    results => Results
+                },
+                [IdxResult | Acc]
+        end
+    end, [], Indexes).
 
 
-get_index_entries({IdxProps}, Doc) ->
-    {Fields} = couch_util:get_value(<<"fields">>, IdxProps),
-    Selector = get_index_partial_filter_selector(IdxProps),
+get_index_entries(IdxDef, Doc) ->
+    {Fields} = couch_util:get_value(<<"fields">>, IdxDef),
+    Selector = get_index_partial_filter_selector(IdxDef),
     case should_index(Selector, Doc) of
         false ->
-            [];
+            [not_found];
         true ->
             Values = get_index_values(Fields, Doc),
-            case lists:member(not_found, Values) of
-                true -> [];
-                false -> [[Values, null]]
-            end
+            Values
+%%            case lists:member(not_found, Values) of
+%%                true -> not_found;
+%%                false -> [Values]
+%%%%                false -> [[Values, null]]
+%%            end
     end.
 
 
 get_index_values(Fields, Doc) ->
-    lists:map(fun({Field, _Dir}) ->
+    Out1 = lists:map(fun({Field, _Dir}) ->
         case mango_doc:get_field(Doc, Field) of
             not_found -> not_found;
             bad_path -> not_found;
             Value -> Value
         end
-    end, Fields).
+    end, Fields),
+    io:format("OUT ~p ~p ~n", [Fields, Out1]),
+    Out1.
 
 
-get_index_partial_filter_selector(IdxProps) ->
-    case couch_util:get_value(<<"partial_filter_selector">>, IdxProps, {[]}) of
+get_index_partial_filter_selector(IdxDef) ->
+    case couch_util:get_value(<<"partial_filter_selector">>, IdxDef, {[]}) of
         {[]} ->
             % this is to support legacy text indexes that had the partial_filter_selector
             % set as selector
-            couch_util:get_value(<<"selector">>, IdxProps, {[]});
+            couch_util:get_value(<<"selector">>, IdxDef, {[]});
         Else ->
             Else
     end.
diff --git a/src/mango/test/01-index-crud-test.py b/src/mango/test/01-index-crud-test.py
index dd9ab1a..6e0208a 100644
--- a/src/mango/test/01-index-crud-test.py
+++ b/src/mango/test/01-index-crud-test.py
@@ -26,63 +26,63 @@ class IndexCrudTests(mango.DbPerClass):
     def setUp(self):
         self.db.recreate()
 
-    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 as e:
-                self.assertEqual(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 as e:
-                self.assertEqual(
-                    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 as e:
-                self.assertEqual(e.response.status_code, 400)
-            else:
-                raise AssertionError("bad create index")
-            try:
-                self.db.create_index(["foo"], ddoc=bn)
-            except Exception as e:
-                self.assertEqual(e.response.status_code, 400)
-            else:
-                raise AssertionError("bad create index")
+    # 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 as e:
+    #             self.assertEqual(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 as e:
+    #             self.assertEqual(
+    #                 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 as e:
+    #             self.assertEqual(e.response.status_code, 400)
+    #         else:
+    #             raise AssertionError("bad create index")
+    #         try:
+    #             self.db.create_index(["foo"], ddoc=bn)
+    #         except Exception as e:
+    #             self.assertEqual(e.response.status_code, 400)
+    #         else:
+    #             raise AssertionError("bad create index")
 
     def test_create_idx_01(self):
         fields = ["foo", "bar"]
@@ -95,301 +95,301 @@ class IndexCrudTests(mango.DbPerClass):
             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 True
-        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
-            self.assertEqual(idx["def"]["fields"], [{"baz": "asc"}, {"foo": "asc"}])
-            return
-        raise AssertionError("index not created")
-
-    def test_ignore_design_docs(self):
-        fields = ["baz", "foo"]
-        ret = self.db.create_index(fields, name="idx_02")
-        assert ret is True
-        self.db.save_doc({
-            "_id": "_design/ignore",
-            "views": {
-                "view1": {
-                    "map": "function (doc) { emit(doc._id, 1)}"
-                }
-            }
-        })
-        Indexes = self.db.list_indexes()
-        self.assertEqual(len(Indexes), 2)
-
-    def test_read_idx_doc(self):
-        self.db.create_index(["foo", "bar"], name="idx_01")
-        self.db.create_index(["hello", "bar"])
-        for idx in self.db.list_indexes():
-            if idx["type"] == "special":
-                continue
-            ddocid = idx["ddoc"]
-            doc = self.db.open_doc(ddocid)
-            self.assertEqual(doc["_id"], ddocid)
-            info = self.db.ddoc_info(ddocid)
-            self.assertEqual(info["name"], ddocid.split("_design/")[-1])
-
-    def test_delete_idx_escaped(self):
-        self.db.create_index(["foo", "bar"], name="idx_01")
-        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
-            self.assertEqual(idx["def"]["fields"], [{"bing": "asc"}])
-            self.db.delete_index(idx["ddoc"].replace("/", "%2F"), idx["name"])
-        post_indexes = self.db.list_indexes()
-        self.assertEqual(pre_indexes, post_indexes)
-
-    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
-            self.assertEqual(idx["def"]["fields"], [{"bing": "asc"}])
-            self.db.delete_index(idx["ddoc"], idx["name"])
-        post_indexes = self.db.list_indexes()
-        self.assertEqual(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
-            self.assertEqual(idx["def"]["fields"], [{"bing": "asc"}])
-            self.db.delete_index(idx["ddoc"].split("/")[-1], idx["name"])
-        post_indexes = self.db.list_indexes()
-        self.assertEqual(pre_indexes, post_indexes)
-
-    def test_bulk_delete(self):
-        fields = ["field1"]
-        ret = self.db.create_index(fields, name="idx_01")
-        assert ret is True
-
-        fields = ["field2"]
-        ret = self.db.create_index(fields, name="idx_02")
-        assert ret is True
-
-        fields = ["field3"]
-        ret = self.db.create_index(fields, name="idx_03")
-        assert ret is True
-
-        docids = []
-
-        for idx in self.db.list_indexes():
-            if idx["ddoc"] is not None:
-                docids.append(idx["ddoc"])
-
-        docids.append("_design/this_is_not_an_index_name")
-
-        ret = self.db.bulk_delete(docids)
-
-        self.assertEqual(ret["fail"][0]["id"], "_design/this_is_not_an_index_name")
-        self.assertEqual(len(ret["success"]), 3)
-
-        for idx in self.db.list_indexes():
-            assert idx["type"] != "json"
-            assert idx["type"] != "text"
-
-    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
-                self.assertEqual(idx["def"]["fields"], [{"bing": "asc"}])
-                self.db.delete_index(idx["ddoc"], idx["name"])
-                break
-            post_indexes = self.db.list_indexes()
-            self.assertEqual(pre_indexes, post_indexes)
-
-    def test_delete_missing(self):
-        # Missing design doc
-        try:
-            self.db.delete_index("this_is_not_a_design_doc_id", "foo")
-        except Exception as e:
-            self.assertEqual(e.response.status_code, 404)
-        else:
-            raise AssertionError("bad index delete")
-
-        # Missing view name
-        ret = self.db.create_index(["fields"], name="idx_01")
-        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:
-            self.db.delete_index(ddocid, "this_is_not_an_index_name")
-        except Exception as e:
-            self.assertEqual(e.response.status_code, 404)
-        else:
-            raise AssertionError("bad index delete")
-
-        # Bad view type
-        try:
-            self.db.delete_index(ddocid, idx["name"], idx_type="not_a_real_type")
-        except Exception as e:
-            self.assertEqual(e.response.status_code, 404)
-        else:
-            raise AssertionError("bad index delete")
-
-    def test_limit_skip_index(self):
-        fields = ["field1"]
-        ret = self.db.create_index(fields, name="idx_01")
-        assert ret is True
-
-        fields = ["field2"]
-        ret = self.db.create_index(fields, name="idx_02")
-        assert ret is True
-
-        fields = ["field3"]
-        ret = self.db.create_index(fields, name="idx_03")
-        assert ret is True
-
-        fields = ["field4"]
-        ret = self.db.create_index(fields, name="idx_04")
-        assert ret is True
-
-        fields = ["field5"]
-        ret = self.db.create_index(fields, name="idx_05")
-        assert ret is True
-
-        self.assertEqual(len(self.db.list_indexes(limit=2)), 2)
-        self.assertEqual(len(self.db.list_indexes(limit=5, skip=4)), 2)
-        self.assertEqual(len(self.db.list_indexes(skip=5)), 1)
-        self.assertEqual(len(self.db.list_indexes(skip=6)), 0)
-        self.assertEqual(len(self.db.list_indexes(skip=100)), 0)
-        self.assertEqual(len(self.db.list_indexes(limit=10000000)), 6)
-
-        try:
-            self.db.list_indexes(skip=-1)
-        except Exception as e:
-            self.assertEqual(e.response.status_code, 500)
-
-        try:
-            self.db.list_indexes(limit=0)
-        except Exception as e:
-            self.assertEqual(e.response.status_code, 500)
-
-    def test_out_of_sync(self):
-        self.db.save_docs(copy.deepcopy(DOCS))
-        self.db.create_index(["age"], name="age")
-
-        selector = {"age": {"$gt": 0}}
-        docs = self.db.find(
-            selector, use_index="_design/a017b603a47036005de93034ff689bbbb6a873c4"
-        )
-        self.assertEqual(len(docs), 2)
-
-        self.db.delete_doc("1")
-
-        docs1 = self.db.find(
-            selector,
-            update="False",
-            use_index="_design/a017b603a47036005de93034ff689bbbb6a873c4",
-        )
-        self.assertEqual(len(docs1), 1)
-
-
-@unittest.skipUnless(mango.has_text_service(), "requires text service")
-class IndexCrudTextTests(mango.DbPerClass):
-    def setUp(self):
-        self.db.recreate()
-
-    def test_create_text_idx(self):
-        fields = [
-            {"name": "stringidx", "type": "string"},
-            {"name": "booleanidx", "type": "boolean"},
-        ]
-        ret = self.db.create_text_index(fields=fields, name="text_idx_01")
-        assert ret is True
-        for idx in self.db.list_indexes():
-            if idx["name"] != "text_idx_01":
-                continue
-            self.assertEqual(
-                idx["def"]["fields"],
-                [{"stringidx": "string"}, {"booleanidx": "boolean"}],
-            )
-            return
-        raise AssertionError("index not created")
-
-    def test_create_bad_text_idx(self):
-        bad_fields = [
-            True,
-            False,
-            "bing",
-            2.0,
-            ["foo", "bar"],
-            [{"name": "foo2"}],
-            [{"name": "foo3", "type": "garbage"}],
-            [{"type": "number"}],
-            [{"name": "age", "type": "number"}, {"name": "bad"}],
-            [{"name": "age", "type": "number"}, "bla"],
-            [{"name": "", "type": "number"}, "bla"],
-        ]
-        for fields in bad_fields:
-            try:
-                self.db.create_text_index(fields=fields)
-            except Exception as e:
-                self.assertEqual(e.response.status_code, 400)
-            else:
-                raise AssertionError("bad create text index")
-
-    def test_limit_skip_index(self):
-        fields = ["field1"]
-        ret = self.db.create_index(fields, name="idx_01")
-        assert ret is True
-
-        fields = ["field2"]
-        ret = self.db.create_index(fields, name="idx_02")
-        assert ret is True
-
-        fields = ["field3"]
-        ret = self.db.create_index(fields, name="idx_03")
-        assert ret is True
-
-        fields = ["field4"]
-        ret = self.db.create_index(fields, name="idx_04")
-        assert ret is True
-
-        fields = [
-            {"name": "stringidx", "type": "string"},
-            {"name": "booleanidx", "type": "boolean"},
-        ]
-        ret = self.db.create_text_index(fields=fields, name="idx_05")
-        assert ret is True
-
-        self.assertEqual(len(self.db.list_indexes(limit=2)), 2)
-        self.assertEqual(len(self.db.list_indexes(limit=5, skip=4)), 2)
-        self.assertEqual(len(self.db.list_indexes(skip=5)), 1)
-        self.assertEqual(len(self.db.list_indexes(skip=6)), 0)
-        self.assertEqual(len(self.db.list_indexes(skip=100)), 0)
-        self.assertEqual(len(self.db.list_indexes(limit=10000000)), 6)
-
-        try:
-            self.db.list_indexes(skip=-1)
-        except Exception as e:
-            self.assertEqual(e.response.status_code, 500)
-
-        try:
-            self.db.list_indexes(limit=0)
-        except Exception as e:
-            self.assertEqual(e.response.status_code, 500)
+#     def test_create_idx_01_exists(self):
+#         fields = ["foo", "bar"]
+#         ret = self.db.create_index(fields, name="idx_01")
+#         assert ret is True
+#         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
+#             self.assertEqual(idx["def"]["fields"], [{"baz": "asc"}, {"foo": "asc"}])
+#             return
+#         raise AssertionError("index not created")
+#
+#     def test_ignore_design_docs(self):
+#         fields = ["baz", "foo"]
+#         ret = self.db.create_index(fields, name="idx_02")
+#         assert ret is True
+#         self.db.save_doc({
+#             "_id": "_design/ignore",
+#             "views": {
+#                 "view1": {
+#                     "map": "function (doc) { emit(doc._id, 1)}"
+#                 }
+#             }
+#         })
+#         Indexes = self.db.list_indexes()
+#         self.assertEqual(len(Indexes), 2)
+#
+#     def test_read_idx_doc(self):
+#         self.db.create_index(["foo", "bar"], name="idx_01")
+#         self.db.create_index(["hello", "bar"])
+#         for idx in self.db.list_indexes():
+#             if idx["type"] == "special":
+#                 continue
+#             ddocid = idx["ddoc"]
+#             doc = self.db.open_doc(ddocid)
+#             self.assertEqual(doc["_id"], ddocid)
+#             info = self.db.ddoc_info(ddocid)
+#             self.assertEqual(info["name"], ddocid.split("_design/")[-1])
+#
+#     def test_delete_idx_escaped(self):
+#         self.db.create_index(["foo", "bar"], name="idx_01")
+#         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
+#             self.assertEqual(idx["def"]["fields"], [{"bing": "asc"}])
+#             self.db.delete_index(idx["ddoc"].replace("/", "%2F"), idx["name"])
+#         post_indexes = self.db.list_indexes()
+#         self.assertEqual(pre_indexes, post_indexes)
+#
+#     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
+#             self.assertEqual(idx["def"]["fields"], [{"bing": "asc"}])
+#             self.db.delete_index(idx["ddoc"], idx["name"])
+#         post_indexes = self.db.list_indexes()
+#         self.assertEqual(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
+#             self.assertEqual(idx["def"]["fields"], [{"bing": "asc"}])
+#             self.db.delete_index(idx["ddoc"].split("/")[-1], idx["name"])
+#         post_indexes = self.db.list_indexes()
+#         self.assertEqual(pre_indexes, post_indexes)
+#
+#     def test_bulk_delete(self):
+#         fields = ["field1"]
+#         ret = self.db.create_index(fields, name="idx_01")
+#         assert ret is True
+#
+#         fields = ["field2"]
+#         ret = self.db.create_index(fields, name="idx_02")
+#         assert ret is True
+#
+#         fields = ["field3"]
+#         ret = self.db.create_index(fields, name="idx_03")
+#         assert ret is True
+#
+#         docids = []
+#
+#         for idx in self.db.list_indexes():
+#             if idx["ddoc"] is not None:
+#                 docids.append(idx["ddoc"])
+#
+#         docids.append("_design/this_is_not_an_index_name")
+#
+#         ret = self.db.bulk_delete(docids)
+#
+#         self.assertEqual(ret["fail"][0]["id"], "_design/this_is_not_an_index_name")
+#         self.assertEqual(len(ret["success"]), 3)
+#
+#         for idx in self.db.list_indexes():
+#             assert idx["type"] != "json"
+#             assert idx["type"] != "text"
+#
+#     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
+#                 self.assertEqual(idx["def"]["fields"], [{"bing": "asc"}])
+#                 self.db.delete_index(idx["ddoc"], idx["name"])
+#                 break
+#             post_indexes = self.db.list_indexes()
+#             self.assertEqual(pre_indexes, post_indexes)
+#
+#     def test_delete_missing(self):
+#         # Missing design doc
+#         try:
+#             self.db.delete_index("this_is_not_a_design_doc_id", "foo")
+#         except Exception as e:
+#             self.assertEqual(e.response.status_code, 404)
+#         else:
+#             raise AssertionError("bad index delete")
+#
+#         # Missing view name
+#         ret = self.db.create_index(["fields"], name="idx_01")
+#         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:
+#             self.db.delete_index(ddocid, "this_is_not_an_index_name")
+#         except Exception as e:
+#             self.assertEqual(e.response.status_code, 404)
+#         else:
+#             raise AssertionError("bad index delete")
+#
+#         # Bad view type
+#         try:
+#             self.db.delete_index(ddocid, idx["name"], idx_type="not_a_real_type")
+#         except Exception as e:
+#             self.assertEqual(e.response.status_code, 404)
+#         else:
+#             raise AssertionError("bad index delete")
+#
+#     def test_limit_skip_index(self):
+#         fields = ["field1"]
+#         ret = self.db.create_index(fields, name="idx_01")
+#         assert ret is True
+#
+#         fields = ["field2"]
+#         ret = self.db.create_index(fields, name="idx_02")
+#         assert ret is True
+#
+#         fields = ["field3"]
+#         ret = self.db.create_index(fields, name="idx_03")
+#         assert ret is True
+#
+#         fields = ["field4"]
+#         ret = self.db.create_index(fields, name="idx_04")
+#         assert ret is True
+#
+#         fields = ["field5"]
+#         ret = self.db.create_index(fields, name="idx_05")
+#         assert ret is True
+#
+#         self.assertEqual(len(self.db.list_indexes(limit=2)), 2)
+#         self.assertEqual(len(self.db.list_indexes(limit=5, skip=4)), 2)
+#         self.assertEqual(len(self.db.list_indexes(skip=5)), 1)
+#         self.assertEqual(len(self.db.list_indexes(skip=6)), 0)
+#         self.assertEqual(len(self.db.list_indexes(skip=100)), 0)
+#         self.assertEqual(len(self.db.list_indexes(limit=10000000)), 6)
+#
+#         try:
+#             self.db.list_indexes(skip=-1)
+#         except Exception as e:
+#             self.assertEqual(e.response.status_code, 500)
+#
+#         try:
+#             self.db.list_indexes(limit=0)
+#         except Exception as e:
+#             self.assertEqual(e.response.status_code, 500)
+#
+#     def test_out_of_sync(self):
+#         self.db.save_docs(copy.deepcopy(DOCS))
+#         self.db.create_index(["age"], name="age")
+#
+#         selector = {"age": {"$gt": 0}}
+#         docs = self.db.find(
+#             selector, use_index="_design/a017b603a47036005de93034ff689bbbb6a873c4"
+#         )
+#         self.assertEqual(len(docs), 2)
+#
+#         self.db.delete_doc("1")
+#
+#         docs1 = self.db.find(
+#             selector,
+#             update="False",
+#             use_index="_design/a017b603a47036005de93034ff689bbbb6a873c4",
+#         )
+#         self.assertEqual(len(docs1), 1)
+#
+#
+# @unittest.skipUnless(mango.has_text_service(), "requires text service")
+# class IndexCrudTextTests(mango.DbPerClass):
+#     def setUp(self):
+#         self.db.recreate()
+#
+#     def test_create_text_idx(self):
+#         fields = [
+#             {"name": "stringidx", "type": "string"},
+#             {"name": "booleanidx", "type": "boolean"},
+#         ]
+#         ret = self.db.create_text_index(fields=fields, name="text_idx_01")
+#         assert ret is True
+#         for idx in self.db.list_indexes():
+#             if idx["name"] != "text_idx_01":
+#                 continue
+#             self.assertEqual(
+#                 idx["def"]["fields"],
+#                 [{"stringidx": "string"}, {"booleanidx": "boolean"}],
+#             )
+#             return
+#         raise AssertionError("index not created")
+#
+#     def test_create_bad_text_idx(self):
+#         bad_fields = [
+#             True,
+#             False,
+#             "bing",
+#             2.0,
+#             ["foo", "bar"],
+#             [{"name": "foo2"}],
+#             [{"name": "foo3", "type": "garbage"}],
+#             [{"type": "number"}],
+#             [{"name": "age", "type": "number"}, {"name": "bad"}],
+#             [{"name": "age", "type": "number"}, "bla"],
+#             [{"name": "", "type": "number"}, "bla"],
+#         ]
+#         for fields in bad_fields:
+#             try:
+#                 self.db.create_text_index(fields=fields)
+#             except Exception as e:
+#                 self.assertEqual(e.response.status_code, 400)
+#             else:
+#                 raise AssertionError("bad create text index")
+#
+#     def test_limit_skip_index(self):
+#         fields = ["field1"]
+#         ret = self.db.create_index(fields, name="idx_01")
+#         assert ret is True
+#
+#         fields = ["field2"]
+#         ret = self.db.create_index(fields, name="idx_02")
+#         assert ret is True
+#
+#         fields = ["field3"]
+#         ret = self.db.create_index(fields, name="idx_03")
+#         assert ret is True
+#
+#         fields = ["field4"]
+#         ret = self.db.create_index(fields, name="idx_04")
+#         assert ret is True
+#
+#         fields = [
+#             {"name": "stringidx", "type": "string"},
+#             {"name": "booleanidx", "type": "boolean"},
+#         ]
+#         ret = self.db.create_text_index(fields=fields, name="idx_05")
+#         assert ret is True
+#
+#         self.assertEqual(len(self.db.list_indexes(limit=2)), 2)
+#         self.assertEqual(len(self.db.list_indexes(limit=5, skip=4)), 2)
+#         self.assertEqual(len(self.db.list_indexes(skip=5)), 1)
+#         self.assertEqual(len(self.db.list_indexes(skip=6)), 0)
+#         self.assertEqual(len(self.db.list_indexes(skip=100)), 0)
+#         self.assertEqual(len(self.db.list_indexes(limit=10000000)), 6)
+#
+#         try:
+#             self.db.list_indexes(skip=-1)
+#         except Exception as e:
+#             self.assertEqual(e.response.status_code, 500)
+#
+#         try:
+#             self.db.list_indexes(limit=0)
+#         except Exception as e:
+#             self.assertEqual(e.response.status_code, 500)
diff --git a/src/mango/test/02-basic-find-test.py b/src/mango/test/02-basic-find-test.py
index 0fc4248..632ad4f 100644
--- a/src/mango/test/02-basic-find-test.py
+++ b/src/mango/test/02-basic-find-test.py
@@ -16,286 +16,289 @@ 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 as 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 as 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 as 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 as 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 as 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 as 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 as e:
-                assert e.response.status_code == 400
-            else:
-                raise AssertionError("bad find")
-
+    # 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 as 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 as 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 as 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 as 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 as 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 as 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 as e:
+    #             assert e.response.status_code == 400
+    #         else:
+    #             raise AssertionError("bad find")
+    #
     def test_simple_find(self):
-        docs = self.db.find({"age": {"$lt": 35}})
+        print("OK")
+        docs = self.db.find({"age": {"$lt": 45}})
+        print("DOC")
+        print(docs)
         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_duplicate_field(self):
-        # need to explicitly define JSON as dict won't allow duplicate keys
-        body = (
-            '{"selector":{"location.city":{"$regex": "^L+"},'
-            '"location.city":{"$exists":true}}}'
-        )
-        r = self.db.sess.post(self.db.path("_find"), data=body)
-        r.raise_for_status()
-        docs = r.json()["docs"]
-
-        # expectation is that only the second instance
-        # of the "location.city" field is used
-        self.assertEqual(len(docs), 15)
-
-    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_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_sort_desc_complex(self):
-        docs = self.db.find(
-            {
-                "company": {"$lt": "M"},
-                "$or": [{"company": "Dreamia"}, {"manager": True}],
-            },
-            sort=[{"company": "desc"}, {"manager": "desc"}],
-        )
-
-        companies_returned = list(d["company"] for d in docs)
-        desc_companies = sorted(companies_returned, reverse=True)
-        self.assertEqual(desc_companies, companies_returned)
-
-    def test_sort_with_primary_sort_not_in_selector(self):
-        try:
-            docs = self.db.find(
-                {"name.last": {"$lt": "M"}}, sort=[{"name.first": "desc"}]
-            )
-        except Exception as e:
-            self.assertEqual(e.response.status_code, 400)
-            resp = e.response.json()
-            self.assertEqual(resp["error"], "no_usable_index")
-        else:
-            raise AssertionError("expected find error")
-
-    def test_sort_exists_true(self):
-        docs1 = self.db.find(
-            {"age": {"$gt": 0, "$exists": True}}, sort=[{"age": "asc"}]
-        )
-        docs2 = list(sorted(docs1, key=lambda d: d["age"]))
-        assert docs1 is not docs2 and docs1 == docs2
-
-    def test_sort_desc_complex_error(self):
-        try:
-            self.db.find(
-                {
-                    "company": {"$lt": "M"},
-                    "$or": [{"company": "Dreamia"}, {"manager": True}],
-                },
-                sort=[{"company": "desc"}],
-            )
-        except Exception as e:
-            self.assertEqual(e.response.status_code, 400)
-            resp = e.response.json()
-            self.assertEqual(resp["error"], "no_usable_index")
-        else:
-            raise AssertionError("expected find error")
-
-    def test_fields(self):
-        selector = {"age": {"$gt": 0}}
-        docs = self.db.find(selector, fields=["user_id", "location.address"])
-        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_empty(self):
-        docs = self.db.find({})
-        # 15 users
-        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"
-
-    def test_unsatisfiable_range(self):
-        docs = self.db.find({"$and": [{"age": {"$gt": 0}}, {"age": {"$lt": 0}}]})
-        assert len(docs) == 0
-
-    def test_explain_view_args(self):
-        explain = self.db.find({"age": {"$gt": 0}}, fields=["manager"], explain=True)
-        assert explain["mrargs"]["stable"] == False
-        assert explain["mrargs"]["update"] == True
-        assert explain["mrargs"]["reduce"] == False
-        assert explain["mrargs"]["start_key"] == [0]
-        assert explain["mrargs"]["end_key"] == ["<MAX>"]
-        assert explain["mrargs"]["include_docs"] == True
-
-    def test_sort_with_all_docs(self):
-        explain = self.db.find(
-            {"_id": {"$gt": 0}, "age": {"$gt": 0}}, sort=["_id"], explain=True
-        )
-        self.assertEqual(explain["index"]["type"], "special")
+        # 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_duplicate_field(self):
+    #     # need to explicitly define JSON as dict won't allow duplicate keys
+    #     body = (
+    #         '{"selector":{"location.city":{"$regex": "^L+"},'
+    #         '"location.city":{"$exists":true}}}'
+    #     )
+    #     r = self.db.sess.post(self.db.path("_find"), data=body)
+    #     r.raise_for_status()
+    #     docs = r.json()["docs"]
+    #
+    #     # expectation is that only the second instance
+    #     # of the "location.city" field is used
+    #     self.assertEqual(len(docs), 15)
+    #
+    # 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_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_sort_desc_complex(self):
+    #     docs = self.db.find(
+    #         {
+    #             "company": {"$lt": "M"},
+    #             "$or": [{"company": "Dreamia"}, {"manager": True}],
+    #         },
+    #         sort=[{"company": "desc"}, {"manager": "desc"}],
+    #     )
+    #
+    #     companies_returned = list(d["company"] for d in docs)
+    #     desc_companies = sorted(companies_returned, reverse=True)
+    #     self.assertEqual(desc_companies, companies_returned)
+    #
+    # def test_sort_with_primary_sort_not_in_selector(self):
+    #     try:
+    #         docs = self.db.find(
+    #             {"name.last": {"$lt": "M"}}, sort=[{"name.first": "desc"}]
+    #         )
+    #     except Exception as e:
+    #         self.assertEqual(e.response.status_code, 400)
+    #         resp = e.response.json()
+    #         self.assertEqual(resp["error"], "no_usable_index")
+    #     else:
+    #         raise AssertionError("expected find error")
+    #
+    # def test_sort_exists_true(self):
+    #     docs1 = self.db.find(
+    #         {"age": {"$gt": 0, "$exists": True}}, sort=[{"age": "asc"}]
+    #     )
+    #     docs2 = list(sorted(docs1, key=lambda d: d["age"]))
+    #     assert docs1 is not docs2 and docs1 == docs2
+    #
+    # def test_sort_desc_complex_error(self):
+    #     try:
+    #         self.db.find(
+    #             {
+    #                 "company": {"$lt": "M"},
+    #                 "$or": [{"company": "Dreamia"}, {"manager": True}],
+    #             },
+    #             sort=[{"company": "desc"}],
+    #         )
+    #     except Exception as e:
+    #         self.assertEqual(e.response.status_code, 400)
+    #         resp = e.response.json()
+    #         self.assertEqual(resp["error"], "no_usable_index")
+    #     else:
+    #         raise AssertionError("expected find error")
+    #
+    # def test_fields(self):
+    #     selector = {"age": {"$gt": 0}}
+    #     docs = self.db.find(selector, fields=["user_id", "location.address"])
+    #     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_empty(self):
+    #     docs = self.db.find({})
+    #     # 15 users
+    #     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"
+    #
+    # def test_unsatisfiable_range(self):
+    #     docs = self.db.find({"$and": [{"age": {"$gt": 0}}, {"age": {"$lt": 0}}]})
+    #     assert len(docs) == 0
+    #
+    # def test_explain_view_args(self):
+    #     explain = self.db.find({"age": {"$gt": 0}}, fields=["manager"], explain=True)
+    #     assert explain["mrargs"]["stable"] == False
+    #     assert explain["mrargs"]["update"] == True
+    #     assert explain["mrargs"]["reduce"] == False
+    #     assert explain["mrargs"]["start_key"] == [0]
+    #     assert explain["mrargs"]["end_key"] == ["<MAX>"]
+    #     assert explain["mrargs"]["include_docs"] == True
+    #
+    # def test_sort_with_all_docs(self):
+    #     explain = self.db.find(
+    #         {"_id": {"$gt": 0}, "age": {"$gt": 0}}, sort=["_id"], explain=True
+    #     )
+    #     self.assertEqual(explain["index"]["type"], "special")
diff --git a/src/mango/test/exunit/mango_indexer_test.exs b/src/mango/test/exunit/mango_indexer_test.exs
index 3a86ae4..16c6e49 100644
--- a/src/mango/test/exunit/mango_indexer_test.exs
+++ b/src/mango/test/exunit/mango_indexer_test.exs
@@ -1,68 +1,107 @@
 defmodule MangoIndexerTest do
-    use Couch.Test.ExUnit.Case
+  use Couch.Test.ExUnit.Case
 
-    alias Couch.Test.Utils
-    alias Couch.Test.Setup
-    alias Couch.Test.Setup.Step
+  alias Couch.Test.Utils
+  alias Couch.Test.Setup
+  alias Couch.Test.Setup.Step
 
-    setup_all do
-        test_ctx =
-          :test_util.start_couch([:couch_log, :fabric, :couch_js, :couch_jobs])
+  setup_all do
+    test_ctx = :test_util.start_couch([:couch_log, :fabric, :couch_js, :couch_jobs])
 
-        on_exit(fn ->
-            :test_util.stop_couch(test_ctx)
-        end)
-    end
+    on_exit(fn ->
+      :test_util.stop_couch(test_ctx)
+    end)
+  end
 
-    setup do
-        db_name = Utils.random_name("db")
+  setup do
+    db_name = Utils.random_name("db")
 
-        admin_ctx =
-          {:user_ctx,
-              Utils.erlang_record(:user_ctx, "couch/include/couch_db.hrl", roles: ["_admin"])}
+    admin_ctx =
+      {:user_ctx,
+       Utils.erlang_record(:user_ctx, "couch/include/couch_db.hrl", roles: ["_admin"])}
 
-        {:ok, db} = :fabric2_db.create(db_name, [admin_ctx])
+    {:ok, db} = :fabric2_db.create(db_name, [admin_ctx])
 
-        docs = create_docs()
-        ddoc = create_ddoc()
+    ddocs = create_ddocs()
+    idx_ddocs = create_indexes(db)
+    docs = create_docs()
 
-        {ok, _} = :fabric2_db.update_docs(db, [ddoc | docs])
+    IO.inspect idx_ddocs
+    {ok, _} = :fabric2_db.update_docs(db, ddocs ++ idx_ddocs)
+    {ok, _} = :fabric2_db.update_docs(db, docs)
 
-        on_exit(fn ->
-            :fabric2_db.delete(db_name, [admin_ctx])
-        end)
+    on_exit(fn ->
+      :fabric2_db.delete(db_name, [admin_ctx])
+    end)
 
-        %{
-            :db_name => db_name,
-            :db => db,
-            :ddoc => ddoc
-        }
-    end
+    %{
+      db_name: db_name,
+      db: db,
+      ddoc: ddocs,
+      idx: idx_ddocs
+    }
+  end
 
-    test "create design doc through _index", context do
-        db = context[:db]
-    end
+  test "create design doc through _index", context do
+    db = context[:db]
+  end
+
+  defp create_indexes(db) do
+    opts = [
+      {:def, {[{"fields", ["group", "value"]}]}},
+      {:type, "json"},
+      {:name, "idx_01"},
+      {:ddoc, :auto_name},
+      {:w, 3},
+      {:partitioned, :db_default}
+    ]
 
-#    Create 1 design doc that should be filtered out and ignored
-    defp create_ddocs() do
-        views = %{
-            "_id" => "_design/bar",
-            "views" => %{
-                "dates_sum" => %{
-                    "map" => """
-                        function(doc) {
-                            if (doc.date) {
-                                emit(doc.date, doc.date_val);
-                            }
-                        }
-                  """
+    {:ok, idx} = :mango_idx.new(db, opts)
+    db_opts = [{:user_ctx, db["user_ctx"]}, :deleted, :ejson_body]
+    {:ok, ddoc} = :mango_util.load_ddoc(db, :mango_idx.ddoc(idx), db_opts)
+    {:ok ,new_ddoc} = :mango_idx.add(ddoc, idx)
+    [new_ddoc]
+  end
+
+  #    Create 1 design doc that should be filtered out and ignored
+  defp create_ddocs() do
+    views = %{
+      "_id" => "_design/bar",
+      "views" => %{
+        "dates_sum" => %{
+          "map" => """
+                function(doc) {
+                    if (doc.date) {
+                        emit(doc.date, doc.date_val);
+                    }
                 }
-            }
+          """
         }
-        :couch_doc.from_json_obj(:jiffy.decode(:jiffy.encode(views)))
-    end
+      }
+    }
+
+    ddoc1 = :couch_doc.from_json_obj(:jiffy.decode(:jiffy.encode(views)))
+    []
+  end
+
+  defp create_docs() do
+    for i <- 1..1 do
+      group =
+        if rem(i, 3) == 0 do
+          "first"
+        else
+          "second"
+        end
 
-    defp create_docs() do
-        []
+      :couch_doc.from_json_obj(
+        {[
+          {"_id", "doc-id-#{i}"},
+          {"value", i},
+          {"val_str", Integer.to_string(i, 8)},
+          {"some", "field"},
+          {"group", group}
+        ]}
+      )
     end
-end
\ No newline at end of file
+  end
+end
diff --git a/src/mango/test/exunit/test_helper.exs b/src/mango/test/exunit/test_helper.exs
index f4ab64f..3140500 100644
--- a/src/mango/test/exunit/test_helper.exs
+++ b/src/mango/test/exunit/test_helper.exs
@@ -1,2 +1,2 @@
 ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter])
-ExUnit.start()
\ No newline at end of file
+ExUnit.start()
diff --git a/src/mango/test/mango.py b/src/mango/test/mango.py
index e8ce2c5..6fbbb07 100644
--- a/src/mango/test/mango.py
+++ b/src/mango/test/mango.py
@@ -110,6 +110,7 @@ class Database(object):
     def save_docs(self, docs, **kwargs):
         body = json.dumps({"docs": docs})
         r = self.sess.post(self.path("_bulk_docs"), data=body, params=kwargs)
+        print(r.json())
         r.raise_for_status()
         for doc, result in zip(docs, r.json()):
             doc["_id"] = result["id"]
@@ -277,6 +278,7 @@ class Database(object):
         else:
             path = self.path("_find")
         r = self.sess.post(path, data=body)
+        print(r.json())
         r.raise_for_status()
         if explain or return_raw:
             return r.json()
diff --git a/src/mango/test/user_docs.py b/src/mango/test/user_docs.py
index e049535..45fbd24 100644
--- a/src/mango/test/user_docs.py
+++ b/src/mango/test/user_docs.py
@@ -61,33 +61,35 @@ def setup_users(db, **kwargs):
 
 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)
+    copy_docs = copy.deepcopy(DOCS)
+    resp = db.save_doc(copy_docs[0])
+    # db.save_docs(copy.deepcopy(DOCS))
 
 
 def add_view_indexes(db, kwargs):
     indexes = [
-        (["user_id"], "user_id"),
-        (["name.last", "name.first"], "name"),
+        # (["user_id"], "user_id"),
+        # (["name.last", "name.first"], "name"),
         (["age"], "age"),
-        (
-            [
-                "location.state",
-                "location.city",
-                "location.address.street",
-                "location.address.number",
-            ],
-            "location",
-        ),
-        (["company", "manager"], "company_and_manager"),
-        (["manager"], "manager"),
-        (["favorites"], "favorites"),
-        (["favorites.3"], "favorites_3"),
-        (["twitter"], "twitter"),
-        (["ordered"], "ordered"),
+        # (
+        #     [
+        #         "location.state",
+        #         "location.city",
+        #         "location.address.street",
+        #         "location.address.number",
+        #     ],
+        #     "location",
+        # ),
+        # (["company", "manager"], "company_and_manager"),
+        # (["manager"], "manager"),
+        # (["favorites"], "favorites"),
+        # (["favorites.3"], "favorites_3"),
+        # (["twitter"], "twitter"),
+        # (["ordered"], "ordered"),
     ]
     for (idx, name) in indexes:
         assert db.create_index(idx, name=name, ddoc=name) is True


[couchdb] 02/05: change mango test auth to match elixir

Posted by ga...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

garren pushed a commit to branch fdb-mango-indexes
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 8446f15b99460a03df99ecb5e67af31e81c3f284
Author: Garren Smith <ga...@gmail.com>
AuthorDate: Tue Jan 21 13:41:41 2020 +0200

    change mango test auth to match elixir
---
 src/mango/test/README.md | 4 ++--
 src/mango/test/mango.py  | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/mango/test/README.md b/src/mango/test/README.md
index 509e32e..08693a2 100644
--- a/src/mango/test/README.md
+++ b/src/mango/test/README.md
@@ -23,7 +23,7 @@ Test configuration
 The following environment variables can be used to configure the test fixtures:
 
  * `COUCH_HOST` - root url (including port) of the CouchDB instance to run the tests against. Default is `"http://127.0.0.1:15984"`.
- * `COUCH_USER` - CouchDB username (with admin premissions). Default is `"testuser"`.
- * `COUCH_PASSWORD` -  CouchDB password. Default is `"testpass"`.
+ * `COUCH_USER` - CouchDB username (with admin premissions). Default is `"adm"`.
+ * `COUCH_PASSWORD` -  CouchDB password. Default is `"pass"`.
  * `COUCH_AUTH_HEADER` - Optional Authorization header value. If specified, this is used instead of basic authentication with the username/password variables above.
  * `MANGO_TEXT_INDEXES` - Set to `"1"` to run the tests only applicable to text indexes.
diff --git a/src/mango/test/mango.py b/src/mango/test/mango.py
index de8a638..e8ce2c5 100644
--- a/src/mango/test/mango.py
+++ b/src/mango/test/mango.py
@@ -48,8 +48,8 @@ class Database(object):
         dbname,
         host="127.0.0.1",
         port="15984",
-        user="testuser",
-        password="testpass",
+        user="adm",
+        password="pass",
     ):
         root_url = get_from_environment("COUCH_HOST", "http://{}:{}".format(host, port))
         auth_header = get_from_environment("COUCH_AUTH_HEADER", None)


[couchdb] 05/05: initial creation of fdb startkey/endkey

Posted by ga...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

garren pushed a commit to branch fdb-mango-indexes
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 020de0c3f2954071ee497ac86ef41c793dcfee13
Author: Garren Smith <ga...@gmail.com>
AuthorDate: Mon Jan 27 14:53:37 2020 +0200

    initial creation of fdb startkey/endkey
---
 src/mango/src/mango_cursor_view.erl   | 228 ++++++++++++++++++----------------
 src/mango/src/mango_fdb.erl           |  74 ++++++++++-
 src/mango/src/mango_idx_view.erl      |   3 +-
 src/mango/src/mango_idx_view.hrl      |   3 +-
 src/mango/src/mango_indexer.erl       |   9 +-
 src/mango/src/mango_json_bookmark.erl |  24 ++--
 src/mango/test/02-basic-find-test.py  |  13 +-
 src/mango/test/mango.py               |   1 -
 src/mango/test/user_docs.py           |   6 +-
 9 files changed, 223 insertions(+), 138 deletions(-)

diff --git a/src/mango/src/mango_cursor_view.erl b/src/mango/src/mango_cursor_view.erl
index 7b47a40..57cab21 100644
--- a/src/mango/src/mango_cursor_view.erl
+++ b/src/mango/src/mango_cursor_view.erl
@@ -19,7 +19,7 @@
 ]).
 
 -export([
-    view_cb/2,
+%%    view_cb/2,
     handle_message/2,
     handle_all_docs_message/2,
     composite_indexes/2,
@@ -63,22 +63,23 @@ explain(Cursor) ->
     #cursor{
         opts = Opts
     } = Cursor,
-
-    BaseArgs = base_args(Cursor),
-    Args = apply_opts(Opts, BaseArgs),
-
-    [{mrargs, {[
-        {include_docs, Args#mrargs.include_docs},
-        {view_type, Args#mrargs.view_type},
-        {reduce, Args#mrargs.reduce},
-        {partition, couch_mrview_util:get_extra(Args, partition, null)},
-        {start_key, maybe_replace_max_json(Args#mrargs.start_key)},
-        {end_key, maybe_replace_max_json(Args#mrargs.end_key)},
-        {direction, Args#mrargs.direction},
-        {stable, Args#mrargs.stable},
-        {update, Args#mrargs.update},
-        {conflicts, Args#mrargs.conflicts}
-    ]}}].
+    [{marargs, {[]}}].
+
+%%    BaseArgs = base_args(Cursor),
+%%    Args = apply_opts(Opts, BaseArgs),
+%%
+%%    [{mrargs, {[
+%%        {include_docs, Args#mrargs.include_docs},
+%%        {view_type, Args#mrargs.view_type},
+%%        {reduce, Args#mrargs.reduce},
+%%        {partition, couch_mrview_util:get_extra(Args, partition, null)},
+%%        {start_key, maybe_replace_max_json(Args#mrargs.start_key)},
+%%        {end_key, maybe_replace_max_json(Args#mrargs.end_key)},
+%%        {direction, Args#mrargs.direction},
+%%        {stable, Args#mrargs.stable},
+%%        {update, Args#mrargs.update},
+%%        {conflicts, Args#mrargs.conflicts}
+%%    ]}}].
 
 
 % replace internal values that cannot
@@ -99,15 +100,31 @@ maybe_replace_max_json([H | T] = EndKey) when is_list(EndKey) ->
 maybe_replace_max_json(EndKey) ->
     EndKey.
 
-base_args(#cursor{index = Idx, selector = Selector} = Cursor) ->
-    #mrargs{
-        view_type = map,
-        reduce = false,
-        start_key = mango_idx:start_key(Idx, Cursor#cursor.ranges),
-        end_key = mango_idx:end_key(Idx, Cursor#cursor.ranges),
-        include_docs = true,
-        extra = [{callback, {?MODULE, view_cb}}, {selector, Selector}]
-    }.
+index_args(#cursor{index = Idx} = Cursor) ->
+    #cursor{
+        index = Idx,
+        opts = Opts,
+        bookmark = Bookmark
+    } = Cursor,
+    Args0 = #{
+        start_key => mango_idx:start_key(Idx, Cursor#cursor.ranges),
+        start_key_docid => <<>>,
+        end_key => mango_idx:end_key(Idx, Cursor#cursor.ranges),
+        end_key_docid => <<>>,
+        skip => 0
+    },
+    Args = mango_json_bookmark:update_args(Bookmark, Args0),
+
+    Sort = couch_util:get_value(sort, Opts, [<<"asc">>]),
+    Args1 = case mango_sort:directions(Sort) of
+        [<<"desc">> | _] -> Args#{direction => rev};
+        _ -> Args#{direction => fwd}
+    end,
+
+    %% TODO: When supported, handle:
+    %% partitions
+    %% conflicts
+    Args1.
 
 
 execute(#cursor{db = Db, index = Idx, execution_stats = Stats} = Cursor0, UserFun, UserAcc) ->
@@ -121,21 +138,23 @@ execute(#cursor{db = Db, index = Idx, execution_stats = Stats} = Cursor0, UserFu
             % empty indicates unsatisfiable ranges, so don't perform search
             {ok, UserAcc};
         _ ->
-            BaseArgs = base_args(Cursor),
+            Args = index_args(Cursor),
             #cursor{opts = Opts, bookmark = Bookmark} = Cursor,
-            Args0 = apply_opts(Opts, BaseArgs),
-            Args = mango_json_bookmark:update_args(Bookmark, Args0), 
+%%            Args0 = BaseArgs,
+%%            Args0 = apply_opts(Opts, BaseArgs),
+%%            Args = mango_json_bookmark:update_args(Bookmark, Args0),
             UserCtx = couch_util:get_value(user_ctx, Opts, #user_ctx{}),
             DbOpts = [{user_ctx, UserCtx}],
             Result = case mango_idx:def(Idx) of
                 all_docs ->
                     CB = fun ?MODULE:handle_all_docs_message/2,
-                    fabric:all_docs(Db, DbOpts, CB, Cursor, Args);
+                    ok;
+%%                    fabric:all_docs(Db, DbOpts, CB, Cursor, Args);
                 _ ->
                     CB = fun ?MODULE:handle_message/2,
                     % Normal view
-                    DDoc = ddocid(Idx),
-                    Name = mango_idx:name(Idx),
+%%                    DDoc = ddocid(Idx),
+%%                    Name = mango_idx:name(Idx),
                     mango_fdb:query(Db, CB, Cursor, Args)
             end,
             case Result of
@@ -343,72 +362,72 @@ ddocid(Idx) ->
     end.
 
 
-apply_opts([], Args) ->
-    Args;
-apply_opts([{r, RStr} | Rest], Args) ->
-    IncludeDocs = case list_to_integer(RStr) of
-        1 ->
-            true;
-        R when R > 1 ->
-            % We don't load the doc in the view query because
-            % we have to do a quorum read in the coordinator
-            % so there's no point.
-            false
-    end,
-    NewArgs = Args#mrargs{include_docs = IncludeDocs},
-    apply_opts(Rest, NewArgs);
-apply_opts([{conflicts, true} | Rest], Args) ->
-    NewArgs = Args#mrargs{conflicts = true},
-    apply_opts(Rest, NewArgs);
-apply_opts([{conflicts, false} | Rest], Args) ->
-    % Ignored cause default
-    apply_opts(Rest, Args);
-apply_opts([{sort, Sort} | Rest], Args) ->
-    % We only support single direction sorts
-    % so nothing fancy here.
-    case mango_sort:directions(Sort) of
-        [] ->
-            apply_opts(Rest, Args);
-        [<<"asc">> | _] ->
-            apply_opts(Rest, Args);
-        [<<"desc">> | _] ->
-            SK = Args#mrargs.start_key,
-            SKDI = Args#mrargs.start_key_docid,
-            EK = Args#mrargs.end_key,
-            EKDI = Args#mrargs.end_key_docid,
-            NewArgs = Args#mrargs{
-                direction = rev,
-                start_key = EK,
-                start_key_docid = EKDI,
-                end_key = SK,
-                end_key_docid = SKDI
-            },
-            apply_opts(Rest, NewArgs)
-    end;
-apply_opts([{stale, ok} | Rest], Args) ->
-    NewArgs = Args#mrargs{
-        stable = true,
-        update = false
-    },
-    apply_opts(Rest, NewArgs);
-apply_opts([{stable, true} | Rest], Args) ->
-    NewArgs = Args#mrargs{
-        stable = true
-    },
-    apply_opts(Rest, NewArgs);
-apply_opts([{update, false} | Rest], Args) ->
-    NewArgs = Args#mrargs{
-        update = false
-    },
-    apply_opts(Rest, NewArgs);
-apply_opts([{partition, <<>>} | Rest], Args) ->
-    apply_opts(Rest, Args);
-apply_opts([{partition, Partition} | Rest], Args) when is_binary(Partition) ->
-    NewArgs = couch_mrview_util:set_extra(Args, partition, Partition),
-    apply_opts(Rest, NewArgs);
-apply_opts([{_, _} | Rest], Args) ->
-    % Ignore unknown options
-    apply_opts(Rest, Args).
+%%apply_opts([], Args) ->
+%%    Args;
+%%apply_opts([{r, RStr} | Rest], Args) ->
+%%    IncludeDocs = case list_to_integer(RStr) of
+%%        1 ->
+%%            true;
+%%        R when R > 1 ->
+%%            % We don't load the doc in the view query because
+%%            % we have to do a quorum read in the coordinator
+%%            % so there's no point.
+%%            false
+%%    end,
+%%    NewArgs = Args#mrargs{include_docs = IncludeDocs},
+%%    apply_opts(Rest, NewArgs);
+%%apply_opts([{conflicts, true} | Rest], Args) ->
+%%    NewArgs = Args#mrargs{conflicts = true},
+%%    apply_opts(Rest, NewArgs);
+%%apply_opts([{conflicts, false} | Rest], Args) ->
+%%    % Ignored cause default
+%%    apply_opts(Rest, Args);
+%%apply_opts([{sort, Sort} | Rest], Args) ->
+%%    % We only support single direction sorts
+%%    % so nothing fancy here.
+%%    case mango_sort:directions(Sort) of
+%%        [] ->
+%%            apply_opts(Rest, Args);
+%%        [<<"asc">> | _] ->
+%%            apply_opts(Rest, Args);
+%%        [<<"desc">> | _] ->
+%%            SK = Args#mrargs.start_key,
+%%            SKDI = Args#mrargs.start_key_docid,
+%%            EK = Args#mrargs.end_key,
+%%            EKDI = Args#mrargs.end_key_docid,
+%%            NewArgs = Args#mrargs{
+%%                direction = rev,
+%%                start_key = EK,
+%%                start_key_docid = EKDI,
+%%                end_key = SK,
+%%                end_key_docid = SKDI
+%%            },
+%%            apply_opts(Rest, NewArgs)
+%%    end;
+%%apply_opts([{stale, ok} | Rest], Args) ->
+%%    NewArgs = Args#mrargs{
+%%        stable = true,
+%%        update = false
+%%    },
+%%    apply_opts(Rest, NewArgs);
+%%apply_opts([{stable, true} | Rest], Args) ->
+%%    NewArgs = Args#mrargs{
+%%        stable = true
+%%    },
+%%    apply_opts(Rest, NewArgs);
+%%apply_opts([{update, false} | Rest], Args) ->
+%%    NewArgs = Args#mrargs{
+%%        update = false
+%%    },
+%%    apply_opts(Rest, NewArgs);
+%%apply_opts([{partition, <<>>} | Rest], Args) ->
+%%    apply_opts(Rest, Args);
+%%apply_opts([{partition, Partition} | Rest], Args) when is_binary(Partition) ->
+%%    NewArgs = couch_mrview_util:set_extra(Args, partition, Partition),
+%%    apply_opts(Rest, NewArgs);
+%%apply_opts([{_, _} | Rest], Args) ->
+%%    % Ignore unknown options
+%%    apply_opts(Rest, Args).
 
 
 doc_member(Cursor, DocProps) ->
@@ -448,13 +467,12 @@ doc_member(Cursor, DocProps) ->
 
 
 match_doc(Selector, Doc, ExecutionStats) ->
-    {ok, Doc, {execution_stats, ExecutionStats}}.
-%%    case mango_selector:match(Selector, Doc) of
-%%        true ->
-%%            {ok, Doc, {execution_stats, ExecutionStats}};
-%%        false ->
-%%            {no_match, Doc, {execution_stats, ExecutionStats}}
-%%    end.
+    case mango_selector:match(Selector, Doc) of
+        true ->
+            {ok, Doc, {execution_stats, ExecutionStats}};
+        false ->
+            {no_match, Doc, {execution_stats, ExecutionStats}}
+    end.
 
 
 is_design_doc(RowProps) ->
diff --git a/src/mango/src/mango_fdb.erl b/src/mango/src/mango_fdb.erl
index 36cb400..1cde268 100644
--- a/src/mango/src/mango_fdb.erl
+++ b/src/mango/src/mango_fdb.erl
@@ -38,8 +38,9 @@ query(Db, CallBack, Cursor, Args) ->
             db => TxDb,
             callback => CallBack
         },
-        io:format("DB ~p ~n", [TxDb]),
-        Acc1 = fabric2_fdb:fold_range(TxDb, MangoIdxPrefix, fun fold_cb/2, Acc0, []),
+
+        Opts = args_to_fdb_opts(Args),
+        Acc1 = fabric2_fdb:fold_range(TxDb, MangoIdxPrefix, fun fold_cb/2, Acc0, Opts),
         #{
             cursor := Cursor1
         } = Acc1,
@@ -47,6 +48,71 @@ query(Db, CallBack, Cursor, Args) ->
     end).
 
 
+args_to_fdb_opts(Args) ->
+    #{
+        start_key := StartKey0,
+        start_key_docid := StartKeyDocId,
+        end_key := EndKey0,
+        end_key_docid := EndKeyDocId,
+        direction := Direction,
+        skip := Skip
+    } = Args,
+
+    StartKey1 = if StartKey0 == undefined -> undefined; true ->
+        couch_views_encoding:encode(StartKey0, key)
+    end,
+
+    StartKeyOpts = case {StartKey1, StartKeyDocId} of
+        {undefined, _} ->
+            [];
+        {StartKey1, StartKeyDocId} ->
+            [{start_key, {StartKey1, StartKeyDocId}}]
+    end,
+
+    {EndKey1, InclusiveEnd} = get_endkey_inclusive(EndKey0),
+
+    EndKeyOpts = case {EndKey1, EndKeyDocId, Direction} of
+        {undefined, _, _} ->
+            [];
+        {EndKey1, <<>>, rev} when not InclusiveEnd ->
+            % When we iterate in reverse with
+            % inclusive_end=false we have to set the
+            % EndKeyDocId to <<255>> so that we don't
+            % include matching rows.
+            [{end_key_gt, {EndKey1, <<255>>}}];
+        {EndKey1, <<255>>, _} when not InclusiveEnd ->
+            % When inclusive_end=false we need to
+            % elide the default end_key_docid so as
+            % to not sort past the docids with the
+            % given end key.
+            [{end_key_gt, {EndKey1}}];
+        {EndKey1, EndKeyDocId, _} when not InclusiveEnd ->
+            [{end_key_gt, {EndKey1, EndKeyDocId}}];
+        {EndKey1, EndKeyDocId, _} when InclusiveEnd ->
+            [{end_key, {EndKey1, EndKeyDocId}}]
+    end,
+    [
+        {skip, Skip},
+        {dir, Direction},
+        {streaming_mode, want_all}
+    ] ++ StartKeyOpts ++ EndKeyOpts.
+
+
+get_endkey_inclusive(undefined) ->
+    {undefined, true};
+
+get_endkey_inclusive(EndKey) when is_list(EndKey) ->
+    {EndKey1, InclusiveEnd} = case lists:member(less_than, EndKey) of
+        false ->
+            {EndKey, true};
+        true ->
+            Filtered = lists:filter(fun (Key) -> Key /= less_than end, EndKey),
+            io:format("FIL be ~p after ~p ~n", [EndKey, Filtered]),
+            {Filtered, false}
+    end,
+    {couch_views_encoding:encode(EndKey1, key), InclusiveEnd}.
+
+
 fold_cb({Key, _}, Acc) ->
     #{
         prefix := MangoIdxPrefix,
@@ -55,7 +121,7 @@ fold_cb({Key, _}, Acc) ->
         cursor := Cursor
 
     } = Acc,
-    {_, DocId} = erlfdb_tuple:unpack(Key, MangoIdxPrefix),
+    {{_, DocId}} = erlfdb_tuple:unpack(Key, MangoIdxPrefix),
     {ok, Doc} = fabric2_db:open_doc(Db, DocId),
     io:format("PRINT ~p ~p ~n", [DocId, Doc]),
     {ok, Cursor1} = Callback(Doc, Cursor),
@@ -88,6 +154,6 @@ add_key(TxDb, MangoIdxPrefix, Results, DocId) ->
         tx := Tx
     } = TxDb,
     EncodedResults = couch_views_encoding:encode(Results, key),
-    Key = erlfdb_tuple:pack({EncodedResults, DocId}, MangoIdxPrefix),
+    Key = erlfdb_tuple:pack({{EncodedResults, DocId}}, MangoIdxPrefix),
     erlfdb:set(Tx, Key, <<0>>).
 
diff --git a/src/mango/src/mango_idx_view.erl b/src/mango/src/mango_idx_view.erl
index 3791149..f960ef7 100644
--- a/src/mango/src/mango_idx_view.erl
+++ b/src/mango/src/mango_idx_view.erl
@@ -176,7 +176,8 @@ end_key([]) ->
 end_key([{_, _, '$lt', Key} | Rest]) ->
     case mango_json:special(Key) of
         true ->
-            [?MAX_JSON_OBJ];
+%%            [?MAX_JSON_OBJ];
+              [less_than];
         false ->
             [Key | end_key(Rest)]
     end;
diff --git a/src/mango/src/mango_idx_view.hrl b/src/mango/src/mango_idx_view.hrl
index 0d213e5..f1acc67 100644
--- a/src/mango/src/mango_idx_view.hrl
+++ b/src/mango/src/mango_idx_view.hrl
@@ -10,4 +10,5 @@
 % License for the specific language governing permissions and limitations under
 % the License.
 
--define(MAX_JSON_OBJ, {<<255, 255, 255, 255>>}).
\ No newline at end of file
+%%-define(MAX_JSON_OBJ, {<<255, 255, 255, 255>>}).
+-define(MAX_JSON_OBJ, less_than).
diff --git a/src/mango/src/mango_indexer.erl b/src/mango/src/mango_indexer.erl
index 2040059..36eb2d3 100644
--- a/src/mango/src/mango_indexer.erl
+++ b/src/mango/src/mango_indexer.erl
@@ -37,7 +37,7 @@ update(Db, updated, Doc, OldDoc) ->
 update(Db, created, Doc, _) ->
     #doc{id = DocId} = Doc,
     Indexes = mango_idx:list(Db),
-    Indexes1 = filter_and_to_json(Indexes),
+    Indexes1 = filter_json_indexes(Indexes),
     io:format("UPDATE INDEXES ~p ~n filtered ~p ~n", [Indexes, Indexes1]),
     JSONDoc = mango_json:to_binary(couch_doc:to_json_obj(Doc, [])),
     io:format("DOC ~p ~n", [Doc]),
@@ -46,12 +46,9 @@ update(Db, created, Doc, _) ->
     mango_fdb:write_doc(Db, DocId, Results).
 
 
-filter_and_to_json(Indexes) ->
+filter_json_indexes(Indexes) ->
     lists:filter(fun (Idx) ->
-        case Idx#idx.type == <<"special">> of
-            true -> false;
-            false -> true
-        end
+        Idx#idx.type == <<"json">>
     end, Indexes).
 
 
diff --git a/src/mango/src/mango_json_bookmark.erl b/src/mango/src/mango_json_bookmark.erl
index 97f81cf..edc83cf 100644
--- a/src/mango/src/mango_json_bookmark.erl
+++ b/src/mango/src/mango_json_bookmark.erl
@@ -23,23 +23,28 @@
 -include("mango_cursor.hrl").
 -include("mango.hrl").
 
-update_args(EncodedBookmark,  #mrargs{skip = Skip} = Args) ->
+update_args(EncodedBookmark, FdbOpts) ->
+    #{
+        skip := Skip
+    } = FdbOpts,
     Bookmark = unpack(EncodedBookmark),
     case is_list(Bookmark) of
         true -> 
             {startkey, Startkey} = lists:keyfind(startkey, 1, Bookmark),
-            {startkey_docid, StartkeyDocId} = lists:keyfind(startkey_docid, 1, Bookmark),
-            Args#mrargs{
-                start_key = Startkey,
-                start_key_docid = StartkeyDocId,
-                skip = 1 + Skip
+            {startkey_docid, StartkeyDocId} = lists:keyfind(startkey_docid, 1,
+                Bookmark),
+            FdbOpts#{
+                start_key => Startkey,
+                start_key_docid => StartkeyDocId,
+                skip => 1 + Skip
             };
         false ->
-            Args
+            FdbOpts
     end.
     
 
-create(#cursor{bookmark_docid = BookmarkDocId, bookmark_key = BookmarkKey}) when BookmarkKey =/= undefined ->
+create(#cursor{bookmark_docid = BookmarkDocId, bookmark_key = BookmarkKey})
+        when BookmarkKey =/= undefined ->
     QueryArgs = [
         {startkey_docid, BookmarkDocId},
         {startkey, BookmarkKey}
@@ -61,7 +66,8 @@ unpack(Packed) ->
     end.
 
 verify(Bookmark) when is_list(Bookmark) ->
-    case lists:keymember(startkey, 1, Bookmark) andalso lists:keymember(startkey_docid, 1, Bookmark) of
+    case lists:keymember(startkey, 1, Bookmark)
+            andalso lists:keymember(startkey_docid, 1, Bookmark) of
         true -> Bookmark;
         _ -> throw(invalid_bookmark)
     end;
diff --git a/src/mango/test/02-basic-find-test.py b/src/mango/test/02-basic-find-test.py
index 632ad4f..8c2a9b1 100644
--- a/src/mango/test/02-basic-find-test.py
+++ b/src/mango/test/02-basic-find-test.py
@@ -118,16 +118,13 @@ class BasicFindTests(mango.UserDocsTests):
     #             assert e.response.status_code == 400
     #         else:
     #             raise AssertionError("bad find")
-    #
+
     def test_simple_find(self):
-        print("OK")
-        docs = self.db.find({"age": {"$lt": 45}})
-        print("DOC")
-        print(docs)
+        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
+        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"})
diff --git a/src/mango/test/mango.py b/src/mango/test/mango.py
index 6fbbb07..5ce4219 100644
--- a/src/mango/test/mango.py
+++ b/src/mango/test/mango.py
@@ -110,7 +110,6 @@ class Database(object):
     def save_docs(self, docs, **kwargs):
         body = json.dumps({"docs": docs})
         r = self.sess.post(self.path("_bulk_docs"), data=body, params=kwargs)
-        print(r.json())
         r.raise_for_status()
         for doc, result in zip(docs, r.json()):
             doc["_id"] = result["id"]
diff --git a/src/mango/test/user_docs.py b/src/mango/test/user_docs.py
index 45fbd24..c021f66 100644
--- a/src/mango/test/user_docs.py
+++ b/src/mango/test/user_docs.py
@@ -65,9 +65,9 @@ def setup(db, index_type="view", **kwargs):
         add_view_indexes(db, kwargs)
     elif index_type == "text":
         add_text_indexes(db, kwargs)
-    copy_docs = copy.deepcopy(DOCS)
-    resp = db.save_doc(copy_docs[0])
-    # db.save_docs(copy.deepcopy(DOCS))
+    # copy_docs = copy.deepcopy(DOCS)
+    # resp = db.save_doc(copy_docs[0])
+    db.save_docs(copy.deepcopy(DOCS))
 
 
 def add_view_indexes(db, kwargs):


[couchdb] 01/05: add crude mango hook and indexer setup

Posted by ga...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

garren pushed a commit to branch fdb-mango-indexes
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit a03c704a213432ba973e5dd0f02d5cc86b7fc298
Author: Garren Smith <ga...@gmail.com>
AuthorDate: Tue Jan 21 13:39:56 2020 +0200

    add crude mango hook and indexer setup
---
 src/fabric/src/fabric2_fdb.erl               |  13 ++--
 src/mango/src/mango_fdb.erl                  |  35 ++++++++++
 src/mango/src/mango_indexer.erl              | 101 +++++++++++++++++++++++++++
 src/mango/test/exunit/mango_indexer_test.exs |  68 ++++++++++++++++++
 src/mango/test/exunit/test_helper.exs        |   2 +
 5 files changed, 215 insertions(+), 4 deletions(-)

diff --git a/src/fabric/src/fabric2_fdb.erl b/src/fabric/src/fabric2_fdb.erl
index 6abe1f6..723f14d 100644
--- a/src/fabric/src/fabric2_fdb.erl
+++ b/src/fabric/src/fabric2_fdb.erl
@@ -697,13 +697,15 @@ write_doc(#{} = Db0, Doc, NewWinner0, OldWinner, ToUpdate, ToRemove) ->
             if not IsDDoc -> ok; true ->
                 incr_stat(Db, <<"doc_design_count">>, 1)
             end,
-            incr_stat(Db, <<"doc_count">>, 1);
+            incr_stat(Db, <<"doc_count">>, 1),
+            mango_indexer:update(Db, created, Doc, not_found);
         recreated ->
             if not IsDDoc -> ok; true ->
                 incr_stat(Db, <<"doc_design_count">>, 1)
             end,
             incr_stat(Db, <<"doc_count">>, 1),
-            incr_stat(Db, <<"doc_del_count">>, -1);
+            incr_stat(Db, <<"doc_del_count">>, -1),
+            mango_indexer:update(Db, created, Doc, not_found);
         replicate_deleted ->
             incr_stat(Db, <<"doc_del_count">>, 1);
         ignore ->
@@ -713,9 +715,12 @@ write_doc(#{} = Db0, Doc, NewWinner0, OldWinner, ToUpdate, ToRemove) ->
                 incr_stat(Db, <<"doc_design_count">>, -1)
             end,
             incr_stat(Db, <<"doc_count">>, -1),
-            incr_stat(Db, <<"doc_del_count">>, 1);
+            incr_stat(Db, <<"doc_del_count">>, 1),
+            OldDoc = get_doc_body(Db, DocId, OldWinner),
+            mango_indexer:update(Db, deleted, not_found, OldDoc);
         updated ->
-            ok
+            OldDoc = get_doc_body(Db, DocId, OldWinner),
+            mango_indexer:update(Db, updated, Doc, OldDoc)
     end,
 
     ok.
diff --git a/src/mango/src/mango_fdb.erl b/src/mango/src/mango_fdb.erl
new file mode 100644
index 0000000..c29ae8f
--- /dev/null
+++ b/src/mango/src/mango_fdb.erl
@@ -0,0 +1,35 @@
+% 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_fdb).
+
+
+-export([
+    write_doc/4
+]).
+
+
+write_doc(Db, Doc, Indexes, Results) ->
+    lists:foreach(fun (Index) ->
+        MangoIdxPrefix = mango_idx_prefix(Db, Index),
+        ok
+        end, Indexes).
+
+
+mango_idx_prefix(Db, Index) ->
+    #{
+        db_prefix := DbPrefix
+    } = Db,
+    io:format("INDEX ~p ~n", [Index]),
+    ok.
+
diff --git a/src/mango/src/mango_indexer.erl b/src/mango/src/mango_indexer.erl
new file mode 100644
index 0000000..b217ce1
--- /dev/null
+++ b/src/mango/src/mango_indexer.erl
@@ -0,0 +1,101 @@
+% 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_indexer).
+
+
+-export([
+    update/4
+]).
+
+
+update(Db, deleted, _, OldDoc) ->
+    ok;
+update(Db, updated, Doc, OldDoc) ->
+    ok;
+update(Db, created, Doc, _) ->
+%%    Indexes = mango_idx:list(Db),
+%%    Fun = fun (DDoc, Acc) ->
+%%        io:format("DESIGN DOC ~p ~n", [DDoc]),
+%%        Acc
+%%    end,
+%%    fabric2_db:fold_design_docs(Db, Fun, [], []),
+%%    % maybe validate indexes here
+%%    JSONDoc = mango_json:to_binary(couch_doc:to_json_obj(Doc, [])),
+%%    io:format("Update ~p ~n, ~p ~n", [Doc, JSONDoc]),
+%%    Results = index_doc(Indexes, JSONDoc),
+    ok.
+
+
+index_doc(Indexes, Doc) ->
+    lists:map(fun(Idx) -> get_index_entries(Idx, Doc) end, Indexes).
+
+
+get_index_entries({IdxProps}, Doc) ->
+    {Fields} = couch_util:get_value(<<"fields">>, IdxProps),
+    Selector = get_index_partial_filter_selector(IdxProps),
+    case should_index(Selector, Doc) of
+        false ->
+            [];
+        true ->
+            Values = get_index_values(Fields, Doc),
+            case lists:member(not_found, Values) of
+                true -> [];
+                false -> [[Values, null]]
+            end
+    end.
+
+
+get_index_values(Fields, Doc) ->
+    lists:map(fun({Field, _Dir}) ->
+        case mango_doc:get_field(Doc, Field) of
+            not_found -> not_found;
+            bad_path -> not_found;
+            Value -> Value
+        end
+    end, Fields).
+
+
+get_index_partial_filter_selector(IdxProps) ->
+    case couch_util:get_value(<<"partial_filter_selector">>, IdxProps, {[]}) of
+        {[]} ->
+            % this is to support legacy text indexes that had the partial_filter_selector
+            % set as selector
+            couch_util:get_value(<<"selector">>, IdxProps, {[]});
+        Else ->
+            Else
+    end.
+
+
+should_index(Selector, Doc) ->
+    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.
+
+
+validate_index_info(IndexInfo) ->
+    IdxTypes = [mango_idx_view, mango_idx_text],
+    Results = lists:foldl(fun(IdxType, Results0) ->
+        try
+            IdxType:validate_index_def(IndexInfo),
+            [valid_index | Results0]
+        catch _:_ ->
+            [invalid_index | Results0]
+        end
+    end, [], IdxTypes),
+    lists:member(valid_index, Results).
+
diff --git a/src/mango/test/exunit/mango_indexer_test.exs b/src/mango/test/exunit/mango_indexer_test.exs
new file mode 100644
index 0000000..3a86ae4
--- /dev/null
+++ b/src/mango/test/exunit/mango_indexer_test.exs
@@ -0,0 +1,68 @@
+defmodule MangoIndexerTest do
+    use Couch.Test.ExUnit.Case
+
+    alias Couch.Test.Utils
+    alias Couch.Test.Setup
+    alias Couch.Test.Setup.Step
+
+    setup_all do
+        test_ctx =
+          :test_util.start_couch([:couch_log, :fabric, :couch_js, :couch_jobs])
+
+        on_exit(fn ->
+            :test_util.stop_couch(test_ctx)
+        end)
+    end
+
+    setup do
+        db_name = Utils.random_name("db")
+
+        admin_ctx =
+          {:user_ctx,
+              Utils.erlang_record(:user_ctx, "couch/include/couch_db.hrl", roles: ["_admin"])}
+
+        {:ok, db} = :fabric2_db.create(db_name, [admin_ctx])
+
+        docs = create_docs()
+        ddoc = create_ddoc()
+
+        {ok, _} = :fabric2_db.update_docs(db, [ddoc | docs])
+
+        on_exit(fn ->
+            :fabric2_db.delete(db_name, [admin_ctx])
+        end)
+
+        %{
+            :db_name => db_name,
+            :db => db,
+            :ddoc => ddoc
+        }
+    end
+
+    test "create design doc through _index", context do
+        db = context[:db]
+    end
+
+#    Create 1 design doc that should be filtered out and ignored
+    defp create_ddocs() do
+        views = %{
+            "_id" => "_design/bar",
+            "views" => %{
+                "dates_sum" => %{
+                    "map" => """
+                        function(doc) {
+                            if (doc.date) {
+                                emit(doc.date, doc.date_val);
+                            }
+                        }
+                  """
+                }
+            }
+        }
+        :couch_doc.from_json_obj(:jiffy.decode(:jiffy.encode(views)))
+    end
+
+    defp create_docs() do
+        []
+    end
+end
\ No newline at end of file
diff --git a/src/mango/test/exunit/test_helper.exs b/src/mango/test/exunit/test_helper.exs
new file mode 100644
index 0000000..f4ab64f
--- /dev/null
+++ b/src/mango/test/exunit/test_helper.exs
@@ -0,0 +1,2 @@
+ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter])
+ExUnit.start()
\ No newline at end of file