diff options
-rw-r--r-- | share/www/script/test/all_docs.js | 7 | ||||
-rw-r--r-- | share/www/script/test/view_collation_raw.js | 9 | ||||
-rw-r--r-- | src/couchdb/couch_btree.erl | 1 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_db.erl | 4 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_view.erl | 54 |
5 files changed, 56 insertions, 19 deletions
diff --git a/share/www/script/test/all_docs.js b/share/www/script/test/all_docs.js index 1d83aa95..1afe701d 100644 --- a/share/www/script/test/all_docs.js +++ b/share/www/script/test/all_docs.js @@ -41,6 +41,13 @@ couchTests.all_docs = function(debug) { var all = db.allDocs({startkey:"2"}); T(all.offset == 2); + // Confirm that queries may assume raw collation. + var raw = db.allDocs({ + startkey: "org.couchdb.user:", + endkey: "org.couchdb.user;" + }); + TEquals(0, raw.rows.length); + // check that the docs show up in the seq view in the order they were created var changes = db.changes(); var ids = ["0","3","1","2"]; diff --git a/share/www/script/test/view_collation_raw.js b/share/www/script/test/view_collation_raw.js index 31624cdb..779f7eb8 100644 --- a/share/www/script/test/view_collation_raw.js +++ b/share/www/script/test/view_collation_raw.js @@ -76,12 +76,19 @@ couchTests.view_collation_raw = function(debug) { } } T(db.save(designDoc).ok); + + // Confirm that everything collates correctly. var rows = db.view("test/test").rows; for (i=0; i<values.length; i++) { T(equals(rows[i].key, values[i])); } - // everything has collated correctly. Now to check the descending output + // Confirm that couch allows raw semantics in key ranges. + rows = db.view("test/test", {startkey:"Z", endkey:"a"}).rows; + TEquals(1, rows.length); + TEquals("a", rows[0].key); + + // Check the descending output. rows = db.view("test/test", {descending: true}).rows; for (i=0; i<values.length; i++) { T(equals(rows[i].key, values[values.length - 1 -i])); diff --git a/src/couchdb/couch_btree.erl b/src/couchdb/couch_btree.erl index 91bc8f1b..0ddf7a48 100644 --- a/src/couchdb/couch_btree.erl +++ b/src/couchdb/couch_btree.erl @@ -15,6 +15,7 @@ -export([open/2, open/3, query_modify/4, add/2, add_remove/3]). -export([fold/4, full_reduce/1, final_reduce/2, foldl/3, foldl/4]). -export([fold_reduce/4, lookup/2, get_state/1, set_options/2]). +-export([less/3]). -define(CHUNK_THRESHOLD, 16#4ff). diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index 71204598..db430bbd 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -477,6 +477,7 @@ db_req(#httpd{path_parts=[_, DocId | FileNameParts]}=Req, Db) -> db_attachment_req(Req, Db, DocId, FileNameParts). all_docs_view(Req, Db, Keys) -> + RawCollator = fun(A, B) -> A < B end, #view_query_args{ start_key = StartKey, start_docid = StartDocId, @@ -486,7 +487,8 @@ all_docs_view(Req, Db, Keys) -> skip = SkipCount, direction = Dir, inclusive_end = Inclusive - } = QueryArgs = couch_httpd_view:parse_view_params(Req, Keys, map), + } = QueryArgs + = couch_httpd_view:parse_view_params(Req, Keys, map, RawCollator), {ok, Info} = couch_db:get_db_info(Db), CurrentEtag = couch_httpd:make_etag(Info), couch_httpd:etag_respond(Req, CurrentEtag, fun() -> diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl index 1f279417..082a5039 100644 --- a/src/couchdb/couch_httpd_view.erl +++ b/src/couchdb/couch_httpd_view.erl @@ -15,7 +15,7 @@ -export([handle_view_req/3,handle_temp_view_req/2]). --export([parse_view_params/3]). +-export([parse_view_params/4]). -export([make_view_fold_fun/7, finish_view_fold/4, finish_view_fold/5, view_row_obj/4]). -export([view_etag/5, make_reduce_fold_funs/6]). -export([design_doc_view/5, parse_bool_param/1, doc_member/3]). @@ -34,18 +34,19 @@ design_doc_view(Req, Db, DName, ViewName, Keys) -> Reduce = get_reduce_type(Req), Result = case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of {ok, View, Group} -> - QueryArgs = parse_view_params(Req, Keys, map), + QueryArgs = parse_view_params(Req, Keys, map, view_collator(View)), output_map_view(Req, View, Group, Db, QueryArgs, Keys); {not_found, Reason} -> case couch_view:get_reduce_view(Db, DesignId, ViewName, Stale) of {ok, ReduceView, Group} -> + Collator = view_collator(ReduceView), case Reduce of false -> - QueryArgs = parse_view_params(Req, Keys, red_map), + QueryArgs = parse_view_params(Req, Keys, red_map, Collator), MapView = couch_view:extract_map_view(ReduceView), output_map_view(Req, MapView, Group, Db, QueryArgs, Keys); _ -> - QueryArgs = parse_view_params(Req, Keys, reduce), + QueryArgs = parse_view_params(Req, Keys, reduce, Collator), output_reduce_view(Req, Db, ReduceView, Group, QueryArgs, Keys) end; _ -> @@ -90,19 +91,19 @@ handle_temp_view_req(#httpd{method='POST'}=Req, Db) -> Reduce = get_reduce_type(Req), case couch_util:get_value(<<"reduce">>, Props, null) of null -> - QueryArgs = parse_view_params(Req, Keys, map), {ok, View, Group} = couch_view:get_temp_map_view(Db, Language, DesignOptions, MapSrc), + QueryArgs = parse_view_params(Req, Keys, map, view_collator(View)), output_map_view(Req, View, Group, Db, QueryArgs, Keys); _ when Reduce =:= false -> - QueryArgs = parse_view_params(Req, Keys, red_map), {ok, View, Group} = couch_view:get_temp_map_view(Db, Language, DesignOptions, MapSrc), + QueryArgs = parse_view_params(Req, Keys, red_map, view_collator(View)), output_map_view(Req, View, Group, Db, QueryArgs, Keys); RedSrc -> - QueryArgs = parse_view_params(Req, Keys, reduce), {ok, View, Group} = couch_view:get_temp_reduce_view(Db, Language, DesignOptions, MapSrc, RedSrc), + QueryArgs = parse_view_params(Req, Keys, reduce, view_collator(View)), output_reduce_view(Req, Db, View, Group, QueryArgs, Keys) end; @@ -209,18 +210,19 @@ load_view(Req, Db, {ViewDesignId, ViewName}, Keys) -> Reduce = get_reduce_type(Req), case couch_view:get_map_view(Db, ViewDesignId, ViewName, Stale) of {ok, View, Group} -> - QueryArgs = parse_view_params(Req, Keys, map), + QueryArgs = parse_view_params(Req, Keys, map, view_collator(View)), {map, View, Group, QueryArgs}; {not_found, _Reason} -> case couch_view:get_reduce_view(Db, ViewDesignId, ViewName, Stale) of {ok, ReduceView, Group} -> + Collator = view_collator(ReduceView), case Reduce of false -> - QueryArgs = parse_view_params(Req, Keys, map_red), + QueryArgs = parse_view_params(Req, Keys, map_red, Collator), MapView = couch_view:extract_map_view(ReduceView), {map, MapView, Group, QueryArgs}; _ -> - QueryArgs = parse_view_params(Req, Keys, reduce), + QueryArgs = parse_view_params(Req, Keys, reduce, Collator), {reduce, ReduceView, Group, QueryArgs} end; {not_found, Reason} -> @@ -228,12 +230,30 @@ load_view(Req, Db, {ViewDesignId, ViewName}, Keys) -> end end. +view_collator({reduce, _N, _Lang, View}) -> + view_collator(View); + +view_collator({temp_reduce, View}) -> + view_collator(View); + +view_collator(#view{btree=Btree}) -> + % Return an "is-less-than" predicate by calling into the btree's + % collator. For raw collation, couch_btree compares arbitrary + % Erlang terms, but for normal (ICU) collation, it expects + % {Json, Id} tuples. + fun + ({_JsonA, _IdA}=A, {_JsonB, _IdB}=B) -> + couch_btree:less(Btree, A, B); + (JsonA, JsonB) -> + couch_btree:less(Btree, {JsonA, null}, {JsonB, null}) + end. + % query_parse_error could be removed % we wouldn't need to pass the view type, it'd just parse params. % I'm not sure what to do about the error handling, but % it might simplify things to have a parse_view_params function % that doesn't throw(). -parse_view_params(Req, Keys, ViewType) -> +parse_view_params(Req, Keys, ViewType, LessThan) -> QueryList = couch_httpd:qs(Req), QueryParams = lists:foldl(fun({K, V}, Acc) -> @@ -247,7 +267,7 @@ parse_view_params(Req, Keys, ViewType) -> QueryArgs = lists:foldl(fun({K, V}, Args2) -> validate_view_query(K, V, Args2) end, Args, lists:reverse(QueryParams)), % Reverse to match QS order. - warn_on_empty_key_range(QueryArgs), + warn_on_empty_key_range(QueryArgs, LessThan), GroupLevel = QueryArgs#view_query_args.group_level, case {ViewType, GroupLevel, IsMultiGet} of {reduce, exact, true} -> @@ -328,15 +348,15 @@ parse_view_param("callback", _) -> parse_view_param(Key, Value) -> [{extra, {Key, Value}}]. -warn_on_empty_key_range(#view_query_args{start_key=undefined}) -> +warn_on_empty_key_range(#view_query_args{start_key=undefined}, _Lt) -> ok; -warn_on_empty_key_range(#view_query_args{end_key=undefined}) -> +warn_on_empty_key_range(#view_query_args{end_key=undefined}, _Lt) -> ok; -warn_on_empty_key_range(#view_query_args{start_key=A, end_key=A}) -> +warn_on_empty_key_range(#view_query_args{start_key=A, end_key=A}, _Lt) -> ok; warn_on_empty_key_range(#view_query_args{ - start_key=StartKey, end_key=EndKey, direction=Dir}) -> - case {Dir, couch_view:less_json(StartKey, EndKey)} of + start_key=StartKey, end_key=EndKey, direction=Dir}, LessThan) -> + case {Dir, LessThan(StartKey, EndKey)} of {fwd, false} -> throw({query_parse_error, <<"No rows can match your key range, reverse your ", |