Skip to content

Commit bd22428

Browse files
committed
[WIP] Selector-based simple views
Try to see what a simple selector-based views might look like. Inspired by comments in the "allow_fallback" pr and the discussion in CouchDB dev meeting. This is as simple as possible: * We already have support for design doc languages, so go with that: use "selector" as a language, then use the regular `_design/../_view` API * `selector` picks documents to be indexed. The usual selector syntax, nothing new * `keys` - list of field values emitted as the key (an array) * `values` - list of fields values emitted as the value (an array) Example (DB=http://adm:pass@127.0.0.1:15984) ``` echo "Recreating DB" http -q delete $DB/db1 http -b put $DB/db1 echo "Adding some docs" http -b put $DB/db1/a k:='1' v=x s:='10' http -b put $DB/db1/b k:='3' v=x s:='11' http -b put $DB/db1/c k:='4' v=y s:='12' http -b put $DB/db1/d k:='2' v=x s:='13' http -b put $DB/db1/e k:='5' v=x s:='14' http -b put $DB/db1/f w:='1' v=x s:='15' http -b put $DB/db1/g k:='3' o=o s:='16' echo "Add two silly selector thingers" http -b put $DB/db1/_design/s language=selector views:='{ "v1": {"map": {"selector": {"s":{"$gt":12}}, "keys":["k"], "values":["v"]}}, "v2": {"map": {"keys":["w"]}} }' echo "Query the silly selector thinger" http -b $DB/db1/_design/s/_view/v1 echo "Query the even sillier one without a selector" http -b $DB/db1/_design/s/_view/v2 ``` Query the silly selector thinger ``` { "offset": 0, "rows": [ {"id": "d", "key": [2], "value": ["x"]}, {"id": "e", "key": [5], "value": ["x"]} ], "total_rows": 2 } ``` Query the even sillier one without a selector. Pick docs based on `keys` only. ``` { "offset": 0, "rows": [ {"id": "f", "key": [1], "value": null} ], "total_rows": 1 } ```
1 parent 1f3396c commit bd22428

File tree

3 files changed

+166
-0
lines changed

3 files changed

+166
-0
lines changed

src/couch/src/couch_proc_manager.erl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ init([]) ->
142142
ets:insert(?SERVERS, get_servers_from_env("COUCHDB_QUERY_SERVER_")),
143143
ets:insert(?SERVERS, get_servers_from_env("COUCHDB_NATIVE_QUERY_SERVER_")),
144144
ets:insert(?SERVERS, [{"QUERY", {mango_native_proc, start_link, []}}]),
145+
ets:insert(?SERVERS, [{"SELECTOR", {mango_simple_proc, start_link, []}}]),
145146
maybe_configure_erlang_native_servers(),
146147
configure_js_engine(couch_server:get_js_engine()),
147148

src/couch_mrview/src/couch_mrview.erl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ validate_ddoc_field1([{Key, Value} | Rest], Type) ->
177177
map_function_type({Props}) ->
178178
case couch_util:get_value(<<"language">>, Props) of
179179
<<"query">> -> object;
180+
<<"selector">> -> object;
180181
_ -> string
181182
end.
182183

src/mango/src/mango_simple_proc.erl

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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(mango_simple_proc).
14+
-behavior(gen_server).
15+
16+
-export([
17+
start_link/0,
18+
set_timeout/2,
19+
prompt/2
20+
]).
21+
22+
-export([
23+
init/1,
24+
handle_call/3,
25+
handle_cast/2
26+
]).
27+
28+
% DDoc Fields
29+
-define(SELECTOR, <<"selector">>).
30+
-define(KEYS, <<"keys">>).
31+
-define(VALUES, <<"values">>).
32+
33+
% Proc commands
34+
-define(RESET, <<"reset">>).
35+
-define(ADD_FUN, <<"add_fun">>).
36+
-define(MAP_DOC, <<"map_doc">>).
37+
-define(REDUCE, <<"reduce">>).
38+
-define(REREDUCE, <<"rereduce">>).
39+
40+
-record(st, {
41+
indexes = [],
42+
timeout = 5000
43+
}).
44+
45+
-record(idx, {
46+
selector,
47+
keys,
48+
values
49+
}).
50+
51+
start_link() ->
52+
gen_server:start_link(?MODULE, [], []).
53+
54+
set_timeout(Pid, TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
55+
gen_server:call(Pid, {set_timeout, TimeOut}).
56+
57+
prompt(Pid, Data) ->
58+
gen_server:call(Pid, {prompt, Data}).
59+
60+
init(_) ->
61+
{ok, #st{}}.
62+
63+
handle_call({set_timeout, TimeOut}, _From, #st{} = St) ->
64+
{reply, ok, St#st{timeout = TimeOut}};
65+
handle_call({prompt, [?RESET | _QueryConfig]}, _From, #st{} = St) ->
66+
{reply, true, St#st{indexes = []}};
67+
handle_call({prompt, [?ADD_FUN, IndexInfo | _IgnoreRest]}, _From, #st{} = St) ->
68+
#st{indexes = Indexes} = St,
69+
case get_index_def(IndexInfo) of
70+
#idx{} = Idx -> {reply, true, St#st{indexes = Indexes ++ [Idx]}};
71+
undefined -> {reply, true, St}
72+
end;
73+
handle_call({prompt, [?MAP_DOC, Doc]}, _From, St) ->
74+
{reply, map_doc(St, mango_json:to_binary(Doc)), St};
75+
handle_call({prompt, [?REDUCE, RedSrcs, _]}, _From, St) ->
76+
{reply, [true, [null || _ <- RedSrcs]], St};
77+
handle_call({prompt, [?REREDUCE, RedSrcs, _]}, _From, St) ->
78+
{reply, [true, [null || _ <- RedSrcs]], St};
79+
handle_call(Msg, _From, St) ->
80+
{stop, {invalid_call, Msg}, {invalid_call, Msg}, St}.
81+
82+
handle_cast(garbage_collect, St) ->
83+
erlang:garbage_collect(),
84+
{noreply, St};
85+
handle_cast(stop, St) ->
86+
{stop, normal, St};
87+
handle_cast(Msg, St) ->
88+
{stop, {invalid_cast, Msg}, St}.
89+
90+
map_doc(#st{indexes = Indexes}, Doc) ->
91+
lists:map(fun(Idx) -> get_index_entries(Idx, Doc) end, Indexes).
92+
93+
get_index_entries(#idx{} = Idx, Doc) ->
94+
#idx{selector = Selector, keys = Keys, values = Values} = Idx,
95+
case should_index(Selector, Doc) of
96+
false -> [];
97+
true -> process_doc(Keys, Values, Doc)
98+
end.
99+
100+
process_doc(Keys, undefined, Doc) ->
101+
case get_index_values(Keys, Doc) of
102+
[] -> [];
103+
[_ | _] = KeyResults -> [[KeyResults, null]]
104+
end;
105+
process_doc(Keys, [_ | _] = Values, Doc) ->
106+
case get_index_values(Keys, Doc) of
107+
[] ->
108+
[];
109+
[_ | _] = KeyResults ->
110+
case get_index_values(Values, Doc) of
111+
[] -> [];
112+
[_ | _] = ValueResults -> [[KeyResults, ValueResults]]
113+
end
114+
end.
115+
116+
get_index_values(Fields, Doc) ->
117+
MapF = fun(Field) ->
118+
case mango_doc:get_field(Doc, Field) of
119+
not_found -> not_found;
120+
bad_path -> not_found;
121+
Value -> Value
122+
end
123+
end,
124+
Results = lists:map(MapF, Fields),
125+
case lists:member(not_found, Results) of
126+
true -> [];
127+
false -> Results
128+
end.
129+
130+
should_index(Selector, Doc) ->
131+
case mango_doc:get_field(Doc, <<"_id">>) of
132+
<<"_design/", _/binary>> -> false;
133+
_ -> mango_selector:match(Selector, Doc)
134+
end.
135+
136+
get_selector({IdxProps}) ->
137+
case couch_util:get_value(?SELECTOR, IdxProps, {[]}) of
138+
{L} = Selector when is_list(L) -> mango_selector:normalize(Selector);
139+
_ -> undefined
140+
end.
141+
142+
get_field({IdxProps}, <<FieldName/binary>>) ->
143+
case couch_util:get_value(FieldName, IdxProps) of
144+
Fields when is_list(Fields) ->
145+
case lists:all(fun is_binary/1, Fields) of
146+
true -> Fields;
147+
false -> undefined
148+
end;
149+
_ ->
150+
undefined
151+
end.
152+
153+
get_index_def(IndexInfo) ->
154+
Selector = get_selector(IndexInfo),
155+
Keys = get_field(IndexInfo, ?KEYS),
156+
Values = get_field(IndexInfo, ?VALUES),
157+
case {Selector, Keys, Values} of
158+
{{_}, [_ | _], [_ | _]} ->
159+
#idx{selector = Selector, keys = Keys, values = Values};
160+
{{_}, [_ | _], undefined} ->
161+
#idx{selector = Selector, keys = Keys, values = undefined};
162+
{_, _, _} ->
163+
undefined
164+
end.

0 commit comments

Comments
 (0)