blob: 68c9d6cc453817621231b174196f6ca17cccc249 [file] [log] [blame]
% 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(mango_crud).
-export([
insert/3,
find/5,
update/4,
delete/3,
explain/3
]).
-export([
collect_cb/2
]).
-include_lib("couch/include/couch_db.hrl").
-include("mango.hrl").
insert(Db, #doc{}=Doc, Opts) ->
insert(Db, [Doc], Opts);
insert(Db, {_}=Doc, Opts) ->
insert(Db, [Doc], Opts);
insert(Db, Docs, Opts0) when is_list(Docs) ->
Opts1 = maybe_add_user_ctx(Db, Opts0),
Opts2 = maybe_int_to_str(w, Opts1),
case fabric:update_docs(Db, Docs, Opts2) of
{ok, Results0} ->
{ok, lists:zipwith(fun result_to_json/2, Docs, Results0)};
{accepted, Results0} ->
{ok, lists:zipwith(fun result_to_json/2, Docs, Results0)};
{aborted, Errors} ->
{error, lists:map(fun result_to_json/1, Errors)}
end.
find(Db, Selector, Callback, UserAcc, Opts0) ->
Opts1 = maybe_add_user_ctx(Db, Opts0),
Opts2 = maybe_int_to_str(r, Opts1),
{ok, Cursor} = mango_cursor:create(Db, Selector, Opts2),
mango_cursor:execute(Cursor, Callback, UserAcc).
update(Db, Selector, Update, Options) ->
Upsert = proplists:get_value(upsert, Options),
case collect_docs(Db, Selector, Options) of
{ok, []} when Upsert ->
InitDoc = mango_doc:update_as_insert(Update),
case mango_doc:has_operators(InitDoc) of
true ->
?MANGO_ERROR(invalid_upsert_with_operators);
false ->
% Probably need to catch and rethrow errors from
% this function.
Doc = couch_doc:from_json_obj(InitDoc),
NewDoc = case Doc#doc.id of
<<"">> ->
Doc#doc{id=couch_uuids:new(), revs={0, []}};
_ ->
Doc
end,
insert(Db, NewDoc, Options)
end;
{ok, Docs} ->
NewDocs = lists:map(fun(Doc) ->
mango_doc:apply_update(Doc, Update)
end, Docs),
insert(Db, NewDocs, Options);
Else ->
Else
end.
delete(Db, Selector, Options) ->
case collect_docs(Db, Selector, Options) of
{ok, Docs} ->
NewDocs = lists:map(fun({Props}) ->
{[
{<<"_id">>, proplists:get_value(<<"_id">>, Props)},
{<<"_rev">>, proplists:get_value(<<"_rev">>, Props)},
{<<"_deleted">>, true}
]}
end, Docs),
insert(Db, NewDocs, Options);
Else ->
Else
end.
explain(Db, Selector, Opts0) ->
Opts1 = maybe_add_user_ctx(Db, Opts0),
Opts2 = maybe_int_to_str(r, Opts1),
{ok, Cursor} = mango_cursor:create(Db, Selector, Opts2),
mango_cursor:explain(Cursor).
maybe_add_user_ctx(Db, Opts) ->
case lists:keyfind(user_ctx, 1, Opts) of
{user_ctx, _} ->
Opts;
false ->
[{user_ctx, Db#db.user_ctx} | Opts]
end.
maybe_int_to_str(_Key, []) ->
[];
maybe_int_to_str(Key, [{Key, Val} | Rest]) when is_integer(Val) ->
[{Key, integer_to_list(Val)} | maybe_int_to_str(Key, Rest)];
maybe_int_to_str(Key, [KV | Rest]) ->
[KV | maybe_int_to_str(Key, Rest)].
result_to_json(#doc{id=Id}, Result) ->
result_to_json(Id, Result);
result_to_json({Props}, Result) ->
Id = couch_util:get_value(<<"_id">>, Props),
result_to_json(Id, Result);
result_to_json(DocId, {ok, NewRev}) ->
{[
{id, DocId},
{rev, couch_doc:rev_to_str(NewRev)}
]};
result_to_json(DocId, {accepted, NewRev}) ->
{[
{id, DocId},
{rev, couch_doc:rev_to_str(NewRev)},
{accepted, true}
]};
result_to_json(DocId, Error) ->
% chttpd:error_info/1 because this is coming from fabric
% and not internal mango operations.
{_Code, ErrorStr, Reason} = chttpd:error_info(Error),
{[
{id, DocId},
{error, ErrorStr},
{reason, Reason}
]}.
% This is for errors because for some reason we
% need a different return value for errors? Blargh.
result_to_json({{Id, Rev}, Error}) ->
{_Code, ErrorStr, Reason} = chttpd:error_info(Error),
{[
{id, Id},
{rev, couch_doc:rev_to_str(Rev)},
{error, ErrorStr},
{reason, Reason}
]}.
collect_docs(Db, Selector, Options) ->
Cb = fun ?MODULE:collect_cb/2,
case find(Db, Selector, Cb, [], Options) of
{ok, Docs} ->
{ok, lists:reverse(Docs)};
Else ->
Else
end.
collect_cb({row, Doc}, Acc) ->
{ok, [Doc | Acc]}.