summaryrefslogtreecommitdiff
path: root/src/couchdb/couch_ft_query.erl
blob: 67fa5948e85e5471a52b4f258c8c5695bc5734e1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
% 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(couch_ft_query).
-behaviour(gen_server).

-export([start_link/0, execute/2]).

-export([init/1, terminate/2, handle_call/3, handle_cast/2, handle_info/2,code_change/3, stop/0]).

start_link() ->
    gen_server:start_link({local, couch_ft_query}, couch_ft_query, [], []).

stop() ->
    exit(whereis(couch_ft_query), close).

execute(DatabaseName, QueryString) ->
    gen_server:call(couch_ft_query, {ft_query, DatabaseName, QueryString}).

init([]) ->
    ok = couch_config:register(
        fun({"Search", "QueryServer"}) ->
            ?MODULE:stop()
        end),
    
    case couch_config:get({"Search", "QueryServer"}, none) of
    none ->
        {ok, none};
    QueryExec ->
        Port = open_port({spawn, QueryExec}, [{line, 1000}, exit_status, hide]),
        {ok, Port}
    end.

terminate(_Reason, _Server) ->
    ok.

handle_call({ft_query, _Database, _QueryText}, _From, none) ->
    {reply, {error, no_full_test_query_specified_in_config}, none};
handle_call({ft_query, Database, QueryText}, _From, Port) ->
    % send the database name
    true = port_command(Port, Database ++ "\n"),
    true = port_command(Port, QueryText ++ "\n"),
    case get_line(Port) of
    "ok" ->
        DocIds = read_query_results(Port, []),
        {reply, {ok, DocIds}, Port};
    "error" ->
        ErrorId = get_line(Port),
        ErrorMsg = get_line(Port),
        {reply, {list_to_atom(ErrorId), ErrorMsg}, Port}
    end.

read_query_results(Port, Acc) ->
    case get_line(Port) of
    "" -> % line by itself means all done
        lists:reverse(Acc);
    DocId ->
        Score = get_line(Port),
        read_query_results(Port, [{DocId, Score} | Acc])
    end.


get_line(Port) ->
    receive
    {Port, {data, {eol, Line}}} ->
        Line;
    % would love to use ?ERR_HANDLE here, but edoc doesn't like it.
    % TODO: find a way to skip that.
    {Port, {exit_status, Status}} -> {stop, {unknown_error, Status}, {unknown_error, Status}, Port}
    end.

handle_cast(_Whatever, State) ->
    {noreply, State}.

handle_info({Port, {exit_status, Status}}, Port) ->
    {stop, {os_process_exited, Status}, Port};
handle_info(_Whatever, State) ->
    {noreply, State}.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.