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);