From 292091ece9a97c51f92e2dda547e732848db1515 Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Thu, 19 Mar 2009 11:20:36 +0000 Subject: inclusive_end query option for views, defaults to true (does not change current behavior). inclusive_end=false treats endkey as an open interval. this is progress on COUCHDB-194. I think it is enough for 0.9, we can discuss switching the default behavior, and adding a similar option for startkey. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@755926 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/script/test/view_collation.js | 24 ++++++++++++ src/couchdb/couch_db.hrl | 1 + src/couchdb/couch_httpd_view.erl | 67 ++++++++++++++++++++++----------- 3 files changed, 70 insertions(+), 22 deletions(-) diff --git a/share/www/script/test/view_collation.js b/share/www/script/test/view_collation.js index bcb63404..f59204c7 100644 --- a/share/www/script/test/view_collation.js +++ b/share/www/script/test/view_collation.js @@ -85,4 +85,28 @@ couchTests.view_collation = function(debug) { rows = db.query(queryFun, null, queryOptions).rows; T(rows.length == 1 && equals(rows[0].key, values[i])); } + + // test inclusive_end=true (the default) + // the inclusive_end=true functionality is limited to endkey currently + // if you need inclusive_start=false for startkey, please do implement. ;) + var rows = db.query(queryFun, null, {endkey : "b", inclusive_end:true}).rows; + T(rows[rows.length-1].key == "b") + // descending=true + var rows = db.query(queryFun, null, {endkey : "b", + descending:true, inclusive_end:true}).rows; + T(rows[rows.length-1].key == "b") + + // test inclusive_end=false + var rows = db.query(queryFun, null, {endkey : "b", inclusive_end:false}).rows; + T(rows[rows.length-1].key == "aa") + // descending=true + var rows = db.query(queryFun, null, {endkey : "b", + descending:true, inclusive_end:false}).rows; + T(rows[rows.length-1].key == "B") + + // inclusive_end=false overrides endkey_docid + var rows = db.query(queryFun, null, { + endkey : "b", endkey_docid: "b", + inclusive_end:false}).rows; + T(rows[rows.length-1].key == "aa") }; diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl index f460f450..6d9081a8 100644 --- a/src/couchdb/couch_db.hrl +++ b/src/couchdb/couch_db.hrl @@ -155,6 +155,7 @@ skip = 0, group_level = 0, reduce = true, + inclusive_end=true, % aka a closed-interval include_docs = false }). diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl index 7f338655..4e922d50 100644 --- a/src/couchdb/couch_httpd_view.erl +++ b/src/couchdb/couch_httpd_view.erl @@ -259,7 +259,7 @@ parse_view_query(Req, Keys, IsReduce, IgnoreExtra) -> Args#view_query_args{start_key=JsonKey,end_key=JsonKey}; _ -> Msg = io_lib:format("Query parameter \"~s\" not compatible with multi key mode.", [Key]), - throw({query_parse_error, Msg}) + throw({query_parse_error, ?l2b(Msg)}) end; {"startkey_docid", DocId} -> Args#view_query_args{start_docid=list_to_binary(DocId)}; @@ -271,7 +271,7 @@ parse_view_query(Req, Keys, IsReduce, IgnoreExtra) -> Args#view_query_args{start_key=?JSON_DECODE(Value)}; _ -> Msg = io_lib:format("Query parameter \"~s\" not compatible with multi key mode.", [Key]), - throw({query_parse_error, Msg}) + throw({query_parse_error, ?l2b(Msg)}) end; {"endkey", Value} -> case Keys of @@ -279,27 +279,27 @@ parse_view_query(Req, Keys, IsReduce, IgnoreExtra) -> Args#view_query_args{end_key=?JSON_DECODE(Value)}; _ -> Msg = io_lib:format("Query parameter \"~s\" not compatible with multi key mode.", [Key]), - throw({query_parse_error, Msg}) + throw({query_parse_error, ?l2b(Msg)}) end; {"limit", Value} -> case (catch list_to_integer(Value)) of Limit when is_integer(Limit) -> if Limit < 0 -> Msg = io_lib:format("Limit must be a positive integer: limit=~s", [Value]), - throw({query_parse_error, Msg}); + throw({query_parse_error, ?l2b(Msg)}); true -> Args#view_query_args{limit=Limit} end; _Error -> Msg = io_lib:format("Bad URL query value, number expected: limit=~s", [Value]), - throw({query_parse_error, Msg}) + throw({query_parse_error, ?l2b(Msg)}) end; {"count", Value} -> - throw({query_parse_error, "URL query parameter 'count' has been changed to 'limit'."}); + throw({query_parse_error, <<"URL query parameter 'count' has been changed to 'limit'.">>}); {"stale", "ok"} -> Args#view_query_args{stale=ok}; {"update", "false"} -> - throw({query_parse_error, "URL query parameter 'update=false' has been changed to 'stale=ok'."}); + throw({query_parse_error, <<"URL query parameter 'update=false' has been changed to 'stale=ok'.">>}); {"descending", "true"} -> case Args#view_query_args.direction of fwd -> @@ -329,7 +329,7 @@ parse_view_query(Req, Keys, IsReduce, IgnoreExtra) -> _Error -> Msg = lists:flatten(io_lib:format( "Bad URL query value, number expected: skip=~s", [Value])), - throw({query_parse_error, Msg}) + throw({query_parse_error, ?l2b(Msg)}) end; {"group", Value} -> case Value of @@ -339,7 +339,7 @@ parse_view_query(Req, Keys, IsReduce, IgnoreExtra) -> Args#view_query_args{group_level=0}; _ -> Msg = "Bad URL query value for 'group' expected \"true\" or \"false\".", - throw({query_parse_error, Msg}) + throw({query_parse_error, ?l2b(Msg)}) end; {"group_level", LevelStr} -> case Keys of @@ -347,8 +347,12 @@ parse_view_query(Req, Keys, IsReduce, IgnoreExtra) -> Args#view_query_args{group_level=list_to_integer(LevelStr)}; _ -> Msg = lists:flatten(io_lib:format("Multi-key fetches for a reduce view must include group=true", [])), - throw({query_parse_error, Msg}) + throw({query_parse_error, ?l2b(Msg)}) end; + {"inclusive_end", "true"} -> + Args#view_query_args{inclusive_end=true}; + {"inclusive_end", "false"} -> + Args#view_query_args{inclusive_end=false}; {"reduce", "true"} -> Args#view_query_args{reduce=true}; {"reduce", "false"} -> @@ -361,7 +365,7 @@ parse_view_query(Req, Keys, IsReduce, IgnoreExtra) -> Args#view_query_args{include_docs=false}; _ -> Msg1 = "Bad URL query value for 'include_docs' expected \"true\" or \"false\".", - throw({query_parse_error, Msg1}) + throw({query_parse_error, ?l2b(Msg1)}) end; {"format", _} -> % we just ignore format, so that JS can have it @@ -373,7 +377,7 @@ parse_view_query(Req, Keys, IsReduce, IgnoreExtra) -> false -> Msg = lists:flatten(io_lib:format( "Bad URL query key:~s", [Key])), - throw({query_parse_error, Msg}) + throw({query_parse_error, ?l2b(Msg)}) end end end, #view_query_args{}, QueryList), @@ -381,7 +385,7 @@ parse_view_query(Req, Keys, IsReduce, IgnoreExtra) -> true -> case QueryArgs#view_query_args.include_docs and QueryArgs#view_query_args.reduce of true -> - ErrMsg = "Bad URL query key for reduce operation: include_docs", + ErrMsg = <<"Bad URL query key for reduce operation: include_docs">>, throw({query_parse_error, ErrMsg}); _ -> ok @@ -404,8 +408,7 @@ parse_view_query(Req, Keys, IsReduce, IgnoreExtra) -> #view_query_args{reduce=OptReduce} = QueryArgs, case OptReduce of true -> - Msg = lists:flatten(io_lib:format( - "Multi-key fetches for a reduce view must include group=true", [])), + Msg = <<"Multi-key fetches for a reduce view must include group=true">>, throw({query_parse_error, Msg}); _ -> QueryArgs @@ -419,6 +422,7 @@ make_view_fold_fun(Req, QueryArgs, Etag, Db, #view_query_args{ end_key = EndKey, end_docid = EndDocId, + inclusive_end = InclusiveEnd, direction = Dir } = QueryArgs, @@ -427,7 +431,8 @@ make_view_fold_fun(Req, QueryArgs, Etag, Db, start_response = StartRespFun, send_row = SendRowFun, reduce_count = ReduceCountFun - } = apply_default_helper_funs(HelperFuns, {Dir, EndKey, EndDocId}), + } = apply_default_helper_funs(HelperFuns, + {Dir, EndKey, EndDocId, InclusiveEnd}), #view_query_args{ include_docs = IncludeDocs @@ -467,9 +472,9 @@ apply_default_helper_funs(#view_fold_helper_funs{ passed_end = PassedEnd, start_response = StartResp, send_row = SendRow -}=Helpers, {Dir, EndKey, EndDocId}) -> +}=Helpers, {Dir, EndKey, EndDocId, InclusiveEnd}) -> PassedEnd2 = case PassedEnd of - undefined -> make_passed_end_fun(Dir, EndKey, EndDocId); + undefined -> make_passed_end_fun(Dir, EndKey, EndDocId, InclusiveEnd); _ -> PassedEnd end, @@ -508,15 +513,33 @@ apply_default_helper_funs(#reduce_fold_helper_funs{ send_row = SendRow2 }. -make_passed_end_fun(Dir, EndKey, EndDocId) -> - case Dir of - fwd -> +make_passed_end_fun(fwd, EndKey, EndDocId, InclusiveEnd) -> + case InclusiveEnd of + true -> fun(ViewKey, ViewId) -> couch_view:less_json([EndKey, EndDocId], [ViewKey, ViewId]) end; - rev-> + false -> + fun + (ViewKey, _ViewId) when ViewKey == EndKey -> + true; + (ViewKey, ViewId) -> + couch_view:less_json([EndKey, EndDocId], [ViewKey, ViewId]) + end + end; + +make_passed_end_fun(rev, EndKey, EndDocId, InclusiveEnd) -> + case InclusiveEnd of + true -> fun(ViewKey, ViewId) -> couch_view:less_json([ViewKey, ViewId], [EndKey, EndDocId]) + end; + false-> + fun + (ViewKey, _ViewId) when ViewKey == EndKey -> + true; + (ViewKey, ViewId) -> + couch_view:less_json([ViewKey, ViewId], [EndKey, EndDocId]) end end. -- cgit v1.2.3