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.
diff --git a/script/test/attachment_views.js b/script/test/attachment_views.js
index a92a8ad..b55aabe 100644
--- a/script/test/attachment_views.js
+++ b/script/test/attachment_views.js
@@ -19,13 +19,15 @@
 
   // 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 @@
     _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 @@
     _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 @@
   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/script/test/changes.js b/script/test/changes.js
index fcd8306..0fba9f9 100644
--- a/script/test/changes.js
+++ b/script/test/changes.js
@@ -646,6 +646,74 @@
   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();
 };