PageRenderTime 27ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/apps/couch/src/couch_view.erl

http://github.com/cloudant/bigcouch
Erlang | 480 lines | 385 code | 73 blank | 22 comment | 13 complexity | ab44bb3d6714bb1e95f1b1580cba896b MD5 | raw file
Possible License(s): Apache-2.0
  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. -module(couch_view).
  13. -behaviour(gen_server).
  14. -export([start_link/0,fold/4,less_json/2,less_json_ids/2,expand_dups/2,
  15. detuple_kvs/2,init/1,terminate/2,handle_call/3,handle_cast/2,handle_info/2,
  16. code_change/3,get_reduce_view/4,get_temp_reduce_view/5,get_temp_map_view/4,
  17. get_map_view/4,get_row_count/1,reduce_to_count/1,fold_reduce/4,
  18. extract_map_view/1,get_group_server/2,get_group_info/2,
  19. cleanup_index_files/1,config_change/2, data_size/2]).
  20. -include("couch_db.hrl").
  21. -record(server,{
  22. root_dir = []}).
  23. start_link() ->
  24. gen_server:start_link({local, couch_view}, couch_view, [], []).
  25. get_temp_updater(DbName, Language, DesignOptions, MapSrc, RedSrc) ->
  26. {ok, Group} =
  27. couch_view_group:open_temp_group(DbName, Language, DesignOptions, MapSrc, RedSrc),
  28. case gen_server:call(couch_view, {get_group_server, DbName, Group}, infinity) of
  29. {ok, Pid} ->
  30. Pid;
  31. Error ->
  32. throw(Error)
  33. end.
  34. get_group_server(DbName, GroupId) ->
  35. case couch_view_group:open_db_group(DbName, GroupId) of
  36. {ok, Group} ->
  37. case gen_server:call(couch_view, {get_group_server, DbName, Group}, infinity) of
  38. {ok, Pid} ->
  39. Pid;
  40. Error ->
  41. throw(Error)
  42. end;
  43. Error ->
  44. throw(Error)
  45. end.
  46. get_group(Db, GroupId, Stale) ->
  47. MinUpdateSeq = case Stale of
  48. ok -> 0;
  49. update_after -> 0;
  50. _Else -> couch_db:get_update_seq(Db)
  51. end,
  52. GroupPid = get_group_server(couch_db:name(Db), GroupId),
  53. Result = couch_view_group:request_group(GroupPid, MinUpdateSeq),
  54. case Stale of
  55. update_after ->
  56. % best effort, process might die
  57. spawn(fun() ->
  58. LastSeq = couch_db:get_update_seq(Db),
  59. couch_view_group:request_group(GroupPid, LastSeq)
  60. end);
  61. _ ->
  62. ok
  63. end,
  64. Result.
  65. get_temp_group(Db, Language, DesignOptions, MapSrc, RedSrc) ->
  66. couch_view_group:request_group(
  67. get_temp_updater(couch_db:name(Db), Language, DesignOptions, MapSrc, RedSrc),
  68. couch_db:get_update_seq(Db)).
  69. get_group_info(Db, GroupId) ->
  70. couch_view_group:request_group_info(
  71. get_group_server(couch_db:name(Db), GroupId)).
  72. cleanup_index_files(Db) ->
  73. % load all ddocs
  74. {ok, DesignDocs} = couch_db:get_design_docs(Db),
  75. % make unique list of group sigs
  76. Sigs = lists:map(fun(#doc{id = GroupId}) ->
  77. {ok, Info} = get_group_info(Db, GroupId),
  78. ?b2l(couch_util:get_value(signature, Info))
  79. end, [DD||DD <- DesignDocs, DD#doc.deleted == false]),
  80. FileList = list_index_files(Db),
  81. DeleteFiles =
  82. if length(Sigs) =:= 0 ->
  83. FileList;
  84. true ->
  85. % regex that matches all ddocs
  86. RegExp = "("++ string:join(Sigs, "|") ++")",
  87. % filter out the ones in use
  88. [FilePath || FilePath <- FileList,
  89. re:run(FilePath, RegExp, [{capture, none}]) =:= nomatch]
  90. end,
  91. % delete unused files
  92. ?LOG_DEBUG("deleting unused view index files: ~p",[DeleteFiles]),
  93. RootDir = couch_config:get("couchdb", "view_index_dir"),
  94. [couch_file:delete(RootDir,File,false)||File <- DeleteFiles],
  95. ok.
  96. list_index_files(Db) ->
  97. % call server to fetch the index files
  98. RootDir = couch_config:get("couchdb", "view_index_dir"),
  99. filelib:wildcard(RootDir ++ "/." ++ ?b2l(couch_db:name(Db)) ++ "_design"++"/*").
  100. get_row_count(#view{btree=Bt}) ->
  101. {ok, {Count, _, _}} = couch_btree:full_reduce(Bt),
  102. {ok, Count}.
  103. get_temp_reduce_view(Db, Language, DesignOptions, MapSrc, RedSrc) ->
  104. {ok, #group{views=[View]}=Group} =
  105. get_temp_group(Db, Language, DesignOptions, MapSrc, RedSrc),
  106. {ok, {temp_reduce, View}, Group}.
  107. get_reduce_view(Db, GroupId, Name, Update) ->
  108. case get_group(Db, GroupId, Update) of
  109. {ok, #group{views=Views,def_lang=Lang}=Group} ->
  110. case get_reduce_view0(Name, Lang, Views) of
  111. {ok, View} ->
  112. {ok, View, Group};
  113. Else ->
  114. Else
  115. end;
  116. Error ->
  117. Error
  118. end.
  119. get_reduce_view0(_Name, _Lang, []) ->
  120. {not_found, missing_named_view};
  121. get_reduce_view0(Name, Lang, [#view{reduce_funs=RedFuns}=View|Rest]) ->
  122. case get_key_pos(Name, RedFuns, 0) of
  123. 0 -> get_reduce_view0(Name, Lang, Rest);
  124. N -> {ok, {reduce, N, Lang, View}}
  125. end.
  126. extract_map_view({reduce, _N, _Lang, View}) ->
  127. View.
  128. detuple_kvs([], Acc) ->
  129. lists:reverse(Acc);
  130. detuple_kvs([KV | Rest], Acc) ->
  131. {{Key,Id},Value} = KV,
  132. NKV = [[Key, Id], Value],
  133. detuple_kvs(Rest, [NKV | Acc]).
  134. expand_dups([], Acc) ->
  135. lists:reverse(Acc);
  136. expand_dups([{Key, {dups, Vals}} | Rest], Acc) ->
  137. Expanded = [{Key, Val} || Val <- Vals],
  138. expand_dups(Rest, Expanded ++ Acc);
  139. expand_dups([KV | Rest], Acc) ->
  140. expand_dups(Rest, [KV | Acc]).
  141. data_size(KVList, Reduction) ->
  142. lists:foldl(fun([[Key, _], Value], Acc) ->
  143. size(term_to_binary(Key)) +
  144. size(term_to_binary(Value)) +
  145. Acc
  146. end,size(term_to_binary(Reduction)),KVList).
  147. fold_reduce({temp_reduce, #view{btree=Bt}}, Fun, Acc, Options) ->
  148. WrapperFun = fun({GroupedKey, _}, PartialReds, Acc0) ->
  149. {_, [Red]} = couch_btree:final_reduce(Bt, PartialReds),
  150. Fun(GroupedKey, Red, Acc0)
  151. end,
  152. couch_btree:fold_reduce(Bt, WrapperFun, Acc, Options);
  153. fold_reduce({reduce, NthRed, Lang, #view{btree=Bt, reduce_funs=RedFuns}}, Fun, Acc, Options) ->
  154. PreResultPadding = lists:duplicate(NthRed - 1, []),
  155. PostResultPadding = lists:duplicate(length(RedFuns) - NthRed, []),
  156. {_Name, FunSrc} = lists:nth(NthRed,RedFuns),
  157. ReduceFun =
  158. fun(reduce, KVs) ->
  159. {ok, Reduced} = couch_query_servers:reduce(Lang, [FunSrc], detuple_kvs(expand_dups(KVs, []),[])),
  160. {0, PreResultPadding ++ Reduced ++ PostResultPadding};
  161. (rereduce, Reds) ->
  162. UserReds = [[lists:nth(NthRed, element(2, R))] || R <- Reds],
  163. {ok, Reduced} = couch_query_servers:rereduce(Lang, [FunSrc], UserReds),
  164. {0, PreResultPadding ++ Reduced ++ PostResultPadding}
  165. end,
  166. WrapperFun = fun({GroupedKey, _}, PartialReds, Acc0) ->
  167. {_, Reds} = couch_btree:final_reduce(ReduceFun, PartialReds),
  168. Fun(GroupedKey, lists:nth(NthRed, Reds), Acc0)
  169. end,
  170. couch_btree:fold_reduce(Bt, WrapperFun, Acc, Options).
  171. get_key_pos(_Key, [], _N) ->
  172. 0;
  173. get_key_pos(Key, [{Key1,_Value}|_], N) when Key == Key1 ->
  174. N + 1;
  175. get_key_pos(Key, [_|Rest], N) ->
  176. get_key_pos(Key, Rest, N+1).
  177. get_temp_map_view(Db, Language, DesignOptions, Src) ->
  178. {ok, #group{views=[View]}=Group} = get_temp_group(Db, Language, DesignOptions, Src, []),
  179. {ok, View, Group}.
  180. get_map_view(Db, GroupId, Name, Stale) ->
  181. case get_group(Db, GroupId, Stale) of
  182. {ok, #group{views=Views}=Group} ->
  183. case get_map_view0(Name, Views) of
  184. {ok, View} ->
  185. {ok, View, Group};
  186. Else ->
  187. Else
  188. end;
  189. Error ->
  190. Error
  191. end.
  192. get_map_view0(_Name, []) ->
  193. {not_found, missing_named_view};
  194. get_map_view0(Name, [#view{map_names=MapNames}=View|Rest]) ->
  195. case lists:member(Name, MapNames) of
  196. true -> {ok, View};
  197. false -> get_map_view0(Name, Rest)
  198. end.
  199. reduce_to_count(Reductions) ->
  200. {Count, _} =
  201. couch_btree:final_reduce(
  202. fun(reduce, KVs) ->
  203. Count = lists:sum(
  204. [case V of {dups, Vals} -> length(Vals); _ -> 1 end
  205. || {_,V} <- KVs]),
  206. {Count, []};
  207. (rereduce, Reds) ->
  208. {lists:sum([Count0 || {Count0, _} <- Reds]), []}
  209. end, Reductions),
  210. Count.
  211. fold_fun(_Fun, [], _, Acc) ->
  212. {ok, Acc};
  213. fold_fun(Fun, [KV|Rest], {KVReds, Reds}, Acc) ->
  214. case Fun(KV, {KVReds, Reds}, Acc) of
  215. {ok, Acc2} ->
  216. fold_fun(Fun, Rest, {[KV|KVReds], Reds}, Acc2);
  217. {stop, Acc2} ->
  218. {stop, Acc2}
  219. end.
  220. fold(#view{btree=Btree}, Fun, Acc, Options) ->
  221. WrapperFun =
  222. fun(visit, KV, Reds, Acc2) ->
  223. fold_fun(Fun, expand_dups([KV],[]), Reds, Acc2);
  224. (traverse, LK, Red, Acc2)
  225. when is_function(Fun, 4) ->
  226. Fun(traverse, LK, Red, Acc2);
  227. (traverse, _LK, Red, {_, Skip, _, _} = Acc2)
  228. when Skip >= element(1, Red) ->
  229. {skip, setelement(2, Acc2, Skip - element(1, Red))};
  230. (traverse, _, _, Acc2) ->
  231. {ok, Acc2}
  232. end,
  233. {ok, _LastReduce, _AccResult} = couch_btree:fold(Btree, WrapperFun, Acc, Options).
  234. init([]) ->
  235. % read configuration settings and register for configuration changes
  236. RootDir = couch_config:get("couchdb", "view_index_dir"),
  237. ok = couch_config:register(fun ?MODULE:config_change/2),
  238. couch_db_update_notifier:start_link(
  239. fun({deleted, DbName}) ->
  240. gen_server:cast(couch_view, {reset_indexes, DbName});
  241. ({created, DbName}) ->
  242. gen_server:cast(couch_view, {reset_indexes, DbName});
  243. (_Else) ->
  244. ok
  245. end),
  246. ets:new(couch_groups_by_db, [bag, private, named_table]),
  247. ets:new(group_servers_by_sig, [set, protected, named_table]),
  248. ets:new(couch_groups_by_updater, [set, private, named_table]),
  249. process_flag(trap_exit, true),
  250. ok = couch_file:init_delete_dir(RootDir),
  251. {ok, #server{root_dir=RootDir}}.
  252. terminate(_Reason, _Srv) ->
  253. [couch_util:shutdown_sync(Pid) || {Pid, _} <-
  254. ets:tab2list(couch_groups_by_updater)],
  255. ok.
  256. handle_call({get_group_server, DbName, #group{sig=Sig}=Group}, From,
  257. #server{root_dir=Root}=Server) ->
  258. case ets:lookup(group_servers_by_sig, {DbName, Sig}) of
  259. [] ->
  260. spawn_monitor(fun() -> new_group(Root, DbName, Group) end),
  261. ets:insert(group_servers_by_sig, {{DbName, Sig}, [From]}),
  262. {noreply, Server};
  263. [{_, WaitList}] when is_list(WaitList) ->
  264. ets:insert(group_servers_by_sig, {{DbName, Sig}, [From | WaitList]}),
  265. {noreply, Server};
  266. [{_, ExistingPid}] ->
  267. {reply, {ok, ExistingPid}, Server}
  268. end;
  269. handle_call({reset_indexes, DbName}, _From, #server{root_dir=Root}=Server) ->
  270. do_reset_indexes(DbName, Root),
  271. {reply, ok, Server}.
  272. handle_cast({reset_indexes, DbName}, #server{root_dir=Root}=Server) ->
  273. do_reset_indexes(DbName, Root),
  274. {noreply, Server}.
  275. new_group(Root, DbName, #group{name=GroupId, sig=Sig} = Group) ->
  276. ?LOG_DEBUG("Spawning new group server for view group ~s in database ~s.",
  277. [GroupId, DbName]),
  278. case (catch couch_view_group:start_link({Root, DbName, Group})) of
  279. {ok, NewPid} ->
  280. unlink(NewPid),
  281. exit({DbName, Sig, {ok, NewPid}});
  282. {error, invalid_view_seq} ->
  283. ok = gen_server:call(couch_view, {reset_indexes, DbName}),
  284. new_group(Root, DbName, Group);
  285. Error ->
  286. exit({DbName, Sig, Error})
  287. end.
  288. do_reset_indexes(DbName, Root) ->
  289. % shutdown all the updaters and clear the files, the db got changed
  290. Names = ets:lookup(couch_groups_by_db, DbName),
  291. lists:foreach(
  292. fun({_DbName, Sig}) ->
  293. ?LOG_DEBUG("Killing update process for view group ~s. in database ~s.", [Sig, DbName]),
  294. [{_, Pid}] = ets:lookup(group_servers_by_sig, {DbName, Sig}),
  295. couch_util:shutdown_sync(Pid),
  296. delete_from_ets(Pid, DbName, Sig)
  297. end, Names),
  298. delete_index_dir(Root, DbName),
  299. RootDelDir = couch_config:get("couchdb", "view_index_dir"),
  300. couch_file:delete(RootDelDir, Root ++ "/." ++ ?b2l(DbName) ++ "_temp").
  301. handle_info({'EXIT', FromPid, Reason}, Server) ->
  302. case ets:lookup(couch_groups_by_updater, FromPid) of
  303. [] ->
  304. if Reason =/= normal, Reason =/= no_db_file ->
  305. % non-updater linked process died, we propagate the error
  306. ?LOG_ERROR("Exit on non-updater process: ~p", [Reason]),
  307. exit(Reason);
  308. true -> ok
  309. end;
  310. [{_, {DbName, GroupId}}] ->
  311. delete_from_ets(FromPid, DbName, GroupId)
  312. end,
  313. {noreply, Server};
  314. handle_info({'DOWN', _, _, _, {DbName, Sig, Reply}}, Server) ->
  315. [{_, WaitList}] = ets:lookup(group_servers_by_sig, {DbName, Sig}),
  316. [gen_server:reply(From, Reply) || From <- WaitList],
  317. case Reply of {ok, NewPid} ->
  318. link(NewPid),
  319. add_to_ets(NewPid, DbName, Sig);
  320. _ -> ok end,
  321. {noreply, Server}.
  322. config_change("couchdb", "view_index_dir") ->
  323. exit(whereis(couch_view), config_change).
  324. add_to_ets(Pid, DbName, Sig) ->
  325. true = ets:insert(couch_groups_by_updater, {Pid, {DbName, Sig}}),
  326. true = ets:insert(group_servers_by_sig, {{DbName, Sig}, Pid}),
  327. true = ets:insert(couch_groups_by_db, {DbName, Sig}).
  328. delete_from_ets(Pid, DbName, Sig) ->
  329. true = ets:delete(couch_groups_by_updater, Pid),
  330. true = ets:delete(group_servers_by_sig, {DbName, Sig}),
  331. true = ets:delete_object(couch_groups_by_db, {DbName, Sig}).
  332. code_change(_OldVsn, State, _Extra) ->
  333. {ok, State}.
  334. delete_index_dir(RootDir, DbName) ->
  335. nuke_dir(RootDir, RootDir ++ "/." ++ ?b2l(DbName) ++ "_design").
  336. nuke_dir(RootDelDir, Dir) ->
  337. case file:list_dir(Dir) of
  338. {error, enoent} -> ok; % doesn't exist
  339. {ok, Files} ->
  340. lists:foreach(
  341. fun(File)->
  342. Full = Dir ++ "/" ++ File,
  343. case couch_file:delete(RootDelDir, Full, false) of
  344. ok -> ok;
  345. {error, eperm} ->
  346. ok = nuke_dir(RootDelDir, Full)
  347. end
  348. end,
  349. Files),
  350. ok = file:del_dir(Dir)
  351. end.
  352. % keys come back in the language of btree - tuples.
  353. less_json_ids({JsonA, IdA}, {JsonB, IdB}) ->
  354. case less_json0(JsonA, JsonB) of
  355. 0 ->
  356. IdA < IdB;
  357. Result ->
  358. Result < 0
  359. end.
  360. less_json(A,B) ->
  361. less_json0(A,B) < 0.
  362. less_json0(A,A) -> 0;
  363. less_json0(A,B) when is_atom(A), is_atom(B) -> atom_sort(A) - atom_sort(B);
  364. less_json0(A,_) when is_atom(A) -> -1;
  365. less_json0(_,B) when is_atom(B) -> 1;
  366. less_json0(A,B) when is_number(A), is_number(B) -> A - B;
  367. less_json0(A,_) when is_number(A) -> -1;
  368. less_json0(_,B) when is_number(B) -> 1;
  369. less_json0(A,B) when is_binary(A), is_binary(B) -> couch_util:collate(A,B);
  370. less_json0(A,_) when is_binary(A) -> -1;
  371. less_json0(_,B) when is_binary(B) -> 1;
  372. less_json0(A,B) when is_list(A), is_list(B) -> less_list(A,B);
  373. less_json0(A,_) when is_list(A) -> -1;
  374. less_json0(_,B) when is_list(B) -> 1;
  375. less_json0({A},{B}) when is_list(A), is_list(B) -> less_props(A,B);
  376. less_json0({A},_) when is_list(A) -> -1;
  377. less_json0(_,{B}) when is_list(B) -> 1.
  378. atom_sort(null) -> 1;
  379. atom_sort(false) -> 2;
  380. atom_sort(true) -> 3.
  381. less_props([], [_|_]) ->
  382. -1;
  383. less_props(_, []) ->
  384. 1;
  385. less_props([{AKey, AValue}|RestA], [{BKey, BValue}|RestB]) ->
  386. case couch_util:collate(AKey, BKey) of
  387. 0 ->
  388. case less_json0(AValue, BValue) of
  389. 0 ->
  390. less_props(RestA, RestB);
  391. Result ->
  392. Result
  393. end;
  394. Result ->
  395. Result
  396. end.
  397. less_list([], [_|_]) ->
  398. -1;
  399. less_list(_, []) ->
  400. 1;
  401. less_list([A|RestA], [B|RestB]) ->
  402. case less_json0(A,B) of
  403. 0 ->
  404. less_list(RestA, RestB);
  405. Result ->
  406. Result
  407. end.