-module(couch_numidx). -behaviour(gen_server). % gen_server exports -export([start_link/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). % API exports -export([get_docs/2, range_search/1]). -include("couch_db.hrl"). -define(FILENAME, "/tmp/couchdb_numidx.bin"). -record(ni_daemon, { seq = 0, ni_pos = nil, fd = nil, btree = nil }). get_docs(Db, DDoc) -> gen_server:call(couch_numidx, {do_get_docs, Db, DDoc}). range_search(Range) -> gen_server:call(couch_numidx, {do_range_search, Range}). open_file(Filename) -> case couch_file:open(Filename) of {ok, Fd} -> {ok, State} = couch_file:read_header(Fd), {ok, Btree} = couch_btree:open(State#ni_daemon.btree, Fd), {ok, State#ni_daemon{fd=Fd, btree=Btree}}; {error, enoent} -> case couch_file:open(Filename, [create, overwrite]) of {ok, Fd} -> {ok, Btree} = couch_btree:open(nil, Fd), State = #ni_daemon{fd=Fd, btree=Btree}, couch_file:write_header(Fd, State), {ok, State}; {error, Reason} -> io:format("ERROR (~s): Couldn't open file (~s) for tree storage~n", [Reason, Filename]), {error, Reason} end; {error, Reason} -> io:format("ERROR (~s): Couldn't open file (~s) for tree storage~n", [Reason, Filename]), {error, Reason} end. start_link() -> ?LOG_DEBUG("Numidx daemon: starting link.", []), gen_server:start_link({local, couch_numidx}, couch_numidx, [], []). init([]) -> open_file(?FILENAME). terminate(_Reason, _Srv) -> ok. handle_call({do_get_docs, Db, DDoc}, _From, State) -> {ok, _Cnt, StateNew} = couch_db:enum_docs_since(Db, State#ni_daemon.seq, fun(DocInfo, _, #ni_daemon{fd=Fd, ni_pos=Pos, btree=Btree}=StateCur) -> {doc_info, DocId, DocSeq, [RevInfo|_]} = DocInfo, ?LOG_DEBUG("doc-seq: ~p", [DocSeq]), case RevInfo#rev_info.deleted of false -> {ok, Doc} = couch_db:open_doc(Db, DocInfo), JsonDoc = couch_query_servers:json_doc(Doc), [<<"numidx">>, Numidx] = couch_query_servers:ddoc_prompt( DDoc, [<<"numidx">>], [JsonDoc]), case Numidx of false -> {ok, StateCur#ni_daemon{seq=DocSeq}}; _ -> {ok, NewPos} = numidx:add({Fd, Pos}, {DocId, Numidx}), {ok, NewBtree} = couch_btree:add(Btree, [{DocId, Numidx}]), {ok, StateCur#ni_daemon{seq=DocSeq, ni_pos=NewPos, btree=NewBtree}} end; true -> ?LOG_DEBUG("deleted: ~p", [DocSeq]), case couch_btree:lookup(Btree, [DocId]) of [{ok, Numidx}] -> {ok, NewPos} = numidx:delete({Fd, Pos}, {DocId, Numidx}), {ok, NewBtree} = couch_btree:add_remove(Btree, [], [DocId]), {ok, StateCur#ni_daemon{seq=DocSeq, ni_pos=NewPos, btree=NewBtree}}; [not_found] -> % If it's not in the Back-Index it's not in numidxx {ok, StateCur#ni_daemon{seq=DocSeq}} end end end, State, []), couch_file:write_header(StateNew#ni_daemon.fd, StateNew), {reply, ok, StateNew}; handle_call({do_range_search, Range}, _From, #ni_daemon{fd=Fd, ni_pos=Pos}=State) -> Result = numidx:range({Fd, Pos}, Range), {reply, Result, State}; handle_call(_Req, _From, State) -> {noreply, State}. handle_cast(_Msg, State) -> {noreply, State}. handle_info(_Msg, Server) -> {noreply, Server}. code_change(_OldVsn, State, _Extra) -> {ok, State}.