diff options
Diffstat (limited to 'src/couchdb')
-rw-r--r-- | src/couchdb/couch_db.erl | 12 | ||||
-rw-r--r-- | src/couchdb/couch_db.hrl | 3 | ||||
-rw-r--r-- | src/couchdb/couch_db_updater.erl | 12 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_db.erl | 29 | ||||
-rw-r--r-- | src/couchdb/couch_task_status.erl | 2 | ||||
-rw-r--r-- | src/couchdb/couch_view.erl | 160 | ||||
-rw-r--r-- | src/couchdb/couch_view_group.erl | 142 | ||||
-rw-r--r-- | src/couchdb/couch_view_updater.erl | 20 |
8 files changed, 225 insertions, 155 deletions
diff --git a/src/couchdb/couch_db.erl b/src/couchdb/couch_db.erl index 979bd39d..2f0fa847 100644 --- a/src/couchdb/couch_db.erl +++ b/src/couchdb/couch_db.erl @@ -13,7 +13,7 @@ -module(couch_db). -behaviour(gen_server). --export([open/2,close/1,create/2,start_compact/1,get_db_info/1]). +-export([open/2,close/1,create/2,start_compact/1,get_db_info/1,get_design_docs/1]). -export([open_ref_counted/2,is_idle/1,monitor/1,count_changes_since/2]). -export([update_doc/3,update_docs/4,update_docs/2,update_docs/3,delete_doc/3]). -export([get_doc_info/2,open_doc/2,open_doc/3,open_doc_revs/4]). @@ -192,6 +192,16 @@ get_db_info(Db) -> ], {ok, InfoList}. +get_design_docs(#db{fulldocinfo_by_id_btree=Btree}=Db) -> + couch_btree:foldl(Btree, <<"_design/">>, + fun(#full_doc_info{id= <<"_design/",_/binary>>}=FullDocInfo, _Reds, AccDocs) -> + {ok, Doc} = couch_db:open_doc_int(Db, FullDocInfo, []), + {ok, [Doc | AccDocs]}; + (_, _Reds, AccDocs) -> + {stop, AccDocs} + end, + []). + check_is_admin(#db{admins=Admins, user_ctx=#user_ctx{name=Name,roles=Roles}}) -> DbAdmins = [<<"_admin">> | Admins], case DbAdmins -- [Name | Roles] of diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl index 580b91cb..905b489b 100644 --- a/src/couchdb/couch_db.hrl +++ b/src/couchdb/couch_db.hrl @@ -190,8 +190,7 @@ headers = [] }). --record(group, - {type=view, % can also be temp_view +-record(group, { sig=nil, db=nil, fd=nil, diff --git a/src/couchdb/couch_db_updater.erl b/src/couchdb/couch_db_updater.erl index 31ddbf8c..6fef29eb 100644 --- a/src/couchdb/couch_db_updater.erl +++ b/src/couchdb/couch_db_updater.erl @@ -350,7 +350,7 @@ close_db(#db{fd_ref_counter = RefCntr}) -> refresh_validate_doc_funs(Db) -> - {ok, DesignDocs} = get_design_docs(Db), + {ok, DesignDocs} = couch_db:get_design_docs(Db), ProcessDocFuns = lists:flatmap( fun(DesignDoc) -> case couch_doc:get_validate_doc_fun(DesignDoc) of @@ -360,16 +360,6 @@ refresh_validate_doc_funs(Db) -> end, DesignDocs), Db#db{validate_doc_funs=ProcessDocFuns}. -get_design_docs(#db{fulldocinfo_by_id_btree=Btree}=Db) -> - couch_btree:foldl(Btree, <<"_design/">>, - fun(#full_doc_info{id= <<"_design/",_/binary>>}=FullDocInfo, _Reds, AccDocs) -> - {ok, Doc} = couch_db:open_doc_int(Db, FullDocInfo, []), - {ok, [Doc | AccDocs]}; - (_, _Reds, AccDocs) -> - {stop, AccDocs} - end, - []). - % rev tree functions flush_trees(_Db, [], AccFlushedTrees) -> diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index 8c692f8d..edb2f310 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -15,7 +15,8 @@ -export([handle_request/1, handle_compact_req/2, handle_design_req/2, db_req/2, couch_doc_open/4,handle_changes_req/2, - update_doc_result_to_json/1, update_doc_result_to_json/2]). + update_doc_result_to_json/1, update_doc_result_to_json/2, + handle_design_info_req/2, handle_view_cleanup_req/2]). -import(couch_httpd, [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2, @@ -156,6 +157,15 @@ handle_compact_req(#httpd{method='POST'}=Req, Db) -> handle_compact_req(Req, _Db) -> send_method_not_allowed(Req, "POST"). +handle_view_cleanup_req(#httpd{method='POST'}=Req, Db) -> + % delete unreferenced index files + ok = couch_view:cleanup_index_files(Db), + send_json(Req, 202, {[{ok, true}]}); + +handle_view_cleanup_req(Req, _Db) -> + send_method_not_allowed(Req, "POST"). + + handle_design_req(#httpd{ path_parts=[_DbName,_Design,_DesName, <<"_",_/binary>> = Action | _Rest], design_url_handlers = DesignUrlHandlers @@ -166,6 +176,23 @@ handle_design_req(#httpd{ handle_design_req(Req, Db) -> db_req(Req, Db). +handle_design_info_req(#httpd{ + method='GET', + path_parts=[_DbName, _Design, DesignName, _] + }=Req, Db) -> + DesignId = <<"_design/", DesignName/binary>>, + ?LOG_ERROR("DesignId ~p",[DesignId]), + {ok, GroupInfoList} = couch_view:get_group_info(Db, DesignId), + ?LOG_ERROR("GroupInfoList ~p",[GroupInfoList]), + send_json(Req, 200, {[ + {name, DesignName}, + {view_index, {GroupInfoList}} + ]}); + +handle_design_info_req(Req, _Db) -> + send_method_not_allowed(Req, "GET"). + + create_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) -> ok = couch_httpd:verify_is_server_admin(Req), case couch_server:create(DbName, [{user_ctx, UserCtx}]) of diff --git a/src/couchdb/couch_task_status.erl b/src/couchdb/couch_task_status.erl index ff2c7bee..ee7bdff5 100644 --- a/src/couchdb/couch_task_status.erl +++ b/src/couchdb/couch_task_status.erl @@ -107,12 +107,12 @@ handle_call(all, _, Server) -> handle_cast({update_status, Pid, StatusText}, Server) -> [{Pid, {Type, TaskName, _StatusText}}] = ets:lookup(?MODULE, Pid), + ?LOG_DEBUG("New task status for ~s: ~s",[TaskName, StatusText]), true = ets:insert(?MODULE, {Pid, {Type, TaskName, StatusText}}), {noreply, Server}; handle_cast(stop, State) -> {stop, normal, State}. - handle_info({'DOWN', _MonitorRef, _Type, Pid, _Info}, Server) -> %% should we also erlang:demonitor(_MonitorRef), ? ets:delete(?MODULE, Pid), diff --git a/src/couchdb/couch_view.erl b/src/couchdb/couch_view.erl index 301ea8b7..87feea12 100644 --- a/src/couchdb/couch_view.erl +++ b/src/couchdb/couch_view.erl @@ -17,7 +17,7 @@ detuple_kvs/2,init/1,terminate/2,handle_call/3,handle_cast/2,handle_info/2, code_change/3,get_reduce_view/4,get_temp_reduce_view/5,get_temp_map_view/4, get_map_view/4,get_row_count/1,reduce_to_count/1,fold_reduce/7, - extract_map_view/1,get_group_server/2]). + extract_map_view/1,get_group_server/2,get_group_info/2,cleanup_index_files/1]). -include("couch_db.hrl"). @@ -28,18 +28,32 @@ start_link() -> gen_server:start_link({local, couch_view}, couch_view, [], []). -get_temp_updater(DbName, Type, DesignOptions, MapSrc, RedSrc) -> - {ok, Pid} = gen_server:call(couch_view, - {start_temp_updater, DbName, Type, DesignOptions, MapSrc, RedSrc}), - Pid. - -get_group_server(DbName, GroupId) -> - case gen_server:call(couch_view, {start_group_server, DbName, GroupId}) of +get_temp_updater(DbName, Language, DesignOptions, MapSrc, RedSrc) -> + % make temp group + % do we need to close this db? + {ok, _Db, Group} = + couch_view_group:open_temp_group(DbName, Language, DesignOptions, MapSrc, RedSrc), + case gen_server:call(couch_view, {get_group_server, DbName, Group}) of {ok, Pid} -> Pid; Error -> throw(Error) end. + +get_group_server(DbName, GroupId) -> + % get signature for group + case couch_view_group:open_db_group(DbName, GroupId) of + % do we need to close this db? + {ok, _Db, Group} -> + case gen_server:call(couch_view, {get_group_server, DbName, Group}) of + {ok, Pid} -> + Pid; + Error -> + throw(Error) + end; + Error -> + throw(Error) + end. get_group(Db, GroupId, Stale) -> MinUpdateSeq = case Stale of @@ -50,18 +64,52 @@ get_group(Db, GroupId, Stale) -> get_group_server(couch_db:name(Db), GroupId), MinUpdateSeq). - -get_temp_group(Db, Type, DesignOptions, MapSrc, RedSrc) -> +get_temp_group(Db, Language, DesignOptions, MapSrc, RedSrc) -> couch_view_group:request_group( - get_temp_updater(couch_db:name(Db), Type, DesignOptions, MapSrc, RedSrc), - couch_db:get_update_seq(Db)). + get_temp_updater(couch_db:name(Db), Language, DesignOptions, MapSrc, RedSrc), + couch_db:get_update_seq(Db)). + +get_group_info(Db, GroupId) -> + couch_view_group:request_group_info( + get_group_server(couch_db:name(Db), GroupId)). + +cleanup_index_files(Db) -> + % load all ddocs + {ok, DesignDocs} = couch_db:get_design_docs(Db), + + % make unique list of group sigs + Sigs = lists:map(fun(#doc{id = GroupId} = DDoc) -> + {ok, Info} = get_group_info(Db, GroupId), + ?b2l(proplists:get_value(signature, Info)) + end, [DD||DD <- DesignDocs, DD#doc.deleted == false]), + + FileList = list_index_files(Db), + + % regex that matches all ddocs + RegExp = "("++ string:join(Sigs, "|") ++")", + + % filter out the ones in use + DeleteFiles = lists:filter(fun(FilePath) -> + regexp:first_match(FilePath, RegExp)==nomatch + end, FileList), + % delete unused files + ?LOG_DEBUG("deleting unused view index files: ~p",[DeleteFiles]), + [file:delete(File)||File <- DeleteFiles], + ok. + +list_index_files(Db) -> + % call server to fetch the index files + RootDir = couch_config:get("couchdb", "view_index_dir"), + Files = filelib:wildcard(RootDir ++ "/." ++ ?b2l(couch_db:name(Db)) ++ "_design"++"/*"). + get_row_count(#view{btree=Bt}) -> {ok, {Count, _Reds}} = couch_btree:full_reduce(Bt), {ok, Count}. -get_temp_reduce_view(Db, Type, DesignOptions, MapSrc, RedSrc) -> - {ok, #group{views=[View]}=Group} = get_temp_group(Db, Type, DesignOptions, MapSrc, RedSrc), +get_temp_reduce_view(Db, Language, DesignOptions, MapSrc, RedSrc) -> + {ok, #group{views=[View]}=Group} = + get_temp_group(Db, Language, DesignOptions, MapSrc, RedSrc), {ok, {temp_reduce, View}, Group}. @@ -141,8 +189,8 @@ get_key_pos(Key, [_|Rest], N) -> get_key_pos(Key, Rest, N+1). -get_temp_map_view(Db, Type, DesignOptions, Src) -> - {ok, #group{views=[View]}=Group} = get_temp_group(Db, Type, DesignOptions, Src, []), +get_temp_map_view(Db, Language, DesignOptions, Src) -> + {ok, #group{views=[View]}=Group} = get_temp_group(Db, Language, DesignOptions, Src, []), {ok, View, Group}. get_map_view(Db, GroupId, Name, Stale) -> @@ -220,9 +268,8 @@ init([]) -> ok end), ets:new(couch_groups_by_db, [bag, private, named_table]), - ets:new(group_servers_by_name, [set, protected, named_table]), + ets:new(group_servers_by_sig, [set, protected, named_table]), ets:new(couch_groups_by_updater, [set, private, named_table]), - ets:new(couch_temp_group_fd_by_db, [set, protected, named_table]), process_flag(trap_exit, true), {ok, #server{root_dir=RootDir}}. @@ -232,37 +279,15 @@ terminate(Reason, _Srv) -> ok. -handle_call({start_temp_updater, DbName, Lang, DesignOptions, MapSrc, RedSrc}, - _From, #server{root_dir=Root}=Server) -> - <<SigInt:128/integer>> = erlang:md5(term_to_binary({Lang, DesignOptions, MapSrc, RedSrc})), - Name = lists:flatten(io_lib:format("_temp_~.36B",[SigInt])), - Pid = - case ets:lookup(group_servers_by_name, {DbName, Name}) of +handle_call({get_group_server, DbName, + #group{name=GroupId,sig=Sig}=Group}, _From, #server{root_dir=Root}=Server) -> + case ets:lookup(group_servers_by_sig, {DbName, Sig}) of [] -> - case ets:lookup(couch_temp_group_fd_by_db, DbName) of - [] -> - FileName = Root ++ "/." ++ binary_to_list(DbName) ++ "_temp", - {ok, Fd} = couch_file:open(FileName, [create, overwrite]), - Count = 0; - [{_, Fd, Count}] -> - ok - end, - ?LOG_DEBUG("Spawning new temp update process for db ~s.", [DbName]), - {ok, NewPid} = couch_view_group:start_link({slow_view, DbName, Fd, Lang, DesignOptions, MapSrc, RedSrc}), - true = ets:insert(couch_temp_group_fd_by_db, {DbName, Fd, Count + 1}), - add_to_ets(NewPid, DbName, Name), - NewPid; - [{_, ExistingPid0}] -> - ExistingPid0 - end, - {reply, {ok, Pid}, Server}; -handle_call({start_group_server, DbName, GroupId}, _From, #server{root_dir=Root}=Server) -> - case ets:lookup(group_servers_by_name, {DbName, GroupId}) of - [] -> - ?LOG_DEBUG("Spawning new group server for view group ~s in database ~s.", [GroupId, DbName]), - case (catch couch_view_group:start_link({view, Root, DbName, GroupId})) of + ?LOG_DEBUG("Spawning new group server for view group ~s in database ~s.", + [GroupId, DbName]), + case (catch couch_view_group:start_link({Root, DbName, Group})) of {ok, NewPid} -> - add_to_ets(NewPid, DbName, GroupId), + add_to_ets(NewPid, DbName, Sig), {reply, {ok, NewPid}, Server}; Error -> {reply, Error, Server} @@ -272,22 +297,22 @@ handle_call({start_group_server, DbName, GroupId}, _From, #server{root_dir=Root} end. handle_cast({reset_indexes, DbName}, #server{root_dir=Root}=Server) -> - % shutdown all the updaters + % shutdown all the updaters and clear the files, the db got changed Names = ets:lookup(couch_groups_by_db, DbName), lists:foreach( - fun({_DbName, GroupId}) -> - ?LOG_DEBUG("Killing update process for view group ~s. in database ~s.", [GroupId, DbName]), - [{_, Pid}] = ets:lookup(group_servers_by_name, {DbName, GroupId}), + fun({_DbName, Sig}) -> + ?LOG_DEBUG("Killing update process for view group ~s. in database ~s.", [Sig, DbName]), + [{_, Pid}] = ets:lookup(group_servers_by_sig, {DbName, Sig}), exit(Pid, kill), receive {'EXIT', Pid, _} -> - delete_from_ets(Pid, DbName, GroupId) + delete_from_ets(Pid, DbName, Sig) end end, Names), delete_index_dir(Root, DbName), file:delete(Root ++ "/." ++ binary_to_list(DbName) ++ "_temp"), {noreply, Server}. -handle_info({'EXIT', FromPid, Reason}, #server{root_dir=RootDir}=Server) -> +handle_info({'EXIT', FromPid, Reason}, Server) -> case ets:lookup(couch_groups_by_updater, FromPid) of [] -> if Reason /= normal -> @@ -296,40 +321,27 @@ handle_info({'EXIT', FromPid, Reason}, #server{root_dir=RootDir}=Server) -> exit(Reason); true -> ok end; - [{_, {DbName, "_temp_" ++ _ = GroupId}}] -> - delete_from_ets(FromPid, DbName, GroupId), - [{_, Fd, Count}] = ets:lookup(couch_temp_group_fd_by_db, DbName), - case Count of - 1 -> % Last ref - couch_file:close(Fd), - file:delete(RootDir ++ "/." ++ binary_to_list(DbName) ++ "_temp"), - true = ets:delete(couch_temp_group_fd_by_db, DbName); - _ -> - true = ets:insert(couch_temp_group_fd_by_db, {DbName, Fd, Count - 1}) - end; [{_, {DbName, GroupId}}] -> delete_from_ets(FromPid, DbName, GroupId) end, {noreply, Server}. -add_to_ets(Pid, DbName, GroupId) -> - true = ets:insert(couch_groups_by_updater, {Pid, {DbName, GroupId}}), - true = ets:insert(group_servers_by_name, {{DbName, GroupId}, Pid}), - true = ets:insert(couch_groups_by_db, {DbName, GroupId}). +add_to_ets(Pid, DbName, Sig) -> + true = ets:insert(couch_groups_by_updater, {Pid, {DbName, Sig}}), + true = ets:insert(group_servers_by_sig, {{DbName, Sig}, Pid}), + true = ets:insert(couch_groups_by_db, {DbName, Sig}). -delete_from_ets(Pid, DbName, GroupId) -> +delete_from_ets(Pid, DbName, Sig) -> true = ets:delete(couch_groups_by_updater, Pid), - true = ets:delete(group_servers_by_name, {DbName, GroupId}), - true = ets:delete_object(couch_groups_by_db, {DbName, GroupId}). + true = ets:delete(group_servers_by_sig, {DbName, Sig}), + true = ets:delete_object(couch_groups_by_db, {DbName, Sig}). code_change(_OldVsn, State, _Extra) -> {ok, State}. - - delete_index_dir(RootDir, DbName) -> - nuke_dir(RootDir ++ "/." ++ binary_to_list(DbName) ++ "_design"). + nuke_dir(RootDir ++ "/." ++ ?b2l(DbName) ++ "_design"). nuke_dir(Dir) -> case file:list_dir(Dir) of diff --git a/src/couchdb/couch_view_group.erl b/src/couchdb/couch_view_group.erl index 0ab1077e..0b390b22 100644 --- a/src/couchdb/couch_view_group.erl +++ b/src/couchdb/couch_view_group.erl @@ -14,8 +14,8 @@ -behaviour(gen_server). %% API --export([start_link/1, request_group/2]). --export([design_doc_to_view_group/1]). +-export([start_link/1, request_group/2, request_group_info/1]). +-export([open_db_group/2, open_temp_group/5, design_doc_to_view_group/1,design_root/2]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -47,6 +47,22 @@ request_group(Pid, Seq) -> throw(Error) end. +request_group_info(Pid) -> + case gen_server:call(Pid, request_group_info) of + {ok, GroupInfoList} -> + {ok, GroupInfoList}; + Error -> + throw(Error) + end. + +request_index_files(Pid) -> + case gen_server:call(Pid, request_index_files) of + {ok, Filelist} -> + {ok, Filelist}; + Error -> + throw(Error) + end. + % from template start_link(InitArgs) -> @@ -67,9 +83,7 @@ start_link(InitArgs) -> Error end. -% init differentiates between temp and design_doc views. It creates a closure -% which spawns the appropriate view_updater. (It might also spawn the first -% view_updater run.) +% init creates a closure which spawns the appropriate view_updater. init({InitArgs, ReturnPid, Ref}) -> process_flag(trap_exit, true), case prepare_group(InitArgs, false) of @@ -95,8 +109,7 @@ init({InitArgs, ReturnPid, Ref}) -> % view group, and the couch_view_updater, which when spawned, updates the % group and sends it back here. We employ a caching mechanism, so that between % database writes, we don't have to spawn a couch_view_updater with every view -% request. This should give us more control, and the ability to request view -% statuses eventually. +% request. % The caching mechanism: each request is submitted with a seq_id for the % database at the time it was read. We guarantee to return a view from that @@ -137,8 +150,14 @@ handle_call({request_group, RequestSeq}, From, #group_state{waiting_list=WaitList}=State) -> {noreply, State#group_state{ waiting_list=[{From, RequestSeq}|WaitList] - }, infinity}. + }, infinity}; +handle_call(request_group_info, _From, #group_state{ + group = Group, + compactor_pid = CompactorPid + } = State) -> + GroupInfo = get_group_info(Group, CompactorPid), + {reply, {ok, GroupInfo}, State}. handle_cast({start_compact, CompactFun}, #group_state{ compactor_pid=nil, group=Group, init_args={view, RootDir, DbName, GroupId} } = State) -> @@ -154,15 +173,14 @@ handle_cast({start_compact, _}, State) -> handle_cast({compact_done, #group{fd=NewFd, current_seq=NewSeq} = NewGroup}, #group_state{ - group = #group{current_seq=OldSeq} = Group, - init_args = {view, RootDir, DbName, GroupId}, + group = #group{current_seq=OldSeq, sig=GroupSig} = Group, + init_args = {view, RootDir, DbName, _GroupId}, updater_pid = nil, ref_counter = RefCounter } = State) when NewSeq >= OldSeq -> ?LOG_INFO("View Group compaction complete", []), - BaseName = RootDir ++ "/." ++ ?b2l(DbName) ++ ?b2l(GroupId), - FileName = BaseName ++ ".view", - CompactName = BaseName ++".compact.view", + FileName = index_file_name(RootDir, DbName, GroupSig), + CompactName = index_file_name(compact, RootDir, DbName, GroupSig), file:delete(FileName), ok = file:rename(CompactName, FileName), @@ -224,8 +242,7 @@ handle_info({'EXIT', FromPid, {new_group, #group{db=Db}=Group}}, waiting_list=WaitList, waiting_commit=WaitingCommit}=State) when UpPid == FromPid -> ok = couch_db:close(Db), - - if Group#group.type == view andalso not WaitingCommit -> + if not WaitingCommit -> erlang:send_after(1000, self(), delayed_commit); true -> ok end, @@ -306,12 +323,13 @@ reply_all(#group_state{waiting_list=WaitList}=State, Reply) -> [catch gen_server:reply(Pid, Reply) || {Pid, _} <- WaitList], State#group_state{waiting_list=[]}. -prepare_group({view, RootDir, DbName, GroupId}, ForceReset)-> - case open_db_group(DbName, GroupId) of - {ok, Db, #group{sig=Sig}=Group} -> - case open_index_file(RootDir, DbName, GroupId) of +prepare_group({RootDir, DbName, #group{sig=Sig}=Group}, ForceReset)-> + case couch_db:open(DbName, []) of + {ok, Db} -> + case open_index_file(RootDir, DbName, Sig) of {ok, Fd} -> if ForceReset -> + % this can happen if we missed a purge {ok, reset_file(Db, Fd, DbName, Group)}; true -> % 09 UPGRADE CODE @@ -321,32 +339,18 @@ prepare_group({view, RootDir, DbName, GroupId}, ForceReset)-> % sigs match! {ok, init_group(Db, Fd, Group, HeaderInfo)}; _ -> + % this happens on a new file {ok, reset_file(Db, Fd, DbName, Group)} end end; Error -> - catch delete_index_file(RootDir, DbName, GroupId), + catch delete_index_file(RootDir, DbName, Sig), Error end; - Error -> - catch delete_index_file(RootDir, DbName, GroupId), - Error - end; -prepare_group({slow_view, DbName, Fd, Lang, DesignOptions, MapSrc, RedSrc}, _ForceReset) -> - case couch_db:open(DbName, []) of - {ok, Db} -> - View = #view{map_names=[<<"_temp">>], - id_num=0, - btree=nil, - def=MapSrc, - reduce_funs= if RedSrc==[] -> []; true -> [{<<"_temp">>, RedSrc}] end}, - {ok, init_group(Db, Fd, #group{type=slow_view, name= <<"_temp">>, db=Db, - views=[View], def_lang=Lang, design_options=DesignOptions}, nil)}; - Error -> - Error + Else -> + Else end. - get_index_header_data(#group{current_seq=Seq, purge_seq=PurgeSeq, id_btree=IdBtree,views=Views}) -> ViewStates = [couch_btree:get_state(Btree) || #view{btree=Btree} <- Views], @@ -355,14 +359,47 @@ get_index_header_data(#group{current_seq=Seq, purge_seq=PurgeSeq, id_btree_state=couch_btree:get_state(IdBtree), view_states=ViewStates}. +hex_sig(GroupSig) -> + couch_util:to_hex(?b2l(GroupSig)). + +design_root(RootDir, DbName) -> + RootDir ++ "/." ++ ?b2l(DbName) ++ "_design/". + +index_file_name(RootDir, DbName, GroupSig) -> + design_root(RootDir, DbName) ++ hex_sig(GroupSig) ++".view". -open_index_file(RootDir, DbName, GroupId) -> - FileName = RootDir ++ "/." ++ ?b2l(DbName) ++ ?b2l(GroupId) ++".view", +index_file_name(compact, RootDir, DbName, GroupSig) -> + design_root(RootDir, DbName) ++ hex_sig(GroupSig) ++".compact.view". + + +open_index_file(RootDir, DbName, GroupSig) -> + FileName = index_file_name(RootDir, DbName, GroupSig), case couch_file:open(FileName) of {ok, Fd} -> {ok, Fd}; {error, enoent} -> couch_file:open(FileName, [create]); Error -> Error end. + +open_temp_group(DbName, Language, DesignOptions, MapSrc, RedSrc) -> + case couch_db:open(DbName, []) of + {ok, Db} -> + View = #view{map_names=[<<"_temp">>], + id_num=0, + btree=nil, + def=MapSrc, + reduce_funs= if RedSrc==[] -> []; true -> [{<<"_temp">>, RedSrc}] end}, + + {ok, Db, #group{ + name = <<"_temp">>, + db=Db, + views=[View], + def_lang=Language, + design_options=DesignOptions, + sig = erlang:md5(term_to_binary({[View], Language, DesignOptions})) + }}; + Error -> + Error + end. open_db_group(DbName, GroupId) -> case couch_db:open(DbName, []) of @@ -378,12 +415,28 @@ open_db_group(DbName, GroupId) -> Else end. +get_group_info(#group{ + fd = Fd, + sig = GroupSig, + def_lang = Lang + }, CompactorPid) -> + {ok, Size} = couch_file:bytes(Fd), + [ + {signature, ?l2b(hex_sig(GroupSig))}, + {language, Lang}, + {disk_size, Size}, + {compact_running, CompactorPid /= nil} + ]. + % maybe move to another module design_doc_to_view_group(#doc{id=Id,body={Fields}}) -> Language = proplists:get_value(<<"language">>, Fields, <<"javascript">>), {DesignOptions} = proplists:get_value(<<"options">>, Fields, {[]}), {RawViews} = proplists:get_value(<<"views">>, Fields, {[]}), - + % sort the views by name to avoid spurious signature changes + SortedRawViews = lists:sort(fun({Name1, _}, {Name2, _}) -> + Name1 >= Name2 + end, RawViews), % add the views to a dictionary object, with the map source as the key DictBySrc = lists:foldl( @@ -402,7 +455,7 @@ design_doc_to_view_group(#doc{id=Id,body={Fields}}) -> View#view{reduce_funs=[{Name,RedSrc}|View#view.reduce_funs]} end, dict:store(MapSrc, View2, DictBySrcAcc) - end, dict:new(), RawViews), + end, dict:new(), SortedRawViews), % number the views {Views, _N} = lists:mapfoldl( fun({_Src, View}, N) -> @@ -410,7 +463,7 @@ design_doc_to_view_group(#doc{id=Id,body={Fields}}) -> end, 0, dict:to_list(DictBySrc)), Group = #group{name=Id, views=Views, def_lang=Language, design_options=DesignOptions}, - Group#group{sig=erlang:md5(term_to_binary(Group))}. + Group#group{sig=erlang:md5(term_to_binary({Views, Language, DesignOptions}))}. reset_group(#group{views=Views}=Group) -> Views2 = [View#view{btree=nil} || View <- Views], @@ -423,9 +476,8 @@ reset_file(Db, Fd, DbName, #group{sig=Sig,name=Name} = Group) -> ok = couch_file:write_header(Fd, {Sig, nil}), init_group(Db, Fd, reset_group(Group), nil). -delete_index_file(RootDir, DbName, GroupId) -> - file:delete(RootDir ++ "/." ++ binary_to_list(DbName) - ++ binary_to_list(GroupId) ++ ".view"). +delete_index_file(RootDir, DbName, GroupSig) -> + file:delete(index_file_name(RootDir, DbName, GroupSig)). init_group(Db, Fd, #group{views=Views}=Group, nil) -> init_group(Db, Fd, Group, diff --git a/src/couchdb/couch_view_updater.erl b/src/couchdb/couch_view_updater.erl index 3c4c9108..11dfb544 100644 --- a/src/couchdb/couch_view_updater.erl +++ b/src/couchdb/couch_view_updater.erl @@ -106,26 +106,6 @@ process_doc(Db, DocInfo, {Docs, #group{sig=Sig,name=GroupId,design_options=Desig [conflicts, deleted_conflicts] end, case {IncludeDesign, DocId} of - {_, GroupId} -> - % uh oh. this is the design doc with our definitions. See if - % anything in the definition changed. - case couch_db:open_doc_int(Db, DocInfo, DocOpts) of - {ok, Doc} -> - case couch_view_group:design_doc_to_view_group(Doc) of - #group{sig=Sig} -> - % The same md5 signature, keep on computing - case IncludeDesign of - true -> - {[Doc | Docs], Group, ViewKVs, DocIdViewIdKeys}; - _ -> - {Docs, Group, ViewKVs, DocIdViewIdKeys} - end; - _ -> - exit(reset) - end; - {not_found, missing} -> - exit(reset) - end; {false, <<?DESIGN_DOC_PREFIX, _/binary>>} -> % we skip design docs {Docs, Group, ViewKVs, DocIdViewIdKeys}; _ -> |