Limit result set of disco#items for mod_pubsub

The size of a list of nodes returned for disco#items request
is now controlled by option 'max_nodes_discoitems'. The default
value is 100. The name and the default value of the option is
chosen to be consistent with mod_muc's 'max_rooms_discoitems' option.
This commit is contained in:
Evgeny Khramtsov 2019-10-24 14:59:47 +03:00
parent d300a87059
commit c604bdb897
6 changed files with 101 additions and 49 deletions

View File

@ -61,7 +61,7 @@
{error, stanza_error()}.
-callback get_nodes(Host :: host(),
From :: jid:jid())->
Limit :: non_neg_integer() | infinity)->
[pubsubNode()].
-callback get_nodes(Host :: host())->
@ -80,7 +80,7 @@
-callback get_subnodes(Host :: host(),
NodeId :: nodeId(),
From :: jid:jid()) ->
Limit :: non_neg_integer() | infinity) ->
[pubsubNode()].
-callback get_subnodes_tree(Host :: host(),

View File

@ -498,6 +498,7 @@ disco_sm_items(Acc, _From, _To, _Node, _Lang) -> Acc.
-spec disco_items(ljid(), binary(), jid()) -> [disco_item()].
disco_items(Host, <<>>, From) ->
MaxNodes = mod_pubsub_opt:max_nodes_discoitems(serverhost(Host)),
Action =
fun(#pubsub_node{nodeid = {_, Node}, options = Options,
type = Type, id = Nidx, owners = O}, Acc) ->
@ -513,7 +514,7 @@ disco_items(Host, <<>>, From) ->
end
end,
NodeBloc = fun() ->
case tree_call(Host, get_nodes, [Host]) of
case tree_call(Host, get_nodes, [Host, MaxNodes]) of
Nodes when is_list(Nodes) ->
{result, lists:foldl(Action, [], Nodes)};
Error ->
@ -1007,8 +1008,9 @@ iq_disco_info(ServerHost, Host, SNode, From, Lang) ->
-spec iq_disco_items(host(), binary(), jid(), undefined | rsm_set()) ->
{result, disco_items()} | {error, stanza_error()}.
iq_disco_items(Host, <<>>, From, _RSM) ->
case tree_action(Host, get_subnodes, [Host, <<>>, From]) of
iq_disco_items(Host, <<>>, _From, _RSM) ->
MaxNodes = mod_pubsub_opt:max_nodes_discoitems(serverhost(Host)),
case tree_action(Host, get_subnodes, [Host, <<>>, MaxNodes]) of
{error, #stanza_error{}} = Err ->
Err;
Nodes when is_list(Nodes) ->
@ -1039,6 +1041,7 @@ iq_disco_items(Host, Item, From, RSM) ->
[_Node, _ItemId] ->
{result, #disco_items{}};
[Node] ->
MaxNodes = mod_pubsub_opt:max_nodes_discoitems(serverhost(Host)),
Action = fun(#pubsub_node{id = Nidx, type = Type, options = Options, owners = O}) ->
Owners = node_owners_call(Host, Type, Nidx, O),
{NodeItems, RsmOut} = case get_allowed_items_call(
@ -1046,7 +1049,7 @@ iq_disco_items(Host, Item, From, RSM) ->
{result, R} -> R;
_ -> {[], undefined}
end,
case tree_call(Host, get_subnodes, [Host, Node, From]) of
case tree_call(Host, get_subnodes, [Host, Node, MaxNodes]) of
SubNodes when is_list(SubNodes) ->
Nodes = lists:map(
fun(#pubsub_node{nodeid = {_, SubNode}, options = SubOptions}) ->
@ -3154,7 +3157,7 @@ send_last_pep(From, To) ->
Host = host(ServerHost),
Publisher = jid:tolower(From),
Owner = jid:remove_resource(Publisher),
case tree_action(Host, get_nodes, [Owner, From]) of
case tree_action(Host, get_nodes, [Owner, infinity]) of
Nodes when is_list(Nodes) ->
lists:foreach(
fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) ->
@ -4123,6 +4126,8 @@ mod_opt_type(last_item_cache) ->
econf:bool();
mod_opt_type(max_items_node) ->
econf:non_neg_int();
mod_opt_type(max_nodes_discoitems) ->
econf:non_neg_int(infinity);
mod_opt_type(max_subscriptions_node) ->
econf:non_neg_int();
mod_opt_type(force_node_config) ->
@ -4168,6 +4173,7 @@ mod_options(Host) ->
{ignore_pep_from_offline, true},
{last_item_cache, false},
{max_items_node, ?MAXITEMS},
{max_nodes_discoitems, 100},
{nodetree, ?STDTREE},
{pep_mapping, []},
{plugins, [?STDNODE]},

View File

@ -12,6 +12,7 @@
-export([ignore_pep_from_offline/1]).
-export([last_item_cache/1]).
-export([max_items_node/1]).
-export([max_nodes_discoitems/1]).
-export([max_subscriptions_node/1]).
-export([name/1]).
-export([nodetree/1]).
@ -73,6 +74,12 @@ max_items_node(Opts) when is_map(Opts) ->
max_items_node(Host) ->
gen_mod:get_module_opt(Host, mod_pubsub, max_items_node).
-spec max_nodes_discoitems(gen_mod:opts() | global | binary()) -> 'infinity' | non_neg_integer().
max_nodes_discoitems(Opts) when is_map(Opts) ->
gen_mod:get_opt(max_nodes_discoitems, Opts);
max_nodes_discoitems(Host) ->
gen_mod:get_module_opt(Host, mod_pubsub, max_nodes_discoitems).
-spec max_subscriptions_node(gen_mod:opts() | global | binary()) -> 'undefined' | non_neg_integer().
max_subscriptions_node(Opts) when is_map(Opts) ->
gen_mod:get_opt(max_subscriptions_node, Opts);

View File

@ -38,6 +38,7 @@
-author('christophe.romain@process-one.net').
-include_lib("stdlib/include/qlc.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-include("pubsub.hrl").
-include("xmpp.hrl").
@ -81,11 +82,21 @@ get_node(Nidx) ->
_ -> {error, xmpp:err_item_not_found(?T("Node not found"), ejabberd_option:language())}
end.
get_nodes(Host, _From) ->
get_nodes(Host).
get_nodes(Host) ->
mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, _ = '_'}).
get_nodes(Host, infinity).
get_nodes(Host, infinity) ->
mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, _ = '_'});
get_nodes(Host, Limit) ->
case mnesia:select(
pubsub_node,
ets:fun2ms(
fun(#pubsub_node{nodeid = {H, _}} = Node) when H == Host ->
Node
end), Limit, read) of
'$end_of_table' -> [];
{Nodes, _} -> Nodes
end.
get_parentnodes(Host, Node, _From) ->
case catch mnesia:read({pubsub_node, {Host, Node}}) of
@ -109,25 +120,40 @@ get_parentnodes_tree(Host, Node, Level, Acc) ->
Acc
end.
get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node).
get_subnodes(Host, <<>>) ->
Q = qlc:q([N
|| #pubsub_node{nodeid = {NHost, _},
parents = Parents} =
N
<- mnesia:table(pubsub_node),
Host == NHost, Parents == []]),
qlc:e(Q);
get_subnodes(Host, Node) ->
get_subnodes(Host, <<>>, infinity) ->
mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, parents = [], _ = '_'});
get_subnodes(Host, <<>>, Limit) ->
case mnesia:select(
pubsub_node,
ets:fun2ms(
fun(#pubsub_node{nodeid = {H, _}, parents = []} = Node) when H == Host ->
Node
end), Limit, read) of
'$end_of_table' -> [];
{Nodes, _} -> Nodes
end;
get_subnodes(Host, Node, infinity) ->
Q = qlc:q([N
|| #pubsub_node{nodeid = {NHost, _},
parents = Parents} =
N
<- mnesia:table(pubsub_node),
Host == NHost, lists:member(Node, Parents)]),
qlc:e(Q).
qlc:e(Q);
get_subnodes(Host, Node, Limit) ->
case mnesia:select(
pubsub_node,
ets:fun2ms(
fun(#pubsub_node{nodeid = {H, _}, parents = Ps} = N)
when H == Host andalso Ps /= [] -> N
end), Limit, read) of
'$end_of_table' -> [];
{Nodes, _} ->
lists:filter(
fun(#pubsub_node{parents = Parents}) ->
lists:member(Node, Parents)
end, Nodes)
end.
get_subnodes_tree(Host, Node, _From) ->
get_subnodes_tree(Host, Node).

View File

@ -140,16 +140,25 @@ get_node(Nidx) ->
{error, xmpp:err_item_not_found(?T("Node not found"), ejabberd_option:language())}
end.
get_nodes(Host, _From) ->
get_nodes(Host).
get_nodes(Host) ->
get_nodes(Host, infinity).
get_nodes(Host, Limit) ->
H = node_flat_sql:encode_host(Host),
case catch
Query = fun(mssql, _) when is_integer(Limit), Limit>=0 ->
ejabberd_sql:sql_query_t(
?SQL("select @(node)s, @(parent)s, @(plugin)s, @(nodeid)d from pubsub_node "
"where host=%(H)s"))
of
?SQL("select top %(Limit)d @(node)s, @(parent)s, @(plugin)s, @(nodeid)d "
"from pubsub_node where host=%(H)s"));
(_, _) when is_integer(Limit), Limit>=0 ->
ejabberd_sql:sql_query_t(
?SQL("select @(node)s, @(parent)s, @(plugin)s, @(nodeid)d "
"from pubsub_node where host=%(H)s limit %(Limit)d"));
(_, _) ->
ejabberd_sql:sql_query_t(
?SQL("select @(node)s, @(parent)s, @(plugin)s, @(nodeid)d "
"from pubsub_node where host=%(H)s"))
end,
case ejabberd_sql:sql_query_t(Query) of
{selected, RItems} ->
[raw_to_node(Host, Item) || Item <- RItems];
_ ->
@ -178,16 +187,23 @@ get_parentnodes_tree(Host, Node, Level, Acc) ->
Acc
end.
get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node).
get_subnodes(Host, Node) ->
get_subnodes(Host, Node, Limit) ->
H = node_flat_sql:encode_host(Host),
case catch
Query = fun(mssql, _) when is_integer(Limit), Limit>=0 ->
ejabberd_sql:sql_query_t(
?SQL("select @(node)s, @(parent)s, @(plugin)s, @(nodeid)d from pubsub_node "
"where host=%(H)s and parent=%(Node)s"))
of
?SQL("select top %(Limit)d @(node)s, @(parent)s, @(plugin)s, @(nodeid)d "
"from pubsub_node where host=%(H)s and parent=%(Node)s"));
(_, _) when is_integer(Limit), Limit>=0 ->
ejabberd_sql:sql_query_t(
?SQL("select @(node)s, @(parent)s, @(plugin)s, @(nodeid)d "
"from pubsub_node where host=%(H)s and parent=%(Node)s "
"limit %(Limit)d"));
(_, _) ->
ejabberd_sql:sql_query_t(
?SQL("select @(node)s, @(parent)s, @(plugin)s, @(nodeid)d "
"from pubsub_node where host=%(H)s and parent=%(Node)s"))
end,
case ejabberd_sql:sql_query_t(Query) of
{selected, RItems} ->
[raw_to_node(Host, Item) || Item <- RItems];
_ ->

View File

@ -65,10 +65,10 @@ get_node(Nidx) ->
{Host, Node} = nodeid(Nidx),
node_record(Host, Node, Nidx).
get_nodes(Host, _From) ->
get_nodes(Host).
get_nodes(Host) ->
get_nodes(Host, infinity).
get_nodes(_Host) ->
get_nodes(_Host, _Limit) ->
[].
get_parentnodes(_Host, _Node, _From) ->
@ -77,10 +77,7 @@ get_parentnodes(_Host, _Node, _From) ->
get_parentnodes_tree(Host, Node, From) ->
[{0, [get_node(Host, Node, From)]}].
get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node).
get_subnodes(_Host, _Node) ->
get_subnodes(_Host, _Node, _From) ->
[].
get_subnodes_tree(Host, Node, _From) ->