summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--THANKS1
-rw-r--r--share/Makefile.am1
-rw-r--r--share/www/script/couch_tests.js1
-rw-r--r--share/www/script/test/view_update_seq.js76
-rw-r--r--src/couchdb/couch_httpd_db.erl6
-rw-r--r--src/couchdb/couch_httpd_show.erl29
-rw-r--r--src/couchdb/couch_httpd_view.erl55
7 files changed, 129 insertions, 40 deletions
diff --git a/THANKS b/THANKS
index e86dabf2..dd25adaa 100644
--- a/THANKS
+++ b/THANKS
@@ -51,5 +51,6 @@ suggesting improvements or submitting changes. Some of these people are:
* Matt Lyon <matt@flowerpowered.com>
* mikeal <mikeal.rogers@gmail.com>
* Randall Leeds <randall.leeds@gmail.com>
+ * Joscha Feth <joscha@feth.com>
For a list of authors see the `AUTHORS` file.
diff --git a/share/Makefile.am b/share/Makefile.am
index a6911b12..ac4d6fb0 100644
--- a/share/Makefile.am
+++ b/share/Makefile.am
@@ -162,6 +162,7 @@ nobase_dist_localdata_DATA = \
www/script/test/view_multi_key_design.js \
www/script/test/view_multi_key_temp.js \
www/script/test/view_offsets.js \
+ www/script/test/view_update_seq.js \
www/script/test/view_pagination.js \
www/script/test/view_sandboxing.js \
www/script/test/view_xml.js \
diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js
index 943b851b..896f150b 100644
--- a/share/www/script/couch_tests.js
+++ b/share/www/script/couch_tests.js
@@ -91,6 +91,7 @@ loadTest("view_multi_key_temp.js");
loadTest("view_offsets.js");
loadTest("view_pagination.js");
loadTest("view_sandboxing.js");
+loadTest("view_update_seq.js");
loadTest("view_xml.js");
// keep sorted
diff --git a/share/www/script/test/view_update_seq.js b/share/www/script/test/view_update_seq.js
new file mode 100644
index 00000000..3935e153
--- /dev/null
+++ b/share/www/script/test/view_update_seq.js
@@ -0,0 +1,76 @@
+// 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.
+
+couchTests.view_update_seq = function(debug) {
+ var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ T(db.info().update_seq == 0);
+
+ var designDoc = {
+ _id:"_design/test",
+ language: "javascript",
+ views: {
+ all_docs: {
+ map: "function(doc) { emit(doc.integer, doc.string) }"
+ },
+ summate: {
+ map:"function (doc) {emit(doc.integer, doc.integer)};",
+ reduce:"function (keys, values) { return sum(values); };"
+ }
+ }
+ }
+ T(db.save(designDoc).ok);
+
+ T(db.info().update_seq == 1);
+
+ var resp = db.allDocs({});
+
+ T(resp.rows.length == 1);
+ T(resp.update_seq == 1);
+
+ var docs = makeDocs(0, 100);
+ db.bulkSave(docs);
+
+ resp = db.allDocs({limit: 1});
+ T(resp.rows.length == 1);
+ T(resp.update_seq == 101);
+
+ resp = db.view('test/all_docs', {limit: 1});
+ T(resp.rows.length == 1);
+ T(resp.update_seq == 101);
+
+ resp = db.view('test/summate', {});
+ T(resp.rows.length == 1);
+ T(resp.update_seq == 101);
+
+ db.save({"id":"0"});
+ resp = db.view('test/all_docs', {limit: 1,stale: "ok"});
+ T(resp.rows.length == 1);
+ T(resp.update_seq == 101);
+
+ resp = db.view('test/all_docs', {limit: 1});
+ T(resp.rows.length == 1);
+ T(resp.update_seq == 102);
+
+ resp = db.view('test/all_docs',{},["0","1"]);
+ T(resp.update_seq == 102);
+
+ resp = db.view('test/all_docs',{},["0","1"]);
+ T(resp.update_seq == 102);
+
+ resp = db.view('test/summate',{group:true},["0","1"]);
+ T(resp.update_seq == 102);
+
+};
diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
index b473ff6c..463519a8 100644
--- a/src/couchdb/couch_httpd_db.erl
+++ b/src/couchdb/couch_httpd_db.erl
@@ -492,10 +492,10 @@ all_docs_view(Req, Db, Keys) ->
true -> EndDocId
end,
FoldAccInit = {Limit, SkipCount, undefined, []},
-
+ UpdateSeq = couch_db:get_update_seq(Db),
case Keys of
nil ->
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, UpdateSeq,
TotalRowCount, #view_fold_helper_funs{
reduce_count = fun couch_db:enum_docs_reduce_to_count/1
}),
@@ -512,7 +512,7 @@ all_docs_view(Req, Db, Keys) ->
{if Inclusive -> end_key; true -> end_key_gt end, EndId}]),
couch_httpd_view:finish_view_fold(Req, TotalRowCount, LastOffset, FoldResult);
_ ->
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, UpdateSeq,
TotalRowCount, #view_fold_helper_funs{
reduce_count = fun(Offset) -> Offset end
}),
diff --git a/src/couchdb/couch_httpd_show.erl b/src/couchdb/couch_httpd_show.erl
index a472c97a..f40a0421 100644
--- a/src/couchdb/couch_httpd_show.erl
+++ b/src/couchdb/couch_httpd_show.erl
@@ -190,21 +190,21 @@ handle_view_list(Req, Db, DDoc, LName, {ViewDesignName, ViewName}, Keys) ->
{ViewType, View, Group, QueryArgs} = couch_httpd_view:load_view(Req, Db, {ViewDesignId, ViewName}, Keys),
Etag = list_etag(Req, Db, Group, {couch_httpd:doc_etag(DDoc), Keys}),
couch_httpd:etag_respond(Req, Etag, fun() ->
- output_list(ViewType, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys)
+ output_list(ViewType, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group)
end).
list_etag(#httpd{user_ctx=UserCtx}=Req, Db, Group, More) ->
Accept = couch_httpd:header_value(Req, "Accept"),
couch_httpd_view:view_group_etag(Group, Db, {More, Accept, UserCtx#user_ctx.roles}).
-output_list(map, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) ->
- output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys);
-output_list(reduce, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) ->
- output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys).
+output_list(map, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group) ->
+ output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group);
+output_list(reduce, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group) ->
+ output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group).
% next step:
% use with_ddoc_proc/2 to make this simpler
-output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) ->
+output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group) ->
#view_query_args{
limit = Limit,
skip = SkipCount
@@ -220,11 +220,12 @@ output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) ->
reduce_count = fun couch_view:reduce_to_count/1,
start_response = StartListRespFun = make_map_start_resp_fun(QServer, Db, LName),
send_row = make_map_send_row_fun(QServer)
- },
+ },
+ CurrentSeq = Group#group.current_seq,
{ok, _, FoldResult} = case Keys of
nil ->
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Etag, Db, RowCount, ListFoldHelpers),
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Etag, Db, CurrentSeq, RowCount, ListFoldHelpers),
couch_view:fold(View, FoldlFun, FoldAccInit,
couch_httpd_view:make_key_options(QueryArgs));
Keys ->
@@ -234,7 +235,7 @@ output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) ->
start_key = Key,
end_key = Key
},
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs2, Etag, Db, RowCount, ListFoldHelpers),
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs2, Etag, Db, CurrentSeq, RowCount, ListFoldHelpers),
couch_view:fold(View, FoldlFun, FoldAcc,
couch_httpd_view:make_key_options(QueryArgs2))
end, {ok, nil, FoldAccInit}, Keys)
@@ -243,18 +244,20 @@ output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) ->
end).
-output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) ->
+output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group) ->
#view_query_args{
limit = Limit,
skip = SkipCount,
group_level = GroupLevel
} = QueryArgs,
+ CurrentSeq = Group#group.current_seq,
+
couch_query_servers:with_ddoc_proc(DDoc, fun(QServer) ->
StartListRespFun = make_reduce_start_resp_fun(QServer, Db, LName),
SendListRowFun = make_reduce_send_row_fun(QServer, Db),
{ok, GroupRowsFun, RespFun} = couch_httpd_view:make_reduce_fold_funs(Req,
- GroupLevel, QueryArgs, Etag,
+ GroupLevel, QueryArgs, Etag, CurrentSeq,
#reduce_fold_helper_funs{
start_response = StartListRespFun,
send_row = SendListRowFun
@@ -279,8 +282,8 @@ output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) ->
make_map_start_resp_fun(QueryServer, Db, LName) ->
- fun(Req, Etag, TotalRows, Offset, _Acc) ->
- Head = {[{<<"total_rows">>, TotalRows}, {<<"offset">>, Offset}]},
+ fun(Req, Etag, TotalRows, Offset, _Acc, UpdateSeq) ->
+ Head = {[{<<"total_rows">>, TotalRows}, {<<"offset">>, Offset}, {<<"update_seq">>, UpdateSeq}]},
start_list_resp(QueryServer, LName, Req, Db, Head, Etag)
end.
diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl
index 6419ca55..5be702ca 100644
--- a/src/couchdb/couch_httpd_view.erl
+++ b/src/couchdb/couch_httpd_view.erl
@@ -16,8 +16,8 @@
-export([handle_view_req/3,handle_temp_view_req/2]).
-export([get_stale_type/1, get_reduce_type/1, parse_view_params/3]).
--export([make_view_fold_fun/6, finish_view_fold/4, view_row_obj/3]).
--export([view_group_etag/2, view_group_etag/3, make_reduce_fold_funs/5]).
+-export([make_view_fold_fun/7, finish_view_fold/4, view_row_obj/3]).
+-export([view_group_etag/2, view_group_etag/3, make_reduce_fold_funs/6]).
-export([design_doc_view/5, parse_bool_param/1, doc_member/2]).
-export([make_key_options/1, load_view/4]).
@@ -26,6 +26,8 @@
start_json_response/2, start_json_response/3, end_json_response/1,
send_chunked_error/2]).
+-import(couch_db,[get_update_seq/1]).
+
design_doc_view(Req, Db, DName, ViewName, Keys) ->
DesignId = <<"_design/", DName/binary>>,
Stale = get_stale_type(Req),
@@ -111,7 +113,7 @@ output_map_view(Req, View, Group, Db, QueryArgs, nil) ->
CurrentEtag = view_group_etag(Group, Db),
couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
{ok, RowCount} = couch_view:get_row_count(View),
- FoldlFun = make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, RowCount, #view_fold_helper_funs{reduce_count=fun couch_view:reduce_to_count/1}),
+ FoldlFun = make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, Group#group.current_seq, RowCount, #view_fold_helper_funs{reduce_count=fun couch_view:reduce_to_count/1}),
FoldAccInit = {Limit, SkipCount, undefined, []},
{ok, LastReduce, FoldResult} = couch_view:fold(View,
FoldlFun, FoldAccInit, make_key_options(QueryArgs)),
@@ -132,7 +134,7 @@ output_map_view(Req, View, Group, Db, QueryArgs, Keys) ->
fun(Key, {_, FoldAcc}) ->
FoldlFun = make_view_fold_fun(Req,
QueryArgs#view_query_args{
- }, CurrentEtag, Db, RowCount,
+ }, CurrentEtag, Db, Group#group.current_seq, RowCount,
#view_fold_helper_funs{
reduce_count = fun couch_view:reduce_to_count/1
}),
@@ -140,7 +142,7 @@ output_map_view(Req, View, Group, Db, QueryArgs, Keys) ->
make_key_options(QueryArgs#view_query_args{start_key=Key, end_key=Key})),
{LastReduce, FoldResult}
end, {{[],[]}, FoldAccInit}, Keys),
- finish_view_fold(Req, RowCount, couch_view:reduce_to_count(LastReduce), FoldResult)
+ finish_view_fold(Req, RowCount, couch_view:reduce_to_count(LastReduce), FoldResult, [{update_seq,Group#group.current_seq}])
end).
output_reduce_view(Req, Db, View, Group, QueryArgs, nil) ->
@@ -151,7 +153,7 @@ output_reduce_view(Req, Db, View, Group, QueryArgs, nil) ->
} = QueryArgs,
CurrentEtag = view_group_etag(Group, Db),
couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
- {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, QueryArgs, CurrentEtag, #reduce_fold_helper_funs{}),
+ {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, QueryArgs, CurrentEtag, Group#group.current_seq, #reduce_fold_helper_funs{}),
FoldAccInit = {Limit, Skip, undefined, []},
{ok, {_, _, Resp, _}} = couch_view:fold_reduce(View,
RespFun, FoldAccInit, [{key_group_fun, GroupRowsFun} |
@@ -167,7 +169,7 @@ output_reduce_view(Req, Db, View, Group, QueryArgs, Keys) ->
} = QueryArgs,
CurrentEtag = view_group_etag(Group, Db, Keys),
couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
- {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, QueryArgs, CurrentEtag, #reduce_fold_helper_funs{}),
+ {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, QueryArgs, CurrentEtag, Group#group.current_seq, #reduce_fold_helper_funs{}),
{Resp, _RedAcc3} = lists:foldl(
fun(Key, {Resp, RedAcc}) ->
% run the reduce once for each key in keys, with limit etc reapplied for each key
@@ -180,7 +182,7 @@ output_reduce_view(Req, Db, View, Group, QueryArgs, Keys) ->
{Resp2, RedAcc2}
end,
{undefined, []}, Keys), % Start with no comma
- finish_reduce_fold(Req, Resp)
+ finish_reduce_fold(Req, Resp, [{update_seq,Group#group.current_seq}])
end).
reverse_key_default(?MIN_STR) -> ?MAX_STR;
@@ -377,7 +379,7 @@ validate_view_query(include_docs, _Value, Args) ->
validate_view_query(extra, _Value, Args) ->
Args.
-make_view_fold_fun(Req, QueryArgs, Etag, Db, TotalViewCount, HelperFuns) ->
+make_view_fold_fun(Req, QueryArgs, Etag, Db, UpdateSeq, TotalViewCount, HelperFuns) ->
#view_fold_helper_funs{
start_response = StartRespFun,
send_row = SendRowFun,
@@ -400,7 +402,7 @@ make_view_fold_fun(Req, QueryArgs, Etag, Db, TotalViewCount, HelperFuns) ->
% rendering the first row, first we start the response
Offset = ReduceCountFun(OffsetReds),
{ok, Resp2, RowFunAcc0} = StartRespFun(Req, Etag,
- TotalViewCount, Offset, RowFunAcc),
+ TotalViewCount, Offset, RowFunAcc, UpdateSeq),
{Go, RowFunAcc2} = SendRowFun(Resp2, Db, {{Key, DocId}, Value},
IncludeDocs, RowFunAcc0),
{Go, {AccLimit - 1, 0, Resp2, RowFunAcc2}};
@@ -412,7 +414,7 @@ make_view_fold_fun(Req, QueryArgs, Etag, Db, TotalViewCount, HelperFuns) ->
end
end.
-make_reduce_fold_funs(Req, GroupLevel, _QueryArgs, Etag, HelperFuns) ->
+make_reduce_fold_funs(Req, GroupLevel, _QueryArgs, Etag, UpdateSeq, HelperFuns) ->
#reduce_fold_helper_funs{
start_response = StartRespFun,
send_row = SendRowFun
@@ -438,7 +440,7 @@ make_reduce_fold_funs(Req, GroupLevel, _QueryArgs, Etag, HelperFuns) ->
(_Key, Red, {AccLimit, 0, undefined, RowAcc0}) when GroupLevel == 0 ->
% we haven't started responding yet and group=false
- {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0),
+ {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0,UpdateSeq),
{Go, RowAcc2} = SendRowFun(Resp2, {null, Red}, RowAcc),
{Go, {AccLimit - 1, 0, Resp2, RowAcc2}};
(_Key, Red, {AccLimit, 0, Resp, RowAcc}) when GroupLevel == 0 ->
@@ -449,7 +451,7 @@ make_reduce_fold_funs(Req, GroupLevel, _QueryArgs, Etag, HelperFuns) ->
(Key, Red, {AccLimit, 0, undefined, RowAcc0})
when is_integer(GroupLevel), is_list(Key) ->
% group_level and we haven't responded yet
- {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0),
+ {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0,UpdateSeq),
{Go, RowAcc2} = SendRowFun(Resp2, {lists:sublist(Key, GroupLevel), Red}, RowAcc),
{Go, {AccLimit - 1, 0, Resp2, RowAcc2}};
(Key, Red, {AccLimit, 0, Resp, RowAcc})
@@ -460,7 +462,7 @@ make_reduce_fold_funs(Req, GroupLevel, _QueryArgs, Etag, HelperFuns) ->
(Key, Red, {AccLimit, 0, undefined, RowAcc0}) ->
% group=true and we haven't responded yet
- {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0),
+ {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0,UpdateSeq),
{Go, RowAcc2} = SendRowFun(Resp2, {Key, Red}, RowAcc),
{Go, {AccLimit - 1, 0, Resp2, RowAcc2}};
(Key, Red, {AccLimit, 0, Resp, RowAcc}) ->
@@ -476,7 +478,7 @@ apply_default_helper_funs(#view_fold_helper_funs{
}=Helpers) ->
StartResp2 = case StartResp of
- undefined -> fun json_view_start_resp/5;
+ undefined -> fun json_view_start_resp/6;
_ -> StartResp
end,
@@ -496,7 +498,7 @@ apply_default_helper_funs(#reduce_fold_helper_funs{
send_row = SendRow
}=Helpers) ->
StartResp2 = case StartResp of
- undefined -> fun json_reduce_start_resp/3;
+ undefined -> fun json_reduce_start_resp/4;
_ -> StartResp
end,
@@ -538,10 +540,10 @@ make_end_key_option(
inclusive_end = false}) ->
[{end_key_gt, {EndKey,reverse_key_default(EndDocId)}}].
-json_view_start_resp(Req, Etag, TotalViewCount, Offset, _Acc) ->
+json_view_start_resp(Req, Etag, TotalViewCount, Offset, _Acc, UpdateSeq) ->
{ok, Resp} = start_json_response(Req, 200, [{"Etag", Etag}]),
- BeginBody = io_lib:format("{\"total_rows\":~w,\"offset\":~w,\"rows\":[\r\n",
- [TotalViewCount, Offset]),
+ BeginBody = io_lib:format("{\"total_rows\":~w,\"update_seq\":~w,\"offset\":~w,\"rows\":[\r\n",
+ [TotalViewCount, UpdateSeq, Offset]),
{ok, Resp, BeginBody}.
send_json_view_row(Resp, Db, {{Key, DocId}, Value}, IncludeDocs, RowFront) ->
@@ -549,9 +551,9 @@ send_json_view_row(Resp, Db, {{Key, DocId}, Value}, IncludeDocs, RowFront) ->
send_chunk(Resp, RowFront ++ ?JSON_ENCODE(JsonObj)),
{ok, ",\r\n"}.
-json_reduce_start_resp(Req, Etag, _Acc0) ->
+json_reduce_start_resp(Req, Etag, _Acc0, UpdateSeq) ->
{ok, Resp} = start_json_response(Req, 200, [{"Etag", Etag}]),
- {ok, Resp, "{\"rows\":[\r\n"}.
+ {ok, Resp, io_lib:format("{\"update_seq\":~w,\"rows\":[\r\n",[UpdateSeq])}.
send_json_reduce_row(Resp, {Key, Value}, RowFront) ->
send_chunk(Resp, RowFront ++ ?JSON_ENCODE({[{key, Key}, {value, Value}]})),
@@ -599,9 +601,11 @@ doc_member(Db, {DocId, Rev}) ->
_Else ->
[{doc, null}]
end.
-
finish_view_fold(Req, TotalRows, Offset, FoldResult) ->
+ finish_view_fold(Req, TotalRows, Offset, FoldResult, []).
+
+finish_view_fold(Req, TotalRows, Offset, FoldResult, Fields) ->
case FoldResult of
{_, _, undefined, _} ->
% nothing found in the view or keys, nothing has been returned
@@ -610,7 +614,7 @@ finish_view_fold(Req, TotalRows, Offset, FoldResult) ->
{total_rows, TotalRows},
{offset, Offset},
{rows, []}
- ]});
+ ] ++ Fields});
{_, _, Resp, _} ->
% end the view
send_chunk(Resp, "\r\n]}"),
@@ -618,11 +622,14 @@ finish_view_fold(Req, TotalRows, Offset, FoldResult) ->
end.
finish_reduce_fold(Req, Resp) ->
+ finish_reduce_fold(Req, Resp, []).
+
+finish_reduce_fold(Req, Resp, Fields) ->
case Resp of
undefined ->
send_json(Req, 200, {[
{rows, []}
- ]});
+ ] ++ Fields});
Resp ->
send_chunk(Resp, "\r\n]}"),
end_json_response(Resp)