Skip to content

Commit

Permalink
Add the ability to use map view function to filter changes instead of
Browse files Browse the repository at this point in the history
duplicating the 
effort in writing a filter function that does the same, which is 
apparently done a lot. 

Each time a value could be emitted, a change is returned. The url is : 

http://127.0.0.1:5984/testdb/_changes?filter=_view&view=dname/viewname 



git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@1053132 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
Benoit Chesneau committed Dec 27, 2010
1 parent c802e0d commit 56086f0
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 18 deletions.
44 changes: 33 additions & 11 deletions share/server/filter.js
Expand Up @@ -10,14 +10,36 @@
// License for the specific language governing permissions and limitations under
// the License.

var Filter = {
filter : function(fun, ddoc, args) {
var results = [];
var docs = args[0];
var req = args[1];
for (var i=0; i < docs.length; i++) {
results.push((fun.apply(ddoc, [docs[i], req]) && true) || false);
};
respond([true, results]);
}
};
var Filter = (function() {

var view_emit = false;

return {
emit : function(key, value) {
view_emit = true;
},
filter : function(fun, ddoc, args) {
var results = [];
var docs = args[0];
var req = args[1];
for (var i=0; i < docs.length; i++) {
results.push((fun.apply(ddoc, [docs[i], req]) && true) || false);
};
respond([true, results]);
},
filter_view : function(fun, ddoc, args) {
// recompile
var source = fun.toSource();
fun = evalcx(source, filter_sandbox);

var results = [];
var docs = args[0];
for (var i=0; i < docs.length; i++) {
view_emit = false;
fun(docs[i]);
results.push((view_emit && true) || false);
};
respond([true, results]);
}
}
})();
18 changes: 18 additions & 0 deletions share/server/loop.js
Expand Up @@ -11,6 +11,7 @@
// the License.

var sandbox = null;
var filter_sandbox = null;

function init_sandbox() {
try {
Expand All @@ -33,6 +34,22 @@ function init_sandbox() {
};
init_sandbox();

function init_filter_sandbox() {
try {
filter_sandbox = evalcx('');
for (var p in sandbox) {
if (sandbox.hasOwnProperty(p)) {
filter_sandbox[p] = sandbox[p];
}
}
filter_sandbox.emit = Filter.emit;
} catch(e) {
log(e.toSource());
}
};

init_filter_sandbox();

// Commands are in the form of json arrays:
// ["commandname",..optional args...]\n
//
Expand All @@ -43,6 +60,7 @@ var DDoc = (function() {
"lists" : Render.list,
"shows" : Render.show,
"filters" : Filter.filter,
"views" : Filter.filter_view,
"updates" : Render.update,
"validate_doc_update" : Validate.validate
};
Expand Down
26 changes: 20 additions & 6 deletions share/www/script/test/changes.js
Expand Up @@ -190,7 +190,7 @@ couchTests.changes = function(debug) {
_id : "_design/changes_filter",
"filters" : {
"bop" : "function(doc, req) { return (doc.bop);}",
"dynamic" : stringFun(function(doc, req) {
"dynamic" : stringFun(function(doc, req) {
var field = req.query.field;
return doc[field];
}),
Expand All @@ -205,6 +205,13 @@ couchTests.changes = function(debug) {
views : {
local_seq : {
map : "function(doc) {emit(doc._local_seq, null)}"
},
blah: {
map : 'function(doc) {' +
' if (doc._id == "blah") {' +
' emit(doc._id, null);' +
' }' +
'}'
}
}
};
Expand All @@ -213,7 +220,7 @@ couchTests.changes = function(debug) {

var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/bop");
var resp = JSON.parse(req.responseText);
T(resp.results.length == 0);
T(resp.results.length == 0);

db.save({"bop" : "foom"});
db.save({"bop" : false});
Expand Down Expand Up @@ -319,7 +326,16 @@ couchTests.changes = function(debug) {
var resp = JSON.parse(req.responseText);
var expect = (!is_safari && xhr) ? 3: 1;
TEquals(expect, resp.results.length, "should return matching rows");


// test filter on view function (map)
//
T(db.save({"_id":"blah", "bop" : "plankton"}).ok);
var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=_view&view=changes_filter/blah");
var resp = JSON.parse(req.responseText);
T(resp.results.length === 1);
T(resp.results[0].id === "blah");


// test for userCtx
run_on_modified_server(
[{section: "httpd",
Expand Down Expand Up @@ -407,7 +423,7 @@ couchTests.changes = function(debug) {

var options = {
headers: {"Content-Type": "application/json"},
body: JSON.stringify({"doc_ids": ["something", "anotherthing", "andmore"]})
body: JSON.stringify({"doc_ids": ["something", "anotherthing", "andmore"]})
};

var req = CouchDB.request("POST", "/test_suite_db/_changes?filter=_doc_ids", options);
Expand Down Expand Up @@ -450,7 +466,6 @@ couchTests.changes = function(debug) {

T(db.save({"_id":"andmore", "bop" : "plankton"}).ok);


waitForSuccess(function() {
if (xhr.readyState != 4) {
throw("still waiting");
Expand All @@ -461,7 +476,6 @@ couchTests.changes = function(debug) {
T(line.seq == 8);
T(line.id == "andmore");
}

});

};
Expand Down
37 changes: 37 additions & 0 deletions src/couchdb/couch_changes.erl
Expand Up @@ -130,6 +130,9 @@ builtin_filter_fun("_doc_ids", Style, #httpd{method='GET'}=Req, _Db) ->
filter_docids(DocIds, Style);
builtin_filter_fun("_design", Style, _Req, _Db) ->
filter_designdoc(Style);
builtin_filter_fun("_view", Style, Req, Db) ->
ViewName = couch_httpd:qs_value(Req, "view", ""),
filter_view(ViewName, Style, Db);
builtin_filter_fun(_FilterName, _Style, _Req, _Db) ->
throw({bad_request, "unknown builtin filter name"}).

Expand All @@ -153,6 +156,40 @@ filter_designdoc(Style) ->
end
end.

filter_view("", _Style, _Db) ->
throw({bad_request, "`view` filter parameter is not provided."});
filter_view(ViewName, Style, Db) ->
case [list_to_binary(couch_httpd:unquote(Part))
|| Part <- string:tokens(ViewName, "/")] of
[] ->
throw({bad_request, "Invalid `view` parameter."});
[DName, VName] ->
DesignId = <<"_design/", DName/binary>>,
DDoc = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []),
% validate that the ddoc has the filter fun
#doc{body={Props}} = DDoc,
couch_util:get_nested_json_value({Props}, [<<"views">>, VName]),
fun(DocInfo) ->
DocInfos =
case Style of
main_only ->
[DocInfo];
all_docs ->
[DocInfo#doc_info{revs=[Rev]}|| Rev <- DocInfo#doc_info.revs]
end,
Docs = [Doc || {ok, Doc} <- [
couch_db:open_doc(Db, DocInfo2, [deleted, conflicts])
|| DocInfo2 <- DocInfos]],

{ok, Passes} = couch_query_servers:filter_view(
DDoc, VName, Docs
),
[{[{<<"rev">>, couch_doc:rev_to_str({RevPos,RevId})}]}
|| {Pass, #doc{revs={RevPos,[RevId|_]}}}
<- lists:zip(Passes, Docs), Pass == true]
end
end.

builtin_results(Style, [#rev_info{rev=Rev}|_]=Revs) ->
case Style of
main_only ->
Expand Down
9 changes: 8 additions & 1 deletion src/couchdb/couch_query_servers.erl
Expand Up @@ -19,6 +19,7 @@
-export([start_doc_map/3, map_docs/2, stop_doc_map/1]).
-export([reduce/3, rereduce/3,validate_doc_update/5]).
-export([filter_docs/5]).
-export([filter_view/3]).

-export([with_ddoc_proc/2, proc_prompt/2, ddoc_prompt/3, ddoc_proc_prompt/3, json_doc/1]).

Expand Down Expand Up @@ -229,6 +230,11 @@ json_doc(nil) -> null;
json_doc(Doc) ->
couch_doc:to_json_obj(Doc, [revs]).

filter_view(DDoc, VName, Docs) ->
JsonDocs = [couch_doc:to_json_obj(Doc, [revs]) || Doc <- Docs],
[true, Passes] = ddoc_prompt(DDoc, [<<"views">>, VName, <<"map">>], [JsonDocs]),
{ok, Passes}.

filter_docs(Req, Db, DDoc, FName, Docs) ->
JsonReq = case Req of
{json_req, JsonObj} ->
Expand All @@ -237,7 +243,8 @@ filter_docs(Req, Db, DDoc, FName, Docs) ->
couch_httpd_external:json_req_obj(HttpReq, Db)
end,
JsonDocs = [couch_doc:to_json_obj(Doc, [revs]) || Doc <- Docs],
[true, Passes] = ddoc_prompt(DDoc, [<<"filters">>, FName], [JsonDocs, JsonReq]),
[true, Passes] = ddoc_prompt(DDoc, [<<"filters">>, FName],
[JsonDocs, JsonReq]),
{ok, Passes}.

ddoc_proc_prompt({Proc, DDocId}, FunPath, Args) ->
Expand Down

0 comments on commit 56086f0

Please sign in to comment.