C0 code coverage information

Generated on 2009-08-09 21:22:57 with etap 0.3.4.

Name Total lines Lines of code Total coverage Code coverage
couch_doc ?? ?? ??
83% 
....1 % Licensed under the Apache License, Version 2.0 (the "License"); you may not
....2 % use this file except in compliance with the License. You may obtain a copy of
....3 % the License at
....4 %
....5 %   http://www.apache.org/licenses/LICENSE-2.0
....6 %
....7 % Unless required by applicable law or agreed to in writing, software
....8 % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
....9 % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
...10 % License for the specific language governing permissions and limitations under
...11 % the License.
...12 
...13 -module(couch_doc).
...14 
...15 -export([to_doc_info/1,to_doc_info_path/1,parse_rev/1,parse_revs/1,rev_to_str/1,rev_to_strs/1]).
...16 -export([att_foldl/3,get_validate_doc_fun/1]).
...17 -export([from_json_obj/1,to_json_obj/2,has_stubs/1, merge_stubs/2]).
...18 -export([validate_docid/1]).
...19 
...20 -include("couch_db.hrl").
...21 
...22 % helpers used by to_json_obj
...23 to_json_rev(0, []) ->
...24     [];
...25 to_json_rev(Start, [FirstRevId|_]) ->
...26     [{<<"_rev">>, ?l2b([integer_to_list(Start),"-",revid_to_str(FirstRevId)])}].
...27 
...28 to_json_body(true, _Body) ->
...29     [{<<"_deleted">>, true}];
...30 to_json_body(false, {Body}) ->
...31     Body.
...32 
...33 to_json_revisions(Options, Start, RevIds) ->
...34     case lists:member(revs, Options) of
...35     false -> [];
...36     true ->
...37         [{<<"_revisions">>, {[{<<"start">>, Start},
...38                 {<<"ids">>, [revid_to_str(R) ||R <- RevIds]}]}}]
...39     end.
...40 
...41 revid_to_str(RevId) when size(RevId) == 16 ->
...42     ?l2b(couch_util:to_hex(RevId));
...43 revid_to_str(RevId) ->
...44     RevId.
...45 
...46 rev_to_str({Pos, RevId}) ->
...47     ?l2b([integer_to_list(Pos),"-",revid_to_str(RevId)]).
...48                     
...49                     
...50 rev_to_strs([]) ->
...51     [];
...52 rev_to_strs([{Pos, RevId}| Rest]) ->
...53     [rev_to_str({Pos, RevId}) | rev_to_strs(Rest)].
...54 
...55 to_json_meta(Meta) ->
...56     lists:map(
...57         fun({revs_info, Start, RevsInfo}) ->
...58             {JsonRevsInfo, _Pos}  = lists:mapfoldl(
...59                 fun({RevId, Status}, PosAcc) ->
...60                     JsonObj = {[{<<"rev">>, rev_to_str({PosAcc, RevId})},
...61                         {<<"status">>, ?l2b(atom_to_list(Status))}]},
...62                     {JsonObj, PosAcc - 1}
...63                 end, Start, RevsInfo),
...64             {<<"_revs_info">>, JsonRevsInfo};
...65         ({local_seq, Seq}) ->
...66             {<<"_local_seq">>, Seq};
...67         ({conflicts, Conflicts}) ->
...68             {<<"_conflicts">>, rev_to_strs(Conflicts)};
...69         ({deleted_conflicts, DConflicts}) ->
...70             {<<"_deleted_conflicts">>, rev_to_strs(DConflicts)}
...71         end, Meta).
...72 
...73 to_json_attachment_stubs(Attachments) ->
...74     BinProps = lists:map(
...75         fun(#att{name=Name,type=Type,len=Length,revpos=Pos}) ->
...76             {Name, {[
...77                 {<<"stub">>, true},
...78                 {<<"content_type">>, Type},
...79                 {<<"length">>, Length},
...80                 {<<"revpos">>, Pos}
...81             ]}}
...82         end,
...83         Attachments),
...84     case BinProps of
...85         [] -> [];
...86         _ -> [{<<"_attachments">>, {BinProps}}]
...87     end.
...88 
...89 to_json_attachments(Atts) ->
...90     AttProps = lists:map(
...91         fun(#att{data=Fun,len=Len}=Att) when is_function(Fun) ->
...92             Data = read_streamed_attachment(Fun, Len, _Acc = []),
...93             {Att#att.name, {[
...94                 {<<"content_type">>, Att#att.type},
...95                 {<<"revpos">>, Att#att.revpos},
...96                 {<<"data">>, couch_util:encodeBase64(Data)}
...97             ]}};
...98         (Att) ->
...99             {Att#att.name, {[
..100                 {<<"content_type">>, Att#att.type},
..101                 {<<"revpos">>, Att#att.revpos},
..102                 {<<"data">>, couch_util:encodeBase64(att_to_iolist(Att))}
..103             ]}}
..104         end,
..105         Atts),
..106     case AttProps of
..107     [] -> [];
..108     _ -> [{<<"_attachments">>, {AttProps}}]
..109     end.
..110 
..111 to_json_attachments(Attachments, Options) ->
..112     case lists:member(attachments, Options) of
..113     true -> % return the full rev list and the binaries as strings.
..114         to_json_attachments(Attachments);
..115     false ->
..116         to_json_attachment_stubs(Attachments)
..117     end.
..118 
..119 to_json_obj(#doc{id=Id,deleted=Del,body=Body,revs={Start, RevIds},
..120             meta=Meta}=Doc,Options)->
..121     {[{<<"_id">>, Id}]
..122         ++ to_json_rev(Start, RevIds)
..123         ++ to_json_body(Del, Body)
..124         ++ to_json_revisions(Options, Start, RevIds)
..125         ++ to_json_meta(Meta)
..126         ++ to_json_attachments(Doc#doc.atts, Options)
..127     }.
..128 
..129 from_json_obj({Props}) ->
..130     transfer_fields(Props, #doc{body=[]});
..131 
..132 from_json_obj(_Other) ->
..133     throw({bad_request, "Document must be a JSON object"}).
..134 
..135 parse_revid(RevId) when size(RevId) == 32 ->
..136     RevInt = erlang:list_to_integer(?b2l(RevId), 16),
..137      <>;
..138 parse_revid(RevId) when length(RevId) == 32 ->
..139     RevInt = erlang:list_to_integer(RevId, 16),
..140      <>;
..141 parse_revid(RevId) when is_binary(RevId) ->
..142     RevId;
..143 parse_revid(RevId) when is_list(RevId) ->
..144     ?l2b(RevId).
..145 
..146 
..147 parse_rev(Rev) when is_binary(Rev) ->
..148     parse_rev(?b2l(Rev));
..149 parse_rev(Rev) when is_list(Rev) ->
..150     SplitRev = lists:splitwith(fun($-) -> false; (_) -> true end, Rev),
..151     case SplitRev of
..152         {Pos, [$- | RevId]} -> {list_to_integer(Pos), parse_revid(RevId)};
..153         _Else -> throw({bad_request, <<"Invalid rev format">>})
..154     end;
..155 parse_rev(_BadRev) ->
..156     throw({bad_request, <<"Invalid rev format">>}).
..157 
..158 parse_revs([]) ->
..159     [];
..160 parse_revs([Rev | Rest]) ->
..161     [parse_rev(Rev) | parse_revs(Rest)].
..162 
..163 
..164 validate_docid(Id) when is_binary(Id) ->
..165     case Id of
..166     <<"_design/", _/binary>> -> ok;
..167     <<"_local/", _/binary>> -> ok;
..168     <<"_", _/binary>> ->
..169         throw({bad_request, <<"Only reserved document ids may start with underscore.">>});
..170     _Else -> ok
..171     end;
..172 validate_docid(Id) ->
..173     ?LOG_DEBUG("Document id is not a string: ~p", [Id]),
..174     throw({bad_request, <<"Document id must be a string">>}).
..175 
..176 transfer_fields([], #doc{body=Fields}=Doc) ->
..177     % convert fields back to json object
..178     Doc#doc{body={lists:reverse(Fields)}};
..179 
..180 transfer_fields([{<<"_id">>, Id} | Rest], Doc) ->
..181     validate_docid(Id),
..182     transfer_fields(Rest, Doc#doc{id=Id});
..183 
..184 transfer_fields([{<<"_rev">>, Rev} | Rest], #doc{revs={0, []}}=Doc) ->
..185     {Pos, RevId} = parse_rev(Rev),
..186     transfer_fields(Rest,
..187             Doc#doc{revs={Pos, [RevId]}});
..188 
..189 transfer_fields([{<<"_rev">>, _Rev} | Rest], Doc) ->
..190     % we already got the rev from the _revisions
..191     transfer_fields(Rest,Doc);
..192 
..193 transfer_fields([{<<"_attachments">>, {JsonBins}} | Rest], Doc) ->
..194     Atts = lists:map(fun({Name, {BinProps}}) ->
..195         case proplists:get_value(<<"stub">>, BinProps) of
..196         true ->
..197             Type = proplists:get_value(<<"content_type">>, BinProps),
..198             Length = proplists:get_value(<<"length">>, BinProps),
..199             RevPos = proplists:get_value(<<"revpos">>, BinProps, 0),
..200             #att{name=Name, data=stub, type=Type, len=Length, revpos=RevPos};
..201         _ ->
..202             Value = proplists:get_value(<<"data">>, BinProps),
..203             Type = proplists:get_value(<<"content_type">>, BinProps,
..204                     ?DEFAULT_ATTACHMENT_CONTENT_TYPE),
..205             RevPos = proplists:get_value(<<"revpos">>, BinProps, 0),
..206             Bin = couch_util:decodeBase64(Value),
..207             #att{name=Name, data=Bin, type=Type, len=size(Bin), revpos=RevPos}
..208         end
..209     end, JsonBins),
..210     transfer_fields(Rest, Doc#doc{atts=Atts});
..211 
..212 transfer_fields([{<<"_revisions">>, {Props}} | Rest], Doc) ->
..213     RevIds = proplists:get_value(<<"ids">>, Props),
..214     Start = proplists:get_value(<<"start">>, Props),
..215     if not is_integer(Start) ->
..216         throw({doc_validation, "_revisions.start isn't an integer."});
..217     not is_list(RevIds) ->
..218         throw({doc_validation, "_revisions.ids isn't a array."});
..219     true ->
..220         ok
..221     end,
..222     [throw({doc_validation, "RevId isn't a string"}) ||
..223             RevId <- RevIds, not is_binary(RevId)],
..224     RevIds2 = [parse_revid(RevId) || RevId <- RevIds],
..225     transfer_fields(Rest, Doc#doc{revs={Start, RevIds2}});
..226 
..227 transfer_fields([{<<"_deleted">>, B} | Rest], Doc) when (B==true) or (B==false) ->
..228     transfer_fields(Rest, Doc#doc{deleted=B});
..229 
..230 % ignored fields
..231 transfer_fields([{<<"_revs_info">>, _} | Rest], Doc) ->
..232     transfer_fields(Rest, Doc);
..233 transfer_fields([{<<"_local_seq">>, _} | Rest], Doc) ->
..234     transfer_fields(Rest, Doc);
..235 transfer_fields([{<<"_conflicts">>, _} | Rest], Doc) ->
..236     transfer_fields(Rest, Doc);
..237 transfer_fields([{<<"_deleted_conflicts">>, _} | Rest], Doc) ->
..238     transfer_fields(Rest, Doc);
..239 
..240 % unknown special field
..241 transfer_fields([{<<"_",Name/binary>>, _} | _], _) ->
..242     throw({doc_validation,
..243             ?l2b(io_lib:format("Bad special document member: _~s", [Name]))});
..244 
..245 transfer_fields([Field | Rest], #doc{body=Fields}=Doc) ->
..246     transfer_fields(Rest, Doc#doc{body=[Field|Fields]}).
..247 
..248 to_doc_info(FullDocInfo) ->
..249     {DocInfo, _Path} = to_doc_info_path(FullDocInfo),
..250     DocInfo.
..251 
..252 max_seq([], Max) ->
..253     Max;
..254 max_seq([#rev_info{seq=Seq}|Rest], Max) ->
..255     max_seq(Rest, if Max > Seq -> Max; true -> Seq end).
..256 
..257 to_doc_info_path(#full_doc_info{id=Id,rev_tree=Tree}) ->
..258     RevInfosAndPath =
..259         [{#rev_info{deleted=Del,body_sp=Bp,seq=Seq,rev={Pos,RevId}}, Path} ||
..260             {{Del, Bp, Seq},{Pos, [RevId|_]}=Path} <-
..261             couch_key_tree:get_all_leafs(Tree)],
..262     SortedRevInfosAndPath = lists:sort(
..263             fun({#rev_info{deleted=DeletedA,rev=RevA}, _PathA},
..264                 {#rev_info{deleted=DeletedB,rev=RevB}, _PathB}) ->
..265             % sort descending by {not deleted, rev}
..266             {not DeletedA, RevA} > {not DeletedB, RevB}
..267         end, RevInfosAndPath),
..268     [{_RevInfo, WinPath}|_] = SortedRevInfosAndPath,
..269     RevInfos = [RevInfo || {RevInfo, _Path} <- SortedRevInfosAndPath],
..270     {#doc_info{id=Id, high_seq=max_seq(RevInfos, 0), revs=RevInfos}, WinPath}.
..271 
..272 
..273 
..274 
..275 att_foldl(#att{data=Bin}, Fun, Acc) when is_binary(Bin) ->
..276     Fun(Bin, Acc);
..277 att_foldl(#att{data={Fd,Sp},len=Len}, Fun, Acc) when is_tuple(Sp) orelse Sp == null ->
..278     % 09 UPGRADE CODE
..279     couch_stream:old_foldl(Fd, Sp, Len, Fun, Acc);
..280 att_foldl(#att{data={Fd,Sp},md5=Md5}, Fun, Acc) ->
..281     couch_stream:foldl(Fd, Sp, Md5, Fun, Acc).
..282 
..283 att_to_iolist(#att{data=Bin}) when is_binary(Bin) ->
..284     Bin;
..285 att_to_iolist(#att{data=Iolist}) when is_list(Iolist) ->
..286     Iolist;
..287 att_to_iolist(#att{data={Fd,Sp},md5=Md5}) ->
..288     lists:reverse(couch_stream:foldl(Fd, Sp, Md5, fun(Bin,Acc) -> [Bin|Acc] end, [])).
..289 
..290 get_validate_doc_fun(#doc{body={Props}}) ->
..291     Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
..292     case proplists:get_value(<<"validate_doc_update">>, Props) of
..293     undefined ->
..294         nil;
..295     FunSrc ->
..296         fun(EditDoc, DiskDoc, Ctx) ->
..297             couch_query_servers:validate_doc_update(
..298                     Lang, FunSrc, EditDoc, DiskDoc, Ctx)
..299         end
..300     end.
..301 
..302 
..303 has_stubs(#doc{atts=Atts}) ->
..304     has_stubs(Atts);
..305 has_stubs([]) ->
..306     false;
..307 has_stubs([#att{data=stub}|_]) ->
..308     true;
..309 has_stubs([_Att|Rest]) ->
..310     has_stubs(Rest).
..311 
..312 merge_stubs(#doc{atts=MemBins}=StubsDoc, #doc{atts=DiskBins}) ->
..313     BinDict = dict:from_list([{Name, Att} || #att{name=Name}=Att <- DiskBins]),
..314     MergedBins = lists:map(
..315         fun(#att{name=Name, data=stub}) ->
..316             dict:fetch(Name, BinDict);
..317         (Att) ->
..318             Att
..319         end, MemBins),
..320     StubsDoc#doc{atts= MergedBins}.
..321 
..322 read_streamed_attachment(_RcvFun, 0, Acc) ->
..323     list_to_binary(lists:reverse(Acc));
..324 read_streamed_attachment(RcvFun, LenLeft, Acc) ->
..325     Bin = RcvFun(),
..326     read_streamed_attachment(RcvFun, LenLeft - size(Bin), [Bin|Acc]).

Generated using etap 0.3.4.