You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2014/12/04 21:09:32 UTC

[01/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Repository: couchdb-couch
Updated Branches:
  refs/heads/2491-refactor-couch-httpd-auth 41e9cdd62 -> 3e8286d40 (forced update)


Make couch_stream monitor the stream opener

This commit fixes a process leak which would happen when a process
opened a couch_stream and exited with reason normal before closing
the stream.

This patch makes the couch_stream gen_server monitor the process
which opens the stream. When that monitor sends an exit signal
the couch_stream gen_server is stopped and the process dies.

Closes COUCHDB-2365


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/f02a1013
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/f02a1013
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/f02a1013

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: f02a1013dc61f011ccb58f1fddb689a93795af98
Parents: 40c5c85
Author: Mike Wallace <mi...@apache.org>
Authored: Tue Oct 7 23:45:11 2014 +0100
Committer: Mike Wallace <mi...@apache.org>
Committed: Wed Oct 29 23:12:27 2014 +0000

----------------------------------------------------------------------
 src/couch_stream.erl        | 10 ++++++++--
 test/couch_stream_tests.erl | 18 +++++++++++++++++-
 2 files changed, 25 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/f02a1013/src/couch_stream.erl
----------------------------------------------------------------------
diff --git a/src/couch_stream.erl b/src/couch_stream.erl
index e71542c..6877f0f 100644
--- a/src/couch_stream.erl
+++ b/src/couch_stream.erl
@@ -29,6 +29,7 @@
 
 -record(stream,
     {fd = 0,
+    opener_monitor,
     written_pointers=[],
     buffer_list = [],
     buffer_len = 0,
@@ -50,7 +51,7 @@ open(Fd) ->
     open(Fd, []).
 
 open(Fd, Options) ->
-    gen_server:start_link(couch_stream, {Fd, Options}, []).
+    gen_server:start_link(couch_stream, {Fd, self(), Options}, []).
 
 close(Pid) ->
     gen_server:call(Pid, close, infinity).
@@ -197,7 +198,7 @@ write(Pid, Bin) ->
     gen_server:call(Pid, {write, Bin}, infinity).
 
 
-init({Fd, Options}) ->
+init({Fd, OpenerPid, Options}) ->
     {EncodingFun, EndEncodingFun} =
     case couch_util:get_value(encoding, Options, identity) of
     identity ->
@@ -207,6 +208,7 @@ init({Fd, Options}) ->
     end,
     {ok, #stream{
             fd=Fd,
+            opener_monitor=erlang:monitor(process, OpenerPid),
             md5=couch_util:md5_init(),
             identity_md5=couch_util:md5_init(),
             encoding_fun=EncodingFun,
@@ -266,6 +268,7 @@ handle_call({write, Bin}, _From, Stream) ->
 handle_call(close, _From, Stream) ->
     #stream{
         fd = Fd,
+        opener_monitor = MonRef,
         written_len = WrittenLen,
         written_pointers = Written,
         buffer_list = Buffer,
@@ -288,6 +291,7 @@ handle_call(close, _From, Stream) ->
         StreamLen = WrittenLen + iolist_size(WriteBin2),
         {StreamInfo, StreamLen, IdenLen, Md5Final, IdenMd5Final}
     end,
+    erlang:demonitor(MonRef),
     {stop, normal, Result, Stream}.
 
 handle_cast(_Msg, State) ->
@@ -296,5 +300,7 @@ handle_cast(_Msg, State) ->
 code_change(_OldVsn, State, _Extra) ->
     {ok, State}.
 
+handle_info({'DOWN', Ref, _, _, _}, #stream{opener_monitor=Ref} = State) ->
+    {stop, normal, State};
 handle_info(_Info, State) ->
     {noreply, State}.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/f02a1013/test/couch_stream_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_stream_tests.erl b/test/couch_stream_tests.erl
index 3e84ca0..e12e714 100644
--- a/test/couch_stream_tests.erl
+++ b/test/couch_stream_tests.erl
@@ -38,7 +38,8 @@ stream_test_() ->
                 fun should_return_stream_size_on_close/1,
                 fun should_return_valid_pointers/1,
                 fun should_recall_last_pointer_position/1,
-                fun should_stream_more_with_4K_chunk_size/1
+                fun should_stream_more_with_4K_chunk_size/1,
+                fun should_stop_on_normal_exit_of_stream_opener/1
             ]
         }
     }.
@@ -94,6 +95,21 @@ should_stream_more_with_4K_chunk_size({Fd, _}) ->
     ?_assertMatch({[{0, 4100}, {4106, 1020}], 5120, _, _, _},
                   couch_stream:close(Stream)).
 
+should_stop_on_normal_exit_of_stream_opener({Fd, _}) ->
+    RunnerPid = self(),
+    OpenerPid = spawn(
+        fun() ->
+            {ok, StreamPid} = couch_stream:open(Fd),
+            RunnerPid ! {pid, StreamPid}
+        end),
+    StreamPid = receive
+        {pid, StreamPid0} -> StreamPid0
+    end,
+    % Confirm the validity of the test by verifying the stream opener has died
+    ?_assertNot(is_process_alive(OpenerPid)),
+    % Verify the stream itself has also died
+    ?_assertNot(is_process_alive(StreamPid)).
+
 
 read_all(Fd, PosList) ->
     Data = couch_stream:foldl(Fd, PosList, fun(Bin, Acc) -> [Bin, Acc] end, []),


[16/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
TOTP: left-pad with 0's as needed


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/0c0b438c
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/0c0b438c
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/0c0b438c

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 0c0b438c2648f11afd646fbebb1bf9c24f36a8a5
Parents: bfbf8eb
Author: Robert Newson <rn...@apache.org>
Authored: Sun Nov 2 21:17:01 2014 +0000
Committer: Robert Newson <rn...@apache.org>
Committed: Sun Nov 2 21:17:01 2014 +0000

----------------------------------------------------------------------
 src/couch_httpd_auth.erl | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/0c0b438c/src/couch_httpd_auth.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_auth.erl b/src/couch_httpd_auth.erl
index 5e528d9..752dd20 100644
--- a/src/couch_httpd_auth.erl
+++ b/src/couch_httpd_auth.erl
@@ -475,12 +475,15 @@ verify_token(Alg, Key, Len, Token) ->
     end.
 
 generate_token(Alg, Key, Len, Timestamp) ->
-    integer_to_binary(couch_totp:generate(Alg, Key, Timestamp, 30, Len)).
+    integer_to_binary(couch_totp:generate(Alg, Key, Timestamp, 30, Len), Len).
 
-integer_to_binary(Int) when is_integer(Int) ->
-    case erlang:function_exported(erlang, integer_to_binary, 1) of
+integer_to_binary(Int, Len) when is_integer(Int), is_integer(Len) ->
+    Unpadded = case erlang:function_exported(erlang, integer_to_binary, 1) of
         true ->
             erlang:integer_to_binary(Int);
         false ->
-            ?l2b(integer_to_list(Int))
-    end.
+           ?l2b(integer_to_list(Int))
+    end,
+    Padding = binary:copy(<<"0">>, Len),
+    Padded = <<Padding/binary, Unpadded/binary>>,
+    binary:part(Padded, byte_size(Padded), -Len).


[15/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
TOTP secrets are stored in Base32


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/bfbf8ebe
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/bfbf8ebe
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/bfbf8ebe

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: bfbf8ebed19ba511fc50f916f3fd41cea84e815f
Parents: 6125862
Author: Robert Newson <rn...@apache.org>
Authored: Sun Nov 2 17:15:49 2014 +0000
Committer: Robert Newson <rn...@apache.org>
Committed: Sun Nov 2 18:09:18 2014 +0000

----------------------------------------------------------------------
 src/couch_base32.erl        | 127 +++++++++++++++++++++++++++++++++++++++
 src/couch_httpd_auth.erl    |   2 +-
 test/couch_base32_tests.erl |  28 +++++++++
 3 files changed, 156 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/bfbf8ebe/src/couch_base32.erl
----------------------------------------------------------------------
diff --git a/src/couch_base32.erl b/src/couch_base32.erl
new file mode 100644
index 0000000..d8d754f
--- /dev/null
+++ b/src/couch_base32.erl
@@ -0,0 +1,127 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_base32).
+
+-export([encode/1, decode/1]).
+
+-define(SET, <<"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567">>).
+
+
+-spec encode(binary()) -> binary().
+encode(Plain) when is_binary(Plain) ->
+    IoList = encode(Plain, 0, byte_size(Plain) * 8, []),
+    iolist_to_binary(lists:reverse(IoList)).
+
+encode(_Plain, _ByteOffset, 0, Acc) ->
+    Acc;
+
+encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 8 ->
+    <<A:5, B:3>> = binary:part(Plain, ByteOffset, 1),
+    [<<(binary:at(?SET, A)),
+       (binary:at(?SET, B bsl 2)),
+       "======">> | Acc];
+
+encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 16 ->
+    <<A:5, B:5, C:5, D:1>> = binary:part(Plain, ByteOffset, 2),
+    [<<(binary:at(?SET, A)),
+       (binary:at(?SET, B)),
+       (binary:at(?SET, C)),
+       (binary:at(?SET, D bsl 4)),
+       "====">> | Acc];
+
+encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 24 ->
+    <<A:5, B:5, C:5, D:5, E:4>> = binary:part(Plain, ByteOffset, 3),
+    [<<(binary:at(?SET, A)),
+       (binary:at(?SET, B)),
+       (binary:at(?SET, C)),
+       (binary:at(?SET, D)),
+       (binary:at(?SET, E bsl 1)),
+       "===">> | Acc];
+
+encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 32 ->
+    <<A:5, B:5, C:5, D:5, E:5, F:5, G:2>> = binary:part(Plain, ByteOffset, 4),
+    [<<(binary:at(?SET, A)),
+       (binary:at(?SET, B)),
+       (binary:at(?SET, C)),
+       (binary:at(?SET, D)),
+       (binary:at(?SET, E)),
+       (binary:at(?SET, F)),
+       (binary:at(?SET, G bsl 3)),
+       "=">> | Acc];
+
+encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining >= 40 ->
+    <<A:5, B:5, C:5, D:5, E:5, F:5, G:5, H:5>> =
+        binary:part(Plain, ByteOffset, 5),
+    Output = <<(binary:at(?SET, A)),
+               (binary:at(?SET, B)),
+               (binary:at(?SET, C)),
+               (binary:at(?SET, D)),
+               (binary:at(?SET, E)),
+               (binary:at(?SET, F)),
+               (binary:at(?SET, G)),
+               (binary:at(?SET, H))>>,
+    encode(Plain, ByteOffset + 5, BitsRemaining  - 40, [Output | Acc]).
+
+
+-spec decode(binary()) -> binary().
+decode(Encoded) when is_binary(Encoded) ->
+    IoList = decode(Encoded, 0, []),
+    iolist_to_binary(lists:reverse(IoList)).
+
+decode(Encoded, ByteOffset, Acc) when ByteOffset == byte_size(Encoded) ->
+    Acc;
+decode(Encoded, ByteOffset, Acc) ->
+    case binary:part(Encoded, ByteOffset, 8) of
+        <<A:1/binary, B:1/binary, "======">> ->
+            [<<(find_in_set(A)):5,
+               (find_in_set(B) bsr 2):3>> | Acc];
+        <<A:1/binary, B:1/binary, C:1/binary, D:1/binary, "====">> ->
+            [<<(find_in_set(A)):5,
+               (find_in_set(B)):5,
+               (find_in_set(C)):5,
+               (find_in_set(D) bsr 4):1>> | Acc];
+        <<A:1/binary, B:1/binary, C:1/binary, D:1/binary, E:1/binary, "===">> ->
+            [<<(find_in_set(A)):5,
+               (find_in_set(B)):5,
+               (find_in_set(C)):5,
+               (find_in_set(D)):5,
+               (find_in_set(E) bsr 1):4>> | Acc];
+        <<A:1/binary, B:1/binary, C:1/binary, D:1/binary,
+          E:1/binary, F:1/binary, G:1/binary, "=">> ->
+            [<<(find_in_set(A)):5,
+               (find_in_set(B)):5,
+               (find_in_set(C)):5,
+               (find_in_set(D)):5,
+               (find_in_set(E)):5,
+               (find_in_set(F)):5,
+               (find_in_set(G) bsr 3):2>> | Acc];
+        <<A:1/binary, B:1/binary, C:1/binary, D:1/binary,
+          E:1/binary, F:1/binary, G:1/binary, H:1/binary>> ->
+            decode(Encoded, ByteOffset + 8,
+                   [<<(find_in_set(A)):5,
+                      (find_in_set(B)):5,
+                      (find_in_set(C)):5,
+                      (find_in_set(D)):5,
+                      (find_in_set(E)):5,
+                      (find_in_set(F)):5,
+                      (find_in_set(G)):5,
+                      (find_in_set(H)):5>> | Acc])
+    end.
+
+find_in_set(Char) ->
+    case binary:match(?SET, Char) of
+        nomatch ->
+            erlang:error(not_base32);
+        {Offset, _} ->
+            Offset
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/bfbf8ebe/src/couch_httpd_auth.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_auth.erl b/src/couch_httpd_auth.erl
index cda51c5..5e528d9 100644
--- a/src/couch_httpd_auth.erl
+++ b/src/couch_httpd_auth.erl
@@ -448,7 +448,7 @@ verify_totp(User, Form) ->
         undefined ->
             ok;
         {Props} ->
-            Key = couch_util:get_value(<<"key">>, Props),
+            Key = couch_base32:decode(couch_util:get_value(<<"key">>, Props)),
             Alg = couch_util:to_existing_atom(
                 couch_util:get_value(<<"algorithm">>, Props, <<"sha">>)),
             Len = couch_util:get_value(<<"length">>, Props, 6),

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/bfbf8ebe/test/couch_base32_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_base32_tests.erl b/test/couch_base32_tests.erl
new file mode 100644
index 0000000..7e4d59a
--- /dev/null
+++ b/test/couch_base32_tests.erl
@@ -0,0 +1,28 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_base32_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+
+base32_test() ->
+    roundtrip(<<"">>, <<"">>),
+    roundtrip(<<"f">>, <<"MY======">>),
+    roundtrip(<<"fo">>, <<"MZXQ====">>),
+    roundtrip(<<"foo">>, <<"MZXW6===">>),
+    roundtrip(<<"foob">>, <<"MZXW6YQ=">>),
+    roundtrip(<<"fooba">>, <<"MZXW6YTB">>),
+    roundtrip(<<"foobar">>, <<"MZXW6YTBOI======">>).
+
+roundtrip(Plain, Encoded) ->
+    ?assertEqual(Plain, couch_base32:decode(Encoded)),
+    ?assertEqual(Encoded, couch_base32:encode(Plain)).


[06/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
Make include_docs=true work for view changes


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/20cd47e7
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/20cd47e7
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/20cd47e7

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 20cd47e7164975618b32084a10774e4f8103e608
Parents: 8d5c900
Author: Benjamin Bastian <be...@gmail.com>
Authored: Tue Aug 26 15:55:07 2014 +0700
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700

----------------------------------------------------------------------
 src/couch_changes.erl | 46 ++++++++++++++++++++++++++--------------------
 1 file changed, 26 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/20cd47e7/src/couch_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_changes.erl b/src/couch_changes.erl
index 8ff109b..29920da 100644
--- a/src/couch_changes.erl
+++ b/src/couch_changes.erl
@@ -643,9 +643,9 @@ changes_enumerator(Value0, Acc) ->
 
 changes_row(Results, DocInfo, #changes_acc{filter={fast_view,_,_,_}}=Acc) ->
     format_doc_info_change(Results, DocInfo, Acc);
-changes_row(Results, KV, #changes_acc{view=#mrview{}}) ->
-    {{Seq, Key}, {Id, Value, _Rev}} = KV,
-    {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"key">>, Key}, {<<"value">>, Value}, {<<"changes">>, Results}]};
+changes_row(Results, KV, #changes_acc{view=#mrview{}}=Acc) ->
+    {{Seq, Key}, {Id, Value, Rev}} = KV,
+    {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"key">>, Key}, {<<"value">>, Value}, {<<"changes">>, Results}] ++ maybe_get_changes_doc({Id, Rev}, Acc)};
 changes_row(Results, #doc_info{}=DocInfo, Acc) ->
     format_doc_info_change(Results, DocInfo, Acc).
 
@@ -653,29 +653,35 @@ format_doc_info_change(Results, #doc_info{}=DocInfo, Acc) ->
     #doc_info{
         id = Id, high_seq = Seq, revs = [#rev_info{deleted = Del} | _]
     } = DocInfo,
+    {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Results}] ++
+        deleted_item(Del) ++ maybe_get_changes_doc(DocInfo, Acc)}.
+
+maybe_get_changes_doc(Value, #changes_acc{include_docs=true}=Acc) ->
     #changes_acc{
         db = Db,
         include_docs = IncDoc,
         doc_options = DocOpts,
         conflicts = Conflicts
     } = Acc,
-    {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Results}] ++
-        deleted_item(Del) ++ case IncDoc of
-            true ->
-                Opts = case Conflicts of
-                    true -> [deleted, conflicts];
-                    false -> [deleted]
-                end,
-                Doc = couch_index_util:load_doc(Db, DocInfo, Opts),
-                case Doc of
-                    null ->
-                        [{doc, null}];
-                    _ ->
-                        [{doc, couch_doc:to_json_obj(Doc, DocOpts)}]
-                end;
-            false ->
-                []
-        end}.
+    case IncDoc of
+        true ->
+            Opts = case Conflicts of
+                true -> [deleted, conflicts];
+                false -> [deleted]
+            end,
+            Doc = couch_index_util:load_doc(Db, Value, Opts),
+            case Doc of
+                null ->
+                    [{doc, null}];
+                _ ->
+                    [{doc, couch_doc:to_json_obj(Doc, DocOpts)}]
+            end;
+        false ->
+            []
+    end;
+maybe_get_changes_doc(_Value, _Acc) ->
+    [].
+
 
 deleted_item(true) -> [{<<"deleted">>, true}];
 deleted_item(_) -> [].


[04/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
add supports of view changes in the _changes API

Now when the option `seq_indexed=true` is set in the design doc, the
view filter in _changes will use it to retrieve the results. Compared to
the current way, using a view index will be faster to retrieve changes.
It also gives the possibility to filter changes by key or get changes in
a key range. All the view options can be used.

Note 1: if someone is trying to filter a changes with view options when
the views are not indexed by sequence, a 400 error will be returned.
Note 2: The changes will only be returned when the view is updated if
seq_indexed=true


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/7f2af21e
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/7f2af21e
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/7f2af21e

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 7f2af21e573abcd80b8c8412332d6439b4a777b3
Parents: fcb2882
Author: benoitc <bc...@gmail.com>
Authored: Fri Feb 7 15:38:34 2014 +0100
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:52 2014 -0700

----------------------------------------------------------------------
 src/couch_httpd_changes.erl | 250 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 246 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/7f2af21e/src/couch_httpd_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_changes.erl b/src/couch_httpd_changes.erl
index 1e431e9..56ce559 100644
--- a/src/couch_httpd_changes.erl
+++ b/src/couch_httpd_changes.erl
@@ -12,7 +12,9 @@
 
 -module(couch_httpd_changes).
 
--export([handle_changes_req/2]).
+-export([handle_changes_req/2,
+         handle_changes/3,
+         handle_view_changes/3]).
 
 -include_lib("couch/include/couch_db.hrl").
 
@@ -34,9 +36,7 @@ handle_changes_req1(Req, #db{name=DbName}=Db) ->
         % on other databases, _changes is free for all.
         ok
     end,
-    handle_changes_req2(Req, Db).
 
-handle_changes_req2(Req, Db) ->
     MakeCallback = fun(Resp) ->
         fun({change, {ChangeProp}=Change, _}, "eventsource") ->
             Seq = proplists:get_value(<<"seq">>, ChangeProp),
@@ -72,7 +72,7 @@ handle_changes_req2(Req, Db) ->
         end
     end,
     ChangesArgs = parse_changes_query(Req, Db),
-    ChangesFun = couch_changes:handle_changes(ChangesArgs, Req, Db),
+    ChangesFun = handle_changes(ChangesArgs, Req, Db),
     WrapperFun = case ChangesArgs#changes_args.feed of
     "normal" ->
         {ok, Info} = couch_db:get_db_info(Db),
@@ -116,6 +116,164 @@ handle_changes_req2(Req, Db) ->
     )
     end.
 
+
+handle_changes(ChangesArgs, Req, Db) ->
+    case ChangesArgs#changes_args.filter of
+        "_view" ->
+            handle_view_changes(ChangesArgs, Req, Db);
+        _ ->
+            couch_changes:handle_changes(ChangesArgs, Req, Db)
+    end.
+
+%% wrapper around couch_mrview_changes.
+%% This wrapper mimic couch_changes:handle_changes/3 and return a
+%% Changefun that can be used by the handle_changes_req function. Also
+%% while couch_mrview_changes:handle_changes/6 is returning tha view
+%% changes this function return docs corresponding to the changes
+%% instead so it can be used to replace the _view filter.
+handle_view_changes(ChangesArgs, Req, Db) ->
+    %% parse view parameter
+    {DDocId, VName} = parse_view_param(Req),
+
+    %% get view options
+    Query = case Req of
+        {json_req, {Props}} ->
+            {Q} = couch_util:get_value(<<"query">>, Props, {[]}),
+            Q;
+        _ ->
+            couch_httpd:qs(Req)
+    end,
+    ViewOptions = parse_view_options(Query, []),
+
+    {ok, Infos} = couch_mrview:get_info(Db, DDocId),
+    case lists:member(<<"seq_indexed">>,
+                      proplists:get_value(update_options, Infos, [])) of
+        true ->
+            handle_view_changes(Db, DDocId, VName, ViewOptions, ChangesArgs,
+                                Req);
+        false when ViewOptions /= [] ->
+            ?LOG_ERROR("Tried to filter a non sequence indexed view~n",[]),
+            throw({bad_request, seqs_not_indexed});
+        false ->
+            %% old method we are getting changes using the btree instead
+            %% which is not efficient, log it
+            ?LOG_WARN("Get view changes with seq_indexed=false.~n", []),
+            couch_changes:handle_changes(ChangesArgs, Req, Db)
+    end.
+
+handle_view_changes(#db{name=DbName}=Db0, DDocId, VName, ViewOptions,
+                    ChangesArgs, Req) ->
+    #changes_args{
+        feed = ResponseType,
+        since = Since,
+        db_open_options = DbOptions} = ChangesArgs,
+
+    Options0 = [{since, Since},
+                {view_options, ViewOptions}],
+    Options = case ResponseType of
+        "continuous" -> [stream | Options0];
+        "eventsource" -> [stream | Options0];
+        "longpoll" -> [{stream, once} | Options0];
+        _ -> Options0
+    end,
+
+    %% reopen the db with the db options given to the changes args
+    couch_db:close(Db0),
+    DbOptions1 = [{user_ctx, Db0#db.user_ctx} | DbOptions],
+    {ok, Db} = couch_db:open(DbName, DbOptions1),
+
+
+    %% initialise the changes fun
+    ChangesFun = fun(Callback) ->
+            Callback(start, ResponseType),
+
+            Acc0 = {"", 0, Db, Callback, ChangesArgs},
+            couch_mrview_changes:handle_changes(DbName, DDocId, VName,
+                                               fun view_changes_cb/2,
+                                               Acc0, Options)
+    end,
+    ChangesFun.
+
+
+view_changes_cb(stop, {LastSeq, {_, _, _, Callback, Args}}) ->
+    Callback({stop, LastSeq}, Args#changes_args.feed);
+
+view_changes_cb(heartbeat, {_, _, _, Callback, Args}=Acc) ->
+    Callback(timeout, Args#changes_args.feed),
+    {ok, Acc};
+view_changes_cb({{Seq, _Key, DocId}, _VAl},
+                {Prepend, OldLimit, Db0, Callback, Args}=Acc) ->
+
+    #changes_args{
+        feed = ResponseType,
+        limit = Limit} = Args,
+
+    %% if the doc sequence is > to the one in the db record, reopen the
+    %% database since it means we don't have the latest db value.
+    Db = case Db0#db.update_seq >= Seq of
+        true -> Db0;
+        false ->
+            {ok, Db1} = couch_db:reopen_db(Db0),
+            Db1
+    end,
+
+    case couch_db:get_doc_info(Db, DocId) of
+        {ok, DocInfo} ->
+            %% get change row
+            ChangeRow = view_change_row(Db, DocInfo, Args),
+            %% emit change row
+            Callback({change, ChangeRow, Prepend}, ResponseType),
+
+            %% if we achieved the limit, stop here, else continue.
+            NewLimit = OldLimit + 1,
+            if Limit > NewLimit ->
+                    {ok, {<<",\n">>, Db, NewLimit, Callback, Args}};
+                true ->
+                    {stop, {<<"">>, Db, NewLimit, Callback, Args}}
+            end;
+        {error, not_found} ->
+            %% doc not found, continue
+            {ok, Acc};
+        Error ->
+            throw(Error)
+    end.
+
+
+view_change_row(Db, DocInfo, Args) ->
+    #doc_info{id = Id, high_seq = Seq, revs = Revs} = DocInfo,
+    [#rev_info{rev=Rev, deleted=Del} | _] = Revs,
+
+    #changes_args{style=Style,
+                  include_docs=InDoc,
+                  doc_options = DocOpts,
+                  conflicts=Conflicts}=Args,
+
+    Changes = case Style of
+        main_only ->
+            [{[{<<"rev">>, couch_doc:rev_to_str(Rev)}]}];
+        all_docs ->
+            [{[{<<"rev">>, couch_doc:rev_to_str(R)}]}
+                || #rev_info{rev=R} <- Revs]
+    end,
+
+    {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Changes}] ++
+     deleted_item(Del) ++ case InDoc of
+            true ->
+                Opts = case Conflicts of
+                    true -> [deleted, conflicts];
+                    false -> [deleted]
+                end,
+                Doc = couch_index_util:load_doc(Db, DocInfo, Opts),
+                case Doc of
+                    null ->
+                        [{doc, null}];
+                    _ ->
+                        [{doc, couch_doc:to_json_obj(Doc, DocOpts)}]
+                end;
+            false ->
+                []
+        end}.
+
 parse_changes_query(Req, Db) ->
     ChangesArgs = lists:foldl(fun({Key, Value}, Args) ->
         case {string:to_lower(Key), Value} of
@@ -172,3 +330,87 @@ parse_changes_query(Req, Db) ->
         _ ->
             ChangesArgs
     end.
+
+parse_view_param({json_req, {Props}}) ->
+    {Query} = couch_util:get_value(<<"query">>, Props),
+    parse_view_param1(couch_util:get_value(<<"view">>, Query, <<"">>));
+parse_view_param(Req) ->
+    parse_view_param1(list_to_binary(couch_httpd:qs_value(Req, "view", ""))).
+
+parse_view_param1(ViewParam) ->
+    case re:split(ViewParam, <<"/">>) of
+        [DName, ViewName] ->
+            {<< "_design/", DName/binary >>, ViewName};
+        _ ->
+            throw({bad_request, "Invalid `view` parameter."})
+    end.
+
+parse_view_options([], Acc) ->
+    Acc;
+parse_view_options([{K, V} | Rest], Acc) ->
+    Acc1 = case couch_util:to_binary(K) of
+        <<"reduce">> ->
+            [{reduce, couch_mrview_http:parse_boolean(V)}];
+        <<"key">> ->
+            V1 = parse_json(V),
+            [{start_key, V1}, {end_key, V1} | Acc];
+        <<"keys">> ->
+            [{keys, parse_json(V)} | Acc];
+        <<"startkey">> ->
+            [{start_key, parse_json(V)} | Acc];
+        <<"start_key">> ->
+            [{start_key, parse_json(V)} | Acc];
+        <<"startkey_docid">> ->
+            [{start_key_docid, couch_util:to_binary(V)} | Acc];
+        <<"start_key_docid">> ->
+            [{start_key_docid, couch_util:to_binary(V)} | Acc];
+        <<"endkey">> ->
+            [{end_key, parse_json(V)} | Acc];
+        <<"end_key">> ->
+            [{end_key, parse_json(V)} | Acc];
+        <<"endkey_docid">> ->
+            [{start_key_docid, couch_util:to_binary(V)} | Acc];
+        <<"end_key_docid">> ->
+            [{start_key_docid, couch_util:to_binary(V)} | Acc];
+        <<"limit">> ->
+            [{limit, couch_mrview_http:parse_pos_int(V)} | Acc];
+        <<"count">> ->
+            throw({query_parse_error, <<"QS param `count` is not `limit`">>});
+        <<"stale">> when V =:= <<"ok">> orelse V =:= "ok" ->
+            [{stale, ok} | Acc];
+        <<"stale">> when V =:= <<"update_after">> orelse V =:= "update_after" ->
+            [{stale, update_after} | Acc];
+        <<"stale">> ->
+            throw({query_parse_error, <<"Invalid value for `stale`.">>});
+        <<"descending">> ->
+            case couch_mrview_http:parse_boolean(V) of
+                true ->
+                    [{direction, rev} | Acc];
+                _ ->
+                    [{direction, fwd} | Acc]
+            end;
+        <<"skip">> ->
+            [{skip, couch_mrview_http:parse_pos_int(V)} | Acc];
+        <<"group">> ->
+            case couch_mrview_http:parse_booolean(V) of
+                true ->
+                    [{group_level, exact} | Acc];
+                _ ->
+                    [{group_level, 0} | Acc]
+            end;
+        <<"group_level">> ->
+            [{group_level, couch_mrview_http:parse_pos_int(V)} | Acc];
+        <<"inclusive_end">> ->
+            [{inclusive_end, couch_mrview_http:parse_boolean(V)}];
+        _ ->
+            Acc
+    end,
+    parse_view_options(Rest, Acc1).
+
+parse_json(V) when is_list(V) ->
+    ?JSON_DECODE(V);
+parse_json(V) ->
+    V.
+
+deleted_item(true) -> [{<<"deleted">>, true}];
+deleted_item(_) -> [].


[20/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
Fix default CSP setting for Ace Editor

Like @sebastianrothbucher noticed in apache/couchdb-fauxton#5
the Ace editor needs base64 image data as image source for their
icons


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/741a82d4
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/741a82d4
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/741a82d4

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 741a82d4cfda424bba5545a2219ecc2c4cf919c3
Parents: cb52507
Author: Robert Kowalski <ro...@kowalski.gd>
Authored: Tue Jul 22 19:22:02 2014 +0200
Committer: Robert Kowalski <ro...@kowalski.gd>
Committed: Fri Nov 28 21:51:51 2014 +0100

----------------------------------------------------------------------
 src/couch_httpd_misc_handlers.erl | 2 +-
 test/couchdb_csp_tests.erl        | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/741a82d4/src/couch_httpd_misc_handlers.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_misc_handlers.erl b/src/couch_httpd_misc_handlers.erl
index 06fed5e..e90140f 100644
--- a/src/couch_httpd_misc_handlers.erl
+++ b/src/couch_httpd_misc_handlers.erl
@@ -81,7 +81,7 @@ handle_utils_dir_req(Req, _) ->
     send_method_not_allowed(Req, "GET,HEAD").
 
 maybe_add_csp_headers(Headers, "true") ->
-    DefaultValues = "default-src 'self'; img-src 'self'; font-src 'self'; "
+    DefaultValues = "default-src 'self'; img-src 'self' data:; font-src 'self'; "
                     "script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline';",
     Value = config:get("csp", "header_value", DefaultValues),
     [{"Content-Security-Policy", Value} | Headers];

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/741a82d4/test/couchdb_csp_tests.erl
----------------------------------------------------------------------
diff --git a/test/couchdb_csp_tests.erl b/test/couchdb_csp_tests.erl
index 3dbe6e3..5eb33f9 100644
--- a/test/couchdb_csp_tests.erl
+++ b/test/couchdb_csp_tests.erl
@@ -57,7 +57,7 @@ should_not_return_any_csp_headers_when_disabled(Url) ->
 
 should_apply_default_policy(Url) ->
     ?_assertEqual(
-        "default-src 'self'; img-src 'self'; font-src 'self'; "
+        "default-src 'self'; img-src 'self' data:; font-src 'self'; "
         "script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline';",
         begin
             {ok, _, Headers, _} = test_request:get(Url),


[13/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
Add comment and do minor refactoring


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/6e7f19ce
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/6e7f19ce
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/6e7f19ce

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 6e7f19ceaf14f2824c4e3077dbbf1402c12239bc
Parents: da2836b
Author: Benjamin Bastian <be...@gmail.com>
Authored: Fri Aug 29 18:15:55 2014 +0700
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700

----------------------------------------------------------------------
 src/couch_changes.erl | 33 +++++++++++++++------------------
 1 file changed, 15 insertions(+), 18 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/6e7f19ce/src/couch_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_changes.erl b/src/couch_changes.erl
index 29920da..2ff78e7 100644
--- a/src/couch_changes.erl
+++ b/src/couch_changes.erl
@@ -65,23 +65,20 @@ handle_changes(Args1, Req, Db0, Type) ->
     } = Args1,
     Filter = configure_filter(FilterName, Style, Req, Db0),
     Args = Args1#changes_args{filter_fun = Filter},
-    UseViewChanges = case {Type, Filter} of
-        {{view, _, _}, _} ->
-            true;
-        {_, {fast_view, _, _, _}} ->
-            true;
+    % The type of changes feed depends on the supplied filter. If the query is
+    % for an optimized view-filtered db changes, we need to use the view
+    % sequence tree.
+    {UseViewChanges, DDocName, ViewName} = case {Type, Filter} of
+        {{view, DDocName0, ViewName0}, _} ->
+            {true, DDocName0, ViewName0};
+        {_, {fast_view, _, DDoc, ViewName0}} ->
+            {true, DDoc#doc.id, ViewName0};
         _ ->
-            false
+            {false, undefined, undefined}
     end,
-    {StartListenerFun, DDocName, ViewName} = if UseViewChanges ->
-        {DDocName0, ViewName0} = case {Type, Filter} of
-            {{view, DDocName1, ViewName1}, _} ->
-                {DDocName1, ViewName1};
-            {_, {fast_view, _, DDoc, ViewName1}} ->
-                {DDoc#doc.id, ViewName1}
-        end,
+    {StartListenerFun, View} = if UseViewChanges ->
         {ok, {_, View0, _}, _, _} = couch_mrview_util:get_view(
-                Db0#db.name, DDocName0, ViewName0, #mrargs{}),
+                Db0#db.name, DDocName, ViewName, #mrargs{}),
         case View0#mrview.seq_btree of
             #btree{} ->
                 ok;
@@ -90,17 +87,17 @@ handle_changes(Args1, Req, Db0, Type) ->
         end,
         SNFun = fun() ->
             couch_event:link_listener(
-                 ?MODULE, handle_view_event, {self(), DDocName0}, [{dbname, Db0#db.name}]
+                 ?MODULE, handle_view_event, {self(), DDocName}, [{dbname, Db0#db.name}]
             )
         end,
-        {SNFun, DDocName0, ViewName0};
-     true ->
+        {SNFun, View0};
+    true ->
         SNFun = fun() ->
             couch_event:link_listener(
                  ?MODULE, handle_db_event, self(), [{dbname, Db0#db.name}]
             )
         end,
-        {SNFun, undefined, undefined}
+        {SNFun, undefined}
     end,
     Start = fun() ->
         {ok, Db} = couch_db:reopen(Db0),


[07/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
Add rev to view changes respose. See couch_mrview for corresponding changes


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/434b5414
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/434b5414
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/434b5414

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 434b5414ed975747e8144f9c5542cefa3f83fe3c
Parents: 20e585d
Author: Benjamin Bastian <be...@gmail.com>
Authored: Mon Aug 25 16:50:05 2014 +0700
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700

----------------------------------------------------------------------
 src/couch_changes.erl | 22 ++++++++++++++++------
 1 file changed, 16 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/434b5414/src/couch_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_changes.erl b/src/couch_changes.erl
index 259f83c..50f9d50 100644
--- a/src/couch_changes.erl
+++ b/src/couch_changes.erl
@@ -265,7 +265,7 @@ filter(Db, DocInfo, {custom, Style, Req0, DDoc, FName}) ->
     {ok, Passes} = couch_query_servers:filter_docs(Req, Db, DDoc, FName, Docs),
     filter_revs(Passes, Docs).
 
-fast_view_filter(Db, {{Seq, _}, {ID, _}}, {fast_view, Style, _, _}) ->
+fast_view_filter(Db, {{Seq, _}, {ID, _, _}}, {fast_view, Style, _, _}) ->
     case couch_db:get_doc_info(Db, ID) of
         {ok, #doc_info{high_seq=Seq}=DocInfo} ->
             Docs = open_revs(Db, DocInfo, Style),
@@ -284,15 +284,15 @@ fast_view_filter(Db, {{Seq, _}, {ID, _}}, {fast_view, Style, _, _}) ->
             % I left the Seq > HighSeq guard in so if (for some godforsaken
             % reason) the seq in the view is more current than the database,
             % we'll throw an error.
-            {ok, []};
+            {undefined, []};
         {error, not_found} ->
-            {ok, []}
+            {undefined, []}
     end.
 
 
 
-view_filter(_Db, _KV, {default, _Style}) ->
-    [ok]. % TODO: make a real thing
+view_filter(Db, KV, {default, Style}) ->
+    apply_view_style(Db, KV, Style).
 
 
 get_view_qs({json_req, {Props}}) ->
@@ -353,6 +353,16 @@ apply_style(#doc_info{revs=Revs}, main_only) ->
 apply_style(#doc_info{revs=Revs}, all_docs) ->
     [{[{<<"rev">>, couch_doc:rev_to_str(R)}]} || #rev_info{rev=R} <- Revs].
 
+apply_view_style(_Db, {{_Seq, _Key}, {_ID, _Value, Rev}}, main_only) ->
+    [{[{<<"rev">>, couch_doc:rev_to_str(Rev)}]}];
+apply_view_style(Db, {{_Seq, _Key}, {ID, _Value, _Rev}}, all_docs) ->
+    case couch_db:get_doc_info(Db, ID) of
+        {ok, DocInfo} ->
+            apply_style(DocInfo, all_docs);
+        {error, not_found} ->
+            []
+    end.
+
 
 open_revs(Db, DocInfo, Style) ->
     DocInfos = case Style of
@@ -627,7 +637,7 @@ changes_enumerator(Value0, Acc) ->
 changes_row(Results, DocInfo, #changes_acc{filter={fast_view,_,_,_}}=Acc) ->
     format_doc_info_change(Results, DocInfo, Acc);
 changes_row(Results, KV, #changes_acc{view=#mrview{}}) ->
-    {{Seq, Key}, {Id, Value}} = KV,
+    {{Seq, Key}, {Id, Value, _Rev}} = KV,
     {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"key">>, Key}, {<<"value">>, Value}, {<<"changes">>, Results}]};
 changes_row(Results, #doc_info{}=DocInfo, Acc) ->
     format_doc_info_change(Results, DocInfo, Acc).


[21/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
Expose and add helper for tests

In order to make them reusable in the chhtpd integration-tests.
Additionally adding a post and delete helper.

COUCHDB-2462


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/2a45cb03
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/2a45cb03
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/2a45cb03

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 2a45cb0325b72eb0910029bed5d2c38f784f68f9
Parents: 741a82d
Author: Robert Kowalski <ro...@kowalski.gd>
Authored: Sun Nov 30 03:13:36 2014 +0100
Committer: Robert Kowalski <ro...@kowalski.gd>
Committed: Mon Dec 1 21:22:43 2014 +0100

----------------------------------------------------------------------
 src/test_request.erl  | 81 ++++++++++++++++++++++++++++++++++++++++++++++
 src/test_util.erl     |  1 +
 test/test_request.erl | 75 ------------------------------------------
 3 files changed, 82 insertions(+), 75 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/2a45cb03/src/test_request.erl
----------------------------------------------------------------------
diff --git a/src/test_request.erl b/src/test_request.erl
new file mode 100644
index 0000000..9693976
--- /dev/null
+++ b/src/test_request.erl
@@ -0,0 +1,81 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(test_request).
+
+-export([get/1, get/2, get/3]).
+-export([post/3]).
+-export([put/2, put/3]).
+-export([delete/1]).
+-export([options/1, options/2, options/3]).
+-export([request/3, request/4]).
+
+get(Url) ->
+    request(get, Url, []).
+
+get(Url, Headers) ->
+    request(get, Url, Headers).
+get(Url, Headers, Opts) ->
+    request(get, Url, Headers, [], Opts).
+
+post(Url, Headers, Body) ->
+    request(post, Url, Headers, Body).
+
+put(Url, Body) ->
+    request(put, Url, [], Body).
+
+put(Url, Headers, Body) ->
+    request(put, Url, Headers, Body).
+
+delete(Url) ->
+    request(delete, Url, []).
+
+options(Url) ->
+    request(options, Url, []).
+
+options(Url, Headers) ->
+    request(options, Url, Headers).
+
+options(Url, Headers, Opts) ->
+    request(options, Url, Headers, [], Opts).
+
+
+request(Method, Url, Headers) ->
+    request(Method, Url, Headers, []).
+
+request(Method, Url, Headers, Body) ->
+    request(Method, Url, Headers, Body, [], 3).
+
+request(Method, Url, Headers, Body, Opts) ->
+    request(Method, Url, Headers, Body, Opts, 3).
+
+request(_Method, _Url, _Headers, _Body, _Opts, 0) ->
+    {error, request_failed};
+request(Method, Url, Headers, Body, Opts, N) ->
+    case code:is_loaded(ibrowse) of
+        false ->
+            {ok, _} = ibrowse:start();
+        _ ->
+            ok
+    end,
+    case ibrowse:send_req(Url, Headers, Method, Body, Opts) of
+        {ok, Code0, RespHeaders, RespBody0} ->
+            Code = list_to_integer(Code0),
+            RespBody = iolist_to_binary(RespBody0),
+            {ok, Code, RespHeaders, RespBody};
+        {error, {'EXIT', {normal, _}}} ->
+            % Connection closed right after a successful request that
+            % used the same connection.
+            request(Method, Url, Headers, Body, N - 1);
+        Error ->
+            Error
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/2a45cb03/src/test_util.erl
----------------------------------------------------------------------
diff --git a/src/test_util.erl b/src/test_util.erl
index 3f90330..9340d36 100644
--- a/src/test_util.erl
+++ b/src/test_util.erl
@@ -20,6 +20,7 @@
 -export([request/3, request/4]).
 -export([start_couch/0, start_couch/1, stop_couch/0, stop_couch/1]).
 -export([start_config/1, stop_config/1]).
+-export([start_applications/1]).
 
 
 srcdir() ->

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/2a45cb03/test/test_request.erl
----------------------------------------------------------------------
diff --git a/test/test_request.erl b/test/test_request.erl
deleted file mode 100644
index 68e4956..0000000
--- a/test/test_request.erl
+++ /dev/null
@@ -1,75 +0,0 @@
-% Licensed under the Apache License, Version 2.0 (the "License"); you may not
-% use this file except in compliance with the License. You may obtain a copy of
-% the License at
-%
-%   http://www.apache.org/licenses/LICENSE-2.0
-%
-% Unless required by applicable law or agreed to in writing, software
-% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-% License for the specific language governing permissions and limitations under
-% the License.
-
--module(test_request).
-
--export([get/1, get/2, get/3]).
--export([put/2, put/3]).
--export([options/1, options/2, options/3]).
--export([request/3, request/4]).
-
-get(Url) ->
-    request(get, Url, []).
-
-get(Url, Headers) ->
-    request(get, Url, Headers).
-get(Url, Headers, Opts) ->
-    request(get, Url, Headers, [], Opts).
-
-
-put(Url, Body) ->
-    request(put, Url, [], Body).
-
-put(Url, Headers, Body) ->
-    request(put, Url, Headers, Body).
-
-
-options(Url) ->
-    request(options, Url, []).
-
-options(Url, Headers) ->
-    request(options, Url, Headers).
-
-options(Url, Headers, Opts) ->
-    request(options, Url, Headers, [], Opts).
-
-
-request(Method, Url, Headers) ->
-    request(Method, Url, Headers, []).
-
-request(Method, Url, Headers, Body) ->
-    request(Method, Url, Headers, Body, [], 3).
-
-request(Method, Url, Headers, Body, Opts) ->
-    request(Method, Url, Headers, Body, Opts, 3).
-
-request(_Method, _Url, _Headers, _Body, _Opts, 0) ->
-    {error, request_failed};
-request(Method, Url, Headers, Body, Opts, N) ->
-    case code:is_loaded(ibrowse) of
-        false ->
-            {ok, _} = ibrowse:start();
-        _ ->
-            ok
-    end,
-    case ibrowse:send_req(Url, Headers, Method, Body, Opts) of
-        {ok, Code0, RespHeaders, RespBody0} ->
-            Code = list_to_integer(Code0),
-            RespBody = iolist_to_binary(RespBody0),
-            {ok, Code, RespHeaders, RespBody};
-        {error, {'EXIT', {normal, _}}} ->
-            % Connection closed right after a successful request that
-            % used the same connection.
-            request(Method, Url, Headers, Body, N - 1);
-        Error ->
-            Error
-    end.


[19/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
Add missing metrics


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/cb52507c
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/cb52507c
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/cb52507c

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: cb52507c1ced478e6900cae529584461c3d4910b
Parents: be6ddf2
Author: Robert Newson <rn...@apache.org>
Authored: Mon Nov 3 18:04:57 2014 +0000
Committer: Robert Newson <rn...@apache.org>
Committed: Mon Nov 3 18:05:12 2014 +0000

----------------------------------------------------------------------
 priv/stats_descriptions.cfg | 8 ++++++++
 1 file changed, 8 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/cb52507c/priv/stats_descriptions.cfg
----------------------------------------------------------------------
diff --git a/priv/stats_descriptions.cfg b/priv/stats_descriptions.cfg
index 729fca1..2dd77bc 100644
--- a/priv/stats_descriptions.cfg
+++ b/priv/stats_descriptions.cfg
@@ -86,6 +86,10 @@
     {type, counter},
     {desc, <<"number of HTTP HEAD requests">>}
 ]}.
+{[couchdb, httpd_request_methods, 'OPTIONS'], [
+    {type, counter},
+    {desc, <<"number of HTTP OPTIONS requests">>}
+]}.
 {[couchdb, httpd_request_methods, 'POST'], [
     {type, counter},
     {desc, <<"number of HTTP POST requests">>}
@@ -106,6 +110,10 @@
     {type, counter},
     {desc, <<"number of HTTP 202 Accepted responses">>}
 ]}.
+{[couchdb, httpd_status_codes, 204], [
+    {type, counter},
+    {desc, <<"number of HTTP 204 No Content responses">>}
+]}.
 {[couchdb, httpd_status_codes, 301], [
     {type, counter},
     {desc, <<"number of HTTP 301 Moved Permanently responses">>}


[12/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
Change return format of _view_changes

This commit makes KV pairs for _view_changes requests be returned in the
format:

    {..."add": [["key", "val"],["key2", "val2"]], "remove": ["oldkey"]}

Note that the "add" field is a list of lists rather than an object
because: 1) there can be multiple values for a given key and 2) keys can
be non-strings (non-string keys are invalid JSON).

Also note that if a view update causes adds or removes some (but not
all) values for a given key, all values associated with that key will be
returned in the _view_changes response.


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/6125862c
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/6125862c
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/6125862c

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 6125862cfed4e948f0f188faa46780dfb3bcbd0d
Parents: 6e7f19c
Author: Benjamin Bastian <be...@gmail.com>
Authored: Mon Sep 15 19:10:44 2014 -0700
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700

----------------------------------------------------------------------
 src/couch_changes.erl | 154 ++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 131 insertions(+), 23 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/6125862c/src/couch_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_changes.erl b/src/couch_changes.erl
index 2ff78e7..2b2647f 100644
--- a/src/couch_changes.erl
+++ b/src/couch_changes.erl
@@ -49,7 +49,9 @@
     doc_options,
     conflicts,
     timeout,
-    timeout_fun
+    timeout_fun,
+    aggregation_kvs,
+    aggregation_results
 }).
 
 handle_db_changes(Args, Req, Db) ->
@@ -450,7 +452,9 @@ build_acc(Args, Callback, UserAcc, Db, StartSeq, Prepend, Timeout, TimeoutFun, D
         timeout_fun = TimeoutFun,
         ddoc_name = DDocName,
         view_name = ViewName,
-        view = View
+        view = View,
+        aggregation_results=[],
+        aggregation_kvs=[]
     }.
 
 send_changes(Acc, Dir, FirstRound) ->
@@ -460,16 +464,36 @@ send_changes(Acc, Dir, FirstRound) ->
         filter = Filter,
         view = View
     } = Acc,
-    EnumFun = fun changes_enumerator/2,
+    DbEnumFun = fun changes_enumerator/2,
     case can_optimize(FirstRound, Filter) of
         {true, Fun} ->
-            Fun(Db, StartSeq, Dir, EnumFun, Acc, Filter);
+            Fun(Db, StartSeq, Dir, DbEnumFun, Acc, Filter);
         _ ->
-            case View of
-                undefined ->
-                    couch_db:changes_since(Db, StartSeq, EnumFun, [{dir, Dir}], Acc);
-                #mrview{} ->
-                    couch_mrview:view_changes_since(View, StartSeq, EnumFun, [{dir, Dir}], Acc)
+            case {View, Filter}  of
+                {#mrview{}, {fast_view, _, _, _}} ->
+                    couch_mrview:view_changes_since(View, StartSeq, DbEnumFun, [{dir, Dir}], Acc);
+                {undefined, _} ->
+                    couch_db:changes_since(Db, StartSeq, DbEnumFun, [{dir, Dir}], Acc);
+                {#mrview{}, _} ->
+                    ViewEnumFun = fun view_changes_enumerator/2,
+                    {Go, Acc0} = couch_mrview:view_changes_since(View, StartSeq, ViewEnumFun, [{dir, Dir}], Acc),
+                    case Acc0 of
+                        #changes_acc{aggregation_results=[]} ->
+                            {Go, Acc0};
+                        _ ->
+                            #changes_acc{
+                                aggregation_results = AggResults,
+                                aggregation_kvs = AggKVs,
+                                user_acc = UserAcc,
+                                callback = Callback,
+                                resp_type = ResponseType,
+                                prepend = Prepend
+                            } = Acc0,
+                            ChangesRow = view_changes_row(AggResults, AggKVs, Acc0),
+                            UserAcc0 = Callback({change, ChangesRow, Prepend}, ResponseType, UserAcc),
+                            reset_heartbeat(),
+                            {Go, Acc0#changes_acc{user_acc=UserAcc0}}
+                    end
             end
     end.
 
@@ -589,18 +613,84 @@ maybe_refresh_view(Db, DDocName, ViewName) ->
 end_sending_changes(Callback, UserAcc, EndSeq, ResponseType) ->
     Callback({stop, EndSeq}, ResponseType, UserAcc).
 
+view_changes_enumerator(Value, Acc) ->
+    #changes_acc{
+        filter = Filter, callback = Callback, prepend = Prepend,
+        user_acc = UserAcc, limit = Limit, resp_type = ResponseType, db = Db,
+        timeout = Timeout, timeout_fun = TimeoutFun, seq = CurrentSeq,
+        aggregation_kvs=AggKVs, aggregation_results=AggResults
+    } = Acc,
+
+    Results0 = view_filter(Db, Value, Filter),
+    Results = [Result || Result <- Results0, Result /= null],
+    {{Seq, _}, _} = Value,
+
+    Go = if (Limit =< 1) andalso Results =/= [] -> stop; true -> ok end,
+
+    if CurrentSeq =:= Seq ->
+        NewAggKVs = case Results of
+            [] -> AggKVs;
+            _ -> [Value|AggKVs]
+        end,
+        {Done, UserAcc2} = maybe_heartbeat(Timeout, TimeoutFun, UserAcc),
+        Acc0 = Acc#changes_acc{
+            seq = Seq,
+            user_acc = UserAcc2,
+            aggregation_kvs=NewAggKVs
+        },
+        case Done of
+            stop -> {stop, Acc0};
+            ok -> {Go, Acc0}
+        end;
+    AggResults =/= [] ->
+        {NewAggKVs, NewAggResults} = case Results of
+            [] -> {[], []};
+            _ -> {[Value], Results}
+        end,
+        if ResponseType =:= "continuous" orelse ResponseType =:= "eventsource" ->
+            ChangesRow = view_changes_row(AggResults, AggKVs, Acc),
+            UserAcc2 = Callback({change, ChangesRow, <<>>}, ResponseType, UserAcc),
+            reset_heartbeat(),
+            {Go, Acc#changes_acc{
+                seq = Seq, user_acc = UserAcc2, limit = Limit - 1,
+                aggregation_kvs=NewAggKVs, aggregation_results=NewAggResults}};
+        true ->
+            ChangesRow = view_changes_row(AggResults, AggKVs, Acc),
+            UserAcc2 = Callback({change, ChangesRow, Prepend}, ResponseType, UserAcc),
+            reset_heartbeat(),
+            {Go, Acc#changes_acc{
+                seq = Seq, prepend = <<",\n">>, user_acc = UserAcc2,
+                limit = Limit - 1, aggregation_kvs=[Value],
+                aggregation_results=Results}}
+        end;
+    true ->
+        {NewAggKVs, NewAggResults} = case Results of
+            [] -> {[], []};
+            _ -> {[Value], Results}
+        end,
+        {Done, UserAcc2} = maybe_heartbeat(Timeout, TimeoutFun, UserAcc),
+        Acc0 = Acc#changes_acc{
+            seq = Seq,
+            user_acc = UserAcc2,
+            aggregation_kvs=NewAggKVs,
+            aggregation_results=NewAggResults
+        },
+        case Done of
+            stop -> {stop, Acc0};
+            ok -> {Go, Acc0}
+        end
+    end.
+
 changes_enumerator(Value0, Acc) ->
     #changes_acc{
         filter = Filter, callback = Callback, prepend = Prepend,
         user_acc = UserAcc, limit = Limit, resp_type = ResponseType, db = Db,
-        timeout = Timeout, timeout_fun = TimeoutFun, view = View
+        timeout = Timeout, timeout_fun = TimeoutFun
     } = Acc,
-    {Value, Results0} = case {View, Filter} of
-        {_, {fast_view, _, _, _}} ->
+    {Value, Results0} = case Filter of
+        {fast_view, _, _, _} ->
             fast_view_filter(Db, Value0, Filter);
-        {#mrview{}, _} ->
-            {Value0, view_filter(Db, Value0, Filter)};
-        {_, _} ->
+        _ ->
             {Value0, filter(Db, Value0, Filter)}
     end,
     Results = [Result || Result <- Results0, Result /= null],
@@ -638,15 +728,33 @@ changes_enumerator(Value0, Acc) ->
 
 
 
-changes_row(Results, DocInfo, #changes_acc{filter={fast_view,_,_,_}}=Acc) ->
-    format_doc_info_change(Results, DocInfo, Acc);
-changes_row(Results, KV, #changes_acc{view=#mrview{}}=Acc) ->
-    {{Seq, Key}, {Id, Value, Rev}} = KV,
-    {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"key">>, Key}, {<<"value">>, Value}, {<<"changes">>, Results}] ++ maybe_get_changes_doc({Id, Rev}, Acc)};
-changes_row(Results, #doc_info{}=DocInfo, Acc) ->
-    format_doc_info_change(Results, DocInfo, Acc).
+view_changes_row(Results, KVs, Acc) ->
+    {Add, Remove} = lists:foldl(fun(Row, {AddAcc, RemAcc}) ->
+        {{_Seq, Key}, {_Id, Value, _Rev}} = Row,
+        case Value of
+            removed ->
+                {AddAcc, [Key|RemAcc]};
+            {dups, DupValues} ->
+                AddAcc1 = lists:foldl(fun(DupValue, AddAcc0) ->
+                    [[Key, DupValue]|AddAcc0]
+                end, AddAcc, DupValues),
+                {AddAcc1, RemAcc};
+            _ ->
+                {[[Key, Value]|AddAcc], RemAcc}
+        end
+    end, {[], []}, KVs),
+
+    % Seq, Id, and Rev should be the same for all KVs, since we're aggregating
+    % by seq.
+    [{{Seq, _Key}, {Id, _Value, Rev}}|_] = KVs,
+
+    {[
+        {<<"seq">>, Seq}, {<<"id">>, Id}, {<<"add">>, Add},
+        {<<"remove">>, Remove}, {<<"changes">>, Results}
+    ] ++ maybe_get_changes_doc({Id, Rev}, Acc)}.
+
 
-format_doc_info_change(Results, #doc_info{}=DocInfo, Acc) ->
+changes_row(Results, DocInfo, Acc) ->
     #doc_info{
         id = Id, high_seq = Seq, revs = [#rev_info{deleted = Del} | _]
     } = DocInfo,


[18/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
Track the time spent executing validate_doc_update functions


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/be6ddf24
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/be6ddf24
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/be6ddf24

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: be6ddf24f62550bddd1dced0903bb508188b210e
Parents: 3e32256
Author: Alexander Shorin <kx...@apache.org>
Authored: Fri Oct 31 00:40:09 2014 +0300
Committer: Alexander Shorin <kx...@apache.org>
Committed: Mon Nov 3 12:19:59 2014 +0300

----------------------------------------------------------------------
 priv/stats_descriptions.cfg |  4 ++++
 src/couch_db.erl            | 29 +++++++++++++++++------------
 2 files changed, 21 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/be6ddf24/priv/stats_descriptions.cfg
----------------------------------------------------------------------
diff --git a/priv/stats_descriptions.cfg b/priv/stats_descriptions.cfg
index 3f771d2..729fca1 100644
--- a/priv/stats_descriptions.cfg
+++ b/priv/stats_descriptions.cfg
@@ -186,3 +186,7 @@
     {type, counter},
     {desc, <<"number of rejections by validate_doc_update function">>}
 ]}.
+{[couchdb, query_server, vdu_process_time], [
+    {type, histogram},
+    {desc, <<"duration of validate_doc_update function calls">>}
+]}.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/be6ddf24/src/couch_db.erl
----------------------------------------------------------------------
diff --git a/src/couch_db.erl b/src/couch_db.erl
index b5ea64d..86b6558 100644
--- a/src/couch_db.erl
+++ b/src/couch_db.erl
@@ -582,18 +582,23 @@ validate_ddoc(DbName, DDoc) ->
     end.
 
 validate_doc_update_int(Db, Doc, GetDiskDocFun) ->
-    DiskDoc = GetDiskDocFun(),
-    JsonCtx = couch_util:json_user_ctx(Db),
-    SecObj = get_security(Db),
-    try [case Fun(Doc, DiskDoc, JsonCtx, SecObj) of
-            ok -> ok;
-            Error -> throw(Error)
-        end || Fun <- Db#db.validate_doc_funs],
-        ok
-    catch
-        throw:Error ->
-            Error
-    end.
+    Fun = fun() ->
+        DiskDoc = GetDiskDocFun(),
+        JsonCtx = couch_util:json_user_ctx(Db),
+        SecObj = get_security(Db),
+        try
+            [case Fun(Doc, DiskDoc, JsonCtx, SecObj) of
+                ok -> ok;
+                Error -> throw(Error)
+             end || Fun <- Db#db.validate_doc_funs],
+            ok
+        catch
+            throw:Error ->
+                Error
+        end
+    end,
+    couch_stats:update_histogram([couchdb, query_server, vdu_process_time],
+                                 Fun).
 
 
 % to be safe, spawn a middleman here


[10/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
Refactor code for sanity and so the view changes handler can reuse code better


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/da2836b6
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/da2836b6
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/da2836b6

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: da2836b6afa4fcb96a18bff5f32f5ac3d6376de8
Parents: 20cd47e
Author: Benjamin Bastian <be...@gmail.com>
Authored: Thu Aug 28 00:00:50 2014 +0700
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700

----------------------------------------------------------------------
 src/couch_httpd_changes.erl | 259 ---------------------------------------
 src/couch_httpd_db.erl      |  54 ++++----
 2 files changed, 29 insertions(+), 284 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/da2836b6/src/couch_httpd_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_changes.erl b/src/couch_httpd_changes.erl
deleted file mode 100644
index 4963a5f..0000000
--- a/src/couch_httpd_changes.erl
+++ /dev/null
@@ -1,259 +0,0 @@
-% Licensed under the Apache License, Version 2.0 (the "License"); you may not
-% use this file except in compliance with the License. You may obtain a copy of
-% the License at
-%
-%   http://www.apache.org/licenses/LICENSE-2.0
-%
-% Unless required by applicable law or agreed to in writing, software
-% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-% License for the specific language governing permissions and limitations under
-% the License.
-
--module(couch_httpd_changes).
-
--export([handle_db_changes_req/2,
-         handle_changes_req/4,
-         parse_changes_query/2]).
-
--include_lib("couch/include/couch_db.hrl").
-
-handle_db_changes_req(Req, Db) ->
-    ChangesArgs = parse_changes_query(Req, Db),
-    ChangesFun = couch_changes:handle_db_changes(ChangesArgs, Req, Db),
-    handle_changes_req(Req, Db, ChangesArgs, ChangesFun).
-
-handle_changes_req(#httpd{method='POST'}=Req, Db, ChangesArgs, ChangesFun) ->
-    couch_httpd:validate_ctype(Req, "application/json"),
-    handle_changes_req1(Req, Db, ChangesArgs, ChangesFun);
-handle_changes_req(#httpd{method='GET'}=Req, Db, ChangesArgs, ChangesFun) ->
-    handle_changes_req1(Req, Db, ChangesArgs, ChangesFun);
-handle_changes_req(#httpd{}=Req, _Db, _ChangesArgs, _ChangesFun) ->
-    couch_httpd:send_method_not_allowed(Req, "GET,HEAD,POST").
-
-handle_changes_req1(Req, #db{name=DbName}=Db, ChangesArgs, ChangesFun) ->
-    AuthDbName = ?l2b(config:get("couch_httpd_auth", "authentication_db")),
-    case AuthDbName of
-    DbName ->
-        % in the authentication database, _changes is admin-only.
-        ok = couch_db:check_is_admin(Db);
-    _Else ->
-        % on other databases, _changes is free for all.
-        ok
-    end,
-
-    MakeCallback = fun(Resp) ->
-        fun({change, {ChangeProp}=Change, _}, "eventsource") ->
-            Seq = proplists:get_value(<<"seq">>, ChangeProp),
-            couch_httpd:send_chunk(Resp, ["data: ", ?JSON_ENCODE(Change),
-                              "\n", "id: ", ?JSON_ENCODE(Seq),
-                              "\n\n"]);
-        ({change, Change, _}, "continuous") ->
-            couch_httpd:send_chunk(Resp, [?JSON_ENCODE(Change) | "\n"]);
-        ({change, Change, Prepend}, _) ->
-            couch_httpd:send_chunk(Resp, [Prepend, ?JSON_ENCODE(Change)]);
-        (start, "eventsource") ->
-            ok;
-        (start, "continuous") ->
-            ok;
-        (start, _) ->
-            couch_httpd:send_chunk(Resp, "{\"results\":[\n");
-        ({stop, _EndSeq}, "eventsource") ->
-            couch_httpd:end_json_response(Resp);
-        ({stop, EndSeq}, "continuous") ->
-            couch_httpd:send_chunk(
-                Resp,
-                [?JSON_ENCODE({[{<<"last_seq">>, EndSeq}]}) | "\n"]
-            ),
-            couch_httpd:end_json_response(Resp);
-        ({stop, EndSeq}, _) ->
-            couch_httpd:send_chunk(
-                Resp,
-                io_lib:format("\n],\n\"last_seq\":~w}\n", [EndSeq])
-            ),
-            couch_httpd:end_json_response(Resp);
-        (timeout, _) ->
-            couch_httpd:send_chunk(Resp, "\n")
-        end
-    end,
-    WrapperFun = case ChangesArgs#changes_args.feed of
-    "normal" ->
-        {ok, Info} = couch_db:get_db_info(Db),
-        CurrentEtag = couch_httpd:make_etag(Info),
-        fun(FeedChangesFun) ->
-            couch_httpd:etag_respond(
-                Req,
-                CurrentEtag,
-                fun() ->
-                    {ok, Resp} = couch_httpd:start_json_response(
-                         Req, 200, [{"ETag", CurrentEtag}]
-                    ),
-                    FeedChangesFun(MakeCallback(Resp))
-                end
-            )
-        end;
-    "eventsource" ->
-        Headers = [
-            {"Content-Type", "text/event-stream"},
-            {"Cache-Control", "no-cache"}
-        ],
-        {ok, Resp} = couch_httpd:start_chunked_response(Req, 200, Headers),
-        fun(FeedChangesFun) ->
-            FeedChangesFun(MakeCallback(Resp))
-        end;
-    _ ->
-        % "longpoll" or "continuous"
-        {ok, Resp} = couch_httpd:start_json_response(Req, 200),
-        fun(FeedChangesFun) ->
-            FeedChangesFun(MakeCallback(Resp))
-        end
-    end,
-    couch_stats_collector:increment(
-        {httpd, clients_requesting_changes}
-    ),
-    try
-        WrapperFun(ChangesFun)
-    after
-    couch_stats_collector:decrement(
-        {httpd, clients_requesting_changes}
-    )
-    end.
-
-
-parse_changes_query(Req, Db) ->
-    ChangesArgs = lists:foldl(fun({Key, Value}, Args) ->
-        case {string:to_lower(Key), Value} of
-        {"feed", _} ->
-            Args#changes_args{feed=Value};
-        {"descending", "true"} ->
-            Args#changes_args{dir=rev};
-        {"since", "now"} ->
-            UpdateSeq = couch_util:with_db(Db#db.name, fun(WDb) ->
-                                        couch_db:get_update_seq(WDb)
-                                end),
-            Args#changes_args{since=UpdateSeq};
-        {"since", _} ->
-            Args#changes_args{since=list_to_integer(Value)};
-        {"last-event-id", _} ->
-            Args#changes_args{since=list_to_integer(Value)};
-        {"limit", _} ->
-            Args#changes_args{limit=list_to_integer(Value)};
-        {"style", _} ->
-            Args#changes_args{style=list_to_existing_atom(Value)};
-        {"heartbeat", "true"} ->
-            Args#changes_args{heartbeat=true};
-        {"heartbeat", _} ->
-            Args#changes_args{heartbeat=list_to_integer(Value)};
-        {"timeout", _} ->
-            Args#changes_args{timeout=list_to_integer(Value)};
-        {"include_docs", "true"} ->
-            Args#changes_args{include_docs=true};
-        {"attachments", "true"} ->
-            Opts = Args#changes_args.doc_options,
-            Args#changes_args{doc_options=[attachments|Opts]};
-        {"att_encoding_info", "true"} ->
-            Opts = Args#changes_args.doc_options,
-            Args#changes_args{doc_options=[att_encoding_info|Opts]};
-        {"conflicts", "true"} ->
-            Args#changes_args{conflicts=true};
-        {"filter", _} ->
-            Args#changes_args{filter=Value};
-        _Else -> % unknown key value pair, ignore.
-            Args
-        end
-    end, #changes_args{}, couch_httpd:qs(Req)),
-    %% if it's an EventSource request with a Last-event-ID header
-    %% that should override the `since` query string, since it's
-    %% probably the browser reconnecting.
-    case ChangesArgs#changes_args.feed of
-        "eventsource" ->
-            case couch_httpd:header_value(Req, "last-event-id") of
-                undefined ->
-                    ChangesArgs;
-                Value ->
-                    ChangesArgs#changes_args{since=list_to_integer(Value)}
-            end;
-        _ ->
-            ChangesArgs
-    end.
-
-parse_view_param({json_req, {Props}}) ->
-    {Query} = couch_util:get_value(<<"query">>, Props),
-    parse_view_param1(couch_util:get_value(<<"view">>, Query, <<"">>));
-parse_view_param(Req) ->
-    parse_view_param1(list_to_binary(couch_httpd:qs_value(Req, "view", ""))).
-
-parse_view_param1(ViewParam) ->
-    case re:split(ViewParam, <<"/">>) of
-        [DName, ViewName] ->
-            {<< "_design/", DName/binary >>, ViewName};
-        _ ->
-            throw({bad_request, "Invalid `view` parameter."})
-    end.
-
-parse_view_options([], Acc) ->
-    Acc;
-parse_view_options([{K, V} | Rest], Acc) ->
-    Acc1 = case couch_util:to_binary(K) of
-        <<"reduce">> ->
-            [{reduce, couch_mrview_http:parse_boolean(V)}];
-        <<"key">> ->
-            V1 = parse_json(V),
-            [{start_key, V1}, {end_key, V1} | Acc];
-        <<"keys">> ->
-            [{keys, parse_json(V)} | Acc];
-        <<"startkey">> ->
-            [{start_key, parse_json(V)} | Acc];
-        <<"start_key">> ->
-            [{start_key, parse_json(V)} | Acc];
-        <<"startkey_docid">> ->
-            [{start_key_docid, couch_util:to_binary(V)} | Acc];
-        <<"start_key_docid">> ->
-            [{start_key_docid, couch_util:to_binary(V)} | Acc];
-        <<"endkey">> ->
-            [{end_key, parse_json(V)} | Acc];
-        <<"end_key">> ->
-            [{end_key, parse_json(V)} | Acc];
-        <<"endkey_docid">> ->
-            [{start_key_docid, couch_util:to_binary(V)} | Acc];
-        <<"end_key_docid">> ->
-            [{start_key_docid, couch_util:to_binary(V)} | Acc];
-        <<"limit">> ->
-            [{limit, couch_mrview_http:parse_pos_int(V)} | Acc];
-        <<"count">> ->
-            throw({query_parse_error, <<"QS param `count` is not `limit`">>});
-        <<"stale">> when V =:= <<"ok">> orelse V =:= "ok" ->
-            [{stale, ok} | Acc];
-        <<"stale">> when V =:= <<"update_after">> orelse V =:= "update_after" ->
-            [{stale, update_after} | Acc];
-        <<"stale">> ->
-            throw({query_parse_error, <<"Invalid value for `stale`.">>});
-        <<"descending">> ->
-            case couch_mrview_http:parse_boolean(V) of
-                true ->
-                    [{direction, rev} | Acc];
-                _ ->
-                    [{direction, fwd} | Acc]
-            end;
-        <<"skip">> ->
-            [{skip, couch_mrview_http:parse_pos_int(V)} | Acc];
-        <<"group">> ->
-            case couch_mrview_http:parse_booolean(V) of
-                true ->
-                    [{group_level, exact} | Acc];
-                _ ->
-                    [{group_level, 0} | Acc]
-            end;
-        <<"group_level">> ->
-            [{group_level, couch_mrview_http:parse_pos_int(V)} | Acc];
-        <<"inclusive_end">> ->
-            [{inclusive_end, couch_mrview_http:parse_boolean(V)}];
-        _ ->
-            Acc
-    end,
-    parse_view_options(Rest, Acc1).
-
-parse_json(V) when is_list(V) ->
-    ?JSON_DECODE(V);
-parse_json(V) ->
-    V.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/da2836b6/src/couch_httpd_db.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_db.erl b/src/couch_httpd_db.erl
index 442e872..34f04f2 100644
--- a/src/couch_httpd_db.erl
+++ b/src/couch_httpd_db.erl
@@ -14,9 +14,10 @@
 -include_lib("couch/include/couch_db.hrl").
 
 -export([handle_request/1, handle_compact_req/2, handle_design_req/2,
-    db_req/2, couch_doc_open/4,handle_changes_req/2,
+    db_req/2, couch_doc_open/4, handle_db_changes_req/2,
     update_doc_result_to_json/1, update_doc_result_to_json/2,
-    handle_design_info_req/3, parse_copy_destination_header/1]).
+    handle_design_info_req/3, parse_copy_destination_header/1,
+    parse_changes_query/2, handle_changes_req/4]).
 
 -import(couch_httpd,
     [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
@@ -54,15 +55,22 @@ handle_request(#httpd{path_parts=[DbName|RestParts],method=Method,
         do_db_req(Req, Handler)
     end.
 
-handle_changes_req(#httpd{method='POST'}=Req, Db) ->
+
+handle_db_changes_req(Req, Db) ->
+    ChangesArgs = parse_changes_query(Req, Db),
+    ChangesFun = couch_changes:handle_db_changes(ChangesArgs, Req, Db),
+    handle_changes_req(Req, Db, ChangesArgs, ChangesFun).
+
+
+handle_changes_req(#httpd{method='POST'}=Req, Db, ChangesArgs, ChangesFun) ->
     couch_httpd:validate_ctype(Req, "application/json"),
-    handle_changes_req1(Req, Db);
-handle_changes_req(#httpd{method='GET'}=Req, Db) ->
-    handle_changes_req1(Req, Db);
-handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->
-    send_method_not_allowed(Req, "GET,HEAD,POST").
+    handle_changes_req1(Req, Db, ChangesArgs, ChangesFun);
+handle_changes_req(#httpd{method='GET'}=Req, Db, ChangesArgs, ChangesFun) ->
+    handle_changes_req1(Req, Db, ChangesArgs, ChangesFun);
+handle_changes_req(#httpd{}=Req, _Db, _ChangesArgs, _ChangesFun) ->
+    couch_httpd:send_method_not_allowed(Req, "GET,HEAD,POST").
 
-handle_changes_req1(Req, #db{name=DbName}=Db) ->
+handle_changes_req1(Req, #db{name=DbName}=Db, ChangesArgs, ChangesFun) ->
     AuthDbName = ?l2b(config:get("couch_httpd_auth", "authentication_db")),
     case AuthDbName of
     DbName ->
@@ -72,47 +80,41 @@ handle_changes_req1(Req, #db{name=DbName}=Db) ->
         % on other databases, _changes is free for all.
         ok
     end,
-    handle_changes_req2(Req, Db).
 
-handle_changes_req2(Req, Db) ->
     MakeCallback = fun(Resp) ->
         fun({change, {ChangeProp}=Change, _}, "eventsource") ->
             Seq = proplists:get_value(<<"seq">>, ChangeProp),
-            send_chunk(Resp, ["data: ", ?JSON_ENCODE(Change),
+            couch_httpd:send_chunk(Resp, ["data: ", ?JSON_ENCODE(Change),
                               "\n", "id: ", ?JSON_ENCODE(Seq),
                               "\n\n"]);
         ({change, Change, _}, "continuous") ->
-            send_chunk(Resp, [?JSON_ENCODE(Change) | "\n"]);
+            couch_httpd:send_chunk(Resp, [?JSON_ENCODE(Change) | "\n"]);
         ({change, Change, Prepend}, _) ->
-            send_chunk(Resp, [Prepend, ?JSON_ENCODE(Change)]);
+            couch_httpd:send_chunk(Resp, [Prepend, ?JSON_ENCODE(Change)]);
         (start, "eventsource") ->
             ok;
         (start, "continuous") ->
             ok;
         (start, _) ->
-            send_chunk(Resp, "{\"results\":[\n");
+            couch_httpd:send_chunk(Resp, "{\"results\":[\n");
         ({stop, _EndSeq}, "eventsource") ->
-            end_json_response(Resp);
+            couch_httpd:end_json_response(Resp);
         ({stop, EndSeq}, "continuous") ->
-            send_chunk(
+            couch_httpd:send_chunk(
                 Resp,
                 [?JSON_ENCODE({[{<<"last_seq">>, EndSeq}]}) | "\n"]
             ),
-            end_json_response(Resp);
+            couch_httpd:end_json_response(Resp);
         ({stop, EndSeq}, _) ->
-            send_chunk(
+            couch_httpd:send_chunk(
                 Resp,
                 io_lib:format("\n],\n\"last_seq\":~w}\n", [EndSeq])
             ),
-            end_json_response(Resp);
-        (timeout, "eventsource") ->
-            send_chunk(Resp, "event: heartbeat\ndata: \n\n");
+            couch_httpd:end_json_response(Resp);
         (timeout, _) ->
-            send_chunk(Resp, "\n")
+            couch_httpd:send_chunk(Resp, "\n")
         end
     end,
-    ChangesArgs = parse_changes_query(Req, Db),
-    ChangesFun = couch_changes:handle_db_changes(ChangesArgs, Req, Db),
     WrapperFun = case ChangesArgs#changes_args.feed of
     "normal" ->
         {ok, Info} = couch_db:get_db_info(Db),
@@ -154,6 +156,8 @@ handle_changes_req2(Req, Db) ->
             [couchdb, httpd, clients_requesting_changes])
     end.
 
+
+
 handle_compact_req(#httpd{method='POST'}=Req, Db) ->
     case Req#httpd.path_parts of
         [_DbName, <<"_compact">>] ->


[09/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
Open view in changes start function rather than outside


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/8d5c9009
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/8d5c9009
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/8d5c9009

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 8d5c90094214ad186641fe25f1c70d621adda378
Parents: 434b541
Author: Benjamin Bastian <be...@gmail.com>
Authored: Tue Aug 26 15:34:21 2014 +0700
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700

----------------------------------------------------------------------
 src/couch_changes.erl | 21 ++++++++++++++-------
 1 file changed, 14 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/8d5c9009/src/couch_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_changes.erl b/src/couch_changes.erl
index 50f9d50..8ff109b 100644
--- a/src/couch_changes.erl
+++ b/src/couch_changes.erl
@@ -73,7 +73,7 @@ handle_changes(Args1, Req, Db0, Type) ->
         _ ->
             false
     end,
-    {StartListenerFun, DDocName, ViewName, View} = if UseViewChanges ->
+    {StartListenerFun, DDocName, ViewName} = if UseViewChanges ->
         {DDocName0, ViewName0} = case {Type, Filter} of
             {{view, DDocName1, ViewName1}, _} ->
                 {DDocName1, ViewName1};
@@ -93,14 +93,14 @@ handle_changes(Args1, Req, Db0, Type) ->
                  ?MODULE, handle_view_event, {self(), DDocName0}, [{dbname, Db0#db.name}]
             )
         end,
-        {SNFun, DDocName0, ViewName0, View0};
-    true ->
+        {SNFun, DDocName0, ViewName0};
+     true ->
         SNFun = fun() ->
             couch_event:link_listener(
                  ?MODULE, handle_db_event, self(), [{dbname, Db0#db.name}]
             )
         end,
-        {SNFun, undefined, undefined, undefined}
+        {SNFun, undefined, undefined}
     end,
     Start = fun() ->
         {ok, Db} = couch_db:reopen(Db0),
@@ -110,7 +110,14 @@ handle_changes(Args1, Req, Db0, Type) ->
         fwd ->
             Since
         end,
-        {Db, StartSeq}
+        View2 = if UseViewChanges ->
+            {ok, {_, View1, _}, _, _} = couch_mrview_util:get_view(
+                    Db0#db.name, DDocName, ViewName, #mrargs{}),
+            View1;
+        true ->
+            undefined
+        end,
+        {Db, View2, StartSeq}
     end,
     % begin timer to deal with heartbeat when filter function fails
     case Args#changes_args.heartbeat of
@@ -126,7 +133,7 @@ handle_changes(Args1, Req, Db0, Type) ->
             {Callback, UserAcc} = get_callback_acc(CallbackAcc),
             {ok, Listener} = StartListenerFun(),
 
-            {Db, StartSeq} = Start(),
+            {Db, View, StartSeq} = Start(),
             UserAcc2 = start_sending_changes(Callback, UserAcc, Feed),
             {Timeout, TimeoutFun} = get_changes_timeout(Args, Callback),
             Acc0 = build_acc(Args, Callback, UserAcc2, Db, StartSeq,
@@ -147,7 +154,7 @@ handle_changes(Args1, Req, Db0, Type) ->
             {Callback, UserAcc} = get_callback_acc(CallbackAcc),
             UserAcc2 = start_sending_changes(Callback, UserAcc, Feed),
             {Timeout, TimeoutFun} = get_changes_timeout(Args, Callback),
-            {Db, StartSeq} = Start(),
+            {Db, View, StartSeq} = Start(),
             Acc0 = build_acc(Args#changes_args{feed="normal"}, Callback,
                              UserAcc2, Db, StartSeq, <<>>, Timeout, TimeoutFun,
                              DDocName, ViewName, View),


[14/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
couch_httpd_changes: check removed keys from the view filter

Make sure to only emit deleted document when a deleted key is passed to
the view filter.

Conflicts:
	src/couch_httpd_changes.erl


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/071dedf1
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/071dedf1
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/071dedf1

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 071dedf1d35b6cd5f5bb0e553de7c81acc43954a
Parents: 7f2af21
Author: benoitc <be...@apache.org>
Authored: Sat Feb 8 21:27:58 2014 +0100
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700

----------------------------------------------------------------------
 src/couch_httpd_changes.erl | 40 +++++++++++++++++++++++++++-------------
 1 file changed, 27 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/071dedf1/src/couch_httpd_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_changes.erl b/src/couch_httpd_changes.erl
index 56ce559..b494b0d 100644
--- a/src/couch_httpd_changes.erl
+++ b/src/couch_httpd_changes.erl
@@ -201,9 +201,15 @@ view_changes_cb(stop, {LastSeq, {_, _, _, Callback, Args}}) ->
 view_changes_cb(heartbeat, {_, _, _, Callback, Args}=Acc) ->
     Callback(timeout, Args#changes_args.feed),
     {ok, Acc};
-view_changes_cb({{Seq, _Key, DocId}, _VAl},
+view_changes_cb({{Seq, _Key, DocId}, Val},
                 {Prepend, OldLimit, Db0, Callback, Args}=Acc) ->
 
+    %% is the key removed from the index?
+    Removed = case Val of
+        {[{<<"_removed">>, true}]} -> true;
+        _ -> false
+    end,
+
     #changes_args{
         feed = ResponseType,
         limit = Limit} = Args,
@@ -220,16 +226,24 @@ view_changes_cb({{Seq, _Key, DocId}, _VAl},
     case couch_db:get_doc_info(Db, DocId) of
         {ok, DocInfo} ->
             %% get change row
-            ChangeRow = view_change_row(Db, DocInfo, Args),
-            %% emit change row
-            Callback({change, ChangeRow, Prepend}, ResponseType),
-
-            %% if we achieved the limit, stop here, else continue.
-            NewLimit = OldLimit + 1,
-            if Limit > NewLimit ->
-                    {ok, {<<",\n">>, Db, NewLimit, Callback, Args}};
-                true ->
-                    {stop, {<<"">>, Db, NewLimit, Callback, Args}}
+            {Deleted, ChangeRow} = view_change_row(Db, DocInfo, Args),
+
+            case Removed of
+                true when Deleted /= true ->
+                    %% the key has been removed from the view but the
+                    %% document hasn't been deleted so ignore it.
+                    {ok, Acc};
+                _ ->
+                    %% emit change row
+                    Callback({change, ChangeRow, Prepend}, ResponseType),
+
+                    %% if we achieved the limit, stop here, else continue.
+                    NewLimit = OldLimit + 1,
+                    if Limit > NewLimit ->
+                            {ok, {<<",\n">>, NewLimit, Db, Callback, Args}};
+                        true ->
+                            {stop, {<<"">>, NewLimit, Db, Callback, Args}}
+                    end
             end;
         {error, not_found} ->
             %% doc not found, continue
@@ -256,7 +270,7 @@ view_change_row(Db, DocInfo, Args) ->
                 || #rev_info{rev=R} <- Revs]
     end,
 
-    {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Changes}] ++
+    {Del, {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Changes}] ++
      deleted_item(Del) ++ case InDoc of
             true ->
                 Opts = case Conflicts of
@@ -272,7 +286,7 @@ view_change_row(Db, DocInfo, Args) ->
                 end;
             false ->
                 []
-        end}.
+    end}}.
 
 parse_changes_query(Req, Db) ->
     ChangesArgs = lists:foldl(fun({Key, Value}, Args) ->


[22/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
Refactor couch_httpd_auth's AuthModule construct

We were missing the change necessary to upgrade user documents with the
move to pbkdf2 in a cluster. This also adds the ability for an
AuthModule to return a context that will be used when updating user
credentials.

COUCHDB-2491


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/3e8286d4
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/3e8286d4
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/3e8286d4

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 3e8286d40664fa0d7f32a3d375f64e64d181022c
Parents: 2a45cb0
Author: Paul J. Davis <pa...@gmail.com>
Authored: Thu Dec 4 13:07:56 2014 -0600
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Thu Dec 4 14:04:57 2014 -0600

----------------------------------------------------------------------
 src/couch_auth_cache.erl  | 26 ++++++++++++++++++++------
 src/couch_httpd_auth.erl  | 37 +++++++++++++++++++------------------
 src/couch_httpd_oauth.erl |  2 +-
 3 files changed, 40 insertions(+), 25 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/3e8286d4/src/couch_auth_cache.erl
----------------------------------------------------------------------
diff --git a/src/couch_auth_cache.erl b/src/couch_auth_cache.erl
index 8cf631b..9eaa7c6 100644
--- a/src/couch_auth_cache.erl
+++ b/src/couch_auth_cache.erl
@@ -16,7 +16,8 @@
 -behaviour(config_listener).
 
 % public API
--export([get_user_creds/1, get_admin/1, add_roles/2]).
+-export([get_user_creds/1, get_user_creds/2, update_user_creds/3]).
+-export([get_admin/1, add_roles/2]).
 
 % gen_server API
 -export([start_link/0, init/1, handle_call/3, handle_info/2, handle_cast/2]).
@@ -42,12 +43,18 @@
 
 
 -spec get_user_creds(UserName::string() | binary()) ->
-    Credentials::list() | nil.
-
-get_user_creds(UserName) when is_list(UserName) ->
-    get_user_creds(?l2b(UserName));
+    {ok, Credentials::list(), term()} | nil.
 
 get_user_creds(UserName) ->
+    get_user_creds(nil, UserName).
+
+-spec get_user_creds(Req::#httpd{}, UserName::string() | binary()) ->
+    {ok, Credentials::list(), term()} | nil.
+
+get_user_creds(Req, UserName) when is_list(UserName) ->
+    get_user_creds(Req, ?l2b(UserName));
+
+get_user_creds(_Req, UserName) ->
     UserCreds = case get_admin(UserName) of
     nil ->
         get_from_cache(UserName);
@@ -61,6 +68,13 @@ get_user_creds(UserName) ->
     end,
     validate_user_creds(UserCreds).
 
+update_user_creds(_Req, UserDoc, _AuthCtx) ->
+    DbNameList = config:get("couch_httpd_auth", "authentication_db", "_users"),
+    couch_util:with_db(?l2b(DbNameList), fun(UserDb) ->
+        {ok, _NewRev} = couch_db:update_doc(UserDb, UserDoc, []),
+        ok
+    end).
+
 add_roles(Props, ExtraRoles) ->
     CurrentRoles = couch_util:get_value(<<"roles">>, Props),
     lists:keyreplace(<<"roles">>, 1, Props, {<<"roles">>, CurrentRoles ++ ExtraRoles}).
@@ -123,7 +137,7 @@ validate_user_creds(UserCreds) ->
               " is used for authentication purposes.">>
         })
     end,
-    UserCreds.
+    {ok, UserCreds, nil}.
 
 
 start_link() ->

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/3e8286d4/src/couch_httpd_auth.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_auth.erl b/src/couch_httpd_auth.erl
index 752dd20..e1af621 100644
--- a/src/couch_httpd_auth.erl
+++ b/src/couch_httpd_auth.erl
@@ -73,17 +73,18 @@ default_authentication_handler(Req) ->
 default_authentication_handler(Req, AuthModule) ->
     case basic_name_pw(Req) of
     {User, Pass} ->
-        case AuthModule:get_user_creds(User) of
+        case AuthModule:get_user_creds(Req, User) of
             nil ->
                 throw({unauthorized, <<"Name or password is incorrect.">>});
-            UserProps ->
+            {ok, UserProps, AuthCtx} ->
                 reject_if_totp(UserProps),
                 UserName = ?l2b(User),
                 Password = ?l2b(Pass),
                 case authenticate(Password, UserProps) of
                     true ->
-                        UserProps2 = maybe_upgrade_password_hash(UserName, Password, UserProps,
-								 AuthModule),
+                        UserProps2 = maybe_upgrade_password_hash(
+                            Req, UserName, Password, UserProps,
+                            AuthModule, AuthCtx),
                         Req#httpd{user_ctx=#user_ctx{
                             name=UserName,
                             roles=couch_util:get_value(<<"roles">>, UserProps2, [])
@@ -195,9 +196,9 @@ cookie_authentication_handler(#httpd{mochi_req=MochiReq}=Req, AuthModule) ->
             Req;
         SecretStr ->
             Secret = ?l2b(SecretStr),
-            case AuthModule:get_user_creds(User) of
+            case AuthModule:get_user_creds(Req, User) of
             nil -> Req;
-            UserProps ->
+            {ok, UserProps, _AuthCtx} ->
                 UserSalt = couch_util:get_value(<<"salt">>, UserProps, <<"">>),
                 FullSecret = <<Secret/binary, UserSalt/binary>>,
                 ExpectedHash = crypto:sha_mac(FullSecret, User ++ ":" ++ TimeStr),
@@ -284,14 +285,15 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req, AuthModule) ->
     UserName = ?l2b(couch_util:get_value("name", Form, "")),
     Password = ?l2b(couch_util:get_value("password", Form, "")),
     couch_log:debug("Attempt Login: ~s",[UserName]),
-    UserProps = case AuthModule:get_user_creds(UserName) of
-        nil -> [];
+    {ok, UserProps, AuthCtx} = case AuthModule:get_user_creds(Req, UserName) of
+        nil -> {ok, [], nil};
         Result -> Result
     end,
     case authenticate(Password, UserProps) of
         true ->
             verify_totp(UserProps, Form),
-            UserProps2 = maybe_upgrade_password_hash(UserName, Password, UserProps, AuthModule),
+            UserProps2 = maybe_upgrade_password_hash(
+                Req, UserName, Password, UserProps, AuthModule, AuthCtx),
             % setup the session cookie
             Secret = ?l2b(ensure_cookie_auth_secret()),
             UserSalt = couch_util:get_value(<<"salt">>, UserProps2),
@@ -363,18 +365,17 @@ maybe_value(_Key, undefined, _Fun) -> [];
 maybe_value(Key, Else, Fun) ->
     [{Key, Fun(Else)}].
 
-maybe_upgrade_password_hash(UserName, Password, UserProps, AuthModule) ->
+maybe_upgrade_password_hash(Req, UserName, Password, UserProps,
+        AuthModule, AuthCtx) ->
     IsAdmin = lists:member(<<"_admin">>, couch_util:get_value(<<"roles">>, UserProps, [])),
     case {IsAdmin, couch_util:get_value(<<"password_scheme">>, UserProps, <<"simple">>)} of
     {false, <<"simple">>} ->
-        DbName = ?l2b(config:get("couch_httpd_auth", "authentication_db", "_users")),
-        couch_util:with_db(DbName, fun(UserDb) ->
-            UserProps2 = proplists:delete(<<"password_sha">>, UserProps),
-            UserProps3 = [{<<"password">>, Password} | UserProps2],
-            NewUserDoc = couch_doc:from_json_obj({UserProps3}),
-            {ok, _NewRev} = couch_db:update_doc(UserDb, NewUserDoc, []),
-            AuthModule:get_user_creds(UserName)
-        end);
+        UserProps2 = proplists:delete(<<"password_sha">>, UserProps),
+        UserProps3 = [{<<"password">>, Password} | UserProps2],
+        NewUserDoc = couch_doc:from_json_obj({UserProps3}),
+        ok = AuthModule:update_user_creds(Req, NewUserDoc, AuthCtx),
+        {ok, NewUserProps, _} = AuthModule:get_user_creds(Req, UserName),
+        NewUserProps;
     _ ->
         UserProps
     end.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/3e8286d4/src/couch_httpd_oauth.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_oauth.erl b/src/couch_httpd_oauth.erl
index 0215240..b72e72d 100644
--- a/src/couch_httpd_oauth.erl
+++ b/src/couch_httpd_oauth.erl
@@ -78,7 +78,7 @@ set_user_ctx(Req, Name) ->
             couch_log:debug("OAuth handler: user `~p` credentials not found",
                             [Name]),
             Req;
-        User ->
+        {ok, User, _AuthCtx} ->
             Roles = couch_util:get_value(<<"roles">>, User, []),
             Req#httpd{user_ctx=#user_ctx{name=Name, roles=Roles}}
     end.


[03/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
Implement two factor authentication

If enabled, require a second factor to acquire a session cookie and
reject basic authentication attempts (as second factor cannot be
presented). Allow previous and next token for clock skew.


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/011975a8
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/011975a8
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/011975a8

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 011975a8cdb8611a7b05c3cd4a2f47872469140a
Parents: 9ee298f
Author: Robert Newson <rn...@apache.org>
Authored: Wed Oct 29 17:39:21 2014 +0000
Committer: Robert Newson <rn...@apache.org>
Committed: Fri Oct 31 11:32:17 2014 +0000

----------------------------------------------------------------------
 src/couch_hotp.erl        | 40 ++++++++++++++++++++++++++++++
 src/couch_httpd_auth.erl  | 54 +++++++++++++++++++++++++++++++++++++++++
 src/couch_totp.erl        | 23 ++++++++++++++++++
 test/couch_hotp_tests.erl | 28 +++++++++++++++++++++
 test/couch_totp_tests.erl | 55 ++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 200 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/011975a8/src/couch_hotp.erl
----------------------------------------------------------------------
diff --git a/src/couch_hotp.erl b/src/couch_hotp.erl
new file mode 100644
index 0000000..896c52a
--- /dev/null
+++ b/src/couch_hotp.erl
@@ -0,0 +1,40 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_hotp).
+
+-export([generate/4]).
+
+generate(Alg, Key, Counter, OutputLen)
+  when is_atom(Alg), is_binary(Key), is_integer(Counter), is_integer(OutputLen) ->
+    Hmac = hmac(Alg, Key, <<Counter:64>>),
+    Offset = binary:last(Hmac) band 16#f,
+    Code =
+        ((binary:at(Hmac, Offset) band 16#7f) bsl 24) +
+        ((binary:at(Hmac, Offset + 1) band 16#ff) bsl 16) +
+        ((binary:at(Hmac, Offset + 2) band 16#ff) bsl 8) +
+        ((binary:at(Hmac, Offset + 3) band 16#ff)),
+    case OutputLen of
+        6 -> Code rem 1000000;
+        7 -> Code rem 10000000;
+        8 -> Code rem 100000000
+    end.
+
+hmac(Alg, Key, Data) ->
+    case {Alg, erlang:function_exported(crypto, hmac, 3)} of
+        {_, true} ->
+            crypto:hmac(Alg, Key, Data);
+        {sha, false} ->
+            crypto:sha_mac(Key, Data);
+        {Alg, false} ->
+            throw({unsupported, Alg})
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/011975a8/src/couch_httpd_auth.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_auth.erl b/src/couch_httpd_auth.erl
index 7c55a2b..cda51c5 100644
--- a/src/couch_httpd_auth.erl
+++ b/src/couch_httpd_auth.erl
@@ -23,6 +23,8 @@
 
 -import(couch_httpd, [header_value/2, send_json/2,send_json/4, send_method_not_allowed/2]).
 
+-compile({no_auto_import,[integer_to_binary/1]}).
+
 special_test_authentication_handler(Req) ->
     case header_value(Req, "WWW-Authenticate") of
     "X-Couch-Test-Auth " ++ NamePass ->
@@ -75,6 +77,7 @@ default_authentication_handler(Req, AuthModule) ->
             nil ->
                 throw({unauthorized, <<"Name or password is incorrect.">>});
             UserProps ->
+                reject_if_totp(UserProps),
                 UserName = ?l2b(User),
                 Password = ?l2b(Pass),
                 case authenticate(Password, UserProps) of
@@ -287,6 +290,7 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req, AuthModule) ->
     end,
     case authenticate(Password, UserProps) of
         true ->
+            verify_totp(UserProps, Form),
             UserProps2 = maybe_upgrade_password_hash(UserName, Password, UserProps, AuthModule),
             % setup the session cookie
             Secret = ?l2b(ensure_cookie_auth_secret()),
@@ -430,3 +434,53 @@ max_age() ->
                 config:get("couch_httpd_auth", "timeout", "600")),
             [{max_age, Timeout}]
     end.
+
+reject_if_totp(User) ->
+    case get_totp_config(User) of
+        undefined ->
+            ok;
+        _ ->
+            throw({unauthorized, <<"Name or password is incorrect.">>})
+    end.
+
+verify_totp(User, Form) ->
+    case get_totp_config(User) of
+        undefined ->
+            ok;
+        {Props} ->
+            Key = couch_util:get_value(<<"key">>, Props),
+            Alg = couch_util:to_existing_atom(
+                couch_util:get_value(<<"algorithm">>, Props, <<"sha">>)),
+            Len = couch_util:get_value(<<"length">>, Props, 6),
+            Token = ?l2b(couch_util:get_value("token", Form, "")),
+            verify_token(Alg, Key, Len, Token)
+    end.
+
+get_totp_config(User) ->
+    couch_util:get_value(<<"totp">>, User).
+
+verify_token(Alg, Key, Len, Token) ->
+    Now = make_cookie_time(),
+    Tokens = [generate_token(Alg, Key, Len, Now - 30),
+              generate_token(Alg, Key, Len, Now),
+              generate_token(Alg, Key, Len, Now + 30)],
+    %% evaluate all tokens in constant time
+    Match = lists:foldl(fun(T, Acc) -> couch_util:verify(T, Token) or Acc end,
+                        false, Tokens),
+    case Match of
+        true ->
+            ok;
+        _ ->
+            throw({unauthorized, <<"Name or password is incorrect.">>})
+    end.
+
+generate_token(Alg, Key, Len, Timestamp) ->
+    integer_to_binary(couch_totp:generate(Alg, Key, Timestamp, 30, Len)).
+
+integer_to_binary(Int) when is_integer(Int) ->
+    case erlang:function_exported(erlang, integer_to_binary, 1) of
+        true ->
+            erlang:integer_to_binary(Int);
+        false ->
+            ?l2b(integer_to_list(Int))
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/011975a8/src/couch_totp.erl
----------------------------------------------------------------------
diff --git a/src/couch_totp.erl b/src/couch_totp.erl
new file mode 100644
index 0000000..56e70d8
--- /dev/null
+++ b/src/couch_totp.erl
@@ -0,0 +1,23 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_totp).
+
+-export([generate/5]).
+
+generate(Alg, Key, CounterSecs, StepSecs, OutputLen)
+  when is_atom(Alg),
+       is_binary(Key),
+       is_integer(CounterSecs),
+       is_integer(StepSecs),
+       is_integer(OutputLen) ->
+    couch_hotp:generate(Alg, Key, CounterSecs div StepSecs, OutputLen).

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/011975a8/test/couch_hotp_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_hotp_tests.erl b/test/couch_hotp_tests.erl
new file mode 100644
index 0000000..fee10ff
--- /dev/null
+++ b/test/couch_hotp_tests.erl
@@ -0,0 +1,28 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_hotp_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+
+hotp_test() ->
+    Key = <<"12345678901234567890">>,
+    ?assertEqual(755224, couch_hotp:generate(sha, Key, 0, 6)),
+    ?assertEqual(287082, couch_hotp:generate(sha, Key, 1, 6)),
+    ?assertEqual(359152, couch_hotp:generate(sha, Key, 2, 6)),
+    ?assertEqual(969429, couch_hotp:generate(sha, Key, 3, 6)),
+    ?assertEqual(338314, couch_hotp:generate(sha, Key, 4, 6)),
+    ?assertEqual(254676, couch_hotp:generate(sha, Key, 5, 6)),
+    ?assertEqual(287922, couch_hotp:generate(sha, Key, 6, 6)),
+    ?assertEqual(162583, couch_hotp:generate(sha, Key, 7, 6)),
+    ?assertEqual(399871, couch_hotp:generate(sha, Key, 8, 6)),
+    ?assertEqual(520489, couch_hotp:generate(sha, Key, 9, 6)).

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/011975a8/test/couch_totp_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_totp_tests.erl b/test/couch_totp_tests.erl
new file mode 100644
index 0000000..6817a09
--- /dev/null
+++ b/test/couch_totp_tests.erl
@@ -0,0 +1,55 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_totp_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+
+totp_sha_test() ->
+    Key = <<"12345678901234567890">>,
+    ?assertEqual(94287082, couch_totp:generate(sha, Key, 59, 30, 8)),
+    ?assertEqual(07081804, couch_totp:generate(sha, Key, 1111111109, 30, 8)),
+    ?assertEqual(14050471, couch_totp:generate(sha, Key, 1111111111, 30, 8)),
+    ?assertEqual(89005924, couch_totp:generate(sha, Key, 1234567890, 30, 8)),
+    ?assertEqual(69279037, couch_totp:generate(sha, Key, 2000000000, 30, 8)),
+    ?assertEqual(65353130, couch_totp:generate(sha, Key, 20000000000, 30, 8)).
+
+totp_sha256_test() ->
+    Key = <<"12345678901234567890123456789012">>,
+    case sha_256_512_supported() of
+        true ->
+            ?assertEqual(46119246, couch_totp:generate(sha256, Key, 59, 30, 8)),
+            ?assertEqual(68084774, couch_totp:generate(sha256, Key, 1111111109, 30, 8)),
+            ?assertEqual(67062674, couch_totp:generate(sha256, Key, 1111111111, 30, 8)),
+            ?assertEqual(91819424, couch_totp:generate(sha256, Key, 1234567890, 30, 8)),
+            ?assertEqual(90698825, couch_totp:generate(sha256, Key, 2000000000, 30, 8)),
+            ?assertEqual(77737706, couch_totp:generate(sha256, Key, 20000000000, 30, 8));
+        false ->
+            ?debugMsg("sha256 not supported, tests skipped")
+    end.
+
+totp_sha512_test() ->
+    Key = <<"1234567890123456789012345678901234567890123456789012345678901234">>,
+    case sha_256_512_supported() of
+        true ->
+            ?assertEqual(90693936, couch_totp:generate(sha512, Key, 59, 30, 8)),
+            ?assertEqual(25091201, couch_totp:generate(sha512, Key, 1111111109, 30, 8)),
+            ?assertEqual(99943326, couch_totp:generate(sha512, Key, 1111111111, 30, 8)),
+            ?assertEqual(93441116, couch_totp:generate(sha512, Key, 1234567890, 30, 8)),
+            ?assertEqual(38618901, couch_totp:generate(sha512, Key, 2000000000, 30, 8)),
+            ?assertEqual(47863826, couch_totp:generate(sha512, Key, 20000000000, 30, 8));
+        false ->
+            ?debugMsg("sha512 not supported, tests skipped")
+    end.
+
+sha_256_512_supported() ->
+    erlang:function_exported(crypto, hmac, 3).


[17/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
Count the number of rejections by validate_doc_update functions


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/3e32256c
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/3e32256c
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/3e32256c

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 3e32256cf6922679a9ab3567fb84a83c25641073
Parents: 0c0b438
Author: Alexander Shorin <kx...@apache.org>
Authored: Fri Oct 31 22:13:19 2014 +0300
Committer: Alexander Shorin <kx...@apache.org>
Committed: Mon Nov 3 12:19:54 2014 +0300

----------------------------------------------------------------------
 priv/stats_descriptions.cfg |  4 ++++
 src/couch_query_servers.erl | 11 ++++++++++-
 2 files changed, 14 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/3e32256c/priv/stats_descriptions.cfg
----------------------------------------------------------------------
diff --git a/priv/stats_descriptions.cfg b/priv/stats_descriptions.cfg
index 53aca37..3f771d2 100644
--- a/priv/stats_descriptions.cfg
+++ b/priv/stats_descriptions.cfg
@@ -182,3 +182,7 @@
     {type, counter},
     {desc, <<"number of couch_server LRU operations skipped">>}
 ]}.
+{[couchdb, query_server, vdu_rejects], [
+    {type, counter},
+    {desc, <<"number of rejections by validate_doc_update function">>}
+]}.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/3e32256c/src/couch_query_servers.erl
----------------------------------------------------------------------
diff --git a/src/couch_query_servers.erl b/src/couch_query_servers.erl
index d38f040..c99f673 100644
--- a/src/couch_query_servers.erl
+++ b/src/couch_query_servers.erl
@@ -260,7 +260,16 @@ get_number(Key, Props) ->
 validate_doc_update(DDoc, EditDoc, DiskDoc, Ctx, SecObj) ->
     JsonEditDoc = couch_doc:to_json_obj(EditDoc, [revs]),
     JsonDiskDoc = json_doc(DiskDoc),
-    case ddoc_prompt(DDoc, [<<"validate_doc_update">>], [JsonEditDoc, JsonDiskDoc, Ctx, SecObj]) of
+    Resp = ddoc_prompt(DDoc,
+                       [<<"validate_doc_update">>],
+                       [JsonEditDoc, JsonDiskDoc, Ctx, SecObj]),
+    case Resp of
+        _ when Resp /= 1 ->
+            couch_stats:increment_counter(
+                [couchdb, query_server, vdu_rejects], 1);
+        _ -> ok
+    end,
+    case Resp of
         1 ->
             ok;
         {[{<<"forbidden">>, Message}]} ->


[08/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
Add view filtering optimization to changes feeds


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/20e585dd
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/20e585dd
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/20e585dd

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 20e585dd07e4da0fe67ace09f0a291e3fe759534
Parents: 6d6b801
Author: Benjamin Bastian <be...@gmail.com>
Authored: Fri Aug 22 23:56:56 2014 +0700
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700

----------------------------------------------------------------------
 src/couch_changes.erl       | 181 ++++++++++++++++++++++++---------------
 src/couch_httpd_changes.erl | 180 +-------------------------------------
 2 files changed, 116 insertions(+), 245 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/20e585dd/src/couch_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_changes.erl b/src/couch_changes.erl
index abe8cf9..259f83c 100644
--- a/src/couch_changes.erl
+++ b/src/couch_changes.erl
@@ -63,25 +63,45 @@ handle_changes(Args1, Req, Db0, Type) ->
         dir = Dir,
         since = Since
     } = Args1,
-    {StartListenerFun, DDocName, ViewName, View} = case Type of
-        {view, DDocName0, ViewName0} ->
-            SNFun = fun() ->
-                couch_event:link_listener(
-                     ?MODULE, handle_view_event, self(), [{dbname, Db0#db.name}]
-                )
-            end,
-            {ok, {_, View0, _}, _, _} = couch_mrview_util:get_view(Db0#db.name, DDocName0, ViewName0, #mrargs{}),
-            {SNFun, DDocName0, ViewName0, View0};
-        db ->
-            SNFun = fun() ->
-                couch_event:link_listener(
-                     ?MODULE, handle_db_event, self(), [{dbname, Db0#db.name}]
-                )
-            end,
-            {SNFun, undefined, undefined, undefined}
-    end,
     Filter = configure_filter(FilterName, Style, Req, Db0),
     Args = Args1#changes_args{filter_fun = Filter},
+    UseViewChanges = case {Type, Filter} of
+        {{view, _, _}, _} ->
+            true;
+        {_, {fast_view, _, _, _}} ->
+            true;
+        _ ->
+            false
+    end,
+    {StartListenerFun, DDocName, ViewName, View} = if UseViewChanges ->
+        {DDocName0, ViewName0} = case {Type, Filter} of
+            {{view, DDocName1, ViewName1}, _} ->
+                {DDocName1, ViewName1};
+            {_, {fast_view, _, DDoc, ViewName1}} ->
+                {DDoc#doc.id, ViewName1}
+        end,
+        {ok, {_, View0, _}, _, _} = couch_mrview_util:get_view(
+                Db0#db.name, DDocName0, ViewName0, #mrargs{}),
+        case View0#mrview.seq_btree of
+            #btree{} ->
+                ok;
+            _ ->
+                throw({bad_request, "view changes not enabled"})
+        end,
+        SNFun = fun() ->
+            couch_event:link_listener(
+                 ?MODULE, handle_view_event, {self(), DDocName0}, [{dbname, Db0#db.name}]
+            )
+        end,
+        {SNFun, DDocName0, ViewName0, View0};
+    true ->
+        SNFun = fun() ->
+            couch_event:link_listener(
+                 ?MODULE, handle_db_event, self(), [{dbname, Db0#db.name}]
+            )
+        end,
+        {SNFun, undefined, undefined, undefined}
+    end,
     Start = fun() ->
         {ok, Db} = couch_db:reopen(Db0),
         StartSeq = case Dir of
@@ -180,7 +200,15 @@ configure_filter("_view", Style, Req, Db) ->
         [DName, VName] ->
             {ok, DDoc} = open_ddoc(Db, <<"_design/", DName/binary>>),
             check_member_exists(DDoc, [<<"views">>, VName]),
-            {view, Style, DDoc, VName};
+            try
+                true = couch_util:get_nested_json_value(
+                        DDoc#doc.body,
+                        [<<"options">>, <<"seq_indexed">>]
+                ),
+                {fast_view, Style, DDoc, VName}
+            catch _:_ ->
+                {view, Style, DDoc, VName}
+            end;
         [] ->
             Msg = "`view` must be of the form `designname/viewname`",
             throw({bad_request, Msg})
@@ -237,6 +265,36 @@ filter(Db, DocInfo, {custom, Style, Req0, DDoc, FName}) ->
     {ok, Passes} = couch_query_servers:filter_docs(Req, Db, DDoc, FName, Docs),
     filter_revs(Passes, Docs).
 
+fast_view_filter(Db, {{Seq, _}, {ID, _}}, {fast_view, Style, _, _}) ->
+    case couch_db:get_doc_info(Db, ID) of
+        {ok, #doc_info{high_seq=Seq}=DocInfo} ->
+            Docs = open_revs(Db, DocInfo, Style),
+            Changes = lists:map(fun(#doc{revs={RevPos, [RevId | _]}}) ->
+                RevStr = couch_doc:rev_to_str({RevPos, RevId}),
+                {[{<<"rev">>, RevStr}]}
+            end, Docs),
+            {DocInfo, Changes};
+        {ok, #doc_info{high_seq=HighSeq}} when Seq > HighSeq ->
+            % If the view seq tree is out of date (or if the view seq tree
+            % was opened before the db) seqs may come by from the seq tree
+            % which correspond to the not-most-current revision of a document.
+            % The proper thing to do is to not send this old revision, but wait
+            % until we reopen the up-to-date view seq tree and continue the
+            % fold.
+            % I left the Seq > HighSeq guard in so if (for some godforsaken
+            % reason) the seq in the view is more current than the database,
+            % we'll throw an error.
+            {ok, []};
+        {error, not_found} ->
+            {ok, []}
+    end.
+
+
+
+view_filter(_Db, _KV, {default, _Style}) ->
+    [ok]. % TODO: make a real thing
+
+
 get_view_qs({json_req, {Props}}) ->
     {Query} = couch_util:get_value(<<"query">>, Props, {[]}),
     binary_to_list(couch_util:get_value(<<"view">>, Query, ""));
@@ -477,7 +535,7 @@ keep_sending_changes(Args, Acc0, FirstRound) ->
         db = Db, callback = Callback,
         timeout = Timeout, timeout_fun = TimeoutFun, seq = EndSeq,
         prepend = Prepend2, user_acc = UserAcc2, limit = NewLimit,
-        ddoc_name = DDocName, view_name = ViewName, view = View
+        ddoc_name = DDocName, view_name = ViewName
     } = ChangesAcc,
 
     couch_db:close(Db),
@@ -517,54 +575,27 @@ maybe_refresh_view(Db, DDocName, ViewName) ->
 end_sending_changes(Callback, UserAcc, EndSeq, ResponseType) ->
     Callback({stop, EndSeq}, ResponseType, UserAcc).
 
-changes_enumerator(Value, #changes_acc{resp_type = ResponseType} = Acc)
-        when ResponseType =:= "continuous"
-        orelse ResponseType =:= "eventsource" ->
-    #changes_acc{
-        filter = Filter, callback = Callback,
-        user_acc = UserAcc, limit = Limit, db = Db,
-        timeout = Timeout, timeout_fun = TimeoutFun,
-        view = View
-    } = Acc,
-    {Seq, Results0} = case View of
-        undefined ->
-            {Value#doc_info.high_seq, filter(Db, Value, Filter)};
-        #mrview{} ->
-            {{Seq0, _}, _} = Value,
-            {Seq0, [ok]} % TODO
-    end,
-    Results = [Result || Result <- Results0, Result /= null],
-    %% TODO: I'm thinking this should be < 1 and not =< 1
-    Go = if Limit =< 1 -> stop; true -> ok end,
-    case Results of
-    [] ->
-        {Done, UserAcc2} = maybe_heartbeat(Timeout, TimeoutFun, UserAcc),
-        case Done of
-        stop ->
-            {stop, Acc#changes_acc{seq = Seq, user_acc = UserAcc2}};
-        ok ->
-            {Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2}}
-        end;
-    _ ->
-        ChangesRow = changes_row(Results, Value, Acc),
-        UserAcc2 = Callback({change, ChangesRow, <<>>}, ResponseType, UserAcc),
-        reset_heartbeat(),
-        {Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2, limit = Limit - 1}}
-    end;
-changes_enumerator(Value, Acc) ->
+changes_enumerator(Value0, Acc) ->
     #changes_acc{
         filter = Filter, callback = Callback, prepend = Prepend,
         user_acc = UserAcc, limit = Limit, resp_type = ResponseType, db = Db,
         timeout = Timeout, timeout_fun = TimeoutFun, view = View
     } = Acc,
-    {Seq, Results0} = case View of
-        undefined ->
-            {Value#doc_info.high_seq, filter(Db, Value, Filter)};
-        #mrview{} ->
-            {{Seq0,_}, _} = Value,
-            {Seq0, [ok]} % TODO view filter
+    {Value, Results0} = case {View, Filter} of
+        {_, {fast_view, _, _, _}} ->
+            fast_view_filter(Db, Value0, Filter);
+        {#mrview{}, _} ->
+            {Value0, view_filter(Db, Value0, Filter)};
+        {_, _} ->
+            {Value0, filter(Db, Value0, Filter)}
     end,
     Results = [Result || Result <- Results0, Result /= null],
+    Seq = case Value of
+        #doc_info{} ->
+            Value#doc_info.high_seq;
+        {{Seq0, _}, _} ->
+            Seq0
+    end,
     Go = if (Limit =< 1) andalso Results =/= [] -> stop; true -> ok end,
     case Results of
     [] ->
@@ -576,20 +607,32 @@ changes_enumerator(Value, Acc) ->
             {Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2}}
         end;
     _ ->
-        ChangesRow = changes_row(Results, Value, Acc),
-        UserAcc2 = Callback({change, ChangesRow, Prepend}, ResponseType, UserAcc),
-        reset_heartbeat(),
-        {Go, Acc#changes_acc{
-            seq = Seq, prepend = <<",\n">>,
-            user_acc = UserAcc2, limit = Limit - 1}}
+        if ResponseType =:= "continuous" orelse ResponseType =:= "eventsource" ->
+            ChangesRow = changes_row(Results, Value, Acc),
+            UserAcc2 = Callback({change, ChangesRow, <<>>}, ResponseType, UserAcc),
+            reset_heartbeat(),
+            {Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2, limit = Limit - 1}};
+        true ->
+            ChangesRow = changes_row(Results, Value, Acc),
+            UserAcc2 = Callback({change, ChangesRow, Prepend}, ResponseType, UserAcc),
+            reset_heartbeat(),
+            {Go, Acc#changes_acc{
+                seq = Seq, prepend = <<",\n">>,
+                user_acc = UserAcc2, limit = Limit - 1}}
+        end
     end.
 
 
 
-changes_row(Results, SeqStuff, #changes_acc{view=#mrview{}}) ->
-    {{Seq, Key}, {Id, Value}} = SeqStuff,
+changes_row(Results, DocInfo, #changes_acc{filter={fast_view,_,_,_}}=Acc) ->
+    format_doc_info_change(Results, DocInfo, Acc);
+changes_row(Results, KV, #changes_acc{view=#mrview{}}) ->
+    {{Seq, Key}, {Id, Value}} = KV,
     {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"key">>, Key}, {<<"value">>, Value}, {<<"changes">>, Results}]};
-changes_row(Results, #doc_info{}=DocInfo, #changes_acc{view=undefined}=Acc) ->
+changes_row(Results, #doc_info{}=DocInfo, Acc) ->
+    format_doc_info_change(Results, DocInfo, Acc).
+
+format_doc_info_change(Results, #doc_info{}=DocInfo, Acc) ->
     #doc_info{
         id = Id, high_seq = Seq, revs = [#rev_info{deleted = Del} | _]
     } = DocInfo,

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/20e585dd/src/couch_httpd_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_changes.erl b/src/couch_httpd_changes.erl
index 9f141d9..4963a5f 100644
--- a/src/couch_httpd_changes.erl
+++ b/src/couch_httpd_changes.erl
@@ -14,19 +14,13 @@
 
 -export([handle_db_changes_req/2,
          handle_changes_req/4,
-         handle_view_filtered_changes/3,
-         parse_changes_query/3]).
+         parse_changes_query/2]).
 
 -include_lib("couch/include/couch_db.hrl").
 
 handle_db_changes_req(Req, Db) ->
-    ChangesArgs = parse_changes_query(Req, Db, false),
-    ChangesFun = case ChangesArgs#changes_args.filter of
-        "_view" ->
-            handle_view_filtered_changes(ChangesArgs, Req, Db);
-        _ ->
-            couch_changes:handle_db_changes(ChangesArgs, Req, Db)
-    end,
+    ChangesArgs = parse_changes_query(Req, Db),
+    ChangesFun = couch_changes:handle_db_changes(ChangesArgs, Req, Db),
     handle_changes_req(Req, Db, ChangesArgs, ChangesFun).
 
 handle_changes_req(#httpd{method='POST'}=Req, Db, ChangesArgs, ChangesFun) ->
@@ -126,170 +120,7 @@ handle_changes_req1(Req, #db{name=DbName}=Db, ChangesArgs, ChangesFun) ->
     end.
 
 
-%% wrapper around couch_mrview_changes.
-%% This wrapper mimic couch_changes:handle_db_changes/3 and return a
-%% Changefun that can be used by the handle_changes_req function. Also
-%% while couch_mrview_changes:handle_changes/6 is returning tha view
-%% changes this function return docs corresponding to the changes
-%% instead so it can be used to replace the _view filter.
-handle_view_filtered_changes(ChangesArgs, Req, Db) ->
-    %% parse view parameter
-    {DDocId, VName} = parse_view_param(Req),
-
-    %% get view options
-    Query = case Req of
-        {json_req, {Props}} ->
-            {Q} = couch_util:get_value(<<"query">>, Props, {[]}),
-            Q;
-        _ ->
-            couch_httpd:qs(Req)
-    end,
-    ViewOptions = parse_view_options(Query, []),
-
-    {ok, Infos} = couch_mrview:get_info(Db, DDocId),
-    case lists:member(<<"seq_indexed">>,
-                      proplists:get_value(update_options, Infos, [])) of
-        true ->
-            handle_view_filtered_changes(Db, DDocId, VName, ViewOptions, ChangesArgs,
-                                Req);
-        false when ViewOptions /= [] ->
-            ?LOG_ERROR("Tried to filter a non sequence indexed view~n",[]),
-            throw({bad_request, seqs_not_indexed});
-        false ->
-            %% old method we are getting changes using the btree instead
-            %% which is not efficient, log it
-            ?LOG_WARN("Get view changes with seq_indexed=false.~n", []),
-            couch_changes:handle_db_changes(ChangesArgs, Req, Db)
-    end.
-
-handle_view_filtered_changes(#db{name=DbName}=Db0, DDocId, VName, ViewOptions,
-                    ChangesArgs, Req) ->
-    #changes_args{
-        feed = ResponseType,
-        since = Since,
-        db_open_options = DbOptions} = ChangesArgs,
-
-    Options0 = [{since, Since},
-                {view_options, ViewOptions}],
-    Options = case ResponseType of
-        "continuous" -> [stream | Options0];
-        "eventsource" -> [stream | Options0];
-        "longpoll" -> [{stream, once} | Options0];
-        _ -> Options0
-    end,
-
-    %% reopen the db with the db options given to the changes args
-    couch_db:close(Db0),
-    DbOptions1 = [{user_ctx, Db0#db.user_ctx} | DbOptions],
-    {ok, Db} = couch_db:open(DbName, DbOptions1),
-
-
-    %% initialise the changes fun
-    ChangesFun = fun(Callback) ->
-            Callback(start, ResponseType),
-
-            Acc0 = {"", 0, Db, Callback, ChangesArgs},
-            couch_mrview_changes:handle_changes(DbName, DDocId, VName,
-                                               fun view_changes_cb/2,
-                                               Acc0, Options)
-    end,
-    ChangesFun.
-
-
-view_changes_cb(stop, {LastSeq, {_, _, _, Callback, Args}}) ->
-    Callback({stop, LastSeq}, Args#changes_args.feed);
-
-view_changes_cb(heartbeat, {_, _, _, Callback, Args}=Acc) ->
-    Callback(timeout, Args#changes_args.feed),
-    {ok, Acc};
-view_changes_cb({{Seq, _Key, DocId}, Val},
-                {Prepend, OldLimit, Db0, Callback, Args}=Acc) ->
-
-    %% is the key removed from the index?
-    Removed = case Val of
-        {[{<<"_removed">>, true}]} -> true;
-        _ -> false
-    end,
-
-    #changes_args{
-        feed = ResponseType,
-        limit = Limit} = Args,
-
-    %% if the doc sequence is > to the one in the db record, reopen the
-    %% database since it means we don't have the latest db value.
-    Db = case Db0#db.update_seq >= Seq of
-        true -> Db0;
-        false ->
-            {ok, Db1} = couch_db:reopen_db(Db0),
-            Db1
-    end,
-
-    case couch_db:get_doc_info(Db, DocId) of
-        {ok, DocInfo} ->
-            %% get change row
-            {Deleted, ChangeRow} = view_change_row(Db, DocInfo, Args),
-
-            case Removed of
-                true when Deleted /= true ->
-                    %% the key has been removed from the view but the
-                    %% document hasn't been deleted so ignore it.
-                    {ok, Acc};
-                _ ->
-                    %% emit change row
-                    Callback({change, ChangeRow, Prepend}, ResponseType),
-
-                    %% if we achieved the limit, stop here, else continue.
-                    NewLimit = OldLimit + 1,
-                    if Limit > NewLimit ->
-                            {ok, {<<",\n">>, NewLimit, Db, Callback, Args}};
-                        true ->
-                            {stop, {<<"">>, NewLimit, Db, Callback, Args}}
-                    end
-            end;
-        {error, not_found} ->
-            %% doc not found, continue
-            {ok, Acc};
-        Error ->
-            throw(Error)
-    end.
-
-
-view_change_row(Db, DocInfo, Args) ->
-    #doc_info{id = Id, high_seq = Seq, revs = Revs} = DocInfo,
-    [#rev_info{rev=Rev, deleted=Del} | _] = Revs,
-
-    #changes_args{style=Style,
-                  include_docs=InDoc,
-                  doc_options = DocOpts,
-                  conflicts=Conflicts}=Args,
-
-    Changes = case Style of
-        main_only ->
-            [{[{<<"rev">>, couch_doc:rev_to_str(Rev)}]}];
-        all_docs ->
-            [{[{<<"rev">>, couch_doc:rev_to_str(R)}]}
-                || #rev_info{rev=R} <- Revs]
-    end,
-
-    {Del, {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Changes}] ++
-     deleted_item(Del) ++ case InDoc of
-            true ->
-                Opts = case Conflicts of
-                    true -> [deleted, conflicts];
-                    false -> [deleted]
-                end,
-                Doc = couch_index_util:load_doc(Db, DocInfo, Opts),
-                case Doc of
-                    null ->
-                        [{doc, null}];
-                    _ ->
-                        [{doc, couch_doc:to_json_obj(Doc, DocOpts)}]
-                end;
-            false ->
-                []
-    end}}.
-
-parse_changes_query(Req, Db, IsViewChanges) ->
+parse_changes_query(Req, Db) ->
     ChangesArgs = lists:foldl(fun({Key, Value}, Args) ->
         case {string:to_lower(Key), Value} of
         {"feed", _} ->
@@ -426,6 +257,3 @@ parse_json(V) when is_list(V) ->
     ?JSON_DECODE(V);
 parse_json(V) ->
     V.
-
-deleted_item(true) -> [{<<"deleted">>, true}];
-deleted_item(_) -> [].


[11/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
Add preliminary version of view changes


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/6d6b801f
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/6d6b801f
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/6d6b801f

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 6d6b801f8440d0fdad1f0c22261d0b64da9362d0
Parents: 071dedf
Author: Benjamin Bastian <be...@gmail.com>
Authored: Fri Aug 22 15:51:47 2014 +0700
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700

----------------------------------------------------------------------
 src/couch_changes.erl       | 177 +++++++++++++++++++++++++++------------
 src/couch_db.erl            |   6 +-
 src/couch_httpd_changes.erl |  53 ++++++------
 src/couch_httpd_db.erl      |   2 +-
 4 files changed, 157 insertions(+), 81 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/6d6b801f/src/couch_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_changes.erl b/src/couch_changes.erl
index b5e8f89..abe8cf9 100644
--- a/src/couch_changes.erl
+++ b/src/couch_changes.erl
@@ -12,15 +12,19 @@
 
 -module(couch_changes).
 -include_lib("couch/include/couch_db.hrl").
+-include_lib("couch_mrview/include/couch_mrview.hrl").
 
 -export([
-    handle_changes/3,
+    handle_db_changes/3,
+    handle_changes/4,
     get_changes_timeout/2,
-    wait_db_updated/3,
-    get_rest_db_updated/1,
+    wait_updated/3,
+    get_rest_updated/1,
     configure_filter/4,
     filter/3,
-    handle_db_event/3
+    handle_db_event/3,
+    handle_view_event/3,
+    view_filter/3
 ]).
 
 -export([changes_enumerator/2]).
@@ -31,6 +35,9 @@
 
 -record(changes_acc, {
     db,
+    view_name,
+    ddoc_name,
+    view,
     seq,
     prepend,
     filter,
@@ -45,8 +52,10 @@
     timeout_fun
 }).
 
-%% @type Req -> #httpd{} | {json_req, JsonObj()}
-handle_changes(Args1, Req, Db0) ->
+handle_db_changes(Args, Req, Db) ->
+    handle_changes(Args, Req, Db, db).
+
+handle_changes(Args1, Req, Db0, Type) ->
     #changes_args{
         style = Style,
         filter = FilterName,
@@ -54,6 +63,23 @@ handle_changes(Args1, Req, Db0) ->
         dir = Dir,
         since = Since
     } = Args1,
+    {StartListenerFun, DDocName, ViewName, View} = case Type of
+        {view, DDocName0, ViewName0} ->
+            SNFun = fun() ->
+                couch_event:link_listener(
+                     ?MODULE, handle_view_event, self(), [{dbname, Db0#db.name}]
+                )
+            end,
+            {ok, {_, View0, _}, _, _} = couch_mrview_util:get_view(Db0#db.name, DDocName0, ViewName0, #mrargs{}),
+            {SNFun, DDocName0, ViewName0, View0};
+        db ->
+            SNFun = fun() ->
+                couch_event:link_listener(
+                     ?MODULE, handle_db_event, self(), [{dbname, Db0#db.name}]
+                )
+            end,
+            {SNFun, undefined, undefined, undefined}
+    end,
     Filter = configure_filter(FilterName, Style, Req, Db0),
     Args = Args1#changes_args{filter_fun = Filter},
     Start = fun() ->
@@ -78,14 +104,14 @@ handle_changes(Args1, Req, Db0) ->
     true ->
         fun(CallbackAcc) ->
             {Callback, UserAcc} = get_callback_acc(CallbackAcc),
-            {ok, Listener} = couch_event:link_listener(
-                 ?MODULE, handle_db_event, self(), [{dbname, Db0#db.name}]
-            ),
+            {ok, Listener} = StartListenerFun(),
+
             {Db, StartSeq} = Start(),
             UserAcc2 = start_sending_changes(Callback, UserAcc, Feed),
             {Timeout, TimeoutFun} = get_changes_timeout(Args, Callback),
             Acc0 = build_acc(Args, Callback, UserAcc2, Db, StartSeq,
-                             <<"">>, Timeout, TimeoutFun),
+                             <<"">>, Timeout, TimeoutFun, DDocName, ViewName,
+                             View),
             try
                 keep_sending_changes(
                     Args#changes_args{dir=fwd},
@@ -93,7 +119,7 @@ handle_changes(Args1, Req, Db0) ->
                     true)
             after
                 couch_event:stop_listener(Listener),
-                get_rest_db_updated(ok) % clean out any remaining update messages
+                get_rest_updated(ok) % clean out any remaining update messages
             end
         end;
     false ->
@@ -103,11 +129,12 @@ handle_changes(Args1, Req, Db0) ->
             {Timeout, TimeoutFun} = get_changes_timeout(Args, Callback),
             {Db, StartSeq} = Start(),
             Acc0 = build_acc(Args#changes_args{feed="normal"}, Callback,
-                             UserAcc2, Db, StartSeq, <<>>, Timeout, TimeoutFun),
+                             UserAcc2, Db, StartSeq, <<>>, Timeout, TimeoutFun,
+                             DDocName, ViewName, View),
             {ok, #changes_acc{seq = LastSeq, user_acc = UserAcc3}} =
                 send_changes(
-                    Args#changes_args{feed="normal"},
                     Acc0,
+                    Dir,
                     true),
             end_sending_changes(Callback, UserAcc3, LastSeq, Feed)
         end
@@ -115,13 +142,24 @@ handle_changes(Args1, Req, Db0) ->
 
 
 handle_db_event(_DbName, updated, Parent) ->
-    Parent ! db_updated,
+    Parent ! updated,
     {ok, Parent};
 
 handle_db_event(_DbName, _Event, Parent) ->
     {ok, Parent}.
 
 
+handle_view_event(_DbName, Msg, {Parent, DDocId}) ->
+    case Msg of
+        {index_commit, DDocId} ->
+            Parent ! updated;
+        {index_delete, DDocId} ->
+            Parent ! deleted;
+        _ ->
+            ok
+    end,
+    {ok, {Parent, DDocId}}.
+
 get_callback_acc({Callback, _UserAcc} = Pair) when is_function(Callback, 3) ->
     Pair;
 get_callback_acc(Callback) when is_function(Callback, 2) ->
@@ -133,7 +171,7 @@ configure_filter("_doc_ids", Style, Req, _Db) ->
 configure_filter("_design", Style, _Req, _Db) ->
     {design_docs, Style};
 configure_filter("_view", Style, Req, Db) ->
-    ViewName = couch_httpd:qs_value(Req, "view", ""),
+    ViewName = get_view_qs(Req),
     if ViewName /= "" -> ok; true ->
         throw({bad_request, "`view` filter parameter is not provided."})
     end,
@@ -199,6 +237,11 @@ filter(Db, DocInfo, {custom, Style, Req0, DDoc, FName}) ->
     {ok, Passes} = couch_query_servers:filter_docs(Req, Db, DDoc, FName, Docs),
     filter_revs(Passes, Docs).
 
+get_view_qs({json_req, {Props}}) ->
+    {Query} = couch_util:get_value(<<"query">>, Props, {[]}),
+    binary_to_list(couch_util:get_value(<<"view">>, Query, ""));
+get_view_qs(Req) ->
+    couch_httpd:qs_value(Req, "view", "").
 
 get_doc_ids({json_req, {Props}}) ->
     check_docids(couch_util:get_value(<<"doc_ids">>, Props));
@@ -310,7 +353,7 @@ start_sending_changes(_Callback, UserAcc, ResponseType)
 start_sending_changes(Callback, UserAcc, ResponseType) ->
     Callback(start, ResponseType, UserAcc).
 
-build_acc(Args, Callback, UserAcc, Db, StartSeq, Prepend, Timeout, TimeoutFun) ->
+build_acc(Args, Callback, UserAcc, Db, StartSeq, Prepend, Timeout, TimeoutFun, DDocName, ViewName, View) ->
     #changes_args{
         include_docs = IncludeDocs,
         doc_options = DocOpts,
@@ -332,24 +375,30 @@ build_acc(Args, Callback, UserAcc, Db, StartSeq, Prepend, Timeout, TimeoutFun) -
         doc_options = DocOpts,
         conflicts = Conflicts,
         timeout = Timeout,
-        timeout_fun = TimeoutFun
+        timeout_fun = TimeoutFun,
+        ddoc_name = DDocName,
+        view_name = ViewName,
+        view = View
     }.
 
-send_changes(Args, Acc0, FirstRound) ->
-    #changes_args{
-        dir = Dir
-    } = Args,
+send_changes(Acc, Dir, FirstRound) ->
     #changes_acc{
         db = Db,
         seq = StartSeq,
-        filter = Filter
-    } = Acc0,
-    EnumFun = fun ?MODULE:changes_enumerator/2,
+        filter = Filter,
+        view = View
+    } = Acc,
+    EnumFun = fun changes_enumerator/2,
     case can_optimize(FirstRound, Filter) of
         {true, Fun} ->
-            Fun(Db, StartSeq, Dir, EnumFun, Acc0, Filter);
+            Fun(Db, StartSeq, Dir, EnumFun, Acc, Filter);
         _ ->
-            couch_db:changes_since(Db, StartSeq, EnumFun, [{dir, Dir}], Acc0)
+            case View of
+                undefined ->
+                    couch_db:changes_since(Db, StartSeq, EnumFun, [{dir, Dir}], Acc);
+                #mrview{} ->
+                    couch_mrview:view_changes_since(View, StartSeq, EnumFun, [{dir, Dir}], Acc)
+            end
     end.
 
 
@@ -422,20 +471,20 @@ keep_sending_changes(Args, Acc0, FirstRound) ->
         db_open_options = DbOptions
     } = Args,
 
-    {ok, ChangesAcc} = send_changes(
-        Args#changes_args{dir=fwd},
-        Acc0,
-        FirstRound),
+    {ok, ChangesAcc} = send_changes(Acc0, fwd, FirstRound),
+
     #changes_acc{
-        db = Db, callback = Callback, timeout = Timeout, timeout_fun = TimeoutFun,
-        seq = EndSeq, prepend = Prepend2, user_acc = UserAcc2, limit = NewLimit
+        db = Db, callback = Callback,
+        timeout = Timeout, timeout_fun = TimeoutFun, seq = EndSeq,
+        prepend = Prepend2, user_acc = UserAcc2, limit = NewLimit,
+        ddoc_name = DDocName, view_name = ViewName, view = View
     } = ChangesAcc,
 
     couch_db:close(Db),
     if Limit > NewLimit, ResponseType == "longpoll" ->
         end_sending_changes(Callback, UserAcc2, EndSeq, ResponseType);
     true ->
-        case wait_db_updated(Timeout, TimeoutFun, UserAcc2) of
+        case wait_updated(Timeout, TimeoutFun, UserAcc2) of
         {updated, UserAcc4} ->
             DbOptions1 = [{user_ctx, Db#db.user_ctx} | DbOptions],
             case couch_db:open(Db#db.name, DbOptions1) of
@@ -444,6 +493,7 @@ keep_sending_changes(Args, Acc0, FirstRound) ->
                   Args#changes_args{limit=NewLimit},
                   ChangesAcc#changes_acc{
                     db = Db2,
+                    view = maybe_refresh_view(Db2, DDocName, ViewName),
                     user_acc = UserAcc4,
                     seq = EndSeq,
                     prepend = Prepend2,
@@ -458,19 +508,31 @@ keep_sending_changes(Args, Acc0, FirstRound) ->
         end
     end.
 
+maybe_refresh_view(_, undefined, undefined) ->
+    undefined;
+maybe_refresh_view(Db, DDocName, ViewName) ->
+    {ok, {_, View, _}, _, _} = couch_mrview_util:get_view(Db#db.name, DDocName, ViewName, #mrargs{}),
+    View.
+
 end_sending_changes(Callback, UserAcc, EndSeq, ResponseType) ->
     Callback({stop, EndSeq}, ResponseType, UserAcc).
 
-changes_enumerator(DocInfo, #changes_acc{resp_type = ResponseType} = Acc)
+changes_enumerator(Value, #changes_acc{resp_type = ResponseType} = Acc)
         when ResponseType =:= "continuous"
         orelse ResponseType =:= "eventsource" ->
     #changes_acc{
         filter = Filter, callback = Callback,
         user_acc = UserAcc, limit = Limit, db = Db,
-        timeout = Timeout, timeout_fun = TimeoutFun
+        timeout = Timeout, timeout_fun = TimeoutFun,
+        view = View
     } = Acc,
-    #doc_info{high_seq = Seq} = DocInfo,
-    Results0 = filter(Db, DocInfo, Filter),
+    {Seq, Results0} = case View of
+        undefined ->
+            {Value#doc_info.high_seq, filter(Db, Value, Filter)};
+        #mrview{} ->
+            {{Seq0, _}, _} = Value,
+            {Seq0, [ok]} % TODO
+    end,
     Results = [Result || Result <- Results0, Result /= null],
     %% TODO: I'm thinking this should be < 1 and not =< 1
     Go = if Limit =< 1 -> stop; true -> ok end,
@@ -484,19 +546,24 @@ changes_enumerator(DocInfo, #changes_acc{resp_type = ResponseType} = Acc)
             {Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2}}
         end;
     _ ->
-        ChangesRow = changes_row(Results, DocInfo, Acc),
+        ChangesRow = changes_row(Results, Value, Acc),
         UserAcc2 = Callback({change, ChangesRow, <<>>}, ResponseType, UserAcc),
         reset_heartbeat(),
         {Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2, limit = Limit - 1}}
     end;
-changes_enumerator(DocInfo, Acc) ->
+changes_enumerator(Value, Acc) ->
     #changes_acc{
         filter = Filter, callback = Callback, prepend = Prepend,
         user_acc = UserAcc, limit = Limit, resp_type = ResponseType, db = Db,
-        timeout = Timeout, timeout_fun = TimeoutFun
+        timeout = Timeout, timeout_fun = TimeoutFun, view = View
     } = Acc,
-    #doc_info{high_seq = Seq} = DocInfo,
-    Results0 = filter(Db, DocInfo, Filter),
+    {Seq, Results0} = case View of
+        undefined ->
+            {Value#doc_info.high_seq, filter(Db, Value, Filter)};
+        #mrview{} ->
+            {{Seq0,_}, _} = Value,
+            {Seq0, [ok]} % TODO view filter
+    end,
     Results = [Result || Result <- Results0, Result /= null],
     Go = if (Limit =< 1) andalso Results =/= [] -> stop; true -> ok end,
     case Results of
@@ -509,7 +576,7 @@ changes_enumerator(DocInfo, Acc) ->
             {Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2}}
         end;
     _ ->
-        ChangesRow = changes_row(Results, DocInfo, Acc),
+        ChangesRow = changes_row(Results, Value, Acc),
         UserAcc2 = Callback({change, ChangesRow, Prepend}, ResponseType, UserAcc),
         reset_heartbeat(),
         {Go, Acc#changes_acc{
@@ -518,7 +585,11 @@ changes_enumerator(DocInfo, Acc) ->
     end.
 
 
-changes_row(Results, DocInfo, Acc) ->
+
+changes_row(Results, SeqStuff, #changes_acc{view=#mrview{}}) ->
+    {{Seq, Key}, {Id, Value}} = SeqStuff,
+    {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"key">>, Key}, {<<"value">>, Value}, {<<"changes">>, Results}]};
+changes_row(Results, #doc_info{}=DocInfo, #changes_acc{view=undefined}=Acc) ->
     #doc_info{
         id = Id, high_seq = Seq, revs = [#rev_info{deleted = Del} | _]
     } = DocInfo,
@@ -549,25 +620,27 @@ changes_row(Results, DocInfo, Acc) ->
 deleted_item(true) -> [{<<"deleted">>, true}];
 deleted_item(_) -> [].
 
-% waits for a db_updated msg, if there are multiple msgs, collects them.
-wait_db_updated(Timeout, TimeoutFun, UserAcc) ->
+% waits for a updated msg, if there are multiple msgs, collects them.
+wait_updated(Timeout, TimeoutFun, UserAcc) ->
     receive
-    db_updated ->
-        get_rest_db_updated(UserAcc)
+    updated ->
+        get_rest_updated(UserAcc);
+    deleted ->
+        {stop, UserAcc}
     after Timeout ->
         {Go, UserAcc2} = TimeoutFun(UserAcc),
         case Go of
         ok ->
-            wait_db_updated(Timeout, TimeoutFun, UserAcc2);
+            wait_updated(Timeout, TimeoutFun, UserAcc2);
         stop ->
             {stop, UserAcc2}
         end
     end.
 
-get_rest_db_updated(UserAcc) ->
+get_rest_updated(UserAcc) ->
     receive
-    db_updated ->
-        get_rest_db_updated(UserAcc)
+    updated ->
+        get_rest_updated(UserAcc)
     after 0 ->
         {updated, UserAcc}
     end.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/6d6b801f/src/couch_db.erl
----------------------------------------------------------------------
diff --git a/src/couch_db.erl b/src/couch_db.erl
index 5d4619a..b5ea64d 100644
--- a/src/couch_db.erl
+++ b/src/couch_db.erl
@@ -1173,7 +1173,9 @@ enum_docs_reduce_to_count(Reds) ->
 changes_since(Db, StartSeq, Fun, Acc) ->
     changes_since(Db, StartSeq, Fun, [], Acc).
 
-changes_since(Db, StartSeq, Fun, Options, Acc) ->
+changes_since(Db, StartSeq, Fun, Options, Acc) when is_record(Db, db) ->
+    changes_since(Db#db.seq_tree, StartSeq, Fun, Options, Acc);
+changes_since(SeqTree, StartSeq, Fun, Options, Acc) ->
     Wrapper = fun(FullDocInfo, _Offset, Acc2) ->
         DocInfo = case FullDocInfo of
             #full_doc_info{} ->
@@ -1183,7 +1185,7 @@ changes_since(Db, StartSeq, Fun, Options, Acc) ->
         end,
         Fun(DocInfo, Acc2)
     end,
-    {ok, _LastReduction, AccOut} = couch_btree:fold(Db#db.seq_tree,
+    {ok, _LastReduction, AccOut} = couch_btree:fold(SeqTree,
         Wrapper, Acc, [{start_key, StartSeq + 1}] ++ Options),
     {ok, AccOut}.
 

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/6d6b801f/src/couch_httpd_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_changes.erl b/src/couch_httpd_changes.erl
index b494b0d..9f141d9 100644
--- a/src/couch_httpd_changes.erl
+++ b/src/couch_httpd_changes.erl
@@ -12,22 +12,33 @@
 
 -module(couch_httpd_changes).
 
--export([handle_changes_req/2,
-         handle_changes/3,
-         handle_view_changes/3]).
+-export([handle_db_changes_req/2,
+         handle_changes_req/4,
+         handle_view_filtered_changes/3,
+         parse_changes_query/3]).
 
 -include_lib("couch/include/couch_db.hrl").
 
-handle_changes_req(#httpd{method='POST'}=Req, Db) ->
+handle_db_changes_req(Req, Db) ->
+    ChangesArgs = parse_changes_query(Req, Db, false),
+    ChangesFun = case ChangesArgs#changes_args.filter of
+        "_view" ->
+            handle_view_filtered_changes(ChangesArgs, Req, Db);
+        _ ->
+            couch_changes:handle_db_changes(ChangesArgs, Req, Db)
+    end,
+    handle_changes_req(Req, Db, ChangesArgs, ChangesFun).
+
+handle_changes_req(#httpd{method='POST'}=Req, Db, ChangesArgs, ChangesFun) ->
     couch_httpd:validate_ctype(Req, "application/json"),
-    handle_changes_req1(Req, Db);
-handle_changes_req(#httpd{method='GET'}=Req, Db) ->
-    handle_changes_req1(Req, Db);
-handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->
+    handle_changes_req1(Req, Db, ChangesArgs, ChangesFun);
+handle_changes_req(#httpd{method='GET'}=Req, Db, ChangesArgs, ChangesFun) ->
+    handle_changes_req1(Req, Db, ChangesArgs, ChangesFun);
+handle_changes_req(#httpd{}=Req, _Db, _ChangesArgs, _ChangesFun) ->
     couch_httpd:send_method_not_allowed(Req, "GET,HEAD,POST").
 
-handle_changes_req1(Req, #db{name=DbName}=Db) ->
-    AuthDbName = ?l2b(couch_config:get("couch_httpd_auth", "authentication_db")),
+handle_changes_req1(Req, #db{name=DbName}=Db, ChangesArgs, ChangesFun) ->
+    AuthDbName = ?l2b(config:get("couch_httpd_auth", "authentication_db")),
     case AuthDbName of
     DbName ->
         % in the authentication database, _changes is admin-only.
@@ -71,8 +82,6 @@ handle_changes_req1(Req, #db{name=DbName}=Db) ->
             couch_httpd:send_chunk(Resp, "\n")
         end
     end,
-    ChangesArgs = parse_changes_query(Req, Db),
-    ChangesFun = handle_changes(ChangesArgs, Req, Db),
     WrapperFun = case ChangesArgs#changes_args.feed of
     "normal" ->
         {ok, Info} = couch_db:get_db_info(Db),
@@ -117,21 +126,13 @@ handle_changes_req1(Req, #db{name=DbName}=Db) ->
     end.
 
 
-handle_changes(ChangesArgs, Req, Db) ->
-    case ChangesArgs#changes_args.filter of
-        "_view" ->
-            handle_view_changes(ChangesArgs, Req, Db);
-        _ ->
-            couch_changes:handle_changes(ChangesArgs, Req, Db)
-    end.
-
 %% wrapper around couch_mrview_changes.
-%% This wrapper mimic couch_changes:handle_changes/3 and return a
+%% This wrapper mimic couch_changes:handle_db_changes/3 and return a
 %% Changefun that can be used by the handle_changes_req function. Also
 %% while couch_mrview_changes:handle_changes/6 is returning tha view
 %% changes this function return docs corresponding to the changes
 %% instead so it can be used to replace the _view filter.
-handle_view_changes(ChangesArgs, Req, Db) ->
+handle_view_filtered_changes(ChangesArgs, Req, Db) ->
     %% parse view parameter
     {DDocId, VName} = parse_view_param(Req),
 
@@ -149,7 +150,7 @@ handle_view_changes(ChangesArgs, Req, Db) ->
     case lists:member(<<"seq_indexed">>,
                       proplists:get_value(update_options, Infos, [])) of
         true ->
-            handle_view_changes(Db, DDocId, VName, ViewOptions, ChangesArgs,
+            handle_view_filtered_changes(Db, DDocId, VName, ViewOptions, ChangesArgs,
                                 Req);
         false when ViewOptions /= [] ->
             ?LOG_ERROR("Tried to filter a non sequence indexed view~n",[]),
@@ -158,10 +159,10 @@ handle_view_changes(ChangesArgs, Req, Db) ->
             %% old method we are getting changes using the btree instead
             %% which is not efficient, log it
             ?LOG_WARN("Get view changes with seq_indexed=false.~n", []),
-            couch_changes:handle_changes(ChangesArgs, Req, Db)
+            couch_changes:handle_db_changes(ChangesArgs, Req, Db)
     end.
 
-handle_view_changes(#db{name=DbName}=Db0, DDocId, VName, ViewOptions,
+handle_view_filtered_changes(#db{name=DbName}=Db0, DDocId, VName, ViewOptions,
                     ChangesArgs, Req) ->
     #changes_args{
         feed = ResponseType,
@@ -288,7 +289,7 @@ view_change_row(Db, DocInfo, Args) ->
                 []
     end}}.
 
-parse_changes_query(Req, Db) ->
+parse_changes_query(Req, Db, IsViewChanges) ->
     ChangesArgs = lists:foldl(fun({Key, Value}, Args) ->
         case {string:to_lower(Key), Value} of
         {"feed", _} ->

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/6d6b801f/src/couch_httpd_db.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_db.erl b/src/couch_httpd_db.erl
index 4ff4d98..442e872 100644
--- a/src/couch_httpd_db.erl
+++ b/src/couch_httpd_db.erl
@@ -112,7 +112,7 @@ handle_changes_req2(Req, Db) ->
         end
     end,
     ChangesArgs = parse_changes_query(Req, Db),
-    ChangesFun = couch_changes:handle_changes(ChangesArgs, Req, Db),
+    ChangesFun = couch_changes:handle_db_changes(ChangesArgs, Req, Db),
     WrapperFun = case ChangesArgs#changes_args.feed of
     "normal" ->
         {ok, Info} = couch_db:get_db_info(Db),


[05/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
extract couch_httpd changes API in its own module


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/fcb2882d
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/fcb2882d
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/fcb2882d

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: fcb2882dfb12c36b1be3e9cda6b1328c324e667e
Parents: 011975a
Author: benoitc <bc...@gmail.com>
Authored: Sun Feb 2 19:54:01 2014 +0100
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:52 2014 -0700

----------------------------------------------------------------------
 src/couch_httpd_changes.erl | 174 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 174 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/fcb2882d/src/couch_httpd_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_changes.erl b/src/couch_httpd_changes.erl
new file mode 100644
index 0000000..1e431e9
--- /dev/null
+++ b/src/couch_httpd_changes.erl
@@ -0,0 +1,174 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_httpd_changes).
+
+-export([handle_changes_req/2]).
+
+-include_lib("couch/include/couch_db.hrl").
+
+handle_changes_req(#httpd{method='POST'}=Req, Db) ->
+    couch_httpd:validate_ctype(Req, "application/json"),
+    handle_changes_req1(Req, Db);
+handle_changes_req(#httpd{method='GET'}=Req, Db) ->
+    handle_changes_req1(Req, Db);
+handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->
+    couch_httpd:send_method_not_allowed(Req, "GET,HEAD,POST").
+
+handle_changes_req1(Req, #db{name=DbName}=Db) ->
+    AuthDbName = ?l2b(couch_config:get("couch_httpd_auth", "authentication_db")),
+    case AuthDbName of
+    DbName ->
+        % in the authentication database, _changes is admin-only.
+        ok = couch_db:check_is_admin(Db);
+    _Else ->
+        % on other databases, _changes is free for all.
+        ok
+    end,
+    handle_changes_req2(Req, Db).
+
+handle_changes_req2(Req, Db) ->
+    MakeCallback = fun(Resp) ->
+        fun({change, {ChangeProp}=Change, _}, "eventsource") ->
+            Seq = proplists:get_value(<<"seq">>, ChangeProp),
+            couch_httpd:send_chunk(Resp, ["data: ", ?JSON_ENCODE(Change),
+                              "\n", "id: ", ?JSON_ENCODE(Seq),
+                              "\n\n"]);
+        ({change, Change, _}, "continuous") ->
+            couch_httpd:send_chunk(Resp, [?JSON_ENCODE(Change) | "\n"]);
+        ({change, Change, Prepend}, _) ->
+            couch_httpd:send_chunk(Resp, [Prepend, ?JSON_ENCODE(Change)]);
+        (start, "eventsource") ->
+            ok;
+        (start, "continuous") ->
+            ok;
+        (start, _) ->
+            couch_httpd:send_chunk(Resp, "{\"results\":[\n");
+        ({stop, _EndSeq}, "eventsource") ->
+            couch_httpd:end_json_response(Resp);
+        ({stop, EndSeq}, "continuous") ->
+            couch_httpd:send_chunk(
+                Resp,
+                [?JSON_ENCODE({[{<<"last_seq">>, EndSeq}]}) | "\n"]
+            ),
+            couch_httpd:end_json_response(Resp);
+        ({stop, EndSeq}, _) ->
+            couch_httpd:send_chunk(
+                Resp,
+                io_lib:format("\n],\n\"last_seq\":~w}\n", [EndSeq])
+            ),
+            couch_httpd:end_json_response(Resp);
+        (timeout, _) ->
+            couch_httpd:send_chunk(Resp, "\n")
+        end
+    end,
+    ChangesArgs = parse_changes_query(Req, Db),
+    ChangesFun = couch_changes:handle_changes(ChangesArgs, Req, Db),
+    WrapperFun = case ChangesArgs#changes_args.feed of
+    "normal" ->
+        {ok, Info} = couch_db:get_db_info(Db),
+        CurrentEtag = couch_httpd:make_etag(Info),
+        fun(FeedChangesFun) ->
+            couch_httpd:etag_respond(
+                Req,
+                CurrentEtag,
+                fun() ->
+                    {ok, Resp} = couch_httpd:start_json_response(
+                         Req, 200, [{"ETag", CurrentEtag}]
+                    ),
+                    FeedChangesFun(MakeCallback(Resp))
+                end
+            )
+        end;
+    "eventsource" ->
+        Headers = [
+            {"Content-Type", "text/event-stream"},
+            {"Cache-Control", "no-cache"}
+        ],
+        {ok, Resp} = couch_httpd:start_chunked_response(Req, 200, Headers),
+        fun(FeedChangesFun) ->
+            FeedChangesFun(MakeCallback(Resp))
+        end;
+    _ ->
+        % "longpoll" or "continuous"
+        {ok, Resp} = couch_httpd:start_json_response(Req, 200),
+        fun(FeedChangesFun) ->
+            FeedChangesFun(MakeCallback(Resp))
+        end
+    end,
+    couch_stats_collector:increment(
+        {httpd, clients_requesting_changes}
+    ),
+    try
+        WrapperFun(ChangesFun)
+    after
+    couch_stats_collector:decrement(
+        {httpd, clients_requesting_changes}
+    )
+    end.
+
+parse_changes_query(Req, Db) ->
+    ChangesArgs = lists:foldl(fun({Key, Value}, Args) ->
+        case {string:to_lower(Key), Value} of
+        {"feed", _} ->
+            Args#changes_args{feed=Value};
+        {"descending", "true"} ->
+            Args#changes_args{dir=rev};
+        {"since", "now"} ->
+            UpdateSeq = couch_util:with_db(Db#db.name, fun(WDb) ->
+                                        couch_db:get_update_seq(WDb)
+                                end),
+            Args#changes_args{since=UpdateSeq};
+        {"since", _} ->
+            Args#changes_args{since=list_to_integer(Value)};
+        {"last-event-id", _} ->
+            Args#changes_args{since=list_to_integer(Value)};
+        {"limit", _} ->
+            Args#changes_args{limit=list_to_integer(Value)};
+        {"style", _} ->
+            Args#changes_args{style=list_to_existing_atom(Value)};
+        {"heartbeat", "true"} ->
+            Args#changes_args{heartbeat=true};
+        {"heartbeat", _} ->
+            Args#changes_args{heartbeat=list_to_integer(Value)};
+        {"timeout", _} ->
+            Args#changes_args{timeout=list_to_integer(Value)};
+        {"include_docs", "true"} ->
+            Args#changes_args{include_docs=true};
+        {"attachments", "true"} ->
+            Opts = Args#changes_args.doc_options,
+            Args#changes_args{doc_options=[attachments|Opts]};
+        {"att_encoding_info", "true"} ->
+            Opts = Args#changes_args.doc_options,
+            Args#changes_args{doc_options=[att_encoding_info|Opts]};
+        {"conflicts", "true"} ->
+            Args#changes_args{conflicts=true};
+        {"filter", _} ->
+            Args#changes_args{filter=Value};
+        _Else -> % unknown key value pair, ignore.
+            Args
+        end
+    end, #changes_args{}, couch_httpd:qs(Req)),
+    %% if it's an EventSource request with a Last-event-ID header
+    %% that should override the `since` query string, since it's
+    %% probably the browser reconnecting.
+    case ChangesArgs#changes_args.feed of
+        "eventsource" ->
+            case couch_httpd:header_value(Req, "last-event-id") of
+                undefined ->
+                    ChangesArgs;
+                Value ->
+                    ChangesArgs#changes_args{since=list_to_integer(Value)}
+            end;
+        _ ->
+            ChangesArgs
+    end.


[02/22] couch commit: updated refs/heads/2491-refactor-couch-httpd-auth to 3e8286d

Posted by da...@apache.org.
Fix header for documents with newlines in the name

Properly urlencode the Document-Id in the Location field of the
header

Based on a patch from Sean Bartell <wi...@gmail.com>

Added some eunit-tests

COUCHDB-708


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/9ee298ff
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/9ee298ff
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/9ee298ff

Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 9ee298ff0af335f7449eca0f839caf0c5646887a
Parents: f02a101
Author: Robert Kowalski <ro...@kowalski.gd>
Authored: Sat Oct 25 03:16:38 2014 +0200
Committer: Robert Kowalski <ro...@apache.org>
Committed: Thu Oct 30 15:20:32 2014 +0100

----------------------------------------------------------------------
 src/couch_httpd_db.erl                 |  5 +-
 test/couchdb_location_header_tests.erl | 78 +++++++++++++++++++++++++++++
 2 files changed, 80 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/9ee298ff/src/couch_httpd_db.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_db.erl b/src/couch_httpd_db.erl
index 13e2c44..4ff4d98 100644
--- a/src/couch_httpd_db.erl
+++ b/src/couch_httpd_db.erl
@@ -721,7 +721,7 @@ update_doc_result_to_json(DocId, Error) ->
 
 
 update_doc(Req, Db, DocId, #doc{deleted=false}=Doc) ->
-    Loc = absolute_uri(Req, "/" ++ ?b2l(Db#db.name) ++ "/" ++ ?b2l(DocId)),
+    Loc = absolute_uri(Req, "/" ++ ?b2l(Db#db.name) ++ "/" ++ couch_util:url_encode(DocId)),
     update_doc(Req, Db, DocId, Doc, [{"Location", Loc}]);
 update_doc(Req, Db, DocId, Doc) ->
     update_doc(Req, Db, DocId, Doc, []).
@@ -1022,7 +1022,7 @@ db_attachment_req(#httpd{method=Method,mochi_req=MochiReq}=Req, Db, DocId, FileN
     _ ->
         [{"Location", absolute_uri(Req, "/" ++
             ?b2l(Db#db.name) ++ "/" ++
-            ?b2l(DocId) ++ "/" ++
+            couch_util:url_encode(DocId) ++ "/" ++
             ?b2l(FileName)
         )}]
     end,
@@ -1233,4 +1233,3 @@ validate_attachment_name(Name) ->
         true -> Name;
         false -> throw({bad_request, <<"Attachment name is not UTF-8 encoded">>})
     end.
-

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/9ee298ff/test/couchdb_location_header_tests.erl
----------------------------------------------------------------------
diff --git a/test/couchdb_location_header_tests.erl b/test/couchdb_location_header_tests.erl
new file mode 100644
index 0000000..74d1281
--- /dev/null
+++ b/test/couchdb_location_header_tests.erl
@@ -0,0 +1,78 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couchdb_location_header_tests).
+
+-include_lib("couch/include/couch_eunit.hrl").
+-include_lib("couch/include/couch_db.hrl").
+
+-define(TIMEOUT, 1000).
+
+
+setup() ->
+    DbName = ?tempdb(),
+    {ok, Db} = couch_db:create(DbName, [?ADMIN_USER]),
+    couch_db:close(Db),
+
+    Addr = config:get("httpd", "bind_address", "127.0.0.1"),
+    Port = integer_to_list(mochiweb_socket_server:get(couch_httpd, port)),
+    Host = "http://" ++ Addr ++ ":" ++ Port,
+    {Host, ?b2l(DbName)}.
+
+teardown({_, DbName}) ->
+    ok = couch_server:delete(?l2b(DbName), [?ADMIN_USER]),
+    ok.
+
+
+header_test_() ->
+    {
+        "CouchDB Location Header Tests",
+        {
+            setup,
+            fun test_util:start_couch/0, fun test_util:stop_couch/1,
+            {
+                foreach,
+                fun setup/0, fun teardown/1,
+                [
+                    fun should_work_with_newlines_in_docs/1,
+                    fun should_work_with_newlines_in_attachments/1
+                ]
+            }
+        }
+    }.
+
+should_work_with_newlines_in_docs({Host, DbName}) ->
+    Url = Host ++ "/" ++ DbName ++ "/docid%0A",
+    {"COUCHDB-708",
+        ?_assertEqual(
+            Url,
+            begin
+                {ok, _, Headers, _} = test_request:put(Url,
+                    [{"Content-Type", "application/json"}], "{}"),
+                proplists:get_value("Location", Headers)
+            end)}.
+
+should_work_with_newlines_in_attachments({Host, DbName}) ->
+    Url = Host ++ "/" ++ DbName,
+    AttUrl = Url ++ "/docid%0A/readme.txt",
+    {"COUCHDB-708",
+        ?_assertEqual(
+            AttUrl,
+            begin
+                Body = "We all live in a yellow submarine!",
+                Headers0 = [
+                    {"Content-Length", "34"},
+                    {"Content-Type", "text/plain"}
+                ],
+                {ok, _, Headers, _} = test_request:put(AttUrl, Headers0, Body),
+                proplists:get_value("Location", Headers)
+            end)}.