Skip to content

Instantly share code, notes, and snippets.

@janl
Last active December 3, 2021 16:47
Show Gist options
  • Save janl/e5469f6f08c9be0405f31451889d5030 to your computer and use it in GitHub Desktop.
Save janl/e5469f6f08c9be0405f31451889d5030 to your computer and use it in GitHub Desktop.

Mango Functions & Values

Inspired by apache/couchdb#3828, a draft proposal for adding functions to Mango that can manipulate the data that goes into the index.

Extends the fields index syntax from:

"fields": [{"fieldname":"sort_dir"}]

to

"fields: [{"key": {
    "$fun_name": { "$arg1":"arg1Value", "$arg2": "arg2Value", …}
}

key is the name of the index key that is present in the result set and that can be used in the selector to query on.

$fun_name is the name of the function. I’m imagining the usual slew of string and array funs, maybe some date handling, etc. Probably modelled after the JS standard lib functions.

$argN and $argNValue are additional arguments passed to the function. This could be references to other fields, or any other JSON properties that make sense for a function.

We can also easily extend this to reproduce the value functionality of M/R views emit(key, value) where Mango today only supports emit(key) effectively, even with the additional functions.

Like so:

map: {
  fields: { ... },
  values: {
    "field_name": {
      "$fun_name": {"$arg1": "$arg1Value",…}
    }
  }
}

Where fieldname is where the function result is stored, and returned in the view result like so:

"docs":[
    {"_id":"doc","_rev":"1-XX","bar":"a b c", "_values": { "field_name": "fun_result" }}
  ],
{
"language": "query",
"views": {
"foo-json-index": {
"map": {
"fields": {
"bar_exp": {
"$explode": { "$field":"bar", "$separator": " "}
}
},
"partial_filter_selector": {}
},
"reduce": "_count",
"options": {
"def": {
"fields": [
"bar_exp"
]
}
}
}
}
}
{"bar": "a b c"}
{"selector": {"bar_exp": {"$eq": "b"}}, "use_index":"mangofun/foo-json-index"}
{
"docs":[
{"_id":"doc","_rev":"1-62a23e7b7f0ded1624f42489e5d0c800","bar":"a b c","bar_exp":"b"}
],
"bookmark": "g1AAAAA6eJzLYWBgYMpgSmHgKy5JLCrJTq2MT8lPzkzJBYozA1kgKQ6YVA5QkBEkw5iUlQUAxrsPKA"
}
diff --git a/src/mango/src/mango_cursor_view.erl b/src/mango/src/mango_cursor_view.erl
index 68d7c3b62..88ffa8ba3 100644
--- a/src/mango/src/mango_cursor_view.erl
+++ b/src/mango/src/mango_cursor_view.erl
@@ -230,6 +230,8 @@ choose_best_index(_DbName, IndexRanges) ->
{SelectedIndex, SelectedIndexRanges, _} = hd(lists:sort(Cmp, IndexRanges)),
{SelectedIndex, SelectedIndexRanges}.
+add_virtual_field({Props}, Key, [Value]) ->
+ {lists:append(Props, [{Key, Value}])}.
view_cb({meta, Meta}, Acc) ->
% Map function starting
@@ -238,9 +240,10 @@ view_cb({meta, Meta}, Acc) ->
ok = rexi:stream2({meta, Meta}),
{ok, Acc};
view_cb({row, Row}, #mrargs{extra = Options} = Acc) ->
+ Key = couch_util:get_value(key, Row),
ViewRow = #view_row{
id = couch_util:get_value(id, Row),
- key = couch_util:get_value(key, Row),
+ key = Key,
doc = couch_util:get_value(doc, Row)
},
case ViewRow#view_row.doc of
@@ -254,9 +257,12 @@ view_cb({row, Row}, #mrargs{extra = Options} = Acc) ->
put(mango_docs_examined, get(mango_docs_examined) + 1),
Selector = couch_util:get_value(selector, Options),
couch_stats:increment_counter([mango, docs_examined]),
- case mango_selector:match(Selector, Doc) of
+ {[{VirtualField, _}]} = Selector,
+ Doc1 = add_virtual_field(Doc, VirtualField, Key),
+ case mango_selector:match(Selector, Doc1) of
true ->
- ok = rexi:stream2(ViewRow),
+ ViewRow1 =ViewRow#view_row{doc = Doc1},
+ ok = rexi:stream2(ViewRow1),
set_mango_msg_timestamp();
false ->
maybe_send_mango_ping()
diff --git a/src/mango/src/mango_doc.erl b/src/mango/src/mango_doc.erl
index c22b15544..2038359f9 100644
--- a/src/mango/src/mango_doc.erl
+++ b/src/mango/src/mango_doc.erl
@@ -22,6 +22,7 @@
get_field/2,
get_field/3,
+ get_field_fun/2,
rem_field/2,
set_field/3
]).
@@ -408,6 +409,19 @@ get_field(Values, [Name | Rest], Validator) when is_list(Values) ->
get_field(_, [_|_], _) ->
bad_path.
+get_field_fun(Props, MangoFun) ->
+ {FunName, {Args}} = MangoFun,
+ case FunName of
+ <<"$explode">> -> handle_explode(Props, Args);
+ _ -> bad_path
+ end.
+
+handle_explode({Doc}, Args) ->
+ FieldName = proplists:get_value(<<"$field">>, Args),
+ Separator = proplists:get_value(<<"$separator">>, Args),
+ {_, FieldValue} = lists:keyfind(FieldName, 1, Doc),
+ R = string:split(FieldValue, Separator, all),
+ {fn, R}.
rem_field(Props, Field) when is_binary(Field) ->
{ok, Path} = mango_util:parse_field(Field),
diff --git a/src/mango/src/mango_native_proc.erl b/src/mango/src/mango_native_proc.erl
index 274ae11de..df0418bc6 100644
--- a/src/mango/src/mango_native_proc.erl
+++ b/src/mango/src/mango_native_proc.erl
@@ -146,13 +146,21 @@ get_index_entries({IdxProps}, Doc) ->
Values = get_index_values(Fields, Doc),
case lists:member(not_found, Values) of
true -> [];
- false -> [[Values, null]]
+ false -> case Values of
+ [{fn, Values1}] -> lists:map(fun(V) -> [[V], null] end, Values1);
+ _Else -> [[Values, null]]
+ end
end
end.
-
get_index_values(Fields, Doc) ->
- lists:map(fun({Field, _Dir}) ->
+ lists:map(fun({_Field, {[MangoFun]}}) ->
+ case mango_doc:get_field_fun(Doc, MangoFun) of
+ not_found -> not_found;
+ bad_path -> not_found;
+ Value -> Value
+ end;
+ ({Field, _Dir}) ->
case mango_doc:get_field(Doc, Field) of
not_found -> not_found;
bad_path -> not_found;
#!/bin/sh -x
COUCH=a:a@127.0.0.1:15984
curl -sX DELETE $COUCH/mangofun
curl -sX PUT $COUCH/mangofun?q=1
curl -sX PUT $COUCH/mangofun/_design%2fmangofun --data-binary @ddoc.json
#curl -sX PUT $COUCH/mangofun/_design%2fmangofun2 --data-binary @ddoc2.json
#curl -sX PUT $COUCH/mangofun/_design%2fmangofun3 --data-binary @ddoc3.json
curl -sX PUT $COUCH/mangofun/doc --data-binary @doc.json
#curl -sX PUT $COUCH/mangofun/doc2 --data-binary @doc2.json
curl -sX POST -H content-type:application/json $COUCH/mangofun/_find --data-binary @selector.json
#curl -sX POST -H content-type:application/json $COUCH/mangofun/_find --data-binary @selector2.json
#curl -s $COUCH/mangofun/_design/mangofun3/_view/foo?startkey='"b"'
# curl -sX POST -H content-type:application/json $COUCH/mangofun/_explain --data-binary @selector.json | jq
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment