You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ko...@apache.org on 2017/03/01 16:39:00 UTC

[13/50] couch commit: updated refs/heads/2971-count-distinct to ee32cd5

Add optional `fields` to change feed selectors

When using selectors with `include_docs=true` can specify an optional fields
array in the POST request JSON body.

Each element in the array can be a json field (or even a key path
specified as field1.field2...). Resulting documents will contain only the
specified document fields.

For example:
`
http://.../d1/_changes?filter=_selector&include_docs=true

{
  "selector": {"z" : {"$gte" : 1} }, "fields": ["field1", "field2"]

}
`
Will first select only document with "z" value >= 1, then will return only field1 and field2 in documents.

{ "field1": "field1value", "field2": "field2value"}

(This requires a companion pr in fabric to work)

Jira: COUCHDB-2988


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

Branch: refs/heads/2971-count-distinct
Commit: b4cd6709e98ad3034f482b7c266e4533cce4b891
Parents: bd64fa1
Author: Nick Vatamaniuc <va...@gmail.com>
Authored: Thu Jun 2 17:04:17 2016 -0400
Committer: Nick Vatamaniuc <va...@apache.org>
Committed: Wed Nov 9 17:13:12 2016 -0500

----------------------------------------------------------------------
 src/couch_changes.erl        | 65 ++++++++++++++++++++++++++++-----------
 test/couch_changes_tests.erl | 53 +++++++++++++++++++++++++++++--
 2 files changed, 97 insertions(+), 21 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/b4cd6709/src/couch_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_changes.erl b/src/couch_changes.erl
index b37aabf..52ff39d 100644
--- a/src/couch_changes.erl
+++ b/src/couch_changes.erl
@@ -197,7 +197,7 @@ get_callback_acc(Callback) when is_function(Callback, 2) ->
 configure_filter("_doc_ids", Style, Req, _Db) ->
     {doc_ids, Style, get_doc_ids(Req)};
 configure_filter("_selector", Style, Req, _Db) ->
-    {selector, Style,  get_selector(Req)};
+    {selector, Style,  get_selector_and_fields(Req)};
 configure_filter("_design", Style, _Req, _Db) ->
     {design_docs, Style};
 configure_filter("_view", Style, Req, Db) ->
@@ -269,7 +269,7 @@ filter(_Db, DocInfo, {doc_ids, Style, DocIds}) ->
         false ->
             []
     end;
-filter(Db, DocInfo, {selector, Style, Selector}) ->
+filter(Db, DocInfo, {selector, Style, {Selector, _Fields}}) ->
     Docs = open_revs(Db, DocInfo, Style),
     Passes = [mango_selector:match(Selector, couch_doc:to_json_obj(Doc, []))
         || Doc <- Docs],
@@ -344,12 +344,14 @@ get_doc_ids(_) ->
     throw({bad_request, no_doc_ids_provided}).
 
 
-get_selector({json_req, {Props}}) ->
-    check_selector(couch_util:get_value(<<"selector">>, Props));
-get_selector(#httpd{method='POST'}=Req) ->
+get_selector_and_fields({json_req, {Props}}) ->
+    Selector = check_selector(couch_util:get_value(<<"selector">>, Props)),
+    Fields = check_fields(couch_util:get_value(<<"fields">>, Props, nil)),
+    {Selector, Fields};
+get_selector_and_fields(#httpd{method='POST'}=Req) ->
     couch_httpd:validate_ctype(Req, "application/json"),
-    get_selector({json_req,  couch_httpd:json_body_obj(Req)});
-get_selector(_) ->
+    get_selector_and_fields({json_req,  couch_httpd:json_body_obj(Req)});
+get_selector_and_fields(_) ->
     throw({bad_request, "Selector must be specified in POST payload"}).
 
 
@@ -378,6 +380,21 @@ check_selector(_Selector) ->
     throw({bad_request, "Selector error: expected a JSON object"}).
 
 
+check_fields(nil) ->
+    nil;
+check_fields(Fields) when is_list(Fields) ->
+    try
+        {ok, Fields1} = mango_fields:new(Fields),
+        Fields1
+    catch
+        {mango_error, Mod, Reason0} ->
+            {_StatusCode, _Error, Reason} = mango_error:info(Mod, Reason0),
+            throw({bad_request, Reason})
+    end;
+check_fields(_Fields) ->
+    throw({bad_request, "Selector error: fields must be JSON array"}).
+
+
 open_ddoc(#db{name=DbName, id_tree=undefined}, DDocId) ->
     case ddoc_cache:open_doc(mem3:dbname(DbName), DDocId) of
         {ok, _} = Resp -> Resp;
@@ -806,23 +823,35 @@ maybe_get_changes_doc(Value, #changes_acc{include_docs=true}=Acc) ->
     #changes_acc{
         db = Db,
         doc_options = DocOpts,
-        conflicts = Conflicts
+        conflicts = Conflicts,
+        filter = Filter
     } = Acc,
     Opts = case Conflicts of
-        true -> [deleted, conflicts];
-        false -> [deleted]
-    end,
-    Doc = couch_index_util:load_doc(Db, Value, Opts),
-    case Doc of
-        null ->
-            [{doc, null}];
-        _ ->
-            [{doc, couch_doc:to_json_obj(Doc, DocOpts)}]
-    end;
+               true -> [deleted, conflicts];
+               false -> [deleted]
+           end,
+    load_doc(Db, Value, Opts, DocOpts, Filter);
+
 maybe_get_changes_doc(_Value, _Acc) ->
     [].
 
 
+load_doc(Db, Value, Opts, DocOpts, Filter) ->
+    case couch_index_util:load_doc(Db, Value, Opts) of
+        null ->
+            [{doc, null}];
+        Doc ->
+            [{doc, doc_to_json(Doc, DocOpts, Filter)}]
+    end.
+
+
+doc_to_json(Doc, DocOpts, {selector, _Style, {_Selector, Fields}})
+    when Fields =/= nil ->
+    mango_fields:extract(couch_doc:to_json_obj(Doc, DocOpts), Fields);
+doc_to_json(Doc, DocOpts, _Filter) ->
+    couch_doc:to_json_obj(Doc, DocOpts).
+
+
 deleted_item(true) -> [{<<"deleted">>, true}];
 deleted_item(_) -> [].
 

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/b4cd6709/test/couch_changes_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_changes_tests.erl b/test/couch_changes_tests.erl
index 7e38a02..e7a42ce 100644
--- a/test/couch_changes_tests.erl
+++ b/test/couch_changes_tests.erl
@@ -21,7 +21,8 @@
 -record(row, {
     id,
     seq,
-    deleted = false
+    deleted = false,
+    doc = nil
 }).
 
 setup() ->
@@ -98,7 +99,9 @@ filter_by_selector() ->
                 fun should_select_when_no_result/1,
                 fun should_select_with_deleted_docs/1,
                 fun should_select_with_continuous/1,
-                fun should_stop_selector_when_db_deleted/1
+                fun should_stop_selector_when_db_deleted/1,
+                fun should_select_with_empty_fields/1,
+                fun should_select_with_fields/1
             ]
         }
     }.
@@ -500,6 +503,49 @@ should_stop_selector_when_db_deleted({DbName, _Revs}) ->
        end).
 
 
+should_select_with_empty_fields({DbName, _}) ->
+    ?_test(
+        begin
+            ChArgs = #changes_args{filter = "_selector", include_docs=true},
+            Selector = {[{<<"_id">>, <<"doc3">>}]},
+            Req = {json_req, {[{<<"selector">>, Selector},
+                               {<<"fields">>, []}]}},
+            Consumer = spawn_consumer(DbName, ChArgs, Req),
+            {Rows, LastSeq} = wait_finished(Consumer),
+            {ok, Db} = couch_db:open_int(DbName, []),
+            UpSeq = couch_db:get_update_seq(Db),
+            couch_db:close(Db),
+            stop_consumer(Consumer),
+            ?assertEqual(1, length(Rows)),
+            [#row{seq = Seq, id = Id, doc = Doc}] = Rows,
+            ?assertEqual(<<"doc3">>, Id),
+            ?assertEqual(6, Seq),
+            ?assertEqual(UpSeq, LastSeq),
+            ?assertMatch({[{_K1, _V1}, {_K2, _V2}]}, Doc)
+        end).
+
+should_select_with_fields({DbName, _}) ->
+    ?_test(
+        begin
+            ChArgs = #changes_args{filter = "_selector", include_docs=true},
+            Selector = {[{<<"_id">>, <<"doc3">>}]},
+            Req = {json_req, {[{<<"selector">>, Selector},
+                               {<<"fields">>, [<<"_id">>, <<"nope">>]}]}},
+            Consumer = spawn_consumer(DbName, ChArgs, Req),
+            {Rows, LastSeq} = wait_finished(Consumer),
+            {ok, Db} = couch_db:open_int(DbName, []),
+            UpSeq = couch_db:get_update_seq(Db),
+            couch_db:close(Db),
+            stop_consumer(Consumer),
+            ?assertEqual(1, length(Rows)),
+            [#row{seq = Seq, id = Id, doc = Doc}] = Rows,
+            ?assertEqual(<<"doc3">>, Id),
+            ?assertEqual(6, Seq),
+            ?assertEqual(UpSeq, LastSeq),
+            ?assertMatch(Doc, {[{<<"_id">>, <<"doc3">>}]})
+        end).
+
+
 should_emit_only_design_documents({DbName, Revs}) ->
     ?_test(
         begin
@@ -793,7 +839,8 @@ spawn_consumer(DbName, ChangesArgs0, Req) ->
                 Id = couch_util:get_value(<<"id">>, Change),
                 Seq = couch_util:get_value(<<"seq">>, Change),
                 Del = couch_util:get_value(<<"deleted">>, Change, false),
-                [#row{id = Id, seq = Seq, deleted = Del} | Acc];
+                Doc = couch_util:get_value(doc, Change, nil),
+                [#row{id = Id, seq = Seq, deleted = Del, doc = Doc} | Acc];
             ({stop, LastSeq}, _, Acc) ->
                 Parent ! {consumer_finished, lists:reverse(Acc), LastSeq},
                 stop_loop(Parent, Acc);