Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id B72C7200C2A for ; Wed, 1 Mar 2017 17:38:51 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id B5FFC160B91; Wed, 1 Mar 2017 16:38:51 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 8424B160B88 for ; Wed, 1 Mar 2017 17:38:50 +0100 (CET) Received: (qmail 80523 invoked by uid 500); 1 Mar 2017 16:38:48 -0000 Mailing-List: contact commits-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@couchdb.apache.org Delivered-To: mailing list commits@couchdb.apache.org Received: (qmail 79903 invoked by uid 99); 1 Mar 2017 16:38:48 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 01 Mar 2017 16:38:48 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 4AAF1E152F; Wed, 1 Mar 2017 16:38:48 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: kocolosk@apache.org To: commits@couchdb.apache.org Date: Wed, 01 Mar 2017 16:39:00 -0000 Message-Id: <5fec4fc0a47a43d1812423474acab8fb@git.apache.org> In-Reply-To: <291c3c1659274535834e6cfd18c686fd@git.apache.org> References: <291c3c1659274535834e6cfd18c686fd@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [13/50] couch commit: updated refs/heads/2971-count-distinct to ee32cd5 archived-at: Wed, 01 Mar 2017 16:38:51 -0000 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 Authored: Thu Jun 2 17:04:17 2016 -0400 Committer: Nick Vatamaniuc 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);