You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2011/05/27 10:12:47 UTC

svn commit: r1128189 - in /couchdb/branches/1.1.x: share/www/script/test/rewrite.js src/couchdb/couch_httpd_rewrite.erl

Author: rnewson
Date: Fri May 27 08:12:47 2011
New Revision: 1128189

URL: http://svn.apache.org/viewvc?rev=1128189&view=rev
Log:
COUCHDB-1074 - fix variable substitution in rewriter

- key= ":key", startkey=[":a", ":b"]
- variable substitution via query arguments
- variable substituin via reversed path matching variables

The variable substition is now a lot easier than the old one. Variables
are decoded from the query if they are json, and we recode them only at
the end.

(Patch by BenoƮt Chesneau)

Modified:
    couchdb/branches/1.1.x/share/www/script/test/rewrite.js
    couchdb/branches/1.1.x/src/couchdb/couch_httpd_rewrite.erl

Modified: couchdb/branches/1.1.x/share/www/script/test/rewrite.js
URL: http://svn.apache.org/viewvc/couchdb/branches/1.1.x/share/www/script/test/rewrite.js?rev=1128189&r1=1128188&r2=1128189&view=diff
==============================================================================
--- couchdb/branches/1.1.x/share/www/script/test/rewrite.js (original)
+++ couchdb/branches/1.1.x/share/www/script/test/rewrite.js Fri May 27 08:12:47 2011
@@ -119,6 +119,10 @@ couchTests.rewrite = function(debug) {
               "query": {
                 "startkey": ":start",
                 "endkey": ":end"
+              },
+              "formats": {
+                "start": "int",
+                "end": "int"
               }
             },
             {
@@ -164,6 +168,18 @@ couchTests.rewrite = function(debug) {
               }
             },
             {
+              "from": "simpleForm/complexView7/:a/:b",
+              "to": "_view/complexView3",
+              "query": {
+                "key": [":a", ":b"],
+                "include_docs": ":doc"
+              },
+              "format": {
+                "doc": "bool"
+              }
+
+            },
+            {
               "from": "/",
               "to": "_view/basicView",
             }
@@ -348,14 +364,14 @@ couchTests.rewrite = function(debug) {
         T(!(/Key: 1/.test(xhr.responseText)));
         T(/FirstKey: 3/.test(xhr.responseText));
         T(/LastKey: 8/.test(xhr.responseText));
-        
+       
         // get with query params
         xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicViewPath/3/8");
         T(xhr.status == 200, "with query params");
         T(!(/Key: 1/.test(xhr.responseText)));
         T(/FirstKey: 3/.test(xhr.responseText));
         T(/LastKey: 8/.test(xhr.responseText));
-        
+
         // get with query params        
         xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView");
         T(xhr.status == 200, "with query params");
@@ -380,6 +396,11 @@ couchTests.rewrite = function(debug) {
         xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView6?a=test&b=essai");
         T(xhr.status == 200, "with query params");
         T(/Value: doc 4/.test(xhr.responseText));
+
+        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView7/test/essai?doc=true");
+        T(xhr.status == 200, "with query params");
+        var result = JSON.parse(xhr.responseText);
+        T(typeof(result.rows[0].doc) === "object");
         
         // test path relative to server
         designDoc.rewrites.push({

Modified: couchdb/branches/1.1.x/src/couchdb/couch_httpd_rewrite.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/1.1.x/src/couchdb/couch_httpd_rewrite.erl?rev=1128189&r1=1128188&r2=1128189&view=diff
==============================================================================
--- couchdb/branches/1.1.x/src/couchdb/couch_httpd_rewrite.erl (original)
+++ couchdb/branches/1.1.x/src/couchdb/couch_httpd_rewrite.erl Fri May 27 08:12:47 2011
@@ -117,8 +117,7 @@ handle_rewrite_req(#httpd{
     % we are in a design handler
     DesignId = <<"_design/", DesignName/binary>>,
     Prefix = <<"/", DbName/binary, "/", DesignId/binary>>,
-    QueryList = couch_httpd:qs(Req),
-    QueryList1 = [{to_binding(K), V} || {K, V} <- QueryList],
+    QueryList = lists:map(fun decode_query_value/1, couch_httpd:qs(Req)),
 
     #doc{body={Props}} = DDoc,
 
@@ -130,10 +129,11 @@ handle_rewrite_req(#httpd{
         Rules ->
             % create dispatch list from rules
             DispatchList =  [make_rule(Rule) || {Rule} <- Rules],
+            Method1 = couch_util:to_binary(Method),
 
             %% get raw path by matching url to a rule.
-            RawPath = case try_bind_path(DispatchList, couch_util:to_binary(Method), PathParts,
-                                    QueryList1) of
+            RawPath = case try_bind_path(DispatchList, Method1, 
+                    PathParts, QueryList) of
                 no_dispatch_path ->
                     throw(not_found);
                 {NewPathParts, Bindings} ->
@@ -141,12 +141,13 @@ handle_rewrite_req(#httpd{
 
                     % build new path, reencode query args, eventually convert
                     % them to json
-                    Path = lists:append(
-                        string:join(Parts, [?SEPARATOR]),
-                        case Bindings of
-                            [] -> [];
-                            _ -> [$?, encode_query(Bindings)]
-                        end),
+                    Bindings1 = maybe_encode_bindings(Bindings),
+                    Path = binary_to_list(
+                        iolist_to_binary([
+                                string:join(Parts, [?SEPARATOR]),
+                                [["?", mochiweb_util:urlencode(Bindings1)] 
+                                    || Bindings1 =/= [] ]
+                            ])),
                     
                     % if path is relative detect it and rewrite path
                     case mochiweb_util:safe_relative_path(Path) of
@@ -193,7 +194,7 @@ quote_plus(X) ->
 try_bind_path([], _Method, _PathParts, _QueryList) ->
     no_dispatch_path;
 try_bind_path([Dispatch|Rest], Method, PathParts, QueryList) ->
-    [{PathParts1, Method1}, RedirectPath, QueryArgs] = Dispatch,
+    [{PathParts1, Method1}, RedirectPath, QueryArgs, Formats] = Dispatch,
     case bind_method(Method1, Method) of
         true ->
             case bind_path(PathParts1, PathParts, []) of
@@ -201,7 +202,8 @@ try_bind_path([Dispatch|Rest], Method, P
                     Bindings1 = Bindings ++ QueryList,
                     % we parse query args from the rule and fill
                     % it eventually with bindings vars
-                    QueryArgs1 = make_query_list(QueryArgs, Bindings1, []),
+                    QueryArgs1 = make_query_list(QueryArgs, Bindings1,
+                        Formats, []),
                     % remove params in QueryLists1 that are already in
                     % QueryArgs1
                     Bindings2 = lists:foldl(fun({K, V}, Acc) ->
@@ -227,61 +229,79 @@ try_bind_path([Dispatch|Rest], Method, P
 %% rewriting dynamically the quey list given as query member in
 %% rewrites. Each value is replaced by one binding or an argument
 %% passed in url.
-make_query_list([], _Bindings, Acc) ->
+make_query_list([], _Bindings, _Formats, Acc) ->
     Acc;
-make_query_list([{Key, {Value}}|Rest], Bindings, Acc) ->
-    Value1 = to_json({Value}),
-    make_query_list(Rest, Bindings, [{to_binding(Key), Value1}|Acc]);
-make_query_list([{Key, Value}|Rest], Bindings, Acc) when is_binary(Value) ->
-    Value1 = replace_var(Key, Value, Bindings),
-    make_query_list(Rest, Bindings, [{to_binding(Key), Value1}|Acc]);
-make_query_list([{Key, Value}|Rest], Bindings, Acc) when is_list(Value) ->
-    Value1 = replace_var(Key, Value, Bindings),
-    make_query_list(Rest, Bindings, [{to_binding(Key), Value1}|Acc]);
-make_query_list([{Key, Value}|Rest], Bindings, Acc) ->
-    make_query_list(Rest, Bindings, [{to_binding(Key), Value}|Acc]).
-
-replace_var(Key, Value, Bindings) ->
-    case Value of
-        <<":", Var/binary>> ->
-            get_var(Var, Bindings, Value);
-        <<"*">> ->
-            get_var(Value, Bindings, Value);
-        _ when is_list(Value) ->
-            Value1 = lists:foldr(fun(V, Acc) ->
-                V1 = case V of
-                    <<":", VName/binary>> ->
-                        case get_var(VName, Bindings, V) of
-                            V2 when is_list(V2) ->
-                                iolist_to_binary(V2);
-                            V2 -> V2
-                        end;
-                    <<"*">> ->
-                        get_var(V, Bindings, V);
-                    _ ->
-                        V
-                end,
-                [V1|Acc]
-            end, [], Value),
-            to_json(Value1);
-        _ when is_binary(Value) ->
-            Value;
-        _ ->
-            case Key of
-                <<"key">> -> to_json(Value);
-                <<"startkey">> -> to_json(Value);
-                <<"start_key">> -> to_json(Value);
-                <<"endkey">> -> to_json(Value);
-                <<"end_key">> -> to_json(Value);
-                _ ->
-                    lists:flatten(?JSON_ENCODE(Value))
-            end
+make_query_list([{Key, {Value}}|Rest], Bindings, Formats, Acc) ->
+    Value1 = {Value},
+    make_query_list(Rest, Bindings, Formats, [{to_binding(Key), Value1}|Acc]);
+make_query_list([{Key, Value}|Rest], Bindings, Formats, Acc) when is_binary(Value) ->
+    Value1 = replace_var(Value, Bindings, Formats),
+    make_query_list(Rest, Bindings, Formats, [{to_binding(Key), Value1}|Acc]);
+make_query_list([{Key, Value}|Rest], Bindings, Formats, Acc) when is_list(Value) ->
+    Value1 = replace_var(Value, Bindings, Formats),
+    make_query_list(Rest, Bindings, Formats, [{to_binding(Key), Value1}|Acc]);
+make_query_list([{Key, Value}|Rest], Bindings, Formats, Acc) ->
+    make_query_list(Rest, Bindings, Formats, [{to_binding(Key), Value}|Acc]).
+
+replace_var(<<"*">>=Value, Bindings, Formats) ->
+    get_var(Value, Bindings, Value, Formats);
+replace_var(<<":", Var/binary>> = Value, Bindings, Formats) ->
+    get_var(Var, Bindings, Value, Formats);
+replace_var(Value, _Bindings, _Formats) when is_binary(Value) ->
+    Value;
+replace_var(Value, Bindings, Formats) when is_list(Value) ->
+    lists:reverse(lists:foldl(fun
+                (<<":", Var/binary>>=Value1, Acc) ->
+                    [get_var(Var, Bindings, Value1, Formats)|Acc];
+                (Value1, Acc) ->
+                    [Value1|Acc]
+            end, [], Value));
+replace_var(Value, _Bindings, _Formats) ->
+    Value.
+                    
+maybe_json(Key, Value) ->
+    case lists:member(Key, [<<"key">>, <<"startkey">>, <<"start_key">>,
+                <<"endkey">>, <<"end_key">>, <<"keys">>]) of
+        true ->
+            ?JSON_ENCODE(Value);
+        false ->
+            Value
     end.
 
-
-get_var(VarName, Props, Default) ->
+get_var(VarName, Props, Default, Formats) ->
     VarName1 = to_binding(VarName),
-    couch_util:get_value(VarName1, Props, Default).
+    Val = couch_util:get_value(VarName1, Props, Default),
+    maybe_format(VarName, Val, Formats).
+
+maybe_format(VarName, Value, Formats) ->
+    case couch_util:get_value(VarName, Formats) of
+        undefined ->
+             Value;
+        Format ->
+            format(Format, Value)
+    end.
+
+format(<<"int">>, Value) when is_integer(Value) ->
+    Value;
+format(<<"int">>, Value) when is_binary(Value) ->
+    format(<<"int">>, ?b2l(Value));
+format(<<"int">>, Value) when is_list(Value) ->
+    case (catch list_to_integer(Value)) of
+        IntVal when is_integer(IntVal) ->
+            IntVal;
+        _ ->
+            Value
+    end;
+format(<<"bool">>, Value) when is_binary(Value) ->
+    format(<<"bool">>, ?b2l(Value));
+format(<<"bool">>, Value) when is_list(Value) ->
+    case string:to_lower(Value) of
+        "true" -> true;
+        "false" -> false;
+        _ -> Value
+    end;
+format(_Format, Value) ->
+   Value. 
 
 %% doc: build new patch from bindings. bindings are query args
 %% (+ dynamic query rewritten if needed) and bindings found in
@@ -297,7 +317,8 @@ make_new_path([?MATCH_ALL|_Rest], _Bindi
 make_new_path([{bind, P}|Rest], Bindings, Remaining, Acc) ->
     P2 = case couch_util:get_value({bind, P}, Bindings) of
         undefined -> << "undefined">>;
-        P1 -> P1
+        P1 -> 
+            iolist_to_binary(P1)
     end,
     make_new_path(Rest, Bindings, Remaining, [P2|Acc]);
 make_new_path([P|Rest], Bindings, Remaining, Acc) ->
@@ -374,7 +395,11 @@ make_rule(Rule) ->
         To ->
             parse_path(To)
         end,
-    [{FromParts, Method}, ToParts, QueryArgs].
+    Formats = case couch_util:get_value(<<"formats">>, Rule) of
+        undefined -> [];
+        {Fmts} -> Fmts
+    end,
+    [{FromParts, Method}, ToParts, QueryArgs, Formats].
 
 parse_path(Path) ->
     {ok, SlashRE} = re:compile(<<"\\/">>),
@@ -407,21 +432,25 @@ path_to_list([P|R], Acc, DotDotCount) ->
     end,
     path_to_list(R, [P1|Acc], DotDotCount).
 
-encode_query(Props) -> 
-    Props1 = lists:foldl(fun ({{bind, K}, V}, Acc) ->
-        case K of
-            <<"*">> -> Acc;
-            _ ->
-                V1 = case is_list(V) orelse is_binary(V) of
-                    true -> V;
-                    false ->
-                        % probably it's a number
-                        quote_plus(V)
-                end,
-                [{K, V1} | Acc]
-        end
-    end, [], Props),
-    lists:flatten(mochiweb_util:urlencode(Props1)).
+maybe_encode_bindings([]) ->
+    [];
+maybe_encode_bindings(Props) -> 
+    lists:foldl(fun 
+            ({{bind, <<"*">>}, _V}, Acc) ->
+                Acc;
+            ({{bind, K}, V}, Acc) ->
+                V1 = iolist_to_binary(maybe_json(K, V)),
+                [{K, V1}|Acc]
+        end, [], Props).
+                
+decode_query_value({K,V}) ->
+    case lists:member(K, ["key", "startkey", "start_key",
+                "endkey", "end_key", "keys"]) of
+        true ->
+            {to_binding(K), ?JSON_DECODE(V)};
+        false ->
+            {to_binding(K), ?l2b(V)}
+    end.
 
 to_binding({bind, V}) ->
     {bind, V};
@@ -429,6 +458,3 @@ to_binding(V) when is_list(V) ->
     to_binding(?l2b(V));
 to_binding(V) ->
     {bind, V}.
-
-to_json(V) ->
-    iolist_to_binary(?JSON_ENCODE(V)).