summaryrefslogtreecommitdiff
path: root/src/couchdb/couch_httpd_db.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/couchdb/couch_httpd_db.erl')
-rw-r--r--src/couchdb/couch_httpd_db.erl196
1 files changed, 147 insertions, 49 deletions
diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
index da62ccb4..0cad21e1 100644
--- a/src/couchdb/couch_httpd_db.erl
+++ b/src/couchdb/couch_httpd_db.erl
@@ -29,7 +29,8 @@
options = [],
rev = nil,
open_revs = [],
- show = nil
+ show = nil,
+ atts_since = nil
}).
% Database request handlers
@@ -476,7 +477,7 @@ db_req(#httpd{method='POST',path_parts=[_,<<"_purge">>]}=Req, Db) ->
case couch_db:purge_docs(Db, IdsRevs2) of
{ok, PurgeSeq, PurgedIdsRevs} ->
- PurgedIdsRevs2 = [{Id, couch_doc:rev_to_strs(Revs)} || {Id, Revs} <- PurgedIdsRevs],
+ PurgedIdsRevs2 = [{Id, couch_doc:revs_to_strs(Revs)} || {Id, Revs} <- PurgedIdsRevs],
send_json(Req, 200, {[{<<"purge_seq">>, PurgeSeq}, {<<"purged">>, {PurgedIdsRevs2}}]});
Error ->
throw(Error)
@@ -507,7 +508,8 @@ db_req(#httpd{method='POST',path_parts=[_,<<"_missing_revs">>]}=Req, Db) ->
{JsonDocIdRevs} = couch_httpd:json_body_obj(Req),
JsonDocIdRevs2 = [{Id, [couch_doc:parse_rev(RevStr) || RevStr <- RevStrs]} || {Id, RevStrs} <- JsonDocIdRevs],
{ok, Results} = couch_db:get_missing_revs(Db, JsonDocIdRevs2),
- Results2 = [{Id, [couch_doc:rev_to_str(Rev) || Rev <- Revs]} || {Id, Revs} <- Results],
+ io:format("Results:~p~n", [Results]),
+ Results2 = [{Id, couch_doc:revs_to_strs(Revs)} || {Id, Revs, _} <- Results],
send_json(Req, {[
{missing_revs, {Results2}}
]});
@@ -515,6 +517,29 @@ db_req(#httpd{method='POST',path_parts=[_,<<"_missing_revs">>]}=Req, Db) ->
db_req(#httpd{path_parts=[_,<<"_missing_revs">>]}=Req, _Db) ->
send_method_not_allowed(Req, "POST");
+
+db_req(#httpd{method='POST',path_parts=[_,<<"_revs_diff">>]}=Req, Db) ->
+ {JsonDocIdRevs} = couch_httpd:json_body_obj(Req),
+ JsonDocIdRevs2 =
+ [{Id, couch_doc:parse_revs(RevStrs)} || {Id, RevStrs} <- JsonDocIdRevs],
+ {ok, Results} = couch_db:get_missing_revs(Db, JsonDocIdRevs2),
+ Results2 =
+ lists:map(fun({Id, MissingRevs, PossibleAncestors}) ->
+ {Id,
+ {[{missing, couch_doc:revs_to_strs(MissingRevs)}] ++
+ if PossibleAncestors == [] ->
+ [];
+ true ->
+ [{possible_ancestors,
+ couch_doc:revs_to_strs(PossibleAncestors)}]
+ end}}
+ end, Results),
+ send_json(Req, {[{missing, {Results2}}]});
+
+db_req(#httpd{path_parts=[_,<<"_revs_diff">>]}=Req, _Db) ->
+ send_method_not_allowed(Req, "POST");
+
+
db_req(#httpd{method='PUT',path_parts=[_,<<"_admins">>]}=Req,
Db) ->
Admins = couch_httpd:json_body(Req),
@@ -655,9 +680,12 @@ db_doc_req(#httpd{method='DELETE'}=Req, Db, DocId) ->
couch_doc_open(Db, DocId, nil, []),
case couch_httpd:qs_value(Req, "rev") of
undefined ->
- update_doc(Req, Db, DocId, {[{<<"_deleted">>,true}]});
+ update_doc(Req, Db, DocId,
+ couch_doc_from_req(Req, DocId, {[{<<"_deleted">>,true}]}));
Rev ->
- update_doc(Req, Db, DocId, {[{<<"_rev">>, ?l2b(Rev)},{<<"_deleted">>,true}]})
+ update_doc(Req, Db, DocId,
+ couch_doc_from_req(Req, DocId,
+ {[{<<"_rev">>, ?l2b(Rev)},{<<"_deleted">>,true}]}))
end;
db_doc_req(#httpd{method='GET'}=Req, Db, DocId) ->
@@ -665,23 +693,21 @@ db_doc_req(#httpd{method='GET'}=Req, Db, DocId) ->
show = Format,
rev = Rev,
open_revs = Revs,
- options = Options
+ options = Options,
+ atts_since = AttsSince
} = parse_doc_query(Req),
case Format of
nil ->
case Revs of
[] ->
Doc = couch_doc_open(Db, DocId, Rev, Options),
- DiskEtag = couch_httpd:doc_etag(Doc),
- case Doc#doc.meta of
- [] ->
- % output etag only when we have no meta
- couch_httpd:etag_respond(Req, DiskEtag, fun() ->
- send_json(Req, 200, [{"Etag", DiskEtag}], couch_doc:to_json_obj(Doc, Options))
- end);
- _ ->
- send_json(Req, 200, [], couch_doc:to_json_obj(Doc, Options))
- end;
+ Options2 =
+ if AttsSince /= nil ->
+ RevPos = find_ancestor_rev_pos(Doc#doc.revs, AttsSince),
+ [{atts_after_revpos, RevPos} | Options];
+ true -> Options
+ end,
+ send_doc(Req, Doc, Options2);
_ ->
{ok, Results} = couch_db:open_doc_revs(Db, DocId, Revs, Options),
{ok, Resp} = start_json_response(Req, 200),
@@ -713,7 +739,7 @@ db_doc_req(#httpd{method='GET'}=Req, Db, DocId) ->
db_doc_req(#httpd{method='POST'}=Req, Db, DocId) ->
couch_doc:validate_docid(DocId),
- case couch_httpd:header_value(Req, "content-type") of
+ case couch_httpd:header_value(Req, "Content-Type") of
"multipart/form-data" ++ _Rest ->
ok;
_Else ->
@@ -756,27 +782,38 @@ db_doc_req(#httpd{method='POST'}=Req, Db, DocId) ->
db_doc_req(#httpd{method='PUT'}=Req, Db, DocId) ->
couch_doc:validate_docid(DocId),
- Json = couch_httpd:json_body(Req),
- case couch_httpd:qs_value(Req, "batch") of
- "ok" ->
- % batch
- Doc = couch_doc_from_req(Req, DocId, Json),
+
+ Loc = absolute_uri(Req, "/" ++ ?b2l(Db#db.name) ++ "/" ++ ?b2l(DocId)),
+ RespHeaders = [{"Location", Loc}],
+ case couch_httpd:header_value(Req, "content-type") of
+ ("multipart/related" ++ _Rest) = ContentType->
+ io:format("Huh!~n"),
+ Doc0 = couch_doc:doc_from_multi_part_stream(ContentType,
+ fun() -> receive_request_data(Req) end),
+ Doc = couch_doc_from_req(Req, DocId, Doc0),
+ update_doc(Req, Db, DocId, Doc, RespHeaders);
+ _ ->
+ case couch_httpd:qs_value(Req, "batch") of
+ "ok" ->
+ % batch
+ Doc = couch_doc_from_req(Req, DocId, couch_httpd:json_body(Req)),
- spawn(fun() ->
- case catch(couch_db:update_doc(Db, Doc, [])) of
- {ok, _} -> ok;
- Error ->
- ?LOG_INFO("Batch doc error (~s): ~p",[DocId, Error])
- end
- end),
- send_json(Req, 202, [], {[
- {ok, true},
- {id, DocId}
- ]});
- _Normal ->
- % normal
- Location = absolute_uri(Req, "/" ++ ?b2l(Db#db.name) ++ "/" ++ ?b2l(DocId)),
- update_doc(Req, Db, DocId, Json, [{"Location", Location}])
+ spawn(fun() ->
+ case catch(couch_db:update_doc(Db, Doc, [])) of
+ {ok, _} -> ok;
+ Error ->
+ ?LOG_INFO("Batch doc error (~s): ~p",[DocId, Error])
+ end
+ end),
+ send_json(Req, 202, [], {[
+ {ok, true},
+ {id, DocId}
+ ]});
+ _Normal ->
+ % normal
+ Doc = couch_doc_from_req(Req, DocId, couch_httpd:json_body(Req)),
+ update_doc(Req, Db, DocId, Doc, RespHeaders)
+ end
end;
db_doc_req(#httpd{method='COPY'}=Req, Db, SourceDocId) ->
@@ -799,7 +836,66 @@ db_doc_req(#httpd{method='COPY'}=Req, Db, SourceDocId) ->
db_doc_req(Req, _Db, _DocId) ->
send_method_not_allowed(Req, "DELETE,GET,HEAD,POST,PUT,COPY").
+find_ancestor_rev_pos({_, []}, _AttsSinceRevs) ->
+ 0;
+find_ancestor_rev_pos(_DocRevs, []) ->
+ 0;
+find_ancestor_rev_pos({RevPos, [RevId|Rest]}, AttsSinceRevs) ->
+ case lists:member({RevPos, RevId}, AttsSinceRevs) of
+ true ->
+ RevPos;
+ false ->
+ find_ancestor_rev_pos({RevPos - 1, Rest}, AttsSinceRevs)
+ end.
+send_doc(Req, Doc, Options) ->
+ case Doc#doc.meta of
+ [] ->
+ DiskEtag = couch_httpd:doc_etag(Doc),
+ % output etag only when we have no meta
+ couch_httpd:etag_respond(Req, DiskEtag, fun() ->
+ send_doc_efficiently(Req, Doc, [{"Etag", DiskEtag}], Options)
+ end);
+ _ ->
+ send_doc_efficiently(Req, Doc, [], Options)
+ end.
+
+
+send_doc_efficiently(Req, #doc{atts=[]}=Doc, Headers, Options) ->
+ send_json(Req, 200, Headers, couch_doc:to_json_obj(Doc, Options));
+send_doc_efficiently(Req, #doc{atts=Atts}=Doc, Headers, Options) ->
+ case lists:member(attachments, Options) orelse
+ proplists:is_defined(atts_after_revpos, Options) of
+ true ->
+ AcceptedTypes = case couch_httpd:header_value(Req, "Accept") of
+ undefined -> [];
+ AcceptHeader -> string:tokens(AcceptHeader, ", ")
+ end,
+ case lists:member(AcceptedTypes, "multipart/related") of
+ false ->
+ send_json(Req, 200, [], couch_doc:to_json_obj(Doc, Options));
+ true ->
+ Boundary = couch_uuids:random(),
+ JsonBytes = ?JSON_ENCODE(couch_doc:to_json_obj(Doc, Options)),
+ AttsSinceRevPos = proplists:get_value(atts_after_revpos, Options, 0),
+ Len = couch_doc:len_doc_to_multi_part_stream(Boundary,JsonBytes,Atts,
+ AttsSinceRevPos),
+ CType = {<<"content-type">>,
+ <<"multipart/related; boundary=", Boundary/binary>>},
+ Resp = start_response_length(Req, 200, [CType | Headers], Len),
+ couch_doc:doc_to_multi_part_stream(Boundary,JsonBytes,Atts,
+ AttsSinceRevPos,
+ fun(Data) -> couch_httpd:send(Resp, Data) end)
+ end;
+ false ->
+ send_json(Req, 200, [], couch_doc:to_json_obj(Doc, Options))
+ end.
+
+
+
+receive_request_data(Req) ->
+ {couch_httpd:recv(Req, 0), fun() -> receive_request_data(Req) end}.
+
update_doc_result_to_json({{Id, Rev}, Error}) ->
{_Code, Err, Msg} = couch_httpd:error_info(Error),
{[{id, Id}, {rev, couch_doc:rev_to_str(Rev)},
@@ -814,12 +910,10 @@ update_doc_result_to_json(DocId, Error) ->
{[{id, DocId}, {error, ErrorStr}, {reason, Reason}]}.
-update_doc(Req, Db, DocId, Json) ->
- update_doc(Req, Db, DocId, Json, []).
-
-update_doc(Req, Db, DocId, Json, Headers) ->
- #doc{deleted=Deleted} = Doc = couch_doc_from_req(Req, DocId, Json),
+update_doc(Req, Db, DocId, Doc) ->
+ update_doc(Req, Db, DocId, Doc, []).
+update_doc(Req, Db, DocId, #doc{deleted=Deleted}=Doc, Headers) ->
case couch_httpd:header_value(Req, "X-Couch-Full-Commit") of
"true" ->
Options = [full_commit];
@@ -837,21 +931,22 @@ update_doc(Req, Db, DocId, Json, Headers) ->
{id, DocId},
{rev, NewRevStr}]}).
-couch_doc_from_req(Req, DocId, Json) ->
- Doc = couch_doc:from_json_obj(Json),
+couch_doc_from_req(Req, DocId, #doc{revs=Revs}=Doc) ->
validate_attachment_names(Doc),
ExplicitDocRev =
- case Doc#doc.revs of
+ case Revs of
{Start,[RevId|_]} -> {Start, RevId};
_ -> undefined
end,
case extract_header_rev(Req, ExplicitDocRev) of
missing_rev ->
- Revs = {0, []};
+ Revs2 = {0, []};
{Pos, Rev} ->
- Revs = {Pos, [Rev]}
+ Revs2 = {Pos, [Rev]}
end,
- Doc#doc{id=DocId, revs=Revs}.
+ Doc#doc{id=DocId, revs=Revs2};
+couch_doc_from_req(Req, DocId, Json) ->
+ couch_doc_from_req(Req, DocId, couch_doc:from_json_obj(Json)).
% Useful for debugging
@@ -1034,7 +1129,10 @@ parse_doc_query(Req) ->
Args#doc_query_args{open_revs=all};
{"open_revs", RevsJsonStr} ->
JsonArray = ?JSON_DECODE(RevsJsonStr),
- Args#doc_query_args{open_revs=[couch_doc:parse_rev(Rev) || Rev <- JsonArray]};
+ Args#doc_query_args{open_revs=couch_doc:parse_revs(JsonArray)};
+ {"atts_since", RevsJsonStr} ->
+ JsonArray = ?JSON_DECODE(RevsJsonStr),
+ Args#doc_query_args{atts_since = couch_doc:parse_revs(JsonArray)};
{"show", FormatStr} ->
Args#doc_query_args{show=parse_doc_format(FormatStr)};
_Else -> % unknown key value pair, ignore.