summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Joseph Davis <davisp@apache.org>2010-11-10 19:44:05 +0000
committerPaul Joseph Davis <davisp@apache.org>2010-11-10 19:44:05 +0000
commit9e53f467f89bc3942f8795f6d07f61d3f5115f88 (patch)
tree8480eaf7c98101322fc3b5fc00891685082fc944
parent1f7e7cb5c22302788e232ce455fd0505f6420201 (diff)
Support the keys parameter in GET requests.
You can now request a list of keys in the query string using a query string like: ?keys=["foo", "bar"] Your query string obviously needs to be properly escaped. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@1033676 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--THANKS1
-rw-r--r--share/www/script/couch.js2
-rw-r--r--share/www/script/test/list_views.js10
-rw-r--r--share/www/script/test/view_multi_key_all_docs.js37
-rw-r--r--share/www/script/test/view_multi_key_design.js78
-rw-r--r--src/couchdb/couch_httpd.erl11
-rw-r--r--src/couchdb/couch_httpd_db.erl3
-rw-r--r--src/couchdb/couch_httpd_show.erl6
-rw-r--r--src/couchdb/couch_httpd_view.erl3
9 files changed, 145 insertions, 6 deletions
diff --git a/THANKS b/THANKS
index 3ed1534c..e6d7e87f 100644
--- a/THANKS
+++ b/THANKS
@@ -71,5 +71,6 @@ suggesting improvements or submitting changes. Some of these people are:
* Klaus Trainer <klaus.trainer@web.de>
* Dale Harvey <dale@arandomurl.com>
* Juuso Väänänen <juuso@vaananen.org>
+ * Jeff Zellner <jeff.zellner@gmail.com>
For a list of authors see the `AUTHORS` file.
diff --git a/share/www/script/couch.js b/share/www/script/couch.js
index 913f58fb..bcc19652 100644
--- a/share/www/script/couch.js
+++ b/share/www/script/couch.js
@@ -272,7 +272,7 @@ function CouchDB(name, httpHeaders) {
for (var name in options) {
if (!options.hasOwnProperty(name)) { continue; };
var value = options[name];
- if (name == "key" || name == "startkey" || name == "endkey") {
+ if (name == "key" || name == "keys" || name == "startkey" || name == "endkey") {
value = toJSON(value);
}
buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
diff --git a/share/www/script/test/list_views.js b/share/www/script/test/list_views.js
index 8578622a..e98a212e 100644
--- a/share/www/script/test/list_views.js
+++ b/share/www/script/test/list_views.js
@@ -327,6 +327,16 @@ couchTests.list_views = function(debug) {
T(/FirstKey: 2/.test(xhr.responseText));
T(/LastKey: 7/.test(xhr.responseText));
+ // multi-key fetch with GET
+ var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/basicView" +
+ "?keys=[2,4,5,7]");
+
+ T(xhr.status == 200, "multi key");
+ T(!(/Key: 1 /.test(xhr.responseText)));
+ T(/Key: 2/.test(xhr.responseText));
+ T(/FirstKey: 2/.test(xhr.responseText));
+ T(/LastKey: 7/.test(xhr.responseText));
+
// no multi-key fetch allowed when group=false
xhr = CouchDB.request("POST", "/test_suite_db/_design/lists/_list/simpleForm/withReduce?group=false", {
body: '{"keys":[2,4,5,7]}'
diff --git a/share/www/script/test/view_multi_key_all_docs.js b/share/www/script/test/view_multi_key_all_docs.js
index 62e49665..1113be4d 100644
--- a/share/www/script/test/view_multi_key_all_docs.js
+++ b/share/www/script/test/view_multi_key_all_docs.js
@@ -25,24 +25,52 @@ couchTests.view_multi_key_all_docs = function(debug) {
for(var i=0; i<rows.length; i++)
T(rows[i].id == keys[i]);
+ // keys in GET parameters
+ rows = db.allDocs({keys:keys}, null).rows;
+ T(rows.length == keys.length);
+ for(var i=0; i<rows.length; i++)
+ T(rows[i].id == keys[i]);
+
rows = db.allDocs({limit: 1}, keys).rows;
T(rows.length == 1);
T(rows[0].id == keys[0]);
+ // keys in GET parameters
+ rows = db.allDocs({limit: 1, keys: keys}, null).rows;
+ T(rows.length == 1);
+ T(rows[0].id == keys[0]);
+
rows = db.allDocs({skip: 2}, keys).rows;
T(rows.length == 3);
for(var i=0; i<rows.length; i++)
T(rows[i].id == keys[i+2]);
+ // keys in GET parameters
+ rows = db.allDocs({skip: 2, keys: keys}, null).rows;
+ T(rows.length == 3);
+ for(var i=0; i<rows.length; i++)
+ T(rows[i].id == keys[i+2]);
+
rows = db.allDocs({descending: "true"}, keys).rows;
T(rows.length == keys.length);
for(var i=0; i<rows.length; i++)
T(rows[i].id == keys[keys.length-i-1]);
+ // keys in GET parameters
+ rows = db.allDocs({descending: "true", keys: keys}, null).rows;
+ T(rows.length == keys.length);
+ for(var i=0; i<rows.length; i++)
+ T(rows[i].id == keys[keys.length-i-1]);
+
rows = db.allDocs({descending: "true", skip: 3, limit:1}, keys).rows;
T(rows.length == 1);
T(rows[0].id == keys[1]);
+ // keys in GET parameters
+ rows = db.allDocs({descending: "true", skip: 3, limit:1, keys: keys}, null).rows;
+ T(rows.length == 1);
+ T(rows[0].id == keys[1]);
+
// Check we get invalid rows when the key doesn't exist
rows = db.allDocs({}, [1, "i_dont_exist", "0"]).rows;
T(rows.length == 3);
@@ -51,4 +79,13 @@ couchTests.view_multi_key_all_docs = function(debug) {
T(rows[1].error == "not_found");
T(!rows[1].id);
T(rows[2].id == rows[2].key && rows[2].key == "0");
+
+ // keys in GET parameters
+ rows = db.allDocs({keys: [1, "i_dont_exist", "0"]}, null).rows;
+ T(rows.length == 3);
+ T(rows[0].error == "not_found");
+ T(!rows[0].id);
+ T(rows[1].error == "not_found");
+ T(!rows[1].id);
+ T(rows[2].id == rows[2].key && rows[2].key == "0");
};
diff --git a/share/www/script/test/view_multi_key_design.js b/share/www/script/test/view_multi_key_design.js
index 19d270e5..b3cec657 100644
--- a/share/www/script/test/view_multi_key_design.js
+++ b/share/www/script/test/view_multi_key_design.js
@@ -54,6 +54,13 @@ couchTests.view_multi_key_design = function(debug) {
T(rows[i].key == rows[i].value);
}
+ // with GET keys
+ rows = db.view("test/all_docs",{keys:keys},null).rows;
+ for(var i=0;i<rows.length; i++) {
+ T(keys.indexOf(rows[i].key) != -1);
+ T(rows[i].key == rows[i].value);
+ }
+
var reduce = db.view("test/summate",{group:true},keys).rows;
T(reduce.length == keys.length);
for(var i=0; i<reduce.length; i++) {
@@ -61,8 +68,18 @@ couchTests.view_multi_key_design = function(debug) {
T(reduce[i].key == reduce[i].value);
}
+ // with GET keys
+ reduce = db.view("test/summate",{group:true,keys:keys},null).rows;
+ T(reduce.length == keys.length);
+ for(var i=0; i<reduce.length; i++) {
+ T(keys.indexOf(reduce[i].key) != -1);
+ T(reduce[i].key == reduce[i].value);
+ }
+
// Test that invalid parameter combinations get rejected
var badargs = [{startkey:0}, {endkey:0}, {key: 0}, {group_level: 2}];
+ var getbadargs = [{startkey:0, keys:keys}, {endkey:0, keys:keys},
+ {key:0, keys:keys}, {group_level: 2, keys:keys}];
for(var i in badargs)
{
try {
@@ -71,6 +88,13 @@ couchTests.view_multi_key_design = function(debug) {
} catch (e) {
T(e.error == "query_parse_error");
}
+
+ try {
+ db.view("test/all_docs",getbadargs[i],null);
+ T(0==1);
+ } catch (e) {
+ T(e.error = "query_parse_error");
+ }
}
try {
@@ -80,10 +104,20 @@ couchTests.view_multi_key_design = function(debug) {
T(e.error == "query_parse_error");
}
+ try {
+ db.view("test/summate",{keys:keys},null);
+ T(0==1);
+ } catch (e) {
+ T(e.error == "query_parse_error");
+ }
+
// Test that a map & reduce containing func support keys when reduce=false
resp = db.view("test/summate", {reduce: false}, keys);
T(resp.rows.length == 5);
+ resp = db.view("test/summate", {reduce: false, keys: keys}, null);
+ T(resp.rows.length == 5);
+
// Check that limiting by startkey_docid and endkey_docid get applied
// as expected.
var curr = db.view("test/multi_emit", {startkey_docid: 21, endkey_docid: 23}, [0, 2]).rows;
@@ -96,34 +130,66 @@ couchTests.view_multi_key_design = function(debug) {
T(curr[i].value == exp_val[i]);
}
+ curr = db.view("test/multi_emit", {startkey_docid: 21, endkey_docid: 23, keys: [0, 2]}, null).rows;
+ T(curr.length == 6);
+ for( var i = 0 ; i < 6 ; i++)
+ {
+ T(curr[i].key == exp_key[i]);
+ T(curr[i].value == exp_val[i]);
+ }
+
// Check limit works
curr = db.view("test/all_docs", {limit: 1}, keys).rows;
T(curr.length == 1);
T(curr[0].key == 10);
+ curr = db.view("test/all_docs", {limit: 1, keys: keys}, null).rows;
+ T(curr.length == 1);
+ T(curr[0].key == 10);
+
// Check offset works
curr = db.view("test/multi_emit", {skip: 1}, [0]).rows;
T(curr.length == 99);
T(curr[0].value == 1);
+ curr = db.view("test/multi_emit", {skip: 1, keys: [0]}, null).rows;
+ T(curr.length == 99);
+ T(curr[0].value == 1);
+
// Check that dir works
curr = db.view("test/multi_emit", {descending: "true"}, [1]).rows;
T(curr.length == 100);
T(curr[0].value == 99);
T(curr[99].value == 0);
+ curr = db.view("test/multi_emit", {descending: "true", keys: [1]}, null).rows;
+ T(curr.length == 100);
+ T(curr[0].value == 99);
+ T(curr[99].value == 0);
+
// Check a couple combinations
curr = db.view("test/multi_emit", {descending: "true", skip: 3, limit: 2}, [2]).rows;
T(curr.length, 2);
T(curr[0].value == 96);
T(curr[1].value == 95);
+ curr = db.view("test/multi_emit", {descending: "true", skip: 3, limit: 2, keys: [2]}, null).rows;
+ T(curr.length, 2);
+ T(curr[0].value == 96);
+ T(curr[1].value == 95);
+
curr = db.view("test/multi_emit", {skip: 2, limit: 3, startkey_docid: "13"}, [0]).rows;
T(curr.length == 3);
T(curr[0].value == 15);
T(curr[1].value == 16);
T(curr[2].value == 17);
+ curr = db.view("test/multi_emit", {skip: 2, limit: 3, startkey_docid: "13", keys: [0]}, null).rows;
+ T(curr.length == 3);
+ T(curr[0].value == 15);
+ T(curr[1].value == 16);
+ T(curr[2].value == 17);
+
curr = db.view("test/multi_emit",
{skip: 1, limit: 5, startkey_docid: "25", endkey_docid: "27"}, [1]).rows;
T(curr.length == 2);
@@ -131,8 +197,20 @@ couchTests.view_multi_key_design = function(debug) {
T(curr[1].value == 27);
curr = db.view("test/multi_emit",
+ {skip: 1, limit: 5, startkey_docid: "25", endkey_docid: "27", keys: [1]}, null).rows;
+ T(curr.length == 2);
+ T(curr[0].value == 26);
+ T(curr[1].value == 27);
+
+ curr = db.view("test/multi_emit",
{skip: 1, limit: 5, startkey_docid: "28", endkey_docid: "26", descending: "true"}, [1]).rows;
T(curr.length == 2);
T(curr[0].value == 27);
T(curr[1].value == 26);
+
+ curr = db.view("test/multi_emit",
+ {skip: 1, limit: 5, startkey_docid: "28", endkey_docid: "26", descending: "true", keys: [1]}, null).rows;
+ T(curr.length == 2);
+ T(curr[0].value == 27);
+ T(curr[1].value == 26);
};
diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl
index e1f6fc28..dea41eeb 100644
--- a/src/couchdb/couch_httpd.erl
+++ b/src/couchdb/couch_httpd.erl
@@ -15,7 +15,8 @@
-export([start_link/0, start_link/1, stop/0, handle_request/5]).
--export([header_value/2,header_value/3,qs_value/2,qs_value/3,qs/1,path/1,absolute_uri/2,body_length/1]).
+-export([header_value/2,header_value/3,qs_value/2,qs_value/3,qs/1,qs_json_value/3]).
+-export([path/1,absolute_uri/2,body_length/1]).
-export([verify_is_server_admin/1,unquote/1,quote/1,recv/2,recv_chunked/4,error_info/1]).
-export([make_fun_spec_strs/1]).
-export([make_arity_1_fun/1, make_arity_2_fun/1, make_arity_3_fun/1]).
@@ -384,6 +385,14 @@ qs_value(Req, Key) ->
qs_value(Req, Key, Default) ->
couch_util:get_value(Key, qs(Req), Default).
+qs_json_value(Req, Key, Default) ->
+ case qs_value(Req, Key, Default) of
+ Default ->
+ Default;
+ Result ->
+ ?JSON_DECODE(Result)
+ end.
+
qs(#httpd{mochi_req=MochiReq}) ->
MochiReq:parse_qs().
diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
index 77b891fe..2ef2fe4f 100644
--- a/src/couchdb/couch_httpd_db.erl
+++ b/src/couchdb/couch_httpd_db.erl
@@ -375,7 +375,8 @@ db_req(#httpd{path_parts=[_,<<"_purge">>]}=Req, _Db) ->
send_method_not_allowed(Req, "POST");
db_req(#httpd{method='GET',path_parts=[_,<<"_all_docs">>]}=Req, Db) ->
- all_docs_view(Req, Db, nil);
+ Keys = couch_httpd:qs_json_value(Req, "keys", nil),
+ all_docs_view(Req, Db, Keys);
db_req(#httpd{method='POST',path_parts=[_,<<"_all_docs">>]}=Req, Db) ->
couch_httpd:validate_ctype(Req, "application/json"),
diff --git a/src/couchdb/couch_httpd_show.erl b/src/couchdb/couch_httpd_show.erl
index fa8cd972..70dd82e1 100644
--- a/src/couchdb/couch_httpd_show.erl
+++ b/src/couchdb/couch_httpd_show.erl
@@ -153,12 +153,14 @@ send_doc_update_response(Req, Db, DDoc, UpdateName, Doc, DocId) ->
% view-list request with view and list from same design doc.
handle_view_list_req(#httpd{method='GET',
path_parts=[_, _, DesignName, _, ListName, ViewName]}=Req, Db, DDoc) ->
- handle_view_list(Req, Db, DDoc, ListName, {DesignName, ViewName}, nil);
+ Keys = couch_httpd:qs_json_value(Req, "keys", nil),
+ handle_view_list(Req, Db, DDoc, ListName, {DesignName, ViewName}, Keys);
% view-list request with view and list from different design docs.
handle_view_list_req(#httpd{method='GET',
path_parts=[_, _, _, _, ListName, ViewDesignName, ViewName]}=Req, Db, DDoc) ->
- handle_view_list(Req, Db, DDoc, ListName, {ViewDesignName, ViewName}, nil);
+ Keys = couch_httpd:qs_json_value(Req, "keys", nil),
+ handle_view_list(Req, Db, DDoc, ListName, {ViewDesignName, ViewName}, Keys);
handle_view_list_req(#httpd{method='GET'}=Req, _Db, _DDoc) ->
send_error(Req, 404, <<"list_error">>, <<"Invalid path.">>);
diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl
index 8829de02..63e737e8 100644
--- a/src/couchdb/couch_httpd_view.erl
+++ b/src/couchdb/couch_httpd_view.erl
@@ -57,7 +57,8 @@ design_doc_view(Req, Db, DName, ViewName, Keys) ->
handle_view_req(#httpd{method='GET',
path_parts=[_, _, DName, _, ViewName]}=Req, Db, _DDoc) ->
- design_doc_view(Req, Db, DName, ViewName, nil);
+ Keys = couch_httpd:qs_json_value(Req, "keys", nil),
+ design_doc_view(Req, Db, DName, ViewName, Keys);
handle_view_req(#httpd{method='POST',
path_parts=[_, _, DName, _, ViewName]}=Req, Db, _DDoc) ->