Skip to content

Commit ca41964

Browse files
KlausTrainerrnewson
authored andcommitted
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.
1 parent 2b80708 commit ca41964

File tree

12 files changed

+220
-31
lines changed

12 files changed

+220
-31
lines changed

share/doc/src/api/database/changes.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@
5959
:query boolean include_docs: Include the associated document with each result.
6060
If there are conflicts, only the winning revision is returned.
6161
Default is ``false``.
62+
:query boolean attachments: Include the Base64-encoded content of
63+
:ref:`attachments <api/doc/attachments>` in the documents that are included
64+
if `include_docs` is ``true``. Ignored if `include_docs` isn't ``true``.
65+
Default is ``false``.
66+
:query boolean att_encoding_info: Include encoding information in attachment
67+
stubs if `include_docs` is ``true`` and the particular attachment is
68+
compressed. Ignored if `include_docs` isn't ``true``. Default is ``false``.
6269
:query number last-event-id: Alias of `Last-Event-ID` header.
6370
:query number limit: Limit number of result rows to the specified value
6471
(note that using ``0`` here has the same effect as ``1``).
@@ -162,6 +169,14 @@
162169
listen changes since current seq number.
163170
.. versionchanged:: 1.3.0 ``eventsource`` feed type added.
164171
.. versionchanged:: 1.4.0 Support ``Last-Event-ID`` header.
172+
.. versionchanged:: 1.6.0 added ``attachments`` and ``att_encoding_info``
173+
parameters
174+
175+
.. warning::
176+
Using the ``attachments`` parameter to include attachments in the changes
177+
feed is not recommended for large attachment sizes. Also note that the
178+
Base64-encoding that is used leads to a 33% overhead (i.e. one third) in
179+
transfer size for attachments.
165180

166181

167182
.. http:post:: /{db}/_changes

share/doc/src/api/ddoc/views.rst

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,15 @@
4141
:query boolean group: Group the results using the reduce function to a group
4242
or single row. Default is ``false``
4343
:query number group_level: Specify the group level to be used. *Optional*
44-
:query boolean include_docs: Include the full content of the documents in
45-
the return. Default is ``false``
44+
:query boolean include_docs: Include the associated document with each row.
45+
Default is ``false``.
46+
:query boolean attachments: Include the Base64-encoded content of
47+
:ref:`attachments <api/doc/attachments>` in the documents that are included
48+
if `include_docs` is ``true``. Ignored if `include_docs` isn't ``true``.
49+
Default is ``false``.
50+
:query boolean att_encoding_info: Include encoding information in attachment
51+
stubs if `include_docs` is ``true`` and the particular attachment is
52+
compressed. Ignored if `include_docs` isn't ``true``. Default is ``false``.
4653
:query boolean inclusive_end: Specifies whether the specified end key should
4754
be included in the result. Default is ``true``
4855
:query string key: Return only documents that match the specified key.
@@ -123,6 +130,15 @@
123130
"total_rows": 3
124131
}
125132
133+
.. versionchanged:: 1.6.0 added ``attachments`` and ``att_encoding_info``
134+
parameters
135+
136+
.. warning::
137+
Using the ``attachments`` parameter to include attachments in view results
138+
is not recommended for large attachment sizes. Also note that the
139+
Base64-encoding that is used leads to a 33% overhead (i.e. one third) in
140+
transfer size for attachments.
141+
126142

127143
.. http:post:: /{db}/_design/{ddoc}/_view/{view}
128144
:synopsis: Returns certain rows for the specified stored view

share/doc/src/api/document/common.rst

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@
8080

8181
:query boolean attachments: Includes attachments bodies in response.
8282
Default is ``false``
83-
:query boolean att_encoding_info: Includes encoding information into
84-
attachment's stubs for compressed ones. Default is ``false``
83+
:query boolean att_encoding_info: Includes encoding information in attachment
84+
stubs if the particular attachment is compressed. Default is ``false``.
8585
:query array atts_since: Includes attachments only since specified revisions.
8686
Doesn't includes attachments for specified revisions. *Optional*
8787
:query boolean conflicts: Includes information about conflicts in document.
@@ -396,16 +396,28 @@ information objects with next structure:
396396

397397
- **content_type** (*string*): Attachment MIME type
398398
- **data** (*string*): Base64-encoded content. Available if attachment content
399-
requested by using ``attachments=true`` or ``atts_since`` query parameters
399+
is requested by using the following query parameters:
400+
- ``attachments=true`` when querying a document
401+
- ``attachments=true&include_docs=true`` when querying a
402+
:ref:`changes feed <api/db/changes>` or a :ref:`view <api/ddoc/view>`
403+
- ``atts_since``.
400404
- **digest** (*string*): Content hash digest.
401405
It starts with prefix which announce hash type (``md5-``) and continues with
402406
Base64-encoded hash digest
403-
- **encoded_length** (*number*): Compressed attachment size in bytes
404-
Available when query parameter ``att_encoding_info=true`` was specified and
405-
``content_type`` is in :config:option:`list of compressiable types
406-
<attachments/compressible_types>`
407-
- **encoding** (*string*): Compression codec. Available when query parameter
408-
``att_encoding_info=true`` was specified
407+
- **encoded_length** (*number*): Compressed attachment size in bytes.
408+
Available if ``content_type`` is in :config:option:`list of compressible types
409+
<attachments/compressible_types>` when the attachment was added and the
410+
following query parameters are specified:
411+
- ``att_encoding_info=true`` when querying a document
412+
- ``att_encoding_info=true&include_docs=true`` when querying a
413+
:ref:`changes feed <api/db/changes>` or a :ref:`view <api/ddoc/view>`
414+
- **encoding** (*string*): Compression codec. Available if ``content_type`` is
415+
in :config:option:`list of compressible types
416+
<attachments/compressible_types>` when the attachment was added and the
417+
following query parameters are specified:
418+
- ``att_encoding_info=true`` when querying a document
419+
- ``att_encoding_info=true&include_docs=true`` when querying a
420+
:ref:`changes feed <api/db/changes>` or a :ref:`view <api/ddoc/view>`
409421
- **length** (*number*): Real attachment size in bytes. Not available if attachment
410422
content requested
411423
- **revpos** (*number*): Revision *number* when attachment was added

share/www/script/test/attachment_views.js

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ couchTests.attachment_views= function(debug) {
1919

2020
// count attachments in a view
2121

22+
var attachmentData = "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=";
23+
2224
db.bulkSave(makeDocs(0, 10));
2325

2426
db.bulkSave(makeDocs(10, 20, {
2527
_attachments:{
2628
"foo.txt": {
2729
content_type:"text/plain",
28-
data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
30+
data: attachmentData
2931
}
3032
}
3133
}));
@@ -34,11 +36,11 @@ couchTests.attachment_views= function(debug) {
3436
_attachments:{
3537
"foo.txt": {
3638
content_type:"text/plain",
37-
data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
39+
data: attachmentData
3840
},
3941
"bar.txt": {
4042
content_type:"text/plain",
41-
data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
43+
data: attachmentData
4244
}
4345
}
4446
}));
@@ -47,15 +49,15 @@ couchTests.attachment_views= function(debug) {
4749
_attachments:{
4850
"foo.txt": {
4951
content_type:"text/plain",
50-
data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
52+
data: attachmentData
5153
},
5254
"bar.txt": {
5355
content_type:"text/plain",
54-
data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
56+
data: attachmentData
5557
},
5658
"baz.txt": {
5759
content_type:"text/plain",
58-
data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
60+
data: attachmentData
5961
}
6062
}
6163
}));
@@ -95,4 +97,44 @@ couchTests.attachment_views= function(debug) {
9597
T(result.rows.length == 1);
9698
T(result.rows[0].value == 20);
9799

100+
var result = db.query(mapFunction, null, {
101+
startkey: 30,
102+
endkey: 39,
103+
include_docs: true
104+
});
105+
106+
T(result.rows.length == 10);
107+
T(result.rows[0].value == 3);
108+
T(result.rows[0].doc._attachments['baz.txt'].stub === true);
109+
T(result.rows[0].doc._attachments['baz.txt'].data === undefined);
110+
T(result.rows[0].doc._attachments['baz.txt'].encoding === undefined);
111+
T(result.rows[0].doc._attachments['baz.txt'].encoded_length === undefined);
112+
113+
var result = db.query(mapFunction, null, {
114+
startkey: 30,
115+
endkey: 39,
116+
include_docs: true,
117+
attachments: true
118+
});
119+
120+
T(result.rows.length == 10);
121+
T(result.rows[0].value == 3);
122+
T(result.rows[0].doc._attachments['baz.txt'].data === attachmentData);
123+
T(result.rows[0].doc._attachments['baz.txt'].stub === undefined);
124+
T(result.rows[0].doc._attachments['baz.txt'].encoding === undefined);
125+
T(result.rows[0].doc._attachments['baz.txt'].encoded_length === undefined);
126+
127+
var result = db.query(mapFunction, null, {
128+
startkey: 30,
129+
endkey: 39,
130+
include_docs: true,
131+
att_encoding_info: true
132+
});
133+
134+
T(result.rows.length == 10);
135+
T(result.rows[0].value == 3);
136+
T(result.rows[0].doc._attachments['baz.txt'].data === undefined);
137+
T(result.rows[0].doc._attachments['baz.txt'].stub === true);
138+
T(result.rows[0].doc._attachments['baz.txt'].encoding === "gzip");
139+
T(result.rows[0].doc._attachments['baz.txt'].encoded_length === 47);
98140
};

share/www/script/test/changes.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,74 @@ couchTests.changes = function(debug) {
646646
T(changes[0][1] === "3");
647647
T(changes[1][1] === "4");
648648

649+
// COUCHDB-1923
650+
T(db.deleteDb());
651+
T(db.createDb());
652+
653+
var attachmentData = "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=";
654+
655+
db.bulkSave(makeDocs(20, 30, {
656+
_attachments:{
657+
"foo.txt": {
658+
content_type:"text/plain",
659+
data: attachmentData
660+
},
661+
"bar.txt": {
662+
content_type:"text/plain",
663+
data: attachmentData
664+
}
665+
}
666+
}));
667+
668+
var mapFunction = function(doc) {
669+
var count = 0;
670+
671+
for(var idx in doc._attachments) {
672+
count = count + 1;
673+
}
674+
675+
emit(parseInt(doc._id), count);
676+
};
677+
678+
var req = CouchDB.request("GET", "/test_suite_db/_changes?include_docs=true");
679+
var resp = JSON.parse(req.responseText);
680+
681+
T(resp.results.length == 10);
682+
T(resp.results[0].doc._attachments['foo.txt'].stub === true);
683+
T(resp.results[0].doc._attachments['foo.txt'].data === undefined);
684+
T(resp.results[0].doc._attachments['foo.txt'].encoding === undefined);
685+
T(resp.results[0].doc._attachments['foo.txt'].encoded_length === undefined);
686+
T(resp.results[0].doc._attachments['bar.txt'].stub === true);
687+
T(resp.results[0].doc._attachments['bar.txt'].data === undefined);
688+
T(resp.results[0].doc._attachments['bar.txt'].encoding === undefined);
689+
T(resp.results[0].doc._attachments['bar.txt'].encoded_length === undefined);
690+
691+
var req = CouchDB.request("GET", "/test_suite_db/_changes?include_docs=true&attachments=true");
692+
var resp = JSON.parse(req.responseText);
693+
694+
T(resp.results.length == 10);
695+
T(resp.results[0].doc._attachments['foo.txt'].stub === undefined);
696+
T(resp.results[0].doc._attachments['foo.txt'].data === attachmentData);
697+
T(resp.results[0].doc._attachments['foo.txt'].encoding === undefined);
698+
T(resp.results[0].doc._attachments['foo.txt'].encoded_length === undefined);
699+
T(resp.results[0].doc._attachments['bar.txt'].stub === undefined);
700+
T(resp.results[0].doc._attachments['bar.txt'].data == attachmentData);
701+
T(resp.results[0].doc._attachments['bar.txt'].encoding === undefined);
702+
T(resp.results[0].doc._attachments['bar.txt'].encoded_length === undefined);
703+
704+
var req = CouchDB.request("GET", "/test_suite_db/_changes?include_docs=true&att_encoding_info=true");
705+
var resp = JSON.parse(req.responseText);
706+
707+
T(resp.results.length == 10);
708+
T(resp.results[0].doc._attachments['foo.txt'].stub === true);
709+
T(resp.results[0].doc._attachments['foo.txt'].data === undefined);
710+
T(resp.results[0].doc._attachments['foo.txt'].encoding === "gzip");
711+
T(resp.results[0].doc._attachments['foo.txt'].encoded_length === 47);
712+
T(resp.results[0].doc._attachments['bar.txt'].stub === true);
713+
T(resp.results[0].doc._attachments['bar.txt'].data === undefined);
714+
T(resp.results[0].doc._attachments['bar.txt'].encoding === "gzip");
715+
T(resp.results[0].doc._attachments['bar.txt'].encoded_length === 47);
716+
649717
// cleanup
650718
db.deleteDb();
651719
};

src/couch_mrview/include/couch_mrview.hrl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
stale = false,
7373
inclusive_end = true,
7474
include_docs = false,
75+
doc_options = [],
7576
update_seq=false,
7677
conflicts,
7778
callback,

src/couch_mrview/src/couch_mrview_http.erl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,22 @@ parse_qs(Key, Val, Args) ->
348348
Args#mrargs{inclusive_end=parse_boolean(Val)};
349349
"include_docs" ->
350350
Args#mrargs{include_docs=parse_boolean(Val)};
351+
"attachments" ->
352+
case parse_boolean(Val) of
353+
true ->
354+
Opts = Args#mrargs.doc_options,
355+
Args#mrargs{doc_options=[attachments|Opts]};
356+
false ->
357+
Args
358+
end;
359+
"att_encoding_info" ->
360+
case parse_boolean(Val) of
361+
true ->
362+
Opts = Args#mrargs.doc_options,
363+
Args#mrargs{doc_options=[att_encoding_info|Opts]};
364+
false ->
365+
Args
366+
end;
351367
"update_seq" ->
352368
Args#mrargs{update_seq=parse_boolean(Val)};
353369
"conflicts" ->

src/couch_mrview/src/couch_mrview_util.erl

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -668,24 +668,24 @@ expand_dups([KV | Rest], Acc) ->
668668

669669
maybe_load_doc(_Db, _DI, #mrargs{include_docs=false}) ->
670670
[];
671-
maybe_load_doc(Db, #doc_info{}=DI, #mrargs{conflicts=true}) ->
672-
doc_row(couch_index_util:load_doc(Db, DI, [conflicts]));
673-
maybe_load_doc(Db, #doc_info{}=DI, _Args) ->
674-
doc_row(couch_index_util:load_doc(Db, DI, [])).
671+
maybe_load_doc(Db, #doc_info{}=DI, #mrargs{conflicts=true, doc_options=Opts}) ->
672+
doc_row(couch_index_util:load_doc(Db, DI, [conflicts]), Opts);
673+
maybe_load_doc(Db, #doc_info{}=DI, #mrargs{doc_options=Opts}) ->
674+
doc_row(couch_index_util:load_doc(Db, DI, []), Opts).
675675

676676

677677
maybe_load_doc(_Db, _Id, _Val, #mrargs{include_docs=false}) ->
678678
[];
679-
maybe_load_doc(Db, Id, Val, #mrargs{conflicts=true}) ->
680-
doc_row(couch_index_util:load_doc(Db, docid_rev(Id, Val), [conflicts]));
681-
maybe_load_doc(Db, Id, Val, _Args) ->
682-
doc_row(couch_index_util:load_doc(Db, docid_rev(Id, Val), [])).
679+
maybe_load_doc(Db, Id, Val, #mrargs{conflicts=true, doc_options=Opts}) ->
680+
doc_row(couch_index_util:load_doc(Db, docid_rev(Id, Val), [conflicts]), Opts);
681+
maybe_load_doc(Db, Id, Val, #mrargs{doc_options=Opts}) ->
682+
doc_row(couch_index_util:load_doc(Db, docid_rev(Id, Val), []), Opts).
683683

684684

685-
doc_row(null) ->
685+
doc_row(null, _Opts) ->
686686
[{doc, null}];
687-
doc_row(Doc) ->
688-
[{doc, couch_doc:to_json_obj(Doc, [])}].
687+
doc_row(Doc, Opts) ->
688+
[{doc, couch_doc:to_json_obj(Doc, Opts)}].
689689

690690

691691
docid_rev(Id, {Props}) ->

src/couchdb/couch_changes.erl

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
resp_type,
3030
limit,
3131
include_docs,
32+
doc_options,
3233
conflicts,
3334
timeout,
3435
timeout_fun
@@ -272,6 +273,7 @@ start_sending_changes(Callback, UserAcc, ResponseType) ->
272273
build_acc(Args, Callback, UserAcc, Db, StartSeq, Prepend, Timeout, TimeoutFun) ->
273274
#changes_args{
274275
include_docs = IncludeDocs,
276+
doc_options = DocOpts,
275277
conflicts = Conflicts,
276278
limit = Limit,
277279
feed = ResponseType,
@@ -287,6 +289,7 @@ build_acc(Args, Callback, UserAcc, Db, StartSeq, Prepend, Timeout, TimeoutFun) -
287289
resp_type = ResponseType,
288290
limit = Limit,
289291
include_docs = IncludeDocs,
292+
doc_options = DocOpts,
290293
conflicts = Conflicts,
291294
timeout = Timeout,
292295
timeout_fun = TimeoutFun
@@ -498,7 +501,12 @@ changes_row(Results, DocInfo, Acc) ->
498501
#doc_info{
499502
id = Id, high_seq = Seq, revs = [#rev_info{deleted = Del} | _]
500503
} = DocInfo,
501-
#changes_acc{db = Db, include_docs = IncDoc, conflicts = Conflicts} = Acc,
504+
#changes_acc{
505+
db = Db,
506+
include_docs = IncDoc,
507+
doc_options = DocOpts,
508+
conflicts = Conflicts
509+
} = Acc,
502510
{[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Results}] ++
503511
deleted_item(Del) ++ case IncDoc of
504512
true ->
@@ -508,8 +516,10 @@ changes_row(Results, DocInfo, Acc) ->
508516
end,
509517
Doc = couch_index_util:load_doc(Db, DocInfo, Opts),
510518
case Doc of
511-
null -> [{doc, null}];
512-
_ -> [{doc, couch_doc:to_json_obj(Doc, [])}]
519+
null ->
520+
[{doc, null}];
521+
_ ->
522+
[{doc, couch_doc:to_json_obj(Doc, DocOpts)}]
513523
end;
514524
false ->
515525
[]

0 commit comments

Comments
 (0)