aboutsummaryrefslogtreecommitdiff
path: root/src/nodetree_tree_sql.erl
diff options
context:
space:
mode:
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>2016-04-20 12:27:32 +0300
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>2016-04-20 13:25:42 +0300
commit1aae8a9fda184b8a8fa139d9fc4bf2aa2c6ba8a0 (patch)
tree678fb932d5bda2fdc2961c054dbaea64d81f977c /src/nodetree_tree_sql.erl
parentRename ejabberd_sm_odbc -> ejabberd_sm_sql (diff)
Rename odbc to sql everywhere
Diffstat (limited to 'src/nodetree_tree_sql.erl')
-rw-r--r--src/nodetree_tree_sql.erl314
1 files changed, 314 insertions, 0 deletions
diff --git a/src/nodetree_tree_sql.erl b/src/nodetree_tree_sql.erl
new file mode 100644
index 000000000..b56543395
--- /dev/null
+++ b/src/nodetree_tree_sql.erl
@@ -0,0 +1,314 @@
+%%%----------------------------------------------------------------------
+%%% File : nodetree_tree_sql.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Standard node tree plugin with ODBC backend
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
+%%%
+%%% This program is free software; you can redistribute it and/or
+%%% modify it under the terms of the GNU General Public License as
+%%% published by the Free Software Foundation; either version 2 of the
+%%% License, or (at your option) any later version.
+%%%
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%%% General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License along
+%%% with this program; if not, write to the Free Software Foundation, Inc.,
+%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+%%%
+%%%----------------------------------------------------------------------
+
+%%% @doc The module <strong>{@module}</strong> is the default PubSub node tree plugin.
+%%% <p>It is used as a default for all unknown PubSub node type. It can serve
+%%% as a developer basis and reference to build its own custom pubsub node tree
+%%% types.</p>
+%%% <p>PubSub node tree plugins are using the {@link gen_nodetree} behaviour.</p>
+%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin
+%%% development is still a work in progress. However, the system is already
+%%% useable and useful as is. Please, send us comments, feedback and
+%%% improvements.</p>
+
+-module(nodetree_tree_sql).
+-behaviour(gen_pubsub_nodetree).
+-author('christophe.romain@process-one.net').
+
+-include("pubsub.hrl").
+-include("jlib.hrl").
+
+-export([init/3, terminate/2, options/0, set_node/1,
+ get_node/3, get_node/2, get_node/1, get_nodes/2,
+ get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3,
+ get_subnodes/3, get_subnodes_tree/3, create_node/6,
+ delete_node/2]).
+
+-export([raw_to_node/2]).
+
+init(_Host, _ServerHost, _Opts) ->
+ ok.
+
+terminate(_Host, _ServerHost) ->
+ ok.
+
+options() ->
+ [{sql, true} | nodetree_tree:options()].
+
+set_node(Record) when is_record(Record, pubsub_node) ->
+ {Host, Node} = Record#pubsub_node.nodeid,
+ Parent = case Record#pubsub_node.parents of
+ [] -> <<>>;
+ [First | _] -> First
+ end,
+ Type = Record#pubsub_node.type,
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
+ P = ejabberd_sql:escape(Parent),
+ Nidx = case nodeidx(Host, Node) of
+ {result, OldNidx} ->
+ catch
+ ejabberd_sql:sql_query_t([<<"delete from pubsub_node_option where "
+ "nodeid='">>, OldNidx, <<"';">>]),
+ catch
+ ejabberd_sql:sql_query_t([<<"update pubsub_node set host='">>,
+ H, <<"' node='">>, N,
+ <<"' parent='">>, P,
+ <<"' type='">>, Type,
+ <<"' where nodeid='">>,
+ OldNidx, <<"';">>]),
+ OldNidx;
+ _ ->
+ catch
+ ejabberd_sql:sql_query_t([<<"insert into pubsub_node(host, node, "
+ "parent, type) values('">>,
+ H, <<"', '">>, N, <<"', '">>, P,
+ <<"', '">>, Type, <<"');">>]),
+ case nodeidx(Host, Node) of
+ {result, NewNidx} -> NewNidx;
+ _ -> none % this should not happen
+ end
+ end,
+ case Nidx of
+ none ->
+ Txt = <<"Node index not found">>,
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, Txt)};
+ _ ->
+ lists:foreach(fun ({Key, Value}) ->
+ SKey = iolist_to_binary(atom_to_list(Key)),
+ SValue = ejabberd_sql:escape(
+ list_to_binary(
+ lists:flatten(io_lib:fwrite("~p", [Value])))),
+ catch
+ ejabberd_sql:sql_query_t([<<"insert into pubsub_node_option(nodeid, "
+ "name, val) values('">>,
+ Nidx, <<"', '">>,
+ SKey, <<"', '">>, SValue, <<"');">>])
+ end,
+ Record#pubsub_node.options),
+ {result, Nidx}
+ end.
+
+get_node(Host, Node, _From) ->
+ get_node(Host, Node).
+
+get_node(Host, Node) ->
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
+ case catch
+ ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
+ "pubsub_node where host='">>,
+ H, <<"' and node='">>, N, <<"';">>])
+ of
+ {selected,
+ [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], [RItem]} ->
+ raw_to_node(Host, RItem);
+ {'EXIT', _Reason} ->
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)};
+ _ ->
+ {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
+ end.
+
+get_node(Nidx) ->
+ case catch
+ ejabberd_sql:sql_query_t([<<"select host, node, parent, type from "
+ "pubsub_node where nodeid='">>,
+ Nidx, <<"';">>])
+ of
+ {selected,
+ [<<"host">>, <<"node">>, <<"parent">>, <<"type">>], [[Host, Node, Parent, Type]]} ->
+ raw_to_node(Host, [Node, Parent, Type, Nidx]);
+ {'EXIT', _Reason} ->
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)};
+ _ ->
+ {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
+ end.
+
+get_nodes(Host, _From) ->
+ get_nodes(Host).
+
+get_nodes(Host) ->
+ H = node_flat_sql:encode_host(Host),
+ case catch
+ ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
+ "pubsub_node where host='">>, H, <<"';">>])
+ of
+ {selected,
+ [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], RItems} ->
+ [raw_to_node(Host, Item) || Item <- RItems];
+ _ ->
+ []
+ end.
+
+get_parentnodes(_Host, _Node, _From) ->
+ [].
+
+%% @doc <p>Default node tree does not handle parents, return a list
+%% containing just this node.</p>
+get_parentnodes_tree(Host, Node, From) ->
+ case get_node(Host, Node, From) of
+ {error, _} -> [];
+ Record -> [{0, [Record]}]
+ end.
+
+get_subnodes(Host, Node, _From) ->
+ get_subnodes(Host, Node).
+
+get_subnodes(Host, Node) ->
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
+ case catch
+ ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
+ "pubsub_node where host='">>,
+ H, <<"' and parent='">>, N, <<"';">>])
+ of
+ {selected,
+ [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], RItems} ->
+ [raw_to_node(Host, Item) || Item <- RItems];
+ _ ->
+ []
+ end.
+
+get_subnodes_tree(Host, Node, _From) ->
+ get_subnodes_tree(Host, Node).
+
+get_subnodes_tree(Host, Node) ->
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
+ case catch
+ ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
+ "pubsub_node where host='">>,
+ H, <<"' and node like '">>, N, <<"%';">>])
+ of
+ {selected,
+ [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], RItems} ->
+ [raw_to_node(Host, Item) || Item <- RItems];
+ _ ->
+ []
+ end.
+
+create_node(Host, Node, Type, Owner, Options, Parents) ->
+ BJID = jid:tolower(jid:remove_resource(Owner)),
+ case nodeidx(Host, Node) of
+ {error, not_found} ->
+ ParentExists = case Host of
+ {_U, _S, _R} ->
+ %% This is special case for PEP handling
+ %% PEP does not uses hierarchy
+ true;
+ _ ->
+ case Parents of
+ [] ->
+ true;
+ [Parent | _] ->
+ case nodeidx(Host, Parent) of
+ {result, PNode} ->
+ case nodeowners(PNode) of
+ [{<<>>, Host, <<>>}] -> true;
+ Owners -> lists:member(BJID, Owners)
+ end;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end
+ end,
+ case ParentExists of
+ true ->
+ case set_node(#pubsub_node{nodeid = {Host, Node},
+ parents = Parents, type = Type,
+ options = Options})
+ of
+ {result, Nidx} -> {ok, Nidx};
+ Other -> Other
+ end;
+ false ->
+ {error, ?ERR_FORBIDDEN}
+ end;
+ {result, _} ->
+ {error, ?ERRT_CONFLICT(?MYLANG, <<"Node already exists">>)};
+ {error, db_fail} ->
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)}
+ end.
+
+delete_node(Host, Node) ->
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
+ Removed = get_subnodes_tree(Host, Node),
+ catch ejabberd_sql:sql_query_t([<<"delete from pubsub_node where host='">>,
+ H, <<"' and node like '">>, N, <<"%';">>]),
+ Removed.
+
+%% helpers
+raw_to_node(Host, [Node, Parent, Type, Nidx]) ->
+ Options = case catch
+ ejabberd_sql:sql_query_t([<<"select name,val from pubsub_node_option "
+ "where nodeid='">>, Nidx, <<"';">>])
+ of
+ {selected, [<<"name">>, <<"val">>], ROptions} ->
+ DbOpts = lists:map(fun ([Key, Value]) ->
+ RKey = jlib:binary_to_atom(Key),
+ Tokens = element(2, erl_scan:string(binary_to_list(<<Value/binary, ".">>))),
+ RValue = element(2, erl_parse:parse_term(Tokens)),
+ {RKey, RValue}
+ end,
+ ROptions),
+ Module = jlib:binary_to_atom(<<"node_", Type/binary, "_sql">>),
+ StdOpts = Module:options(),
+ lists:foldl(fun ({Key, Value}, Acc) ->
+ lists:keyreplace(Key, 1, Acc, {Key, Value})
+ end,
+ StdOpts, DbOpts);
+ _ ->
+ []
+ end,
+ Parents = case Parent of
+ <<>> -> [];
+ _ -> [Parent]
+ end,
+ #pubsub_node{nodeid = {Host, Node},
+ parents = Parents,
+ id = Nidx, type = Type, options = Options}.
+
+nodeidx(Host, Node) ->
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
+ case catch
+ ejabberd_sql:sql_query_t([<<"select nodeid from pubsub_node where "
+ "host='">>,
+ H, <<"' and node='">>, N, <<"';">>])
+ of
+ {selected, [<<"nodeid">>], [[Nidx]]} ->
+ {result, Nidx};
+ {'EXIT', _Reason} ->
+ {error, db_fail};
+ _ ->
+ {error, not_found}
+ end.
+
+nodeowners(Nidx) ->
+ {result, Res} = node_flat_sql:get_node_affiliations(Nidx),
+ [LJID || {LJID, Aff} <- Res, Aff =:= owner].