diff options
author | Klaus Trainer <klaus_trainer@posteo.de> | 2013-11-15 17:02:20 +0100 |
---|---|---|
committer | Robert Newson <rnewson@apache.org> | 2013-11-28 22:59:13 +0000 |
commit | ca41964b70b96b5fa76504e4001523ed29c718b5 (patch) | |
tree | d4d99adcb93f024817943ce24eaa28ed98e2a3d6 | |
parent | 2b8070875b799810a1409d7067afdf9ac6b90d16 (diff) | |
download | couchdb-ca41964b70b96b5fa76504e4001523ed29c718b5.tar.gz |
Extend support for attachment-related query params
Until now, the boolean query parameters `attachments` and
`att_encoding_info` have only been supported for the document API
endpoint (`/{db}/{docid}`).
This extends support for queries to the changes (`/{db}/_changes`) and
view (`/{db}/_design/{ddoc}/_view/{view}`) API endpoints:
* If `include_docs` and `attachments` equal `true`, the Base64-encoded
contents of attachments are included with the documents in changes or
view query results, respectively.
* If `include_docs` and `att_encoding_info` equal `true`, encoding
information is included in attachment stubs if the particular
attachment is compressed.
Closes COUCHDB-1923.
-rw-r--r-- | share/doc/src/api/database/changes.rst | 15 | ||||
-rw-r--r-- | share/doc/src/api/ddoc/views.rst | 20 | ||||
-rw-r--r-- | share/doc/src/api/document/common.rst | 30 | ||||
-rw-r--r-- | share/www/script/test/attachment_views.js | 54 | ||||
-rw-r--r-- | share/www/script/test/changes.js | 68 | ||||
-rw-r--r-- | src/couch_mrview/include/couch_mrview.hrl | 1 | ||||
-rw-r--r-- | src/couch_mrview/src/couch_mrview_http.erl | 16 | ||||
-rw-r--r-- | src/couch_mrview/src/couch_mrview_util.erl | 22 | ||||
-rw-r--r-- | src/couchdb/couch_changes.erl | 16 | ||||
-rw-r--r-- | src/couchdb/couch_db.hrl | 2 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_db.erl | 6 | ||||
-rwxr-xr-x | test/etap/073-changes.t | 1 |
12 files changed, 220 insertions, 31 deletions
diff --git a/share/doc/src/api/database/changes.rst b/share/doc/src/api/database/changes.rst index 203622076..21d44ea9b 100644 --- a/share/doc/src/api/database/changes.rst +++ b/share/doc/src/api/database/changes.rst @@ -59,6 +59,13 @@ :query boolean include_docs: Include the associated document with each result. If there are conflicts, only the winning revision is returned. Default is ``false``. + :query boolean attachments: Include the Base64-encoded content of + :ref:`attachments <api/doc/attachments>` in the documents that are included + if `include_docs` is ``true``. Ignored if `include_docs` isn't ``true``. + Default is ``false``. + :query boolean att_encoding_info: Include encoding information in attachment + stubs if `include_docs` is ``true`` and the particular attachment is + compressed. Ignored if `include_docs` isn't ``true``. Default is ``false``. :query number last-event-id: Alias of `Last-Event-ID` header. :query number limit: Limit number of result rows to the specified value (note that using ``0`` here has the same effect as ``1``). @@ -162,6 +169,14 @@ listen changes since current seq number. .. versionchanged:: 1.3.0 ``eventsource`` feed type added. .. versionchanged:: 1.4.0 Support ``Last-Event-ID`` header. +.. versionchanged:: 1.6.0 added ``attachments`` and ``att_encoding_info`` + parameters + +.. warning:: + Using the ``attachments`` parameter to include attachments in the changes + feed is not recommended for large attachment sizes. Also note that the + Base64-encoding that is used leads to a 33% overhead (i.e. one third) in + transfer size for attachments. .. http:post:: /{db}/_changes diff --git a/share/doc/src/api/ddoc/views.rst b/share/doc/src/api/ddoc/views.rst index 5e56f9c64..3d14396e9 100644 --- a/share/doc/src/api/ddoc/views.rst +++ b/share/doc/src/api/ddoc/views.rst @@ -41,8 +41,15 @@ :query boolean group: Group the results using the reduce function to a group or single row. Default is ``false`` :query number group_level: Specify the group level to be used. *Optional* - :query boolean include_docs: Include the full content of the documents in - the return. Default is ``false`` + :query boolean include_docs: Include the associated document with each row. + Default is ``false``. + :query boolean attachments: Include the Base64-encoded content of + :ref:`attachments <api/doc/attachments>` in the documents that are included + if `include_docs` is ``true``. Ignored if `include_docs` isn't ``true``. + Default is ``false``. + :query boolean att_encoding_info: Include encoding information in attachment + stubs if `include_docs` is ``true`` and the particular attachment is + compressed. Ignored if `include_docs` isn't ``true``. Default is ``false``. :query boolean inclusive_end: Specifies whether the specified end key should be included in the result. Default is ``true`` :query string key: Return only documents that match the specified key. @@ -123,6 +130,15 @@ "total_rows": 3 } +.. versionchanged:: 1.6.0 added ``attachments`` and ``att_encoding_info`` + parameters + +.. warning:: + Using the ``attachments`` parameter to include attachments in view results + is not recommended for large attachment sizes. Also note that the + Base64-encoding that is used leads to a 33% overhead (i.e. one third) in + transfer size for attachments. + .. http:post:: /{db}/_design/{ddoc}/_view/{view} :synopsis: Returns certain rows for the specified stored view diff --git a/share/doc/src/api/document/common.rst b/share/doc/src/api/document/common.rst index 62775cf33..089d8561e 100644 --- a/share/doc/src/api/document/common.rst +++ b/share/doc/src/api/document/common.rst @@ -80,8 +80,8 @@ :query boolean attachments: Includes attachments bodies in response. Default is ``false`` - :query boolean att_encoding_info: Includes encoding information into - attachment's stubs for compressed ones. Default is ``false`` + :query boolean att_encoding_info: Includes encoding information in attachment + stubs if the particular attachment is compressed. Default is ``false``. :query array atts_since: Includes attachments only since specified revisions. Doesn't includes attachments for specified revisions. *Optional* :query boolean conflicts: Includes information about conflicts in document. @@ -396,16 +396,28 @@ information objects with next structure: - **content_type** (*string*): Attachment MIME type - **data** (*string*): Base64-encoded content. Available if attachment content - requested by using ``attachments=true`` or ``atts_since`` query parameters + is requested by using the following query parameters: + - ``attachments=true`` when querying a document + - ``attachments=true&include_docs=true`` when querying a + :ref:`changes feed <api/db/changes>` or a :ref:`view <api/ddoc/view>` + - ``atts_since``. - **digest** (*string*): Content hash digest. It starts with prefix which announce hash type (``md5-``) and continues with Base64-encoded hash digest -- **encoded_length** (*number*): Compressed attachment size in bytes - Available when query parameter ``att_encoding_info=true`` was specified and - ``content_type`` is in :config:option:`list of compressiable types - <attachments/compressible_types>` -- **encoding** (*string*): Compression codec. Available when query parameter - ``att_encoding_info=true`` was specified +- **encoded_length** (*number*): Compressed attachment size in bytes. + Available if ``content_type`` is in :config:option:`list of compressible types + <attachments/compressible_types>` when the attachment was added and the + following query parameters are specified: + - ``att_encoding_info=true`` when querying a document + - ``att_encoding_info=true&include_docs=true`` when querying a + :ref:`changes feed <api/db/changes>` or a :ref:`view <api/ddoc/view>` +- **encoding** (*string*): Compression codec. Available if ``content_type`` is + in :config:option:`list of compressible types + <attachments/compressible_types>` when the attachment was added and the + following query parameters are specified: + - ``att_encoding_info=true`` when querying a document + - ``att_encoding_info=true&include_docs=true`` when querying a + :ref:`changes feed <api/db/changes>` or a :ref:`view <api/ddoc/view>` - **length** (*number*): Real attachment size in bytes. Not available if attachment content requested - **revpos** (*number*): Revision *number* when attachment was added diff --git a/share/www/script/test/attachment_views.js b/share/www/script/test/attachment_views.js index a92a8ad0c..b55aabea1 100644 --- a/share/www/script/test/attachment_views.js +++ b/share/www/script/test/attachment_views.js @@ -19,13 +19,15 @@ couchTests.attachment_views= function(debug) { // count attachments in a view + var attachmentData = "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="; + db.bulkSave(makeDocs(0, 10)); db.bulkSave(makeDocs(10, 20, { _attachments:{ "foo.txt": { content_type:"text/plain", - data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + data: attachmentData } } })); @@ -34,11 +36,11 @@ couchTests.attachment_views= function(debug) { _attachments:{ "foo.txt": { content_type:"text/plain", - data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + data: attachmentData }, "bar.txt": { content_type:"text/plain", - data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + data: attachmentData } } })); @@ -47,15 +49,15 @@ couchTests.attachment_views= function(debug) { _attachments:{ "foo.txt": { content_type:"text/plain", - data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + data: attachmentData }, "bar.txt": { content_type:"text/plain", - data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + data: attachmentData }, "baz.txt": { content_type:"text/plain", - data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + data: attachmentData } } })); @@ -95,4 +97,44 @@ couchTests.attachment_views= function(debug) { T(result.rows.length == 1); T(result.rows[0].value == 20); + var result = db.query(mapFunction, null, { + startkey: 30, + endkey: 39, + include_docs: true + }); + + T(result.rows.length == 10); + T(result.rows[0].value == 3); + T(result.rows[0].doc._attachments['baz.txt'].stub === true); + T(result.rows[0].doc._attachments['baz.txt'].data === undefined); + T(result.rows[0].doc._attachments['baz.txt'].encoding === undefined); + T(result.rows[0].doc._attachments['baz.txt'].encoded_length === undefined); + + var result = db.query(mapFunction, null, { + startkey: 30, + endkey: 39, + include_docs: true, + attachments: true + }); + + T(result.rows.length == 10); + T(result.rows[0].value == 3); + T(result.rows[0].doc._attachments['baz.txt'].data === attachmentData); + T(result.rows[0].doc._attachments['baz.txt'].stub === undefined); + T(result.rows[0].doc._attachments['baz.txt'].encoding === undefined); + T(result.rows[0].doc._attachments['baz.txt'].encoded_length === undefined); + + var result = db.query(mapFunction, null, { + startkey: 30, + endkey: 39, + include_docs: true, + att_encoding_info: true + }); + + T(result.rows.length == 10); + T(result.rows[0].value == 3); + T(result.rows[0].doc._attachments['baz.txt'].data === undefined); + T(result.rows[0].doc._attachments['baz.txt'].stub === true); + T(result.rows[0].doc._attachments['baz.txt'].encoding === "gzip"); + T(result.rows[0].doc._attachments['baz.txt'].encoded_length === 47); }; diff --git a/share/www/script/test/changes.js b/share/www/script/test/changes.js index fcd830619..0fba9f9fa 100644 --- a/share/www/script/test/changes.js +++ b/share/www/script/test/changes.js @@ -646,6 +646,74 @@ couchTests.changes = function(debug) { T(changes[0][1] === "3"); T(changes[1][1] === "4"); + // COUCHDB-1923 + T(db.deleteDb()); + T(db.createDb()); + + var attachmentData = "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="; + + db.bulkSave(makeDocs(20, 30, { + _attachments:{ + "foo.txt": { + content_type:"text/plain", + data: attachmentData + }, + "bar.txt": { + content_type:"text/plain", + data: attachmentData + } + } + })); + + var mapFunction = function(doc) { + var count = 0; + + for(var idx in doc._attachments) { + count = count + 1; + } + + emit(parseInt(doc._id), count); + }; + + var req = CouchDB.request("GET", "/test_suite_db/_changes?include_docs=true"); + var resp = JSON.parse(req.responseText); + + T(resp.results.length == 10); + T(resp.results[0].doc._attachments['foo.txt'].stub === true); + T(resp.results[0].doc._attachments['foo.txt'].data === undefined); + T(resp.results[0].doc._attachments['foo.txt'].encoding === undefined); + T(resp.results[0].doc._attachments['foo.txt'].encoded_length === undefined); + T(resp.results[0].doc._attachments['bar.txt'].stub === true); + T(resp.results[0].doc._attachments['bar.txt'].data === undefined); + T(resp.results[0].doc._attachments['bar.txt'].encoding === undefined); + T(resp.results[0].doc._attachments['bar.txt'].encoded_length === undefined); + + var req = CouchDB.request("GET", "/test_suite_db/_changes?include_docs=true&attachments=true"); + var resp = JSON.parse(req.responseText); + + T(resp.results.length == 10); + T(resp.results[0].doc._attachments['foo.txt'].stub === undefined); + T(resp.results[0].doc._attachments['foo.txt'].data === attachmentData); + T(resp.results[0].doc._attachments['foo.txt'].encoding === undefined); + T(resp.results[0].doc._attachments['foo.txt'].encoded_length === undefined); + T(resp.results[0].doc._attachments['bar.txt'].stub === undefined); + T(resp.results[0].doc._attachments['bar.txt'].data == attachmentData); + T(resp.results[0].doc._attachments['bar.txt'].encoding === undefined); + T(resp.results[0].doc._attachments['bar.txt'].encoded_length === undefined); + + var req = CouchDB.request("GET", "/test_suite_db/_changes?include_docs=true&att_encoding_info=true"); + var resp = JSON.parse(req.responseText); + + T(resp.results.length == 10); + T(resp.results[0].doc._attachments['foo.txt'].stub === true); + T(resp.results[0].doc._attachments['foo.txt'].data === undefined); + T(resp.results[0].doc._attachments['foo.txt'].encoding === "gzip"); + T(resp.results[0].doc._attachments['foo.txt'].encoded_length === 47); + T(resp.results[0].doc._attachments['bar.txt'].stub === true); + T(resp.results[0].doc._attachments['bar.txt'].data === undefined); + T(resp.results[0].doc._attachments['bar.txt'].encoding === "gzip"); + T(resp.results[0].doc._attachments['bar.txt'].encoded_length === 47); + // cleanup db.deleteDb(); }; diff --git a/src/couch_mrview/include/couch_mrview.hrl b/src/couch_mrview/include/couch_mrview.hrl index bf3bcace8..e4ec66dfd 100644 --- a/src/couch_mrview/include/couch_mrview.hrl +++ b/src/couch_mrview/include/couch_mrview.hrl @@ -72,6 +72,7 @@ stale = false, inclusive_end = true, include_docs = false, + doc_options = [], update_seq=false, conflicts, callback, diff --git a/src/couch_mrview/src/couch_mrview_http.erl b/src/couch_mrview/src/couch_mrview_http.erl index 8b914ef17..7b92034a9 100644 --- a/src/couch_mrview/src/couch_mrview_http.erl +++ b/src/couch_mrview/src/couch_mrview_http.erl @@ -348,6 +348,22 @@ parse_qs(Key, Val, Args) -> Args#mrargs{inclusive_end=parse_boolean(Val)}; "include_docs" -> Args#mrargs{include_docs=parse_boolean(Val)}; + "attachments" -> + case parse_boolean(Val) of + true -> + Opts = Args#mrargs.doc_options, + Args#mrargs{doc_options=[attachments|Opts]}; + false -> + Args + end; + "att_encoding_info" -> + case parse_boolean(Val) of + true -> + Opts = Args#mrargs.doc_options, + Args#mrargs{doc_options=[att_encoding_info|Opts]}; + false -> + Args + end; "update_seq" -> Args#mrargs{update_seq=parse_boolean(Val)}; "conflicts" -> diff --git a/src/couch_mrview/src/couch_mrview_util.erl b/src/couch_mrview/src/couch_mrview_util.erl index 18185af20..a3b0581d8 100644 --- a/src/couch_mrview/src/couch_mrview_util.erl +++ b/src/couch_mrview/src/couch_mrview_util.erl @@ -668,24 +668,24 @@ expand_dups([KV | Rest], Acc) -> maybe_load_doc(_Db, _DI, #mrargs{include_docs=false}) -> []; -maybe_load_doc(Db, #doc_info{}=DI, #mrargs{conflicts=true}) -> - doc_row(couch_index_util:load_doc(Db, DI, [conflicts])); -maybe_load_doc(Db, #doc_info{}=DI, _Args) -> - doc_row(couch_index_util:load_doc(Db, DI, [])). +maybe_load_doc(Db, #doc_info{}=DI, #mrargs{conflicts=true, doc_options=Opts}) -> + doc_row(couch_index_util:load_doc(Db, DI, [conflicts]), Opts); +maybe_load_doc(Db, #doc_info{}=DI, #mrargs{doc_options=Opts}) -> + doc_row(couch_index_util:load_doc(Db, DI, []), Opts). maybe_load_doc(_Db, _Id, _Val, #mrargs{include_docs=false}) -> []; -maybe_load_doc(Db, Id, Val, #mrargs{conflicts=true}) -> - doc_row(couch_index_util:load_doc(Db, docid_rev(Id, Val), [conflicts])); -maybe_load_doc(Db, Id, Val, _Args) -> - doc_row(couch_index_util:load_doc(Db, docid_rev(Id, Val), [])). +maybe_load_doc(Db, Id, Val, #mrargs{conflicts=true, doc_options=Opts}) -> + doc_row(couch_index_util:load_doc(Db, docid_rev(Id, Val), [conflicts]), Opts); +maybe_load_doc(Db, Id, Val, #mrargs{doc_options=Opts}) -> + doc_row(couch_index_util:load_doc(Db, docid_rev(Id, Val), []), Opts). -doc_row(null) -> +doc_row(null, _Opts) -> [{doc, null}]; -doc_row(Doc) -> - [{doc, couch_doc:to_json_obj(Doc, [])}]. +doc_row(Doc, Opts) -> + [{doc, couch_doc:to_json_obj(Doc, Opts)}]. docid_rev(Id, {Props}) -> diff --git a/src/couchdb/couch_changes.erl b/src/couchdb/couch_changes.erl index 85c9e54f9..6edde32db 100644 --- a/src/couchdb/couch_changes.erl +++ b/src/couchdb/couch_changes.erl @@ -29,6 +29,7 @@ resp_type, limit, include_docs, + doc_options, conflicts, timeout, timeout_fun @@ -272,6 +273,7 @@ start_sending_changes(Callback, UserAcc, ResponseType) -> build_acc(Args, Callback, UserAcc, Db, StartSeq, Prepend, Timeout, TimeoutFun) -> #changes_args{ include_docs = IncludeDocs, + doc_options = DocOpts, conflicts = Conflicts, limit = Limit, feed = ResponseType, @@ -287,6 +289,7 @@ build_acc(Args, Callback, UserAcc, Db, StartSeq, Prepend, Timeout, TimeoutFun) - resp_type = ResponseType, limit = Limit, include_docs = IncludeDocs, + doc_options = DocOpts, conflicts = Conflicts, timeout = Timeout, timeout_fun = TimeoutFun @@ -498,7 +501,12 @@ changes_row(Results, DocInfo, Acc) -> #doc_info{ id = Id, high_seq = Seq, revs = [#rev_info{deleted = Del} | _] } = DocInfo, - #changes_acc{db = Db, include_docs = IncDoc, conflicts = Conflicts} = Acc, + #changes_acc{ + db = Db, + include_docs = IncDoc, + doc_options = DocOpts, + conflicts = Conflicts + } = Acc, {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Results}] ++ deleted_item(Del) ++ case IncDoc of true -> @@ -508,8 +516,10 @@ changes_row(Results, DocInfo, Acc) -> end, Doc = couch_index_util:load_doc(Db, DocInfo, Opts), case Doc of - null -> [{doc, null}]; - _ -> [{doc, couch_doc:to_json_obj(Doc, [])}] + null -> + [{doc, null}]; + _ -> + [{doc, couch_doc:to_json_obj(Doc, DocOpts)}] end; false -> [] diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl index 40369344a..6888f10da 100644 --- a/src/couchdb/couch_db.hrl +++ b/src/couchdb/couch_db.hrl @@ -219,6 +219,7 @@ view_type = nil, include_docs = false, + doc_options = [], conflicts = false, stale = false, multi_get = false, @@ -269,6 +270,7 @@ filter_fun, filter_args = [], include_docs = false, + doc_options = [], conflicts = false, db_open_options = [] }). diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index f5fe483e2..0a7c17c6f 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -1141,6 +1141,12 @@ parse_changes_query(Req, Db) -> Args#changes_args{timeout=list_to_integer(Value)}; {"include_docs", "true"} -> Args#changes_args{include_docs=true}; + {"attachments", "true"} -> + Opts = Args#changes_args.doc_options, + Args#changes_args{doc_options=[attachments|Opts]}; + {"att_encoding_info", "true"} -> + Opts = Args#changes_args.doc_options, + Args#changes_args{doc_options=[att_encoding_info|Opts]}; {"conflicts", "true"} -> Args#changes_args{conflicts=true}; {"filter", _} -> diff --git a/test/etap/073-changes.t b/test/etap/073-changes.t index 845cd79fa..d632c2f9d 100755 --- a/test/etap/073-changes.t +++ b/test/etap/073-changes.t @@ -34,6 +34,7 @@ filter_fun, filter_args = [], include_docs = false, + doc_options = [], conflicts = false, db_open_options = [] }). |