Generated on 2009-08-09 21:22:54 with etap 0.3.4.
| Name | Total lines | Lines of code | Total coverage | Code coverage | ||||
| couch_view_updater | ?? | ?? | ?? |
|
....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_view_updater). ...14 ...15 -export([update/2]). ...16 ...17 -include("couch_db.hrl"). ...18 ...19 update(Owner, Group) -> ...20 #group{ ...21 db = #db{name=DbName} = Db, ...22 name = GroupName, ...23 current_seq = Seq, ...24 purge_seq = PurgeSeq ...25 } = Group, ...26 couch_task_status:add_task(<<"View Group Indexer">>, <>, <<"Starting index update">>), ...27 ...28 DbPurgeSeq = couch_db:get_purge_seq(Db), ...29 Group2 = ...30 if DbPurgeSeq == PurgeSeq -> ...31 Group; ...32 DbPurgeSeq == PurgeSeq + 1 -> ...33 couch_task_status:update(<<"Removing purged entries from view index.">>), ...34 purge_index(Group); ...35 true -> ...36 couch_task_status:update(<<"Resetting view index due to lost purge entries.">>), ...37 exit(reset) ...38 end, ...39 ...40 ViewEmptyKVs = [{View, []} || View <- Group2#group.views], ...41 % compute on all docs modified since we last computed. ...42 TotalChanges = couch_db:count_changes_since(Db, Seq), ...43 % update status every half second ...44 couch_task_status:set_update_frequency(500), ...45 {ok, {_,{UncomputedDocs, Group3, ViewKVsToAdd, DocIdViewIdKeys}}} ...46 = couch_db:enum_docs_since( ...47 Db, ...48 Seq, ...49 fun(DocInfo, _, {ChangesProcessed, Acc}) -> ...50 couch_task_status:update("Processed ~p of ~p changes (~p%)", ...51 [ChangesProcessed, TotalChanges, (ChangesProcessed*100) div TotalChanges]), ...52 {ok, {ChangesProcessed+1, process_doc(Db, Owner, DocInfo, Acc)}} ...53 end, ...54 {0, {[], Group2, ViewEmptyKVs, []}} ...55 ), ...56 couch_task_status:set_update_frequency(0), ...57 couch_task_status:update("Finishing."), ...58 {Group4, Results} = view_compute(Group3, UncomputedDocs), ...59 {ViewKVsToAdd2, DocIdViewIdKeys2} = view_insert_query_results( ...60 UncomputedDocs, Results, ViewKVsToAdd, DocIdViewIdKeys), ...61 couch_query_servers:stop_doc_map(Group4#group.query_server), ...62 NewSeq = couch_db:get_update_seq(Db), ...63 {ok, Group5} = write_changes(Group4, ViewKVsToAdd2, DocIdViewIdKeys2, ...64 NewSeq), ...65 exit({new_group, Group5#group{query_server=nil}}). ...66 ...67 ...68 purge_index(#group{db=Db, views=Views, id_btree=IdBtree}=Group) -> ...69 {ok, PurgedIdsRevs} = couch_db:get_last_purged(Db), ...70 Ids = [Id || {Id, _Revs} <- PurgedIdsRevs], ...71 {ok, Lookups, IdBtree2} = couch_btree:query_modify(IdBtree, Ids, [], Ids), ...72 ...73 % now populate the dictionary with all the keys to delete ...74 ViewKeysToRemoveDict = lists:foldl( ...75 fun({ok,{DocId,ViewNumRowKeys}}, ViewDictAcc) -> ...76 lists:foldl( ...77 fun({ViewNum, RowKey}, ViewDictAcc2) -> ...78 dict:append(ViewNum, {RowKey, DocId}, ViewDictAcc2) ...79 end, ViewDictAcc, ViewNumRowKeys); ...80 ({not_found, _}, ViewDictAcc) -> ...81 ViewDictAcc ...82 end, dict:new(), Lookups), ...83 ...84 % Now remove the values from the btrees ...85 Views2 = lists:map( ...86 fun(#view{id_num=Num,btree=Btree}=View) -> ...87 case dict:find(Num, ViewKeysToRemoveDict) of ...88 {ok, RemoveKeys} -> ...89 {ok, Btree2} = couch_btree:add_remove(Btree, [], RemoveKeys), ...90 View#view{btree=Btree2}; ...91 error -> % no keys to remove in this view ...92 View ...93 end ...94 end, Views), ...95 Group#group{id_btree=IdBtree2, ...96 views=Views2, ...97 purge_seq=couch_db:get_purge_seq(Db)}. ...98 ...99 % This fun computes once for each document ..100 process_doc(Db, Owner, DocInfo, {Docs, Group, ViewKVs, DocIdViewIdKeys}) -> ..101 #group{ design_options = DesignOptions } = Group, ..102 ..103 #doc_info{id=DocId, revs=[#rev_info{deleted=Deleted}|_]} = DocInfo, ..104 IncludeDesign = proplists:get_value(<<"include_design">>, ..105 DesignOptions, false), ..106 LocalSeq = proplists:get_value(<<"local_seq">>, ..107 DesignOptions, false), ..108 DocOpts = case LocalSeq of ..109 true -> ..110 [conflicts, deleted_conflicts, local_seq]; ..111 _ -> ..112 [conflicts, deleted_conflicts] ..113 end, ..114 case {IncludeDesign, DocId} of ..115 {false, <>} -> % we skip design docs ..116 {Docs, Group, ViewKVs, DocIdViewIdKeys}; ..117 _ -> ..118 {Docs2, DocIdViewIdKeys2} = ..119 if Deleted -> ..120 {Docs, [{DocId, []} | DocIdViewIdKeys]}; ..121 true -> ..122 {ok, Doc} = couch_db:open_doc_int(Db, DocInfo, ..123 DocOpts), ..124 {[Doc | Docs], DocIdViewIdKeys} ..125 end, ..126 ..127 case couch_util:should_flush() of ..128 true -> ..129 {Group1, Results} = view_compute(Group, Docs2), ..130 {ViewKVs3, DocIdViewIdKeys3} = view_insert_query_results(Docs2, ..131 Results, ViewKVs, DocIdViewIdKeys2), ..132 {ok, Group2} = write_changes(Group1, ViewKVs3, DocIdViewIdKeys3, ..133 DocInfo#doc_info.high_seq), ..134 if is_pid(Owner) -> ..135 ok = gen_server:cast(Owner, {partial_update, self(), Group2}); ..136 true -> ok end, ..137 garbage_collect(), ..138 ViewEmptyKeyValues = [{View, []} || View <- Group2#group.views], ..139 {[], Group2, ViewEmptyKeyValues, []}; ..140 false -> ..141 {Docs2, Group, ViewKVs, DocIdViewIdKeys2} ..142 end ..143 end. ..144 ..145 view_insert_query_results([], [], ViewKVs, DocIdViewIdKeysAcc) -> ..146 {ViewKVs, DocIdViewIdKeysAcc}; ..147 view_insert_query_results([Doc|RestDocs], [QueryResults | RestResults], ViewKVs, DocIdViewIdKeysAcc) -> ..148 {NewViewKVs, NewViewIdKeys} = view_insert_doc_query_results(Doc, QueryResults, ViewKVs, [], []), ..149 NewDocIdViewIdKeys = [{Doc#doc.id, NewViewIdKeys} | DocIdViewIdKeysAcc], ..150 view_insert_query_results(RestDocs, RestResults, NewViewKVs, NewDocIdViewIdKeys). ..151 ..152 ..153 view_insert_doc_query_results(_Doc, [], [], ViewKVsAcc, ViewIdKeysAcc) -> ..154 {lists:reverse(ViewKVsAcc), lists:reverse(ViewIdKeysAcc)}; ..155 view_insert_doc_query_results(#doc{id=DocId}=Doc, [ResultKVs|RestResults], [{View, KVs}|RestViewKVs], ViewKVsAcc, ViewIdKeysAcc) -> ..156 % Take any identical keys and combine the values ..157 ResultKVs2 = lists:foldl( ..158 fun({Key,Value}, [{PrevKey,PrevVal}|AccRest]) -> ..159 case Key == PrevKey of ..160 true -> ..161 case PrevVal of ..162 {dups, Dups} -> ..163 [{PrevKey, {dups, [Value|Dups]}} | AccRest]; ..164 _ -> ..165 [{PrevKey, {dups, [Value,PrevVal]}} | AccRest] ..166 end; ..167 false -> ..168 [{Key,Value},{PrevKey,PrevVal}|AccRest] ..169 end; ..170 (KV, []) -> ..171 [KV] ..172 end, [], lists:sort(ResultKVs)), ..173 NewKVs = [{{Key, DocId}, Value} || {Key, Value} <- ResultKVs2], ..174 NewViewKVsAcc = [{View, NewKVs ++ KVs} | ViewKVsAcc], ..175 NewViewIdKeys = [{View#view.id_num, Key} || {Key, _Value} <- ResultKVs2], ..176 NewViewIdKeysAcc = NewViewIdKeys ++ ViewIdKeysAcc, ..177 view_insert_doc_query_results(Doc, RestResults, RestViewKVs, NewViewKVsAcc, NewViewIdKeysAcc). ..178 ..179 view_compute(Group, []) -> ..180 {Group, []}; ..181 view_compute(#group{def_lang=DefLang, query_server=QueryServerIn}=Group, Docs) -> ..182 {ok, QueryServer} = ..183 case QueryServerIn of ..184 nil -> % doc map not started ..185 Definitions = [View#view.def || View <- Group#group.views], ..186 couch_query_servers:start_doc_map(DefLang, Definitions); ..187 _ -> ..188 {ok, QueryServerIn} ..189 end, ..190 {ok, Results} = couch_query_servers:map_docs(QueryServer, Docs), ..191 {Group#group{query_server=QueryServer}, Results}. ..192 ..193 ..194 ..195 write_changes(Group, ViewKeyValuesToAdd, DocIdViewIdKeys, NewSeq) -> ..196 #group{id_btree=IdBtree} = Group, ..197 ..198 AddDocIdViewIdKeys = [{DocId, ViewIdKeys} || {DocId, ViewIdKeys} <- DocIdViewIdKeys, ViewIdKeys /= []], ..199 RemoveDocIds = [DocId || {DocId, ViewIdKeys} <- DocIdViewIdKeys, ViewIdKeys == []], ..200 LookupDocIds = [DocId || {DocId, _ViewIdKeys} <- DocIdViewIdKeys], ..201 {ok, LookupResults, IdBtree2} ..202 = couch_btree:query_modify(IdBtree, LookupDocIds, AddDocIdViewIdKeys, RemoveDocIds), ..203 KeysToRemoveByView = lists:foldl( ..204 fun(LookupResult, KeysToRemoveByViewAcc) -> ..205 case LookupResult of ..206 {ok, {DocId, ViewIdKeys}} -> ..207 lists:foldl( ..208 fun({ViewId, Key}, KeysToRemoveByViewAcc2) -> ..209 dict:append(ViewId, {Key, DocId}, KeysToRemoveByViewAcc2) ..210 end, ..211 KeysToRemoveByViewAcc, ViewIdKeys); ..212 {not_found, _} -> ..213 KeysToRemoveByViewAcc ..214 end ..215 end, ..216 dict:new(), LookupResults), ..217 ..218 Views2 = [ ..219 begin ..220 KeysToRemove = couch_util:dict_find(View#view.id_num, KeysToRemoveByView, []), ..221 {ok, ViewBtree2} = couch_btree:add_remove(View#view.btree, AddKeyValues, KeysToRemove), ..222 View#view{btree = ViewBtree2} ..223 end ..224 || ..225 {View, AddKeyValues} <- ViewKeyValuesToAdd ..226 ], ..227 Group2 = Group#group{views=Views2, current_seq=NewSeq, id_btree=IdBtree2}, ..228 {ok, Group2}. ..229
Generated using etap 0.3.4.