summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKlaus Trainer <klaus_trainer@posteo.de>2013-11-15 17:02:20 +0100
committerRobert Newson <rnewson@apache.org>2013-11-28 22:59:13 +0000
commitca41964b70b96b5fa76504e4001523ed29c718b5 (patch)
treed4d99adcb93f024817943ce24eaa28ed98e2a3d6
parent2b8070875b799810a1409d7067afdf9ac6b90d16 (diff)
downloadcouchdb-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.rst15
-rw-r--r--share/doc/src/api/ddoc/views.rst20
-rw-r--r--share/doc/src/api/document/common.rst30
-rw-r--r--share/www/script/test/attachment_views.js54
-rw-r--r--share/www/script/test/changes.js68
-rw-r--r--src/couch_mrview/include/couch_mrview.hrl1
-rw-r--r--src/couch_mrview/src/couch_mrview_http.erl16
-rw-r--r--src/couch_mrview/src/couch_mrview_util.erl22
-rw-r--r--src/couchdb/couch_changes.erl16
-rw-r--r--src/couchdb/couch_db.hrl2
-rw-r--r--src/couchdb/couch_httpd_db.erl6
-rwxr-xr-xtest/etap/073-changes.t1
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 = []
}).