From 1aae8a9fda184b8a8fa139d9fc4bf2aa2c6ba8a0 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 20 Apr 2016 12:27:32 +0300 Subject: Rename odbc to sql everywhere --- ejabberd.yml.example | 40 +- include/ejabberd_sql_pt.hrl | 4 +- include/pubsub.hrl | 2 +- src/ejabberd_admin.erl | 18 +- src/ejabberd_auth.erl | 2 +- src/ejabberd_auth_internal.erl | 4 +- src/ejabberd_auth_odbc.erl | 483 --------------- src/ejabberd_auth_riak.erl | 4 +- src/ejabberd_auth_sql.erl | 483 +++++++++++++++ src/ejabberd_config.erl | 47 +- src/ejabberd_odbc.erl | 1025 ------------------------------- src/ejabberd_odbc_sup.erl | 226 ------- src/ejabberd_piefxis.erl | 4 +- src/ejabberd_rdbms.erl | 34 +- src/ejabberd_sm.erl | 3 +- src/ejabberd_sm_sql.erl | 32 +- src/ejabberd_sql.erl | 1025 +++++++++++++++++++++++++++++++ src/ejabberd_sql_pt.erl | 8 +- src/ejabberd_sql_sup.erl | 226 +++++++ src/ejabberd_web_admin.erl | 2 +- src/ejd2odbc.erl | 315 ---------- src/ejd2sql.erl | 315 ++++++++++ src/gen_mod.erl | 6 +- src/mod_announce_sql.erl | 32 +- src/mod_blocking_sql.erl | 4 +- src/mod_caps.erl | 4 +- src/mod_caps_sql.erl | 14 +- src/mod_irc_sql.erl | 26 +- src/mod_last_sql.erl | 12 +- src/mod_mam.erl | 5 +- src/mod_mam_sql.erl | 56 +- src/mod_muc_sql.erl | 72 +-- src/mod_offline.erl | 2 +- src/mod_offline_sql.erl | 40 +- src/mod_privacy_sql.erl | 56 +- src/mod_private_sql.erl | 18 +- src/mod_pubsub.erl | 26 +- src/mod_roster_sql.erl | 68 +-- src/mod_shared_roster_sql.erl | 72 +-- src/mod_vcard_sql.erl | 68 +-- src/mod_vcard_xupdate_sql.erl | 22 +- src/node_flat_odbc.erl | 1071 --------------------------------- src/node_flat_sql.erl | 1071 +++++++++++++++++++++++++++++++++ src/node_hometree_odbc.erl | 159 ----- src/node_hometree_sql.erl | 159 +++++ src/node_mix_odbc.erl | 170 ------ src/node_mix_sql.erl | 170 ++++++ src/node_pep_odbc.erl | 254 -------- src/node_pep_sql.erl | 254 ++++++++ src/nodetree_tree_odbc.erl | 314 ---------- src/nodetree_tree_sql.erl | 314 ++++++++++ src/odbc_queries.erl | 645 -------------------- src/pubsub_db_odbc.erl | 141 ----- src/pubsub_db_sql.erl | 141 +++++ src/pubsub_subscription_odbc.erl | 331 ---------- src/pubsub_subscription_sql.erl | 331 ++++++++++ src/sql_queries.erl | 644 ++++++++++++++++++++ test/ejabberd_SUITE.erl | 8 +- test/ejabberd_SUITE_data/ejabberd.yml | 114 ++-- 59 files changed, 5606 insertions(+), 5590 deletions(-) delete mode 100644 src/ejabberd_auth_odbc.erl create mode 100644 src/ejabberd_auth_sql.erl delete mode 100644 src/ejabberd_odbc.erl delete mode 100644 src/ejabberd_odbc_sup.erl create mode 100644 src/ejabberd_sql.erl create mode 100644 src/ejabberd_sql_sup.erl delete mode 100644 src/ejd2odbc.erl create mode 100644 src/ejd2sql.erl delete mode 100644 src/node_flat_odbc.erl create mode 100644 src/node_flat_sql.erl delete mode 100644 src/node_hometree_odbc.erl create mode 100644 src/node_hometree_sql.erl delete mode 100644 src/node_mix_odbc.erl create mode 100644 src/node_mix_sql.erl delete mode 100644 src/node_pep_odbc.erl create mode 100644 src/node_pep_sql.erl delete mode 100644 src/nodetree_tree_odbc.erl create mode 100644 src/nodetree_tree_sql.erl delete mode 100644 src/odbc_queries.erl delete mode 100644 src/pubsub_db_odbc.erl create mode 100644 src/pubsub_db_sql.erl delete mode 100644 src/pubsub_subscription_odbc.erl create mode 100644 src/pubsub_subscription_sql.erl create mode 100644 src/sql_queries.erl diff --git a/ejabberd.yml.example b/ejabberd.yml.example index 11b27e65..b0f62214 100644 --- a/ejabberd.yml.example +++ b/ejabberd.yml.example @@ -254,10 +254,10 @@ auth_method: internal ## extauth_program: "/path/to/authentication/script" ## -## Authentication using ODBC +## Authentication using SQL ## Remember to setup a database in the next section. ## -## auth_method: odbc +## auth_method: sql ## ## Authentication using PAM @@ -330,26 +330,26 @@ auth_method: internal ## ## MySQL server: ## -## odbc_type: mysql -## odbc_server: "server" -## odbc_database: "database" -## odbc_username: "username" -## odbc_password: "password" +## sql_type: mysql +## sql_server: "server" +## sql_database: "database" +## sql_username: "username" +## sql_password: "password" ## ## If you want to specify the port: -## odbc_port: 1234 +## sql_port: 1234 ## ## PostgreSQL server: ## -## odbc_type: pgsql -## odbc_server: "server" -## odbc_database: "database" -## odbc_username: "username" -## odbc_password: "password" +## sql_type: pgsql +## sql_server: "server" +## sql_database: "database" +## sql_username: "username" +## sql_password: "password" ## ## If you want to specify the port: -## odbc_port: 1234 +## sql_port: 1234 ## ## If you use PostgreSQL, have a large database, and need a ## faster but inexact replacement for "select count(*) from users" @@ -359,25 +359,25 @@ auth_method: internal ## ## SQLite: ## -## odbc_type: sqlite -## odbc_database: "/path/to/database.db" +## sql_type: sqlite +## sql_database: "/path/to/database.db" ## ## ODBC compatible or MSSQL server: ## -## odbc_type: odbc -## odbc_server: "DSN=ejabberd;UID=ejabberd;PWD=ejabberd" +## sql_type: odbc +## sql_server: "DSN=ejabberd;UID=ejabberd;PWD=ejabberd" ## ## Number of connections to open to the database for each virtual host ## -## odbc_pool_size: 10 +## sql_pool_size: 10 ## ## Interval to make a dummy SQL request to keep the connections to the ## database alive. Specify in seconds: for example 28800 means 8 hours ## -## odbc_keepalive_interval: undefined +## sql_keepalive_interval: undefined ###. =============== ###' TRAFFIC SHAPERS diff --git a/include/ejabberd_sql_pt.hrl b/include/ejabberd_sql_pt.hrl index 0048661d..f1a3dba3 100644 --- a/include/ejabberd_sql_pt.hrl +++ b/include/ejabberd_sql_pt.hrl @@ -23,9 +23,9 @@ -define(SQL_UPSERT_MARK, sql_upsert__mark_). -define(SQL_UPSERT(Host, Table, Fields), - ejabberd_odbc:sql_query(Host, ?SQL_UPSERT_MARK(Table, Fields))). + ejabberd_sql:sql_query(Host, ?SQL_UPSERT_MARK(Table, Fields))). -define(SQL_UPSERT_T(Table, Fields), - ejabberd_odbc:sql_query_t(?SQL_UPSERT_MARK(Table, Fields))). + ejabberd_sql:sql_query_t(?SQL_UPSERT_MARK(Table, Fields))). -record(sql_query, {hash, format_query, format_res, args, loc}). diff --git a/include/pubsub.hrl b/include/pubsub.hrl index 209f802d..3aa090d3 100644 --- a/include/pubsub.hrl +++ b/include/pubsub.hrl @@ -65,7 +65,7 @@ %% note: pos_integer() should always be used, but we allow anything else coded %% as binary, so one can have a custom implementation of nodetree with custom %% indexing (see nodetree_virtual). this also allows to use any kind of key for -%% indexing nodes, as this can be usefull with external backends such as odbc. +%% indexing nodes, as this can be usefull with external backends such as sql. -type(itemId() :: binary()). %% @type itemId() = string(). diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index b22a7038..a22f8f3d 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -192,20 +192,20 @@ get_commands_spec() -> module = ejabberd_piefxis, function = export_host, args = [{dir, string}, {host, string}], result = {res, rescode}}, - #ejabberd_commands{name = export_odbc, tags = [mnesia, odbc], + #ejabberd_commands{name = export_sql, tags = [mnesia, sql], desc = "Export all tables as SQL queries to a file", - module = ejd2odbc, function = export, + module = ejd2sql, function = export, args = [{host, string}, {file, string}], result = {res, rescode}}, - #ejabberd_commands{name = delete_mnesia, tags = [mnesia, odbc], + #ejabberd_commands{name = delete_mnesia, tags = [mnesia, sql], desc = "Export all tables as SQL queries to a file", - module = ejd2odbc, function = delete, + module = ejd2sql, function = delete, args = [{host, string}], result = {res, rescode}}, - #ejabberd_commands{name = convert_to_scram, tags = [odbc], + #ejabberd_commands{name = convert_to_scram, tags = [sql], desc = "Convert the passwords in 'users' ODBC table to SCRAM", - module = ejabberd_auth_odbc, function = convert_to_scram, + module = ejabberd_auth_sql, function = convert_to_scram, args = [{host, binary}], result = {res, rescode}}, - #ejabberd_commands{name = import_prosody, tags = [mnesia, odbc, riak], + #ejabberd_commands{name = import_prosody, tags = [mnesia, sql, riak], desc = "Import data from Prosody", module = prosody2ejabberd, function = from_dir, args = [{dir, string}], result = {res, rescode}}, @@ -225,9 +225,9 @@ get_commands_spec() -> module = ?MODULE, function = delete_old_messages, args = [{days, integer}], result = {res, rescode}}, - #ejabberd_commands{name = export2odbc, tags = [mnesia], + #ejabberd_commands{name = export2sql, tags = [mnesia], desc = "Export virtual host information from Mnesia tables to SQL files", - module = ejd2odbc, function = export, + module = ejd2sql, function = export, args = [{host, string}, {directory, string}], result = {res, rescode}}, #ejabberd_commands{name = set_master, tags = [mnesia], diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 343ad943..0267a219 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -137,7 +137,7 @@ check_password(User, AuthzId, Server, Password, Digest, %% where %% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external %% | ejabberd_auth_internal | ejabberd_auth_ldap -%% | ejabberd_auth_odbc | ejabberd_auth_pam | ejabberd_auth_riak +%% | ejabberd_auth_sql | ejabberd_auth_pam | ejabberd_auth_riak -spec check_password_with_authmodule(binary(), binary(), binary(), binary()) -> false | {true, atom()}. diff --git a/src/ejabberd_auth_internal.erl b/src/ejabberd_auth_internal.erl index 3b30b360..acbbfe50 100644 --- a/src/ejabberd_auth_internal.erl +++ b/src/ejabberd_auth_internal.erl @@ -474,8 +474,8 @@ export(_Server) -> [{passwd, fun(Host, #passwd{us = {LUser, LServer}, password = Password}) when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), - Pass = ejabberd_odbc:escape(Password), + Username = ejabberd_sql:escape(LUser), + Pass = ejabberd_sql:escape(Password), [[<<"delete from users where username='">>, Username, <<"';">>], [<<"insert into users(username, password) " "values ('">>, Username, <<"', '">>, Pass, <<"');">>]]; diff --git a/src/ejabberd_auth_odbc.erl b/src/ejabberd_auth_odbc.erl deleted file mode 100644 index dc3248fe..00000000 --- a/src/ejabberd_auth_odbc.erl +++ /dev/null @@ -1,483 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : ejabberd_auth_odbc.erl -%%% Author : Alexey Shchepin -%%% Purpose : Authentification via ODBC -%%% Created : 12 Dec 2004 by Alexey Shchepin -%%% -%%% -%%% 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. -%%% -%%%---------------------------------------------------------------------- - --module(ejabberd_auth_odbc). - --behaviour(ejabberd_config). - --author('alexey@process-one.net'). - --behaviour(ejabberd_auth). - --export([start/1, set_password/3, check_password/4, - check_password/6, try_register/3, - dirty_get_registered_users/0, get_vh_registered_users/1, - get_vh_registered_users/2, - get_vh_registered_users_number/1, - get_vh_registered_users_number/2, get_password/2, - get_password_s/2, is_user_exists/2, remove_user/2, - remove_user/3, store_type/0, plain_password_required/0, - convert_to_scram/1, opt_type/1]). - --include("ejabberd.hrl"). --include("logger.hrl"). - --define(SALT_LENGTH, 16). - -%%%---------------------------------------------------------------------- -%%% API -%%%---------------------------------------------------------------------- -start(_Host) -> ok. - -plain_password_required() -> - case is_scrammed() of - false -> false; - true -> true - end. - -store_type() -> - case is_scrammed() of - false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM - true -> scram %% allows: PLAIN SCRAM - end. - -%% @spec (User, AuthzId, Server, Password) -> true | false | {error, Error} -check_password(User, AuthzId, Server, Password) -> - if AuthzId /= <<>> andalso AuthzId /= User -> - false; - true -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - false; - (LUser == <<>>) or (LServer == <<>>) -> - false; - true -> - case is_scrammed() of - true -> - try odbc_queries:get_password_scram(LServer, LUser) of - {selected, - [{StoredKey, ServerKey, Salt, IterationCount}]} -> - Scram = - #scram{storedkey = StoredKey, - serverkey = ServerKey, - salt = Salt, - iterationcount = IterationCount}, - is_password_scram_valid(Password, Scram); - {selected, []} -> - false; %% Account does not exist - {error, _Error} -> - false %% Typical error is that table doesn't exist - catch - _:_ -> - false %% Typical error is database not accessible - end; - false -> - try odbc_queries:get_password(LServer, LUser) of - {selected, [{Password}]} -> - Password /= <<"">>; - {selected, [{_Password2}]} -> - false; %% Password is not correct - {selected, []} -> - false; %% Account does not exist - {error, _Error} -> - false %% Typical error is that table doesn't exist - catch - _:_ -> - false %% Typical error is database not accessible - end - end - end - end. - -%% @spec (User, AuthzId, Server, Password, Digest, DigestGen) -> true | false | {error, Error} -check_password(User, AuthzId, Server, Password, Digest, - DigestGen) -> - if AuthzId /= <<>> andalso AuthzId /= User -> - false; - true -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - false; - (LUser == <<>>) or (LServer == <<>>) -> - false; - true -> - case is_scrammed() of - false -> - try odbc_queries:get_password(LServer, LUser) of - %% Account exists, check if password is valid - {selected, [{Passwd}]} -> - DigRes = if Digest /= <<"">> -> - Digest == DigestGen(Passwd); - true -> false - end, - if DigRes -> true; - true -> (Passwd == Password) and (Password /= <<"">>) - end; - {selected, []} -> - false; %% Account does not exist - {error, _Error} -> - false %% Typical error is that table doesn't exist - catch - _:_ -> - false %% Typical error is database not accessible - end; - true -> - false - end - end - end. - -%% @spec (User::string(), Server::string(), Password::string()) -> -%% ok | {error, invalid_jid} -set_password(User, Server, Password) -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - {error, invalid_jid}; - (LUser == <<>>) or (LServer == <<>>) -> - {error, invalid_jid}; - true -> - case is_scrammed() of - true -> - Scram = password_to_scram(Password), - case catch odbc_queries:set_password_scram_t( - LServer, - LUser, - Scram#scram.storedkey, - Scram#scram.serverkey, - Scram#scram.salt, - Scram#scram.iterationcount - ) - of - {atomic, ok} -> ok; - Other -> {error, Other} - end; - false -> - case catch odbc_queries:set_password_t(LServer, - LUser, Password) - of - {atomic, ok} -> ok; - Other -> {error, Other} - end - end - end. - -%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} -try_register(User, Server, Password) -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - {error, invalid_jid}; - (LUser == <<>>) or (LServer == <<>>) -> - {error, invalid_jid}; - true -> - case is_scrammed() of - true -> - Scram = password_to_scram(Password), - case catch odbc_queries:add_user_scram( - LServer, - LUser, - Scram#scram.storedkey, - Scram#scram.serverkey, - Scram#scram.salt, - Scram#scram.iterationcount - ) of - {updated, 1} -> {atomic, ok}; - _ -> {atomic, exists} - end; - false -> - case catch odbc_queries:add_user(LServer, LUser, - Password) of - {updated, 1} -> {atomic, ok}; - _ -> {atomic, exists} - end - end - end. - -dirty_get_registered_users() -> - Servers = ejabberd_config:get_vh_by_auth_method(odbc), - lists:flatmap(fun (Server) -> - get_vh_registered_users(Server) - end, - Servers). - -get_vh_registered_users(Server) -> - case jid:nameprep(Server) of - error -> []; - <<>> -> []; - LServer -> - case catch odbc_queries:list_users(LServer) of - {selected, Res} -> - [{U, LServer} || {U} <- Res]; - _ -> [] - end - end. - -get_vh_registered_users(Server, Opts) -> - case jid:nameprep(Server) of - error -> []; - <<>> -> []; - LServer -> - case catch odbc_queries:list_users(LServer, Opts) of - {selected, Res} -> - [{U, LServer} || {U} <- Res]; - _ -> [] - end - end. - -get_vh_registered_users_number(Server) -> - case jid:nameprep(Server) of - error -> 0; - <<>> -> 0; - LServer -> - case catch odbc_queries:users_number(LServer) of - {selected, [{Res}]} -> - Res; - _ -> 0 - end - end. - -get_vh_registered_users_number(Server, Opts) -> - case jid:nameprep(Server) of - error -> 0; - <<>> -> 0; - LServer -> - case catch odbc_queries:users_number(LServer, Opts) of - {selected, [{Res}]} -> - Res; - _Other -> 0 - end - end. - -get_password(User, Server) -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - false; - (LUser == <<>>) or (LServer == <<>>) -> - false; - true -> - case is_scrammed() of - true -> - case catch odbc_queries:get_password_scram( - LServer, LUser) of - {selected, - [{StoredKey, ServerKey, Salt, IterationCount}]} -> - {jlib:decode_base64(StoredKey), - jlib:decode_base64(ServerKey), - jlib:decode_base64(Salt), - IterationCount}; - _ -> false - end; - false -> - case catch odbc_queries:get_password(LServer, LUser) - of - {selected, [{Password}]} -> Password; - _ -> false - end - end - end. - -get_password_s(User, Server) -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - <<"">>; - (LUser == <<>>) or (LServer == <<>>) -> - <<"">>; - true -> - case is_scrammed() of - false -> - case catch odbc_queries:get_password(LServer, LUser) of - {selected, [{Password}]} -> Password; - _ -> <<"">> - end; - true -> <<"">> - end - end. - -%% @spec (User, Server) -> true | false | {error, Error} -is_user_exists(User, Server) -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - false; - (LUser == <<>>) or (LServer == <<>>) -> - false; - true -> - try odbc_queries:get_password(LServer, LUser) of - {selected, [{_Password}]} -> - true; %% Account exists - {selected, []} -> - false; %% Account does not exist - {error, Error} -> {error, Error} - catch - _:B -> {error, B} - end - end. - -%% @spec (User, Server) -> ok | error -%% @doc Remove user. -%% Note: it may return ok even if there was some problem removing the user. -remove_user(User, Server) -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - error; - (LUser == <<>>) or (LServer == <<>>) -> - error; - true -> - catch odbc_queries:del_user(LServer, LUser), - ok - end. - -%% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed -%% @doc Remove user if the provided password is correct. -remove_user(User, Server, Password) -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - error; - (LUser == <<>>) or (LServer == <<>>) -> - error; - true -> - case is_scrammed() of - true -> - case check_password(User, <<"">>, Server, Password) of - true -> - remove_user(User, Server), - ok; - false -> not_allowed - end; - false -> - F = fun () -> - Result = odbc_queries:del_user_return_password( - LServer, LUser, Password), - case Result of - {selected, [{Password}]} -> ok; - {selected, []} -> not_exists; - _ -> not_allowed - end - end, - {atomic, Result} = odbc_queries:sql_transaction( - LServer, F), - Result - end - end. - -%%% -%%% SCRAM -%%% - -is_scrammed() -> - scram == - ejabberd_config:get_option({auth_password_format, ?MYNAME}, - fun(V) -> V end). - -password_to_scram(Password) -> - password_to_scram(Password, - ?SCRAM_DEFAULT_ITERATION_COUNT). - -password_to_scram(Password, IterationCount) -> - Salt = crypto:rand_bytes(?SALT_LENGTH), - SaltedPassword = scram:salted_password(Password, Salt, - IterationCount), - StoredKey = - scram:stored_key(scram:client_key(SaltedPassword)), - ServerKey = scram:server_key(SaltedPassword), - #scram{storedkey = jlib:encode_base64(StoredKey), - serverkey = jlib:encode_base64(ServerKey), - salt = jlib:encode_base64(Salt), - iterationcount = IterationCount}. - -is_password_scram_valid(Password, Scram) -> - IterationCount = Scram#scram.iterationcount, - Salt = jlib:decode_base64(Scram#scram.salt), - SaltedPassword = scram:salted_password(Password, Salt, - IterationCount), - StoredKey = - scram:stored_key(scram:client_key(SaltedPassword)), - jlib:decode_base64(Scram#scram.storedkey) == StoredKey. - --define(BATCH_SIZE, 1000). - -set_password_scram_t(Username, - StoredKey, ServerKey, Salt, IterationCount) -> - odbc_queries:update_t(<<"users">>, - [<<"username">>, - <<"password">>, - <<"serverkey">>, - <<"salt">>, - <<"iterationcount">>], - [Username, StoredKey, - ServerKey, Salt, - IterationCount], - [<<"username='">>, Username, - <<"'">>]). - -convert_to_scram(Server) -> - LServer = jid:nameprep(Server), - if - LServer == error; - LServer == <<>> -> - {error, {incorrect_server_name, Server}}; - true -> - F = fun () -> - case ejabberd_odbc:sql_query_t( - [<<"select username, password from users where " - "iterationcount=0 limit ">>, - integer_to_binary(?BATCH_SIZE), - <<";">>]) of - {selected, [<<"username">>, <<"password">>], []} -> - ok; - {selected, [<<"username">>, <<"password">>], Rs} -> - lists:foreach( - fun([LUser, Password]) -> - Username = ejabberd_odbc:escape(LUser), - Scram = password_to_scram(Password), - set_password_scram_t( - Username, - ejabberd_odbc:escape(Scram#scram.storedkey), - ejabberd_odbc:escape(Scram#scram.serverkey), - ejabberd_odbc:escape(Scram#scram.salt), - integer_to_binary(Scram#scram.iterationcount) - ) - end, Rs), - continue; - Err -> {bad_reply, Err} - end - end, - case odbc_queries:sql_transaction(LServer, F) of - {atomic, ok} -> ok; - {atomic, continue} -> convert_to_scram(Server); - {atomic, Error} -> {error, Error}; - Error -> Error - end - end. - -opt_type(auth_password_format) -> fun (V) -> V end; -opt_type(_) -> [auth_password_format]. diff --git a/src/ejabberd_auth_riak.erl b/src/ejabberd_auth_riak.erl index bc745fea..c48b9441 100644 --- a/src/ejabberd_auth_riak.erl +++ b/src/ejabberd_auth_riak.erl @@ -291,8 +291,8 @@ export(_Server) -> [{passwd, fun(Host, #passwd{us = {LUser, LServer}, password = Password}) when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), - Pass = ejabberd_odbc:escape(Password), + Username = ejabberd_sql:escape(LUser), + Pass = ejabberd_sql:escape(Password), [[<<"delete from users where username='">>, Username, <<"';">>], [<<"insert into users(username, password) " "values ('">>, Username, <<"', '">>, Pass, <<"');">>]]; diff --git a/src/ejabberd_auth_sql.erl b/src/ejabberd_auth_sql.erl new file mode 100644 index 00000000..000b4a4f --- /dev/null +++ b/src/ejabberd_auth_sql.erl @@ -0,0 +1,483 @@ +%%%---------------------------------------------------------------------- +%%% File : ejabberd_auth_sql.erl +%%% Author : Alexey Shchepin +%%% Purpose : Authentification via ODBC +%%% Created : 12 Dec 2004 by Alexey Shchepin +%%% +%%% +%%% 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. +%%% +%%%---------------------------------------------------------------------- + +-module(ejabberd_auth_sql). + +-behaviour(ejabberd_config). + +-author('alexey@process-one.net'). + +-behaviour(ejabberd_auth). + +-export([start/1, set_password/3, check_password/4, + check_password/6, try_register/3, + dirty_get_registered_users/0, get_vh_registered_users/1, + get_vh_registered_users/2, + get_vh_registered_users_number/1, + get_vh_registered_users_number/2, get_password/2, + get_password_s/2, is_user_exists/2, remove_user/2, + remove_user/3, store_type/0, plain_password_required/0, + convert_to_scram/1, opt_type/1]). + +-include("ejabberd.hrl"). +-include("logger.hrl"). + +-define(SALT_LENGTH, 16). + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +start(_Host) -> ok. + +plain_password_required() -> + case is_scrammed() of + false -> false; + true -> true + end. + +store_type() -> + case is_scrammed() of + false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM + true -> scram %% allows: PLAIN SCRAM + end. + +%% @spec (User, AuthzId, Server, Password) -> true | false | {error, Error} +check_password(User, AuthzId, Server, Password) -> + if AuthzId /= <<>> andalso AuthzId /= User -> + false; + true -> + LServer = jid:nameprep(Server), + LUser = jid:nodeprep(User), + if (LUser == error) or (LServer == error) -> + false; + (LUser == <<>>) or (LServer == <<>>) -> + false; + true -> + case is_scrammed() of + true -> + try sql_queries:get_password_scram(LServer, LUser) of + {selected, + [{StoredKey, ServerKey, Salt, IterationCount}]} -> + Scram = + #scram{storedkey = StoredKey, + serverkey = ServerKey, + salt = Salt, + iterationcount = IterationCount}, + is_password_scram_valid(Password, Scram); + {selected, []} -> + false; %% Account does not exist + {error, _Error} -> + false %% Typical error is that table doesn't exist + catch + _:_ -> + false %% Typical error is database not accessible + end; + false -> + try sql_queries:get_password(LServer, LUser) of + {selected, [{Password}]} -> + Password /= <<"">>; + {selected, [{_Password2}]} -> + false; %% Password is not correct + {selected, []} -> + false; %% Account does not exist + {error, _Error} -> + false %% Typical error is that table doesn't exist + catch + _:_ -> + false %% Typical error is database not accessible + end + end + end + end. + +%% @spec (User, AuthzId, Server, Password, Digest, DigestGen) -> true | false | {error, Error} +check_password(User, AuthzId, Server, Password, Digest, + DigestGen) -> + if AuthzId /= <<>> andalso AuthzId /= User -> + false; + true -> + LServer = jid:nameprep(Server), + LUser = jid:nodeprep(User), + if (LUser == error) or (LServer == error) -> + false; + (LUser == <<>>) or (LServer == <<>>) -> + false; + true -> + case is_scrammed() of + false -> + try sql_queries:get_password(LServer, LUser) of + %% Account exists, check if password is valid + {selected, [{Passwd}]} -> + DigRes = if Digest /= <<"">> -> + Digest == DigestGen(Passwd); + true -> false + end, + if DigRes -> true; + true -> (Passwd == Password) and (Password /= <<"">>) + end; + {selected, []} -> + false; %% Account does not exist + {error, _Error} -> + false %% Typical error is that table doesn't exist + catch + _:_ -> + false %% Typical error is database not accessible + end; + true -> + false + end + end + end. + +%% @spec (User::string(), Server::string(), Password::string()) -> +%% ok | {error, invalid_jid} +set_password(User, Server, Password) -> + LServer = jid:nameprep(Server), + LUser = jid:nodeprep(User), + if (LUser == error) or (LServer == error) -> + {error, invalid_jid}; + (LUser == <<>>) or (LServer == <<>>) -> + {error, invalid_jid}; + true -> + case is_scrammed() of + true -> + Scram = password_to_scram(Password), + case catch sql_queries:set_password_scram_t( + LServer, + LUser, + Scram#scram.storedkey, + Scram#scram.serverkey, + Scram#scram.salt, + Scram#scram.iterationcount + ) + of + {atomic, ok} -> ok; + Other -> {error, Other} + end; + false -> + case catch sql_queries:set_password_t(LServer, + LUser, Password) + of + {atomic, ok} -> ok; + Other -> {error, Other} + end + end + end. + +%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} +try_register(User, Server, Password) -> + LServer = jid:nameprep(Server), + LUser = jid:nodeprep(User), + if (LUser == error) or (LServer == error) -> + {error, invalid_jid}; + (LUser == <<>>) or (LServer == <<>>) -> + {error, invalid_jid}; + true -> + case is_scrammed() of + true -> + Scram = password_to_scram(Password), + case catch sql_queries:add_user_scram( + LServer, + LUser, + Scram#scram.storedkey, + Scram#scram.serverkey, + Scram#scram.salt, + Scram#scram.iterationcount + ) of + {updated, 1} -> {atomic, ok}; + _ -> {atomic, exists} + end; + false -> + case catch sql_queries:add_user(LServer, LUser, + Password) of + {updated, 1} -> {atomic, ok}; + _ -> {atomic, exists} + end + end + end. + +dirty_get_registered_users() -> + Servers = ejabberd_config:get_vh_by_auth_method(sql), + lists:flatmap(fun (Server) -> + get_vh_registered_users(Server) + end, + Servers). + +get_vh_registered_users(Server) -> + case jid:nameprep(Server) of + error -> []; + <<>> -> []; + LServer -> + case catch sql_queries:list_users(LServer) of + {selected, Res} -> + [{U, LServer} || {U} <- Res]; + _ -> [] + end + end. + +get_vh_registered_users(Server, Opts) -> + case jid:nameprep(Server) of + error -> []; + <<>> -> []; + LServer -> + case catch sql_queries:list_users(LServer, Opts) of + {selected, Res} -> + [{U, LServer} || {U} <- Res]; + _ -> [] + end + end. + +get_vh_registered_users_number(Server) -> + case jid:nameprep(Server) of + error -> 0; + <<>> -> 0; + LServer -> + case catch sql_queries:users_number(LServer) of + {selected, [{Res}]} -> + Res; + _ -> 0 + end + end. + +get_vh_registered_users_number(Server, Opts) -> + case jid:nameprep(Server) of + error -> 0; + <<>> -> 0; + LServer -> + case catch sql_queries:users_number(LServer, Opts) of + {selected, [{Res}]} -> + Res; + _Other -> 0 + end + end. + +get_password(User, Server) -> + LServer = jid:nameprep(Server), + LUser = jid:nodeprep(User), + if (LUser == error) or (LServer == error) -> + false; + (LUser == <<>>) or (LServer == <<>>) -> + false; + true -> + case is_scrammed() of + true -> + case catch sql_queries:get_password_scram( + LServer, LUser) of + {selected, + [{StoredKey, ServerKey, Salt, IterationCount}]} -> + {jlib:decode_base64(StoredKey), + jlib:decode_base64(ServerKey), + jlib:decode_base64(Salt), + IterationCount}; + _ -> false + end; + false -> + case catch sql_queries:get_password(LServer, LUser) + of + {selected, [{Password}]} -> Password; + _ -> false + end + end + end. + +get_password_s(User, Server) -> + LServer = jid:nameprep(Server), + LUser = jid:nodeprep(User), + if (LUser == error) or (LServer == error) -> + <<"">>; + (LUser == <<>>) or (LServer == <<>>) -> + <<"">>; + true -> + case is_scrammed() of + false -> + case catch sql_queries:get_password(LServer, LUser) of + {selected, [{Password}]} -> Password; + _ -> <<"">> + end; + true -> <<"">> + end + end. + +%% @spec (User, Server) -> true | false | {error, Error} +is_user_exists(User, Server) -> + LServer = jid:nameprep(Server), + LUser = jid:nodeprep(User), + if (LUser == error) or (LServer == error) -> + false; + (LUser == <<>>) or (LServer == <<>>) -> + false; + true -> + try sql_queries:get_password(LServer, LUser) of + {selected, [{_Password}]} -> + true; %% Account exists + {selected, []} -> + false; %% Account does not exist + {error, Error} -> {error, Error} + catch + _:B -> {error, B} + end + end. + +%% @spec (User, Server) -> ok | error +%% @doc Remove user. +%% Note: it may return ok even if there was some problem removing the user. +remove_user(User, Server) -> + LServer = jid:nameprep(Server), + LUser = jid:nodeprep(User), + if (LUser == error) or (LServer == error) -> + error; + (LUser == <<>>) or (LServer == <<>>) -> + error; + true -> + catch sql_queries:del_user(LServer, LUser), + ok + end. + +%% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed +%% @doc Remove user if the provided password is correct. +remove_user(User, Server, Password) -> + LServer = jid:nameprep(Server), + LUser = jid:nodeprep(User), + if (LUser == error) or (LServer == error) -> + error; + (LUser == <<>>) or (LServer == <<>>) -> + error; + true -> + case is_scrammed() of + true -> + case check_password(User, <<"">>, Server, Password) of + true -> + remove_user(User, Server), + ok; + false -> not_allowed + end; + false -> + F = fun () -> + Result = sql_queries:del_user_return_password( + LServer, LUser, Password), + case Result of + {selected, [{Password}]} -> ok; + {selected, []} -> not_exists; + _ -> not_allowed + end + end, + {atomic, Result} = sql_queries:sql_transaction( + LServer, F), + Result + end + end. + +%%% +%%% SCRAM +%%% + +is_scrammed() -> + scram == + ejabberd_config:get_option({auth_password_format, ?MYNAME}, + fun(V) -> V end). + +password_to_scram(Password) -> + password_to_scram(Password, + ?SCRAM_DEFAULT_ITERATION_COUNT). + +password_to_scram(Password, IterationCount) -> + Salt = crypto:rand_bytes(?SALT_LENGTH), + SaltedPassword = scram:salted_password(Password, Salt, + IterationCount), + StoredKey = + scram:stored_key(scram:client_key(SaltedPassword)), + ServerKey = scram:server_key(SaltedPassword), + #scram{storedkey = jlib:encode_base64(StoredKey), + serverkey = jlib:encode_base64(ServerKey), + salt = jlib:encode_base64(Salt), + iterationcount = IterationCount}. + +is_password_scram_valid(Password, Scram) -> + IterationCount = Scram#scram.iterationcount, + Salt = jlib:decode_base64(Scram#scram.salt), + SaltedPassword = scram:salted_password(Password, Salt, + IterationCount), + StoredKey = + scram:stored_key(scram:client_key(SaltedPassword)), + jlib:decode_base64(Scram#scram.storedkey) == StoredKey. + +-define(BATCH_SIZE, 1000). + +set_password_scram_t(Username, + StoredKey, ServerKey, Salt, IterationCount) -> + sql_queries:update_t(<<"users">>, + [<<"username">>, + <<"password">>, + <<"serverkey">>, + <<"salt">>, + <<"iterationcount">>], + [Username, StoredKey, + ServerKey, Salt, + IterationCount], + [<<"username='">>, Username, + <<"'">>]). + +convert_to_scram(Server) -> + LServer = jid:nameprep(Server), + if + LServer == error; + LServer == <<>> -> + {error, {incorrect_server_name, Server}}; + true -> + F = fun () -> + case ejabberd_sql:sql_query_t( + [<<"select username, password from users where " + "iterationcount=0 limit ">>, + integer_to_binary(?BATCH_SIZE), + <<";">>]) of + {selected, [<<"username">>, <<"password">>], []} -> + ok; + {selected, [<<"username">>, <<"password">>], Rs} -> + lists:foreach( + fun([LUser, Password]) -> + Username = ejabberd_sql:escape(LUser), + Scram = password_to_scram(Password), + set_password_scram_t( + Username, + ejabberd_sql:escape(Scram#scram.storedkey), + ejabberd_sql:escape(Scram#scram.serverkey), + ejabberd_sql:escape(Scram#scram.salt), + integer_to_binary(Scram#scram.iterationcount) + ) + end, Rs), + continue; + Err -> {bad_reply, Err} + end + end, + case sql_queries:sql_transaction(LServer, F) of + {atomic, ok} -> ok; + {atomic, continue} -> convert_to_scram(Server); + {atomic, Error} -> {error, Error}; + Error -> Error + end + end. + +opt_type(auth_password_format) -> fun (V) -> V end; +opt_type(_) -> [auth_password_format]. diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 13b636c7..f73474fe 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -651,14 +651,27 @@ process_host_term(Term, Host, State, Action) -> {hosts, _} -> State; {Opt, Val} when Action == set -> - set_option({Opt, Host}, Val, State); + set_option({rename_option(Opt), Host}, Val, State); {Opt, Val} when Action == append -> - append_option({Opt, Host}, Val, State); + append_option({rename_option(Opt), Host}, Val, State); Opt -> ?WARNING_MSG("Ignore invalid (outdated?) option ~p", [Opt]), State end. +rename_option(Option) when is_atom(Option) -> + case atom_to_list(Option) of + "odbc_" ++ T -> + NewOption = list_to_atom("sql_" ++ T), + ?WARNING_MSG("Option '~s' is obsoleted, use '~s' instead", + [Option, NewOption]), + NewOption; + _ -> + Option + end; +rename_option(Option) -> + Option. + set_option(Opt, Val, State) -> State#state{opts = [#local_config{key = Opt, value = Val} | State#state.opts]}. @@ -867,20 +880,20 @@ get_mylang() -> fun iolist_to_binary/1, <<"en">>). -replace_module(mod_announce_odbc) -> {mod_announce, odbc}; -replace_module(mod_blocking_odbc) -> {mod_blocking, odbc}; -replace_module(mod_caps_odbc) -> {mod_caps, odbc}; -replace_module(mod_irc_odbc) -> {mod_irc, odbc}; -replace_module(mod_last_odbc) -> {mod_last, odbc}; -replace_module(mod_muc_odbc) -> {mod_muc, odbc}; -replace_module(mod_offline_odbc) -> {mod_offline, odbc}; -replace_module(mod_privacy_odbc) -> {mod_privacy, odbc}; -replace_module(mod_private_odbc) -> {mod_private, odbc}; -replace_module(mod_roster_odbc) -> {mod_roster, odbc}; -replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, odbc}; -replace_module(mod_vcard_odbc) -> {mod_vcard, odbc}; -replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, odbc}; -replace_module(mod_pubsub_odbc) -> {mod_pubsub, odbc}; +replace_module(mod_announce_odbc) -> {mod_announce, sql}; +replace_module(mod_blocking_odbc) -> {mod_blocking, sql}; +replace_module(mod_caps_odbc) -> {mod_caps, sql}; +replace_module(mod_irc_odbc) -> {mod_irc, sql}; +replace_module(mod_last_odbc) -> {mod_last, sql}; +replace_module(mod_muc_odbc) -> {mod_muc, sql}; +replace_module(mod_offline_odbc) -> {mod_offline, sql}; +replace_module(mod_privacy_odbc) -> {mod_privacy, sql}; +replace_module(mod_private_odbc) -> {mod_private, sql}; +replace_module(mod_roster_odbc) -> {mod_roster, sql}; +replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, sql}; +replace_module(mod_vcard_odbc) -> {mod_vcard, sql}; +replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, sql}; +replace_module(mod_pubsub_odbc) -> {mod_pubsub, sql}; replace_module(Module) -> case is_elixir_module(Module) of true -> expand_elixir_module(Module); @@ -985,7 +998,7 @@ transform_terms(Terms) -> mod_last, ejabberd_s2s, ejabberd_listener, - ejabberd_odbc_sup, + ejabberd_sql_sup, shaper, ejabberd_s2s_out, acl, diff --git a/src/ejabberd_odbc.erl b/src/ejabberd_odbc.erl deleted file mode 100644 index f595933a..00000000 --- a/src/ejabberd_odbc.erl +++ /dev/null @@ -1,1025 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : ejabberd_odbc.erl -%%% Author : Alexey Shchepin -%%% Purpose : Serve ODBC connection -%%% Created : 8 Dec 2004 by Alexey Shchepin -%%% -%%% -%%% 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. -%%% -%%%---------------------------------------------------------------------- - --module(ejabberd_odbc). - --behaviour(ejabberd_config). - --author('alexey@process-one.net'). - --define(GEN_FSM, p1_fsm). - --behaviour(?GEN_FSM). - -%% External exports --export([start/1, start_link/2, - sql_query/2, - sql_query_t/1, - sql_transaction/2, - sql_bloc/2, - escape/1, - escape_like/1, - escape_like_arg/1, - to_bool/1, - sqlite_db/1, - sqlite_file/1, - encode_term/1, - decode_term/1, - odbc_config/0, - freetds_config/0, - odbcinst_config/0, - init_mssql/1, - keep_alive/1]). - -%% gen_fsm callbacks --export([init/1, handle_event/3, handle_sync_event/4, - handle_info/3, terminate/3, print_state/1, - code_change/4]). - --export([connecting/2, connecting/3, - session_established/2, session_established/3, - opt_type/1]). - --include("ejabberd.hrl"). --include("logger.hrl"). --include("ejabberd_sql_pt.hrl"). - --record(state, - {db_ref = self() :: pid(), - db_type = odbc :: pgsql | mysql | sqlite | odbc | mssql, - db_version = undefined :: undefined | non_neg_integer(), - start_interval = 0 :: non_neg_integer(), - host = <<"">> :: binary(), - max_pending_requests_len :: non_neg_integer(), - pending_requests = {0, queue:new()} :: {non_neg_integer(), ?TQUEUE}}). - --define(STATE_KEY, ejabberd_odbc_state). - --define(NESTING_KEY, ejabberd_odbc_nesting_level). - --define(TOP_LEVEL_TXN, 0). - --define(PGSQL_PORT, 5432). - --define(MYSQL_PORT, 3306). - --define(MSSQL_PORT, 1433). - --define(MAX_TRANSACTION_RESTARTS, 10). - --define(TRANSACTION_TIMEOUT, 60000). - --define(KEEPALIVE_TIMEOUT, 60000). - --define(KEEPALIVE_QUERY, [<<"SELECT 1;">>]). - --define(PREPARE_KEY, ejabberd_odbc_prepare). - -%%-define(DBGFSM, true). - --ifdef(DBGFSM). - --define(FSMOPTS, [{debug, [trace]}]). - --else. - --define(FSMOPTS, []). - --endif. - -%%%---------------------------------------------------------------------- -%%% API -%%%---------------------------------------------------------------------- -start(Host) -> - (?GEN_FSM):start(ejabberd_odbc, [Host], - fsm_limit_opts() ++ (?FSMOPTS)). - -start_link(Host, StartInterval) -> - (?GEN_FSM):start_link(ejabberd_odbc, - [Host, StartInterval], - fsm_limit_opts() ++ (?FSMOPTS)). - --type sql_query() :: [sql_query() | binary()] | #sql_query{} | - fun(() -> any()) | fun((atom(), _) -> any()). --type sql_query_result() :: {updated, non_neg_integer()} | - {error, binary()} | - {selected, [binary()], - [[binary()]]} | - {selected, [any()]}. - --spec sql_query(binary(), sql_query()) -> sql_query_result(). - -sql_query(Host, Query) -> - check_error(sql_call(Host, {sql_query, Query}), Query). - -%% SQL transaction based on a list of queries -%% This function automatically --spec sql_transaction(binary(), [sql_query()] | fun(() -> any())) -> - {atomic, any()} | - {aborted, any()}. - -sql_transaction(Host, Queries) - when is_list(Queries) -> - F = fun () -> - lists:foreach(fun (Query) -> sql_query_t(Query) end, - Queries) - end, - sql_transaction(Host, F); -%% SQL transaction, based on a erlang anonymous function (F = fun) -sql_transaction(Host, F) when is_function(F) -> - sql_call(Host, {sql_transaction, F}). - -%% SQL bloc, based on a erlang anonymous function (F = fun) -sql_bloc(Host, F) -> sql_call(Host, {sql_bloc, F}). - -sql_call(Host, Msg) -> - case get(?STATE_KEY) of - undefined -> - case ejabberd_odbc_sup:get_random_pid(Host) of - none -> {error, <<"Unknown Host">>}; - Pid -> - (?GEN_FSM):sync_send_event(Pid,{sql_cmd, Msg, - p1_time_compat:monotonic_time(milli_seconds)}, - ?TRANSACTION_TIMEOUT) - end; - _State -> nested_op(Msg) - end. - -keep_alive(PID) -> - (?GEN_FSM):sync_send_event(PID, - {sql_cmd, {sql_query, ?KEEPALIVE_QUERY}, - p1_time_compat:monotonic_time(milli_seconds)}, - ?KEEPALIVE_TIMEOUT). - --spec sql_query_t(sql_query()) -> sql_query_result(). - -%% This function is intended to be used from inside an sql_transaction: -sql_query_t(Query) -> - QRes = sql_query_internal(Query), - case QRes of - {error, Reason} -> throw({aborted, Reason}); - Rs when is_list(Rs) -> - case lists:keysearch(error, 1, Rs) of - {value, {error, Reason}} -> throw({aborted, Reason}); - _ -> QRes - end; - _ -> QRes - end. - -%% Escape character that will confuse an SQL engine -escape(S) -> - << <<(odbc_queries:escape(Char))/binary>> || <> <= S >>. - -%% Escape character that will confuse an SQL engine -%% Percent and underscore only need to be escaped for pattern matching like -%% statement -escape_like(S) when is_binary(S) -> - << <<(escape_like(C))/binary>> || <> <= S >>; -escape_like($%) -> <<"\\%">>; -escape_like($_) -> <<"\\_">>; -escape_like(C) when is_integer(C), C >= 0, C =< 255 -> odbc_queries:escape(C). - -escape_like_arg(S) when is_binary(S) -> - << <<(escape_like_arg(C))/binary>> || <> <= S >>; -escape_like_arg($%) -> <<"\\%">>; -escape_like_arg($_) -> <<"\\_">>; -escape_like_arg($\\) -> <<"\\\\">>; -escape_like_arg(C) when is_integer(C), C >= 0, C =< 255 -> <>. - -to_bool(<<"t">>) -> true; -to_bool(<<"true">>) -> true; -to_bool(<<"1">>) -> true; -to_bool(true) -> true; -to_bool(1) -> true; -to_bool(_) -> false. - -encode_term(Term) -> - escape(list_to_binary( - erl_prettypr:format(erl_syntax:abstract(Term), - [{paper, 65535}, {ribbon, 65535}]))). - -decode_term(Bin) -> - Str = binary_to_list(<>), - {ok, Tokens, _} = erl_scan:string(Str), - {ok, Term} = erl_parse:parse_term(Tokens), - Term. - --spec sqlite_db(binary()) -> atom(). -sqlite_db(Host) -> - list_to_atom("ejabberd_sqlite_" ++ binary_to_list(Host)). - --spec sqlite_file(binary()) -> string(). -sqlite_file(Host) -> - case ejabberd_config:get_option({odbc_database, Host}, - fun iolist_to_binary/1) of - undefined -> - {ok, Cwd} = file:get_cwd(), - filename:join([Cwd, "sqlite", atom_to_list(node()), - binary_to_list(Host), "ejabberd.db"]); - File -> - binary_to_list(File) - end. - -%%%---------------------------------------------------------------------- -%%% Callback functions from gen_fsm -%%%---------------------------------------------------------------------- -init([Host, StartInterval]) -> - case ejabberd_config:get_option( - {odbc_keepalive_interval, Host}, - fun(I) when is_integer(I), I>0 -> I end) of - undefined -> - ok; - KeepaliveInterval -> - timer:apply_interval(KeepaliveInterval * 1000, ?MODULE, - keep_alive, [self()]) - end, - [DBType | _] = db_opts(Host), - (?GEN_FSM):send_event(self(), connect), - ejabberd_odbc_sup:add_pid(Host, self()), - {ok, connecting, - #state{db_type = DBType, host = Host, - max_pending_requests_len = max_fsm_queue(), - pending_requests = {0, queue:new()}, - start_interval = StartInterval}}. - -connecting(connect, #state{host = Host} = State) -> - ConnectRes = case db_opts(Host) of - [mysql | Args] -> apply(fun mysql_connect/5, Args); - [pgsql | Args] -> apply(fun pgsql_connect/5, Args); - [sqlite | Args] -> apply(fun sqlite_connect/1, Args); - [mssql | Args] -> apply(fun odbc_connect/1, Args); - [odbc | Args] -> apply(fun odbc_connect/1, Args) - end, - {_, PendingRequests} = State#state.pending_requests, - case ConnectRes of - {ok, Ref} -> - erlang:monitor(process, Ref), - lists:foreach(fun (Req) -> - (?GEN_FSM):send_event(self(), Req) - end, - queue:to_list(PendingRequests)), - State1 = State#state{db_ref = Ref, - pending_requests = {0, queue:new()}}, - State2 = get_db_version(State1), - {next_state, session_established, State2}; - {error, Reason} -> - ?INFO_MSG("~p connection failed:~n** Reason: ~p~n** " - "Retry after: ~p seconds", - [State#state.db_type, Reason, - State#state.start_interval div 1000]), - (?GEN_FSM):send_event_after(State#state.start_interval, - connect), - {next_state, connecting, State} - end; -connecting(Event, State) -> - ?WARNING_MSG("unexpected event in 'connecting': ~p", - [Event]), - {next_state, connecting, State}. - -connecting({sql_cmd, {sql_query, ?KEEPALIVE_QUERY}, - _Timestamp}, - From, State) -> - (?GEN_FSM):reply(From, - {error, <<"SQL connection failed">>}), - {next_state, connecting, State}; -connecting({sql_cmd, Command, Timestamp} = Req, From, - State) -> - ?DEBUG("queuing pending request while connecting:~n\t~p", - [Req]), - {Len, PendingRequests} = State#state.pending_requests, - NewPendingRequests = if Len < - State#state.max_pending_requests_len -> - {Len + 1, - queue:in({sql_cmd, Command, From, Timestamp}, - PendingRequests)}; - true -> - lists:foreach(fun ({sql_cmd, _, To, - _Timestamp}) -> - (?GEN_FSM):reply(To, - {error, - <<"SQL connection failed">>}) - end, - queue:to_list(PendingRequests)), - {1, - queue:from_list([{sql_cmd, Command, From, - Timestamp}])} - end, - {next_state, connecting, - State#state{pending_requests = NewPendingRequests}}; -connecting(Request, {Who, _Ref}, State) -> - ?WARNING_MSG("unexpected call ~p from ~p in 'connecting'", - [Request, Who]), - {reply, {error, badarg}, connecting, State}. - -session_established({sql_cmd, Command, Timestamp}, From, - State) -> - run_sql_cmd(Command, From, State, Timestamp); -session_established(Request, {Who, _Ref}, State) -> - ?WARNING_MSG("unexpected call ~p from ~p in 'session_establ" - "ished'", - [Request, Who]), - {reply, {error, badarg}, session_established, State}. - -session_established({sql_cmd, Command, From, Timestamp}, - State) -> - run_sql_cmd(Command, From, State, Timestamp); -session_established(Event, State) -> - ?WARNING_MSG("unexpected event in 'session_established': ~p", - [Event]), - {next_state, session_established, State}. - -handle_event(_Event, StateName, State) -> - {next_state, StateName, State}. - -handle_sync_event(_Event, _From, StateName, State) -> - {reply, {error, badarg}, StateName, State}. - -code_change(_OldVsn, StateName, State, _Extra) -> - {ok, StateName, State}. - -%% We receive the down signal when we loose the MySQL connection (we are -%% monitoring the connection) -handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, - _StateName, State) -> - (?GEN_FSM):send_event(self(), connect), - {next_state, connecting, State}; -handle_info(Info, StateName, State) -> - ?WARNING_MSG("unexpected info in ~p: ~p", - [StateName, Info]), - {next_state, StateName, State}. - -terminate(_Reason, _StateName, State) -> - ejabberd_odbc_sup:remove_pid(State#state.host, self()), - case State#state.db_type of - mysql -> catch p1_mysql_conn:stop(State#state.db_ref); - sqlite -> catch sqlite3:close(sqlite_db(State#state.host)); - _ -> ok - end, - ok. - -%%---------------------------------------------------------------------- -%% Func: print_state/1 -%% Purpose: Prepare the state to be printed on error log -%% Returns: State to print -%%---------------------------------------------------------------------- -print_state(State) -> State. - -%%%---------------------------------------------------------------------- -%%% Internal functions -%%%---------------------------------------------------------------------- - -run_sql_cmd(Command, From, State, Timestamp) -> - case p1_time_compat:monotonic_time(milli_seconds) - Timestamp of - Age when Age < (?TRANSACTION_TIMEOUT) -> - put(?NESTING_KEY, ?TOP_LEVEL_TXN), - put(?STATE_KEY, State), - abort_on_driver_error(outer_op(Command), From); - Age -> - ?ERROR_MSG("Database was not available or too slow, " - "discarding ~p milliseconds old request~n~p~n", - [Age, Command]), - {next_state, session_established, State} - end. - -%% Only called by handle_call, only handles top level operations. -%% @spec outer_op(Op) -> {error, Reason} | {aborted, Reason} | {atomic, Result} -outer_op({sql_query, Query}) -> - sql_query_internal(Query); -outer_op({sql_transaction, F}) -> - outer_transaction(F, ?MAX_TRANSACTION_RESTARTS, <<"">>); -outer_op({sql_bloc, F}) -> execute_bloc(F). - -%% Called via sql_query/transaction/bloc from client code when inside a -%% nested operation -nested_op({sql_query, Query}) -> - sql_query_internal(Query); -nested_op({sql_transaction, F}) -> - NestingLevel = get(?NESTING_KEY), - if NestingLevel =:= (?TOP_LEVEL_TXN) -> - outer_transaction(F, ?MAX_TRANSACTION_RESTARTS, <<"">>); - true -> inner_transaction(F) - end; -nested_op({sql_bloc, F}) -> execute_bloc(F). - -%% Never retry nested transactions - only outer transactions -inner_transaction(F) -> - PreviousNestingLevel = get(?NESTING_KEY), - case get(?NESTING_KEY) of - ?TOP_LEVEL_TXN -> - {backtrace, T} = process_info(self(), backtrace), - ?ERROR_MSG("inner transaction called at outer txn " - "level. Trace: ~s", - [T]), - erlang:exit(implementation_faulty); - _N -> ok - end, - put(?NESTING_KEY, PreviousNestingLevel + 1), - Result = (catch F()), - put(?NESTING_KEY, PreviousNestingLevel), - case Result of - {aborted, Reason} -> {aborted, Reason}; - {'EXIT', Reason} -> {'EXIT', Reason}; - {atomic, Res} -> {atomic, Res}; - Res -> {atomic, Res} - end. - -outer_transaction(F, NRestarts, _Reason) -> - PreviousNestingLevel = get(?NESTING_KEY), - case get(?NESTING_KEY) of - ?TOP_LEVEL_TXN -> ok; - _N -> - {backtrace, T} = process_info(self(), backtrace), - ?ERROR_MSG("outer transaction called at inner txn " - "level. Trace: ~s", - [T]), - erlang:exit(implementation_faulty) - end, - sql_query_internal([<<"begin;">>]), - put(?NESTING_KEY, PreviousNestingLevel + 1), - Result = (catch F()), - put(?NESTING_KEY, PreviousNestingLevel), - case Result of - {aborted, Reason} when NRestarts > 0 -> - sql_query_internal([<<"rollback;">>]), - outer_transaction(F, NRestarts - 1, Reason); - {aborted, Reason} when NRestarts =:= 0 -> - ?ERROR_MSG("SQL transaction restarts exceeded~n** " - "Restarts: ~p~n** Last abort reason: " - "~p~n** Stacktrace: ~p~n** When State " - "== ~p", - [?MAX_TRANSACTION_RESTARTS, Reason, - erlang:get_stacktrace(), get(?STATE_KEY)]), - sql_query_internal([<<"rollback;">>]), - {aborted, Reason}; - {'EXIT', Reason} -> - sql_query_internal([<<"rollback;">>]), {aborted, Reason}; - Res -> sql_query_internal([<<"commit;">>]), {atomic, Res} - end. - -execute_bloc(F) -> - case catch F() of - {aborted, Reason} -> {aborted, Reason}; - {'EXIT', Reason} -> {aborted, Reason}; - Res -> {atomic, Res} - end. - -execute_fun(F) when is_function(F, 0) -> - F(); -execute_fun(F) when is_function(F, 2) -> - State = get(?STATE_KEY), - F(State#state.db_type, State#state.db_version). - -sql_query_internal([{_, _} | _] = Queries) -> - State = get(?STATE_KEY), - case select_sql_query(Queries, State) of - undefined -> - {error, <<"no matching query for the current DBMS found">>}; - Query -> - sql_query_internal(Query) - end; -sql_query_internal(#sql_query{} = Query) -> - State = get(?STATE_KEY), - Res = - try - case State#state.db_type of - odbc -> - generic_sql_query(Query); - mssql -> - generic_sql_query(Query); - pgsql -> - Key = {?PREPARE_KEY, Query#sql_query.hash}, - case get(Key) of - undefined -> - case pgsql_prepare(Query, State) of - {ok, _, _, _} -> - put(Key, prepared); - {error, Error} -> - ?ERROR_MSG("PREPARE failed for SQL query " - "at ~p: ~p", - [Query#sql_query.loc, Error]), - put(Key, ignore) - end; - _ -> - ok - end, - case get(Key) of - prepared -> - pgsql_execute_sql_query(Query, State); - _ -> - generic_sql_query(Query) - end; - mysql -> - generic_sql_query(Query); - sqlite -> - generic_sql_query(Query) - end - catch - Class:Reason -> - ST = erlang:get_stacktrace(), - ?ERROR_MSG("Internal error while processing SQL query: ~p", - [{Class, Reason, ST}]), - {error, <<"internal error">>} - end, - case Res of - {error, <<"No SQL-driver information available.">>} -> - {updated, 0}; - _Else -> Res - end; -sql_query_internal(F) when is_function(F) -> - case catch execute_fun(F) of - {'EXIT', Reason} -> {error, Reason}; - Res -> Res - end; -sql_query_internal(Query) -> - State = get(?STATE_KEY), - ?DEBUG("SQL: \"~s\"", [Query]), - Res = case State#state.db_type of - odbc -> - to_odbc(odbc:sql_query(State#state.db_ref, [Query], - (?TRANSACTION_TIMEOUT) - 1000)); - mssql -> - to_odbc(odbc:sql_query(State#state.db_ref, [Query], - (?TRANSACTION_TIMEOUT) - 1000)); - pgsql -> - pgsql_to_odbc(pgsql:squery(State#state.db_ref, Query)); - mysql -> - R = mysql_to_odbc(p1_mysql_conn:squery(State#state.db_ref, - [Query], self(), - [{timeout, (?TRANSACTION_TIMEOUT) - 1000}, - {result_type, binary}])), - %% ?INFO_MSG("MySQL, Received result~n~p~n", [R]), - R; - sqlite -> - Host = State#state.host, - sqlite_to_odbc(Host, sqlite3:sql_exec(sqlite_db(Host), Query)) - end, - case Res of - {error, <<"No SQL-driver information available.">>} -> - {updated, 0}; - _Else -> Res - end. - -select_sql_query(Queries, State) -> - select_sql_query( - Queries, State#state.db_type, State#state.db_version, undefined). - -select_sql_query([], _Type, _Version, undefined) -> - undefined; -select_sql_query([], _Type, _Version, Query) -> - Query; -select_sql_query([{any, Query} | _], _Type, _Version, _) -> - Query; -select_sql_query([{Type, Query} | _], Type, _Version, _) -> - Query; -select_sql_query([{{Type, _Version1}, Query1} | Rest], Type, undefined, _) -> - select_sql_query(Rest, Type, undefined, Query1); -select_sql_query([{{Type, Version1}, Query1} | Rest], Type, Version, Query) -> - if - Version >= Version1 -> - Query1; - true -> - select_sql_query(Rest, Type, Version, Query) - end; -select_sql_query([{_, _} | Rest], Type, Version, Query) -> - select_sql_query(Rest, Type, Version, Query). - -generic_sql_query(SQLQuery) -> - sql_query_format_res( - sql_query_internal(generic_sql_query_format(SQLQuery)), - SQLQuery). - -generic_sql_query_format(SQLQuery) -> - Args = (SQLQuery#sql_query.args)(generic_escape()), - (SQLQuery#sql_query.format_query)(Args). - -generic_escape() -> - #sql_escape{string = fun(X) -> <<"'", (escape(X))/binary, "'">> end, - integer = fun(X) -> integer_to_binary(X) end, - boolean = fun(true) -> <<"1">>; - (false) -> <<"0">> - end - }. - -pgsql_prepare(SQLQuery, State) -> - Escape = #sql_escape{_ = fun(X) -> X end}, - N = length((SQLQuery#sql_query.args)(Escape)), - Args = [<<$$, (integer_to_binary(I))/binary>> || I <- lists:seq(1, N)], - Query = (SQLQuery#sql_query.format_query)(Args), - pgsql:prepare(State#state.db_ref, SQLQuery#sql_query.hash, Query). - -pgsql_execute_escape() -> - #sql_escape{string = fun(X) -> X end, - integer = fun(X) -> [integer_to_binary(X)] end, - boolean = fun(true) -> "1"; - (false) -> "0" - end - }. - -pgsql_execute_sql_query(SQLQuery, State) -> - Args = (SQLQuery#sql_query.args)(pgsql_execute_escape()), - ExecuteRes = - pgsql:execute(State#state.db_ref, SQLQuery#sql_query.hash, Args), -% {T, ExecuteRes} = -% timer:tc(pgsql, execute, [State#state.db_ref, SQLQuery#sql_query.hash, Args]), -% io:format("T ~s ~p~n", [SQLQuery#sql_query.hash, T]), - Res = pgsql_execute_to_odbc(ExecuteRes), - sql_query_format_res(Res, SQLQuery). - - -sql_query_format_res({selected, _, Rows}, SQLQuery) -> - Res = - lists:flatmap( - fun(Row) -> - try - [(SQLQuery#sql_query.format_res)(Row)] - catch - Class:Reason -> - ST = erlang:get_stacktrace(), - ?ERROR_MSG("Error while processing " - "SQL query result: ~p~n" - "row: ~p", - [{Class, Reason, ST}, Row]), - [] - end - end, Rows), - {selected, Res}; -sql_query_format_res(Res, _SQLQuery) -> - Res. - -%% Generate the OTP callback return tuple depending on the driver result. -abort_on_driver_error({error, <<"query timed out">>} = - Reply, - From) -> - (?GEN_FSM):reply(From, Reply), - {stop, timeout, get(?STATE_KEY)}; -abort_on_driver_error({error, - <<"Failed sending data on socket", _/binary>>} = - Reply, - From) -> - (?GEN_FSM):reply(From, Reply), - {stop, closed, get(?STATE_KEY)}; -abort_on_driver_error(Reply, From) -> - (?GEN_FSM):reply(From, Reply), - {next_state, session_established, get(?STATE_KEY)}. - -%% == pure ODBC code - -%% part of init/1 -%% Open an ODBC database connection -odbc_connect(SQLServer) -> - ejabberd:start_app(odbc), - odbc:connect(binary_to_list(SQLServer), - [{scrollable_cursors, off}, - {tuple_row, off}, - {binary_strings, on}]). - -%% == Native SQLite code - -%% part of init/1 -%% Open a database connection to SQLite - -sqlite_connect(Host) -> - File = sqlite_file(Host), - case filelib:ensure_dir(File) of - ok -> - case sqlite3:open(sqlite_db(Host), [{file, File}]) of - {ok, Ref} -> - sqlite3:sql_exec( - sqlite_db(Host), "pragma foreign_keys = on"), - {ok, Ref}; - {error, {already_started, Ref}} -> - {ok, Ref}; - {error, Reason} -> - {error, Reason} - end; - Err -> - Err - end. - -%% Convert SQLite query result to Erlang ODBC result formalism -sqlite_to_odbc(Host, ok) -> - {updated, sqlite3:changes(sqlite_db(Host))}; -sqlite_to_odbc(Host, {rowid, _}) -> - {updated, sqlite3:changes(sqlite_db(Host))}; -sqlite_to_odbc(_Host, [{columns, Columns}, {rows, TRows}]) -> - Rows = [lists:map( - fun(I) when is_integer(I) -> - jlib:integer_to_binary(I); - (B) -> - B - end, tuple_to_list(Row)) || Row <- TRows], - {selected, [list_to_binary(C) || C <- Columns], Rows}; -sqlite_to_odbc(_Host, {error, _Code, Reason}) -> - {error, Reason}; -sqlite_to_odbc(_Host, _) -> - {updated, undefined}. - -%% == Native PostgreSQL code - -%% part of init/1 -%% Open a database connection to PostgreSQL -pgsql_connect(Server, Port, DB, Username, Password) -> - case pgsql:connect([{host, Server}, - {database, DB}, - {user, Username}, - {password, Password}, - {port, Port}, - {as_binary, true}]) of - {ok, Ref} -> - pgsql:squery(Ref, [<<"alter database ">>, DB, <<" set ">>, - <<"standard_conforming_strings='off';">>]), - pgsql:squery(Ref, [<<"set standard_conforming_strings to 'off';">>]), - {ok, Ref}; - Err -> - Err - end. - -%% Convert PostgreSQL query result to Erlang ODBC result formalism -pgsql_to_odbc({ok, PGSQLResult}) -> - case PGSQLResult of - [Item] -> pgsql_item_to_odbc(Item); - Items -> [pgsql_item_to_odbc(Item) || Item <- Items] - end. - -pgsql_item_to_odbc({<<"SELECT", _/binary>>, Rows, - Recs}) -> - {selected, [element(1, Row) || Row <- Rows], Recs}; -pgsql_item_to_odbc({<<"FETCH", _/binary>>, Rows, - Recs}) -> - {selected, [element(1, Row) || Row <- Rows], Recs}; -pgsql_item_to_odbc(<<"INSERT ", OIDN/binary>>) -> - [_OID, N] = str:tokens(OIDN, <<" ">>), - {updated, jlib:binary_to_integer(N)}; -pgsql_item_to_odbc(<<"DELETE ", N/binary>>) -> - {updated, jlib:binary_to_integer(N)}; -pgsql_item_to_odbc(<<"UPDATE ", N/binary>>) -> - {updated, jlib:binary_to_integer(N)}; -pgsql_item_to_odbc({error, Error}) -> {error, Error}; -pgsql_item_to_odbc(_) -> {updated, undefined}. - -pgsql_execute_to_odbc({ok, {<<"SELECT", _/binary>>, Rows}}) -> - {selected, [], [[Field || {_, Field} <- Row] || Row <- Rows]}; -pgsql_execute_to_odbc({ok, {'INSERT', N}}) -> - {updated, N}; -pgsql_execute_to_odbc({ok, {'DELETE', N}}) -> - {updated, N}; -pgsql_execute_to_odbc({ok, {'UPDATE', N}}) -> - {updated, N}; -pgsql_execute_to_odbc({error, Error}) -> {error, Error}; -pgsql_execute_to_odbc(_) -> {updated, undefined}. - - -%% == Native MySQL code - -%% part of init/1 -%% Open a database connection to MySQL -mysql_connect(Server, Port, DB, Username, Password) -> - case p1_mysql_conn:start(binary_to_list(Server), Port, - binary_to_list(Username), - binary_to_list(Password), - binary_to_list(DB), fun log/3) - of - {ok, Ref} -> - p1_mysql_conn:fetch( - Ref, [<<"set names 'utf8mb4' collate 'utf8mb4_bin';">>], self()), - {ok, Ref}; - Err -> Err - end. - -%% Convert MySQL query result to Erlang ODBC result formalism -mysql_to_odbc({updated, MySQLRes}) -> - {updated, p1_mysql:get_result_affected_rows(MySQLRes)}; -mysql_to_odbc({data, MySQLRes}) -> - mysql_item_to_odbc(p1_mysql:get_result_field_info(MySQLRes), - p1_mysql:get_result_rows(MySQLRes)); -mysql_to_odbc({error, MySQLRes}) - when is_binary(MySQLRes) -> - {error, MySQLRes}; -mysql_to_odbc({error, MySQLRes}) - when is_list(MySQLRes) -> - {error, list_to_binary(MySQLRes)}; -mysql_to_odbc({error, MySQLRes}) -> - {error, p1_mysql:get_result_reason(MySQLRes)}; -mysql_to_odbc(ok) -> - ok. - - -%% When tabular data is returned, convert it to the ODBC formalism -mysql_item_to_odbc(Columns, Recs) -> - {selected, [element(2, Column) || Column <- Columns], Recs}. - -to_odbc({selected, Columns, Recs}) -> - Rows = [lists:map( - fun(I) when is_integer(I) -> - jlib:integer_to_binary(I); - (B) -> - B - end, Row) || Row <- Recs], - {selected, [list_to_binary(C) || C <- Columns], Rows}; -to_odbc({error, Reason}) when is_list(Reason) -> - {error, list_to_binary(Reason)}; -to_odbc(Res) -> - Res. - -get_db_version(#state{db_type = pgsql} = State) -> - case pgsql:squery(State#state.db_ref, - <<"select current_setting('server_version_num')">>) of - {ok, [{_, _, [[SVersion]]}]} -> - case catch binary_to_integer(SVersion) of - Version when is_integer(Version) -> - State#state{db_version = Version}; - Error -> - ?WARNING_MSG("error getting pgsql version: ~p", [Error]), - State - end; - Res -> - ?WARNING_MSG("error getting pgsql version: ~p", [Res]), - State - end; -get_db_version(State) -> - State. - -log(Level, Format, Args) -> - case Level of - debug -> ?DEBUG(Format, Args); - normal -> ?INFO_MSG(Format, Args); - error -> ?ERROR_MSG(Format, Args) - end. - -db_opts(Host) -> - Type = ejabberd_config:get_option({odbc_type, Host}, - fun(mysql) -> mysql; - (pgsql) -> pgsql; - (sqlite) -> sqlite; - (mssql) -> mssql; - (odbc) -> odbc - end, odbc), - Server = ejabberd_config:get_option({odbc_server, Host}, - fun iolist_to_binary/1, - <<"localhost">>), - case Type of - odbc -> - [odbc, Server]; - sqlite -> - [sqlite, Host]; - _ -> - Port = ejabberd_config:get_option( - {odbc_port, Host}, - fun(P) when is_integer(P), P > 0, P < 65536 -> P end, - case Type of - mssql -> ?MSSQL_PORT; - mysql -> ?MYSQL_PORT; - pgsql -> ?PGSQL_PORT - end), - DB = ejabberd_config:get_option({odbc_database, Host}, - fun iolist_to_binary/1, - <<"ejabberd">>), - User = ejabberd_config:get_option({odbc_username, Host}, - fun iolist_to_binary/1, - <<"ejabberd">>), - Pass = ejabberd_config:get_option({odbc_password, Host}, - fun iolist_to_binary/1, - <<"">>), - case Type of - mssql -> - [mssql, <<"DSN=", Host/binary, ";UID=", User/binary, - ";PWD=", Pass/binary>>]; - _ -> - [Type, Server, Port, DB, User, Pass] - end - end. - -init_mssql(Host) -> - Server = ejabberd_config:get_option({odbc_server, Host}, - fun iolist_to_binary/1, - <<"localhost">>), - Port = ejabberd_config:get_option( - {odbc_port, Host}, - fun(P) when is_integer(P), P > 0, P < 65536 -> P end, - ?MSSQL_PORT), - DB = ejabberd_config:get_option({odbc_database, Host}, - fun iolist_to_binary/1, - <<"ejabberd">>), - FreeTDS = io_lib:fwrite("[~s]~n" - "\thost = ~s~n" - "\tport = ~p~n" - "\ttds version = 7.1~n", - [Host, Server, Port]), - ODBCINST = io_lib:fwrite("[freetds]~n" - "Description = MSSQL connection~n" - "Driver = libtdsodbc.so~n" - "Setup = libtdsS.so~n" - "UsageCount = 1~n" - "FileUsage = 1~n", []), - ODBCINI = io_lib:fwrite("[~s]~n" - "Description = MS SQL~n" - "Driver = freetds~n" - "Servername = ~s~n" - "Database = ~s~n" - "Port = ~p~n", - [Host, Host, DB, Port]), - ?DEBUG("~s:~n~s", [freetds_config(), FreeTDS]), - ?DEBUG("~s:~n~s", [odbcinst_config(), ODBCINST]), - ?DEBUG("~s:~n~s", [odbc_config(), ODBCINI]), - case filelib:ensure_dir(freetds_config()) of - ok -> - try - ok = file:write_file(freetds_config(), FreeTDS, [append]), - ok = file:write_file(odbcinst_config(), ODBCINST), - ok = file:write_file(odbc_config(), ODBCINI, [append]), - os:putenv("ODBCSYSINI", tmp_dir()), - os:putenv("FREETDS", freetds_config()), - os:putenv("FREETDSCONF", freetds_config()), - ok - catch error:{badmatch, {error, Reason} = Err} -> - ?ERROR_MSG("failed to create temporary files in ~s: ~s", - [tmp_dir(), file:format_error(Reason)]), - Err - end; - {error, Reason} = Err -> - ?ERROR_MSG("failed to create temporary directory ~s: ~s", - [tmp_dir(), file:format_error(Reason)]), - Err - end. - -tmp_dir() -> - filename:join(["/tmp", "ejabberd"]). - -odbc_config() -> - filename:join(tmp_dir(), "odbc.ini"). - -freetds_config() -> - filename:join(tmp_dir(), "freetds.conf"). - -odbcinst_config() -> - filename:join(tmp_dir(), "odbcinst.ini"). - -max_fsm_queue() -> - ejabberd_config:get_option( - max_fsm_queue, - fun(N) when is_integer(N), N > 0 -> N end). - -fsm_limit_opts() -> - case max_fsm_queue() of - N when is_integer(N) -> [{max_queue, N}]; - _ -> [] - end. - -check_error({error, Why} = Err, #sql_query{} = Query) -> - ?ERROR_MSG("SQL query '~s' at ~p failed: ~p", - [Query#sql_query.hash, Query#sql_query.loc, Why]), - Err; -check_error({error, Why} = Err, Query) -> - case catch iolist_to_binary(Query) of - SQuery when is_binary(SQuery) -> - ?ERROR_MSG("SQL query '~s' failed: ~p", [SQuery, Why]); - _ -> - ?ERROR_MSG("SQL query ~p failed: ~p", [Query, Why]) - end, - Err; -check_error(Result, _Query) -> - Result. - -opt_type(max_fsm_queue) -> - fun (N) when is_integer(N), N > 0 -> N end; -opt_type(odbc_database) -> fun iolist_to_binary/1; -opt_type(odbc_keepalive_interval) -> - fun (I) when is_integer(I), I > 0 -> I end; -opt_type(odbc_password) -> fun iolist_to_binary/1; -opt_type(odbc_port) -> - fun (P) when is_integer(P), P > 0, P < 65536 -> P end; -opt_type(odbc_server) -> fun iolist_to_binary/1; -opt_type(odbc_type) -> - fun (mysql) -> mysql; - (pgsql) -> pgsql; - (sqlite) -> sqlite; - (mssql) -> mssql; - (odbc) -> odbc - end; -opt_type(odbc_username) -> fun iolist_to_binary/1; -opt_type(_) -> - [max_fsm_queue, odbc_database, odbc_keepalive_interval, - odbc_password, odbc_port, odbc_server, odbc_type, - odbc_username]. diff --git a/src/ejabberd_odbc_sup.erl b/src/ejabberd_odbc_sup.erl deleted file mode 100644 index 65fa3c1c..00000000 --- a/src/ejabberd_odbc_sup.erl +++ /dev/null @@ -1,226 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : ejabberd_odbc_sup.erl -%%% Author : Alexey Shchepin -%%% Purpose : ODBC connections supervisor -%%% Created : 22 Dec 2004 by Alexey Shchepin -%%% -%%% -%%% 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. -%%% -%%%---------------------------------------------------------------------- - --module(ejabberd_odbc_sup). - --behaviour(ejabberd_config). - --author('alexey@process-one.net'). - --export([start_link/1, init/1, add_pid/2, remove_pid/2, - get_pids/1, get_random_pid/1, transform_options/1, - opt_type/1]). - --include("ejabberd.hrl"). --include("logger.hrl"). - --define(PGSQL_PORT, 5432). - --define(MYSQL_PORT, 3306). - --define(DEFAULT_POOL_SIZE, 10). - --define(DEFAULT_ODBC_START_INTERVAL, 30). - --define(CONNECT_TIMEOUT, 500). - --record(sql_pool, {host, pid}). - -start_link(Host) -> - mnesia:create_table(sql_pool, - [{ram_copies, [node()]}, {type, bag}, - {local_content, true}, - {attributes, record_info(fields, sql_pool)}]), - mnesia:add_table_copy(sql_pool, node(), ram_copies), - F = fun () -> mnesia:delete({sql_pool, Host}) end, - mnesia:ets(F), - supervisor:start_link({local, - gen_mod:get_module_proc(Host, ?MODULE)}, - ?MODULE, [Host]). - -init([Host]) -> - PoolSize = ejabberd_config:get_option( - {odbc_pool_size, Host}, - fun(I) when is_integer(I), I>0 -> I end, - ?DEFAULT_POOL_SIZE), - StartInterval = ejabberd_config:get_option( - {odbc_start_interval, Host}, - fun(I) when is_integer(I), I>0 -> I end, - ?DEFAULT_ODBC_START_INTERVAL), - Type = ejabberd_config:get_option({odbc_type, Host}, - fun(mysql) -> mysql; - (pgsql) -> pgsql; - (sqlite) -> sqlite; - (mssql) -> mssql; - (odbc) -> odbc - end, odbc), - case Type of - sqlite -> - check_sqlite_db(Host); - mssql -> - ejabberd_odbc:init_mssql(Host); - _ -> - ok - end, - - {ok, - {{one_for_one, PoolSize * 10, 1}, - lists:map(fun (I) -> - {I, - {ejabberd_odbc, start_link, - [Host, StartInterval * 1000]}, - transient, 2000, worker, [?MODULE]} - end, - lists:seq(1, PoolSize))}}. - -get_pids(Host) -> - Rs = mnesia:dirty_read(sql_pool, Host), - [R#sql_pool.pid || R <- Rs]. - -get_random_pid(Host) -> - case get_pids(Host) of - [] -> none; - Pids -> lists:nth(erlang:phash(p1_time_compat:unique_integer(), length(Pids)), Pids) - end. - -add_pid(Host, Pid) -> - F = fun () -> - mnesia:write(#sql_pool{host = Host, pid = Pid}) - end, - mnesia:ets(F). - -remove_pid(Host, Pid) -> - F = fun () -> - mnesia:delete_object(#sql_pool{host = Host, pid = Pid}) - end, - mnesia:ets(F). - -transform_options(Opts) -> - lists:foldl(fun transform_options/2, [], Opts). - -transform_options({odbc_server, {Type, Server, Port, DB, User, Pass}}, Opts) -> - [{odbc_type, Type}, - {odbc_server, Server}, - {odbc_port, Port}, - {odbc_database, DB}, - {odbc_username, User}, - {odbc_password, Pass}|Opts]; -transform_options({odbc_server, {mysql, Server, DB, User, Pass}}, Opts) -> - transform_options({odbc_server, {mysql, Server, ?MYSQL_PORT, DB, User, Pass}}, Opts); -transform_options({odbc_server, {pgsql, Server, DB, User, Pass}}, Opts) -> - transform_options({odbc_server, {pgsql, Server, ?PGSQL_PORT, DB, User, Pass}}, Opts); -transform_options({odbc_server, {sqlite, DB}}, Opts) -> - transform_options({odbc_server, {sqlite, DB}}, Opts); -transform_options(Opt, Opts) -> - [Opt|Opts]. - -check_sqlite_db(Host) -> - DB = ejabberd_odbc:sqlite_db(Host), - File = ejabberd_odbc:sqlite_file(Host), - Ret = case filelib:ensure_dir(File) of - ok -> - case sqlite3:open(DB, [{file, File}]) of - {ok, _Ref} -> ok; - {error, {already_started, _Ref}} -> ok; - {error, R} -> {error, R} - end; - Err -> - Err - end, - case Ret of - ok -> - sqlite3:sql_exec(DB, "pragma foreign_keys = on"), - case sqlite3:list_tables(DB) of - [] -> - create_sqlite_tables(DB), - sqlite3:close(DB), - ok; - [_H | _] -> - ok - end; - {error, Reason} -> - ?INFO_MSG("Failed open sqlite database, reason ~p", [Reason]) - end. - -create_sqlite_tables(DB) -> - SqlDir = case code:priv_dir(ejabberd) of - {error, _} -> - ?SQL_DIR; - PrivDir -> - filename:join(PrivDir, "sql") - end, - File = filename:join(SqlDir, "lite.sql"), - case file:open(File, [read, binary]) of - {ok, Fd} -> - Qs = read_lines(Fd, File, []), - ok = sqlite3:sql_exec(DB, "begin"), - [ok = sqlite3:sql_exec(DB, Q) || Q <- Qs], - ok = sqlite3:sql_exec(DB, "commit"); - {error, Reason} -> - ?INFO_MSG("Failed to read SQLite schema file: ~s", - [file:format_error(Reason)]) - end. - -read_lines(Fd, File, Acc) -> - case file:read_line(Fd) of - {ok, Line} -> - NewAcc = case str:strip(str:strip(Line, both, $\r), both, $\n) of - <<"--", _/binary>> -> - Acc; - <<>> -> - Acc; - _ -> - [Line|Acc] - end, - read_lines(Fd, File, NewAcc); - eof -> - QueryList = str:tokens(list_to_binary(lists:reverse(Acc)), <<";">>), - lists:flatmap( - fun(Query) -> - case str:strip(str:strip(Query, both, $\r), both, $\n) of - <<>> -> - []; - Q -> - [<>] - end - end, QueryList); - {error, _} = Err -> - ?ERROR_MSG("Failed read from lite.sql, reason: ~p", [Err]), - [] - end. - -opt_type(odbc_pool_size) -> - fun (I) when is_integer(I), I > 0 -> I end; -opt_type(odbc_start_interval) -> - fun (I) when is_integer(I), I > 0 -> I end; -opt_type(odbc_type) -> - fun (mysql) -> mysql; - (pgsql) -> pgsql; - (sqlite) -> sqlite; - (mssql) -> mssql; - (odbc) -> odbc - end; -opt_type(_) -> - [odbc_pool_size, odbc_start_interval, odbc_type]. diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl index 123189dd..75800123 100644 --- a/src/ejabberd_piefxis.erl +++ b/src/ejabberd_piefxis.erl @@ -28,8 +28,8 @@ %%% Not implemented: %%% - write mod_piefxis with ejabberdctl commands -%%% - Export from mod_offline_odbc.erl -%%% - Export from mod_private_odbc.erl +%%% - Export from mod_offline_sql.erl +%%% - Export from mod_private_sql.erl %%% - XEP-227: 6. Security Considerations %%% - Other schemas of XInclude are not tested, and may not be imported correctly. %%% - If a host has many users, split that host in XML files with 50 users each. diff --git a/src/ejabberd_rdbms.erl b/src/ejabberd_rdbms.erl index ae405db0..5224b035 100644 --- a/src/ejabberd_rdbms.erl +++ b/src/ejabberd_rdbms.erl @@ -35,10 +35,10 @@ -include("logger.hrl"). start() -> - file:delete(ejabberd_odbc:freetds_config()), - file:delete(ejabberd_odbc:odbc_config()), - file:delete(ejabberd_odbc:odbcinst_config()), - case lists:any(fun(H) -> needs_odbc(H) /= false end, + file:delete(ejabberd_sql:freetds_config()), + file:delete(ejabberd_sql:odbc_config()), + file:delete(ejabberd_sql:odbcinst_config()), + case lists:any(fun(H) -> needs_sql(H) /= false end, ?MYHOSTS) of true -> start_hosts(); @@ -49,34 +49,34 @@ start() -> %% Start relationnal DB module on the nodes where it is needed start_hosts() -> lists:foreach(fun (Host) -> - case needs_odbc(Host) of - {true, App} -> start_odbc(Host, App); + case needs_sql(Host) of + {true, App} -> start_sql(Host, App); false -> ok end end, ?MYHOSTS). -%% Start the ODBC module on the given host -start_odbc(Host, App) -> +%% Start the SQL module on the given host +start_sql(Host, App) -> ejabberd:start_app(App), Supervisor_name = gen_mod:get_module_proc(Host, - ejabberd_odbc_sup), + ejabberd_sql_sup), ChildSpec = {Supervisor_name, - {ejabberd_odbc_sup, start_link, [Host]}, transient, - infinity, supervisor, [ejabberd_odbc_sup]}, + {ejabberd_sql_sup, start_link, [Host]}, transient, + infinity, supervisor, [ejabberd_sql_sup]}, case supervisor:start_child(ejabberd_sup, ChildSpec) of {ok, _PID} -> ok; _Error -> ?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying." "..~n", [Supervisor_name, _Error]), - start_odbc(Host, App) + start_sql(Host, App) end. -%% Returns {true, App} if we have configured odbc for the given host -needs_odbc(Host) -> +%% Returns {true, App} if we have configured sql for the given host +needs_sql(Host) -> LHost = jid:nameprep(Host), - case ejabberd_config:get_option({odbc_type, LHost}, + case ejabberd_config:get_option({sql_type, LHost}, fun(mysql) -> mysql; (pgsql) -> pgsql; (sqlite) -> sqlite; @@ -91,11 +91,11 @@ needs_odbc(Host) -> undefined -> false end. -opt_type(odbc_type) -> +opt_type(sql_type) -> fun (mysql) -> mysql; (pgsql) -> pgsql; (sqlite) -> sqlite; (mssql) -> mssql; (odbc) -> odbc end; -opt_type(_) -> [odbc_type]. +opt_type(_) -> [sql_type]. diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index 9170ae67..b7fc3950 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -814,7 +814,8 @@ make_sid() -> opt_type(sm_db_type) -> fun (mnesia) -> mnesia; (internal) -> mnesia; - (odbc) -> odbc; + (sql) -> sql; + (odbc) -> sql; (redis) -> redis end; opt_type(_) -> [sm_db_type]. diff --git a/src/ejabberd_sm_sql.erl b/src/ejabberd_sm_sql.erl index dfc0eb2a..3d4e224d 100644 --- a/src/ejabberd_sm_sql.erl +++ b/src/ejabberd_sm_sql.erl @@ -29,11 +29,11 @@ %%%=================================================================== -spec init() -> ok | {error, any()}. init() -> - Node = ejabberd_odbc:escape(jlib:atom_to_binary(node())), + Node = ejabberd_sql:escape(jlib:atom_to_binary(node())), ?INFO_MSG("Cleaning SQL SM table...", []), lists:foldl( fun(Host, ok) -> - case ejabberd_odbc:sql_query( + case ejabberd_sql:sql_query( Host, [<<"delete from sm where node='">>, Node, <<"'">>]) of {updated, _} -> ok; @@ -47,14 +47,14 @@ init() -> set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R}, priority = Priority, info = Info}) -> - Username = ejabberd_odbc:escape(U), - Resource = ejabberd_odbc:escape(R), - InfoS = ejabberd_odbc:encode_term(Info), + Username = ejabberd_sql:escape(U), + Resource = ejabberd_sql:escape(R), + InfoS = ejabberd_sql:encode_term(Info), PrioS = enc_priority(Priority), TS = now_to_timestamp(Now), PidS = list_to_binary(erlang:pid_to_list(Pid)), - Node = ejabberd_odbc:escape(jlib:atom_to_binary(node(Pid))), - case odbc_queries:update( + Node = ejabberd_sql:escape(jlib:atom_to_binary(node(Pid))), + case sql_queries:update( LServer, <<"sm">>, [<<"usec">>, <<"pid">>, <<"node">>, <<"username">>, @@ -70,12 +70,12 @@ set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R}, delete_session(_LUser, LServer, _LResource, {Now, Pid}) -> TS = now_to_timestamp(Now), PidS = list_to_binary(erlang:pid_to_list(Pid)), - case ejabberd_odbc:sql_query( + case ejabberd_sql:sql_query( LServer, [<<"select usec, pid, username, resource, priority, info ">>, <<"from sm where usec='">>, TS, <<"' and pid='">>,PidS, <<"'">>]) of {selected, _, [Row]} -> - ejabberd_odbc:sql_query( + ejabberd_sql:sql_query( LServer, [<<"delete from sm where usec='">>, TS, <<"' and pid='">>, PidS, <<"'">>]), {ok, row_to_session(LServer, Row)}; @@ -93,7 +93,7 @@ get_sessions() -> end, ejabberd_sm:get_vh_by_backend(?MODULE)). get_sessions(LServer) -> - case ejabberd_odbc:sql_query( + case ejabberd_sql:sql_query( LServer, [<<"select usec, pid, username, ">>, <<"resource, priority, info from sm">>]) of {selected, _, Rows} -> @@ -104,8 +104,8 @@ get_sessions(LServer) -> end. get_sessions(LUser, LServer) -> - Username = ejabberd_odbc:escape(LUser), - case ejabberd_odbc:sql_query( + Username = ejabberd_sql:escape(LUser), + case ejabberd_sql:sql_query( LServer, [<<"select usec, pid, username, ">>, <<"resource, priority, info from sm where ">>, <<"username='">>, Username, <<"'">>]) of @@ -117,9 +117,9 @@ get_sessions(LUser, LServer) -> end. get_sessions(LUser, LServer, LResource) -> - Username = ejabberd_odbc:escape(LUser), - Resource = ejabberd_odbc:escape(LResource), - case ejabberd_odbc:sql_query( + Username = ejabberd_sql:escape(LUser), + Resource = ejabberd_sql:escape(LResource), + case ejabberd_sql:sql_query( LServer, [<<"select usec, pid, username, ">>, <<"resource, priority, info from sm where ">>, <<"username='">>, Username, <<"' and resource='">>, @@ -162,7 +162,7 @@ row_to_session(LServer, [USec, PidS, User, Resource, PrioS, InfoS]) -> Now = timestamp_to_now(USec), Pid = erlang:list_to_pid(binary_to_list(PidS)), Priority = dec_priority(PrioS), - Info = ejabberd_odbc:decode_term(InfoS), + Info = ejabberd_sql:decode_term(InfoS), #session{sid = {Now, Pid}, us = {User, LServer}, usr = {User, LServer, Resource}, priority = Priority, diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl new file mode 100644 index 00000000..4bee08f7 --- /dev/null +++ b/src/ejabberd_sql.erl @@ -0,0 +1,1025 @@ +%%%---------------------------------------------------------------------- +%%% File : ejabberd_odbc.erl +%%% Author : Alexey Shchepin +%%% Purpose : Serve ODBC connection +%%% Created : 8 Dec 2004 by Alexey Shchepin +%%% +%%% +%%% 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. +%%% +%%%---------------------------------------------------------------------- + +-module(ejabberd_sql). + +-behaviour(ejabberd_config). + +-author('alexey@process-one.net'). + +-define(GEN_FSM, p1_fsm). + +-behaviour(?GEN_FSM). + +%% External exports +-export([start/1, start_link/2, + sql_query/2, + sql_query_t/1, + sql_transaction/2, + sql_bloc/2, + escape/1, + escape_like/1, + escape_like_arg/1, + to_bool/1, + sqlite_db/1, + sqlite_file/1, + encode_term/1, + decode_term/1, + odbc_config/0, + freetds_config/0, + odbcinst_config/0, + init_mssql/1, + keep_alive/1]). + +%% gen_fsm callbacks +-export([init/1, handle_event/3, handle_sync_event/4, + handle_info/3, terminate/3, print_state/1, + code_change/4]). + +-export([connecting/2, connecting/3, + session_established/2, session_established/3, + opt_type/1]). + +-include("ejabberd.hrl"). +-include("logger.hrl"). +-include("ejabberd_sql_pt.hrl"). + +-record(state, + {db_ref = self() :: pid(), + db_type = odbc :: pgsql | mysql | sqlite | odbc | mssql, + db_version = undefined :: undefined | non_neg_integer(), + start_interval = 0 :: non_neg_integer(), + host = <<"">> :: binary(), + max_pending_requests_len :: non_neg_integer(), + pending_requests = {0, queue:new()} :: {non_neg_integer(), ?TQUEUE}}). + +-define(STATE_KEY, ejabberd_sql_state). + +-define(NESTING_KEY, ejabberd_sql_nesting_level). + +-define(TOP_LEVEL_TXN, 0). + +-define(PGSQL_PORT, 5432). + +-define(MYSQL_PORT, 3306). + +-define(MSSQL_PORT, 1433). + +-define(MAX_TRANSACTION_RESTARTS, 10). + +-define(TRANSACTION_TIMEOUT, 60000). + +-define(KEEPALIVE_TIMEOUT, 60000). + +-define(KEEPALIVE_QUERY, [<<"SELECT 1;">>]). + +-define(PREPARE_KEY, ejabberd_sql_prepare). + +%%-define(DBGFSM, true). + +-ifdef(DBGFSM). + +-define(FSMOPTS, [{debug, [trace]}]). + +-else. + +-define(FSMOPTS, []). + +-endif. + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +start(Host) -> + (?GEN_FSM):start(ejabberd_sql, [Host], + fsm_limit_opts() ++ (?FSMOPTS)). + +start_link(Host, StartInterval) -> + (?GEN_FSM):start_link(ejabberd_sql, + [Host, StartInterval], + fsm_limit_opts() ++ (?FSMOPTS)). + +-type sql_query() :: [sql_query() | binary()] | #sql_query{} | + fun(() -> any()) | fun((atom(), _) -> any()). +-type sql_query_result() :: {updated, non_neg_integer()} | + {error, binary()} | + {selected, [binary()], + [[binary()]]} | + {selected, [any()]}. + +-spec sql_query(binary(), sql_query()) -> sql_query_result(). + +sql_query(Host, Query) -> + check_error(sql_call(Host, {sql_query, Query}), Query). + +%% SQL transaction based on a list of queries +%% This function automatically +-spec sql_transaction(binary(), [sql_query()] | fun(() -> any())) -> + {atomic, any()} | + {aborted, any()}. + +sql_transaction(Host, Queries) + when is_list(Queries) -> + F = fun () -> + lists:foreach(fun (Query) -> sql_query_t(Query) end, + Queries) + end, + sql_transaction(Host, F); +%% SQL transaction, based on a erlang anonymous function (F = fun) +sql_transaction(Host, F) when is_function(F) -> + sql_call(Host, {sql_transaction, F}). + +%% SQL bloc, based on a erlang anonymous function (F = fun) +sql_bloc(Host, F) -> sql_call(Host, {sql_bloc, F}). + +sql_call(Host, Msg) -> + case get(?STATE_KEY) of + undefined -> + case ejabberd_sql_sup:get_random_pid(Host) of + none -> {error, <<"Unknown Host">>}; + Pid -> + (?GEN_FSM):sync_send_event(Pid,{sql_cmd, Msg, + p1_time_compat:monotonic_time(milli_seconds)}, + ?TRANSACTION_TIMEOUT) + end; + _State -> nested_op(Msg) + end. + +keep_alive(PID) -> + (?GEN_FSM):sync_send_event(PID, + {sql_cmd, {sql_query, ?KEEPALIVE_QUERY}, + p1_time_compat:monotonic_time(milli_seconds)}, + ?KEEPALIVE_TIMEOUT). + +-spec sql_query_t(sql_query()) -> sql_query_result(). + +%% This function is intended to be used from inside an sql_transaction: +sql_query_t(Query) -> + QRes = sql_query_internal(Query), + case QRes of + {error, Reason} -> throw({aborted, Reason}); + Rs when is_list(Rs) -> + case lists:keysearch(error, 1, Rs) of + {value, {error, Reason}} -> throw({aborted, Reason}); + _ -> QRes + end; + _ -> QRes + end. + +%% Escape character that will confuse an SQL engine +escape(S) -> + << <<(sql_queries:escape(Char))/binary>> || <> <= S >>. + +%% Escape character that will confuse an SQL engine +%% Percent and underscore only need to be escaped for pattern matching like +%% statement +escape_like(S) when is_binary(S) -> + << <<(escape_like(C))/binary>> || <> <= S >>; +escape_like($%) -> <<"\\%">>; +escape_like($_) -> <<"\\_">>; +escape_like(C) when is_integer(C), C >= 0, C =< 255 -> sql_queries:escape(C). + +escape_like_arg(S) when is_binary(S) -> + << <<(escape_like_arg(C))/binary>> || <> <= S >>; +escape_like_arg($%) -> <<"\\%">>; +escape_like_arg($_) -> <<"\\_">>; +escape_like_arg($\\) -> <<"\\\\">>; +escape_like_arg(C) when is_integer(C), C >= 0, C =< 255 -> <>. + +to_bool(<<"t">>) -> true; +to_bool(<<"true">>) -> true; +to_bool(<<"1">>) -> true; +to_bool(true) -> true; +to_bool(1) -> true; +to_bool(_) -> false. + +encode_term(Term) -> + escape(list_to_binary( + erl_prettypr:format(erl_syntax:abstract(Term), + [{paper, 65535}, {ribbon, 65535}]))). + +decode_term(Bin) -> + Str = binary_to_list(<>), + {ok, Tokens, _} = erl_scan:string(Str), + {ok, Term} = erl_parse:parse_term(Tokens), + Term. + +-spec sqlite_db(binary()) -> atom(). +sqlite_db(Host) -> + list_to_atom("ejabberd_sqlite_" ++ binary_to_list(Host)). + +-spec sqlite_file(binary()) -> string(). +sqlite_file(Host) -> + case ejabberd_config:get_option({sql_database, Host}, + fun iolist_to_binary/1) of + undefined -> + {ok, Cwd} = file:get_cwd(), + filename:join([Cwd, "sqlite", atom_to_list(node()), + binary_to_list(Host), "ejabberd.db"]); + File -> + binary_to_list(File) + end. + +%%%---------------------------------------------------------------------- +%%% Callback functions from gen_fsm +%%%---------------------------------------------------------------------- +init([Host, StartInterval]) -> + case ejabberd_config:get_option( + {sql_keepalive_interval, Host}, + fun(I) when is_integer(I), I>0 -> I end) of + undefined -> + ok; + KeepaliveInterval -> + timer:apply_interval(KeepaliveInterval * 1000, ?MODULE, + keep_alive, [self()]) + end, + [DBType | _] = db_opts(Host), + (?GEN_FSM):send_event(self(), connect), + ejabberd_sql_sup:add_pid(Host, self()), + {ok, connecting, + #state{db_type = DBType, host = Host, + max_pending_requests_len = max_fsm_queue(), + pending_requests = {0, queue:new()}, + start_interval = StartInterval}}. + +connecting(connect, #state{host = Host} = State) -> + ConnectRes = case db_opts(Host) of + [mysql | Args] -> apply(fun mysql_connect/5, Args); + [pgsql | Args] -> apply(fun pgsql_connect/5, Args); + [sqlite | Args] -> apply(fun sqlite_connect/1, Args); + [mssql | Args] -> apply(fun odbc_connect/1, Args); + [odbc | Args] -> apply(fun odbc_connect/1, Args) + end, + {_, PendingRequests} = State#state.pending_requests, + case ConnectRes of + {ok, Ref} -> + erlang:monitor(process, Ref), + lists:foreach(fun (Req) -> + (?GEN_FSM):send_event(self(), Req) + end, + queue:to_list(PendingRequests)), + State1 = State#state{db_ref = Ref, + pending_requests = {0, queue:new()}}, + State2 = get_db_version(State1), + {next_state, session_established, State2}; + {error, Reason} -> + ?INFO_MSG("~p connection failed:~n** Reason: ~p~n** " + "Retry after: ~p seconds", + [State#state.db_type, Reason, + State#state.start_interval div 1000]), + (?GEN_FSM):send_event_after(State#state.start_interval, + connect), + {next_state, connecting, State} + end; +connecting(Event, State) -> + ?WARNING_MSG("unexpected event in 'connecting': ~p", + [Event]), + {next_state, connecting, State}. + +connecting({sql_cmd, {sql_query, ?KEEPALIVE_QUERY}, + _Timestamp}, + From, State) -> + (?GEN_FSM):reply(From, + {error, <<"SQL connection failed">>}), + {next_state, connecting, State}; +connecting({sql_cmd, Command, Timestamp} = Req, From, + State) -> + ?DEBUG("queuing pending request while connecting:~n\t~p", + [Req]), + {Len, PendingRequests} = State#state.pending_requests, + NewPendingRequests = if Len < + State#state.max_pending_requests_len -> + {Len + 1, + queue:in({sql_cmd, Command, From, Timestamp}, + PendingRequests)}; + true -> + lists:foreach(fun ({sql_cmd, _, To, + _Timestamp}) -> + (?GEN_FSM):reply(To, + {error, + <<"SQL connection failed">>}) + end, + queue:to_list(PendingRequests)), + {1, + queue:from_list([{sql_cmd, Command, From, + Timestamp}])} + end, + {next_state, connecting, + State#state{pending_requests = NewPendingRequests}}; +connecting(Request, {Who, _Ref}, State) -> + ?WARNING_MSG("unexpected call ~p from ~p in 'connecting'", + [Request, Who]), + {reply, {error, badarg}, connecting, State}. + +session_established({sql_cmd, Command, Timestamp}, From, + State) -> + run_sql_cmd(Command, From, State, Timestamp); +session_established(Request, {Who, _Ref}, State) -> + ?WARNING_MSG("unexpected call ~p from ~p in 'session_establ" + "ished'", + [Request, Who]), + {reply, {error, badarg}, session_established, State}. + +session_established({sql_cmd, Command, From, Timestamp}, + State) -> + run_sql_cmd(Command, From, State, Timestamp); +session_established(Event, State) -> + ?WARNING_MSG("unexpected event in 'session_established': ~p", + [Event]), + {next_state, session_established, State}. + +handle_event(_Event, StateName, State) -> + {next_state, StateName, State}. + +handle_sync_event(_Event, _From, StateName, State) -> + {reply, {error, badarg}, StateName, State}. + +code_change(_OldVsn, StateName, State, _Extra) -> + {ok, StateName, State}. + +%% We receive the down signal when we loose the MySQL connection (we are +%% monitoring the connection) +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, + _StateName, State) -> + (?GEN_FSM):send_event(self(), connect), + {next_state, connecting, State}; +handle_info(Info, StateName, State) -> + ?WARNING_MSG("unexpected info in ~p: ~p", + [StateName, Info]), + {next_state, StateName, State}. + +terminate(_Reason, _StateName, State) -> + ejabberd_sql_sup:remove_pid(State#state.host, self()), + case State#state.db_type of + mysql -> catch p1_mysql_conn:stop(State#state.db_ref); + sqlite -> catch sqlite3:close(sqlite_db(State#state.host)); + _ -> ok + end, + ok. + +%%---------------------------------------------------------------------- +%% Func: print_state/1 +%% Purpose: Prepare the state to be printed on error log +%% Returns: State to print +%%---------------------------------------------------------------------- +print_state(State) -> State. + +%%%---------------------------------------------------------------------- +%%% Internal functions +%%%---------------------------------------------------------------------- + +run_sql_cmd(Command, From, State, Timestamp) -> + case p1_time_compat:monotonic_time(milli_seconds) - Timestamp of + Age when Age < (?TRANSACTION_TIMEOUT) -> + put(?NESTING_KEY, ?TOP_LEVEL_TXN), + put(?STATE_KEY, State), + abort_on_driver_error(outer_op(Command), From); + Age -> + ?ERROR_MSG("Database was not available or too slow, " + "discarding ~p milliseconds old request~n~p~n", + [Age, Command]), + {next_state, session_established, State} + end. + +%% Only called by handle_call, only handles top level operations. +%% @spec outer_op(Op) -> {error, Reason} | {aborted, Reason} | {atomic, Result} +outer_op({sql_query, Query}) -> + sql_query_internal(Query); +outer_op({sql_transaction, F}) -> + outer_transaction(F, ?MAX_TRANSACTION_RESTARTS, <<"">>); +outer_op({sql_bloc, F}) -> execute_bloc(F). + +%% Called via sql_query/transaction/bloc from client code when inside a +%% nested operation +nested_op({sql_query, Query}) -> + sql_query_internal(Query); +nested_op({sql_transaction, F}) -> + NestingLevel = get(?NESTING_KEY), + if NestingLevel =:= (?TOP_LEVEL_TXN) -> + outer_transaction(F, ?MAX_TRANSACTION_RESTARTS, <<"">>); + true -> inner_transaction(F) + end; +nested_op({sql_bloc, F}) -> execute_bloc(F). + +%% Never retry nested transactions - only outer transactions +inner_transaction(F) -> + PreviousNestingLevel = get(?NESTING_KEY), + case get(?NESTING_KEY) of + ?TOP_LEVEL_TXN -> + {backtrace, T} = process_info(self(), backtrace), + ?ERROR_MSG("inner transaction called at outer txn " + "level. Trace: ~s", + [T]), + erlang:exit(implementation_faulty); + _N -> ok + end, + put(?NESTING_KEY, PreviousNestingLevel + 1), + Result = (catch F()), + put(?NESTING_KEY, PreviousNestingLevel), + case Result of + {aborted, Reason} -> {aborted, Reason}; + {'EXIT', Reason} -> {'EXIT', Reason}; + {atomic, Res} -> {atomic, Res}; + Res -> {atomic, Res} + end. + +outer_transaction(F, NRestarts, _Reason) -> + PreviousNestingLevel = get(?NESTING_KEY), + case get(?NESTING_KEY) of + ?TOP_LEVEL_TXN -> ok; + _N -> + {backtrace, T} = process_info(self(), backtrace), + ?ERROR_MSG("outer transaction called at inner txn " + "level. Trace: ~s", + [T]), + erlang:exit(implementation_faulty) + end, + sql_query_internal([<<"begin;">>]), + put(?NESTING_KEY, PreviousNestingLevel + 1), + Result = (catch F()), + put(?NESTING_KEY, PreviousNestingLevel), + case Result of + {aborted, Reason} when NRestarts > 0 -> + sql_query_internal([<<"rollback;">>]), + outer_transaction(F, NRestarts - 1, Reason); + {aborted, Reason} when NRestarts =:= 0 -> + ?ERROR_MSG("SQL transaction restarts exceeded~n** " + "Restarts: ~p~n** Last abort reason: " + "~p~n** Stacktrace: ~p~n** When State " + "== ~p", + [?MAX_TRANSACTION_RESTARTS, Reason, + erlang:get_stacktrace(), get(?STATE_KEY)]), + sql_query_internal([<<"rollback;">>]), + {aborted, Reason}; + {'EXIT', Reason} -> + sql_query_internal([<<"rollback;">>]), {aborted, Reason}; + Res -> sql_query_internal([<<"commit;">>]), {atomic, Res} + end. + +execute_bloc(F) -> + case catch F() of + {aborted, Reason} -> {aborted, Reason}; + {'EXIT', Reason} -> {aborted, Reason}; + Res -> {atomic, Res} + end. + +execute_fun(F) when is_function(F, 0) -> + F(); +execute_fun(F) when is_function(F, 2) -> + State = get(?STATE_KEY), + F(State#state.db_type, State#state.db_version). + +sql_query_internal([{_, _} | _] = Queries) -> + State = get(?STATE_KEY), + case select_sql_query(Queries, State) of + undefined -> + {error, <<"no matching query for the current DBMS found">>}; + Query -> + sql_query_internal(Query) + end; +sql_query_internal(#sql_query{} = Query) -> + State = get(?STATE_KEY), + Res = + try + case State#state.db_type of + odbc -> + generic_sql_query(Query); + mssql -> + generic_sql_query(Query); + pgsql -> + Key = {?PREPARE_KEY, Query#sql_query.hash}, + case get(Key) of + undefined -> + case pgsql_prepare(Query, State) of + {ok, _, _, _} -> + put(Key, prepared); + {error, Error} -> + ?ERROR_MSG("PREPARE failed for SQL query " + "at ~p: ~p", + [Query#sql_query.loc, Error]), + put(Key, ignore) + end; + _ -> + ok + end, + case get(Key) of + prepared -> + pgsql_execute_sql_query(Query, State); + _ -> + generic_sql_query(Query) + end; + mysql -> + generic_sql_query(Query); + sqlite -> + generic_sql_query(Query) + end + catch + Class:Reason -> + ST = erlang:get_stacktrace(), + ?ERROR_MSG("Internal error while processing SQL query: ~p", + [{Class, Reason, ST}]), + {error, <<"internal error">>} + end, + case Res of + {error, <<"No SQL-driver information available.">>} -> + {updated, 0}; + _Else -> Res + end; +sql_query_internal(F) when is_function(F) -> + case catch execute_fun(F) of + {'EXIT', Reason} -> {error, Reason}; + Res -> Res + end; +sql_query_internal(Query) -> + State = get(?STATE_KEY), + ?DEBUG("SQL: \"~s\"", [Query]), + Res = case State#state.db_type of + odbc -> + to_odbc(odbc:sql_query(State#state.db_ref, [Query], + (?TRANSACTION_TIMEOUT) - 1000)); + mssql -> + to_odbc(odbc:sql_query(State#state.db_ref, [Query], + (?TRANSACTION_TIMEOUT) - 1000)); + pgsql -> + pgsql_to_odbc(pgsql:squery(State#state.db_ref, Query)); + mysql -> + R = mysql_to_odbc(p1_mysql_conn:squery(State#state.db_ref, + [Query], self(), + [{timeout, (?TRANSACTION_TIMEOUT) - 1000}, + {result_type, binary}])), + %% ?INFO_MSG("MySQL, Received result~n~p~n", [R]), + R; + sqlite -> + Host = State#state.host, + sqlite_to_odbc(Host, sqlite3:sql_exec(sqlite_db(Host), Query)) + end, + case Res of + {error, <<"No SQL-driver information available.">>} -> + {updated, 0}; + _Else -> Res + end. + +select_sql_query(Queries, State) -> + select_sql_query( + Queries, State#state.db_type, State#state.db_version, undefined). + +select_sql_query([], _Type, _Version, undefined) -> + undefined; +select_sql_query([], _Type, _Version, Query) -> + Query; +select_sql_query([{any, Query} | _], _Type, _Version, _) -> + Query; +select_sql_query([{Type, Query} | _], Type, _Version, _) -> + Query; +select_sql_query([{{Type, _Version1}, Query1} | Rest], Type, undefined, _) -> + select_sql_query(Rest, Type, undefined, Query1); +select_sql_query([{{Type, Version1}, Query1} | Rest], Type, Version, Query) -> + if + Version >= Version1 -> + Query1; + true -> + select_sql_query(Rest, Type, Version, Query) + end; +select_sql_query([{_, _} | Rest], Type, Version, Query) -> + select_sql_query(Rest, Type, Version, Query). + +generic_sql_query(SQLQuery) -> + sql_query_format_res( + sql_query_internal(generic_sql_query_format(SQLQuery)), + SQLQuery). + +generic_sql_query_format(SQLQuery) -> + Args = (SQLQuery#sql_query.args)(generic_escape()), + (SQLQuery#sql_query.format_query)(Args). + +generic_escape() -> + #sql_escape{string = fun(X) -> <<"'", (escape(X))/binary, "'">> end, + integer = fun(X) -> integer_to_binary(X) end, + boolean = fun(true) -> <<"1">>; + (false) -> <<"0">> + end + }. + +pgsql_prepare(SQLQuery, State) -> + Escape = #sql_escape{_ = fun(X) -> X end}, + N = length((SQLQuery#sql_query.args)(Escape)), + Args = [<<$$, (integer_to_binary(I))/binary>> || I <- lists:seq(1, N)], + Query = (SQLQuery#sql_query.format_query)(Args), + pgsql:prepare(State#state.db_ref, SQLQuery#sql_query.hash, Query). + +pgsql_execute_escape() -> + #sql_escape{string = fun(X) -> X end, + integer = fun(X) -> [integer_to_binary(X)] end, + boolean = fun(true) -> "1"; + (false) -> "0" + end + }. + +pgsql_execute_sql_query(SQLQuery, State) -> + Args = (SQLQuery#sql_query.args)(pgsql_execute_escape()), + ExecuteRes = + pgsql:execute(State#state.db_ref, SQLQuery#sql_query.hash, Args), +% {T, ExecuteRes} = +% timer:tc(pgsql, execute, [State#state.db_ref, SQLQuery#sql_query.hash, Args]), +% io:format("T ~s ~p~n", [SQLQuery#sql_query.hash, T]), + Res = pgsql_execute_to_odbc(ExecuteRes), + sql_query_format_res(Res, SQLQuery). + + +sql_query_format_res({selected, _, Rows}, SQLQuery) -> + Res = + lists:flatmap( + fun(Row) -> + try + [(SQLQuery#sql_query.format_res)(Row)] + catch + Class:Reason -> + ST = erlang:get_stacktrace(), + ?ERROR_MSG("Error while processing " + "SQL query result: ~p~n" + "row: ~p", + [{Class, Reason, ST}, Row]), + [] + end + end, Rows), + {selected, Res}; +sql_query_format_res(Res, _SQLQuery) -> + Res. + +%% Generate the OTP callback return tuple depending on the driver result. +abort_on_driver_error({error, <<"query timed out">>} = + Reply, + From) -> + (?GEN_FSM):reply(From, Reply), + {stop, timeout, get(?STATE_KEY)}; +abort_on_driver_error({error, + <<"Failed sending data on socket", _/binary>>} = + Reply, + From) -> + (?GEN_FSM):reply(From, Reply), + {stop, closed, get(?STATE_KEY)}; +abort_on_driver_error(Reply, From) -> + (?GEN_FSM):reply(From, Reply), + {next_state, session_established, get(?STATE_KEY)}. + +%% == pure ODBC code + +%% part of init/1 +%% Open an ODBC database connection +odbc_connect(SQLServer) -> + ejabberd:start_app(odbc), + odbc:connect(binary_to_list(SQLServer), + [{scrollable_cursors, off}, + {tuple_row, off}, + {binary_strings, on}]). + +%% == Native SQLite code + +%% part of init/1 +%% Open a database connection to SQLite + +sqlite_connect(Host) -> + File = sqlite_file(Host), + case filelib:ensure_dir(File) of + ok -> + case sqlite3:open(sqlite_db(Host), [{file, File}]) of + {ok, Ref} -> + sqlite3:sql_exec( + sqlite_db(Host), "pragma foreign_keys = on"), + {ok, Ref}; + {error, {already_started, Ref}} -> + {ok, Ref}; + {error, Reason} -> + {error, Reason} + end; + Err -> + Err + end. + +%% Convert SQLite query result to Erlang ODBC result formalism +sqlite_to_odbc(Host, ok) -> + {updated, sqlite3:changes(sqlite_db(Host))}; +sqlite_to_odbc(Host, {rowid, _}) -> + {updated, sqlite3:changes(sqlite_db(Host))}; +sqlite_to_odbc(_Host, [{columns, Columns}, {rows, TRows}]) -> + Rows = [lists:map( + fun(I) when is_integer(I) -> + jlib:integer_to_binary(I); + (B) -> + B + end, tuple_to_list(Row)) || Row <- TRows], + {selected, [list_to_binary(C) || C <- Columns], Rows}; +sqlite_to_odbc(_Host, {error, _Code, Reason}) -> + {error, Reason}; +sqlite_to_odbc(_Host, _) -> + {updated, undefined}. + +%% == Native PostgreSQL code + +%% part of init/1 +%% Open a database connection to PostgreSQL +pgsql_connect(Server, Port, DB, Username, Password) -> + case pgsql:connect([{host, Server}, + {database, DB}, + {user, Username}, + {password, Password}, + {port, Port}, + {as_binary, true}]) of + {ok, Ref} -> + pgsql:squery(Ref, [<<"alter database ">>, DB, <<" set ">>, + <<"standard_conforming_strings='off';">>]), + pgsql:squery(Ref, [<<"set standard_conforming_strings to 'off';">>]), + {ok, Ref}; + Err -> + Err + end. + +%% Convert PostgreSQL query result to Erlang ODBC result formalism +pgsql_to_odbc({ok, PGSQLResult}) -> + case PGSQLResult of + [Item] -> pgsql_item_to_odbc(Item); + Items -> [pgsql_item_to_odbc(Item) || Item <- Items] + end. + +pgsql_item_to_odbc({<<"SELECT", _/binary>>, Rows, + Recs}) -> + {selected, [element(1, Row) || Row <- Rows], Recs}; +pgsql_item_to_odbc({<<"FETCH", _/binary>>, Rows, + Recs}) -> + {selected, [element(1, Row) || Row <- Rows], Recs}; +pgsql_item_to_odbc(<<"INSERT ", OIDN/binary>>) -> + [_OID, N] = str:tokens(OIDN, <<" ">>), + {updated, jlib:binary_to_integer(N)}; +pgsql_item_to_odbc(<<"DELETE ", N/binary>>) -> + {updated, jlib:binary_to_integer(N)}; +pgsql_item_to_odbc(<<"UPDATE ", N/binary>>) -> + {updated, jlib:binary_to_integer(N)}; +pgsql_item_to_odbc({error, Error}) -> {error, Error}; +pgsql_item_to_odbc(_) -> {updated, undefined}. + +pgsql_execute_to_odbc({ok, {<<"SELECT", _/binary>>, Rows}}) -> + {selected, [], [[Field || {_, Field} <- Row] || Row <- Rows]}; +pgsql_execute_to_odbc({ok, {'INSERT', N}}) -> + {updated, N}; +pgsql_execute_to_odbc({ok, {'DELETE', N}}) -> + {updated, N}; +pgsql_execute_to_odbc({ok, {'UPDATE', N}}) -> + {updated, N}; +pgsql_execute_to_odbc({error, Error}) -> {error, Error}; +pgsql_execute_to_odbc(_) -> {updated, undefined}. + + +%% == Native MySQL code + +%% part of init/1 +%% Open a database connection to MySQL +mysql_connect(Server, Port, DB, Username, Password) -> + case p1_mysql_conn:start(binary_to_list(Server), Port, + binary_to_list(Username), + binary_to_list(Password), + binary_to_list(DB), fun log/3) + of + {ok, Ref} -> + p1_mysql_conn:fetch( + Ref, [<<"set names 'utf8mb4' collate 'utf8mb4_bin';">>], self()), + {ok, Ref}; + Err -> Err + end. + +%% Convert MySQL query result to Erlang ODBC result formalism +mysql_to_odbc({updated, MySQLRes}) -> + {updated, p1_mysql:get_result_affected_rows(MySQLRes)}; +mysql_to_odbc({data, MySQLRes}) -> + mysql_item_to_odbc(p1_mysql:get_result_field_info(MySQLRes), + p1_mysql:get_result_rows(MySQLRes)); +mysql_to_odbc({error, MySQLRes}) + when is_binary(MySQLRes) -> + {error, MySQLRes}; +mysql_to_odbc({error, MySQLRes}) + when is_list(MySQLRes) -> + {error, list_to_binary(MySQLRes)}; +mysql_to_odbc({error, MySQLRes}) -> + {error, p1_mysql:get_result_reason(MySQLRes)}; +mysql_to_odbc(ok) -> + ok. + + +%% When tabular data is returned, convert it to the ODBC formalism +mysql_item_to_odbc(Columns, Recs) -> + {selected, [element(2, Column) || Column <- Columns], Recs}. + +to_odbc({selected, Columns, Recs}) -> + Rows = [lists:map( + fun(I) when is_integer(I) -> + jlib:integer_to_binary(I); + (B) -> + B + end, Row) || Row <- Recs], + {selected, [list_to_binary(C) || C <- Columns], Rows}; +to_odbc({error, Reason}) when is_list(Reason) -> + {error, list_to_binary(Reason)}; +to_odbc(Res) -> + Res. + +get_db_version(#state{db_type = pgsql} = State) -> + case pgsql:squery(State#state.db_ref, + <<"select current_setting('server_version_num')">>) of + {ok, [{_, _, [[SVersion]]}]} -> + case catch binary_to_integer(SVersion) of + Version when is_integer(Version) -> + State#state{db_version = Version}; + Error -> + ?WARNING_MSG("error getting pgsql version: ~p", [Error]), + State + end; + Res -> + ?WARNING_MSG("error getting pgsql version: ~p", [Res]), + State + end; +get_db_version(State) -> + State. + +log(Level, Format, Args) -> + case Level of + debug -> ?DEBUG(Format, Args); + normal -> ?INFO_MSG(Format, Args); + error -> ?ERROR_MSG(Format, Args) + end. + +db_opts(Host) -> + Type = ejabberd_config:get_option({sql_type, Host}, + fun(mysql) -> mysql; + (pgsql) -> pgsql; + (sqlite) -> sqlite; + (mssql) -> mssql; + (odbc) -> odbc + end, odbc), + Server = ejabberd_config:get_option({sql_server, Host}, + fun iolist_to_binary/1, + <<"localhost">>), + case Type of + odbc -> + [odbc, Server]; + sqlite -> + [sqlite, Host]; + _ -> + Port = ejabberd_config:get_option( + {sql_port, Host}, + fun(P) when is_integer(P), P > 0, P < 65536 -> P end, + case Type of + mssql -> ?MSSQL_PORT; + mysql -> ?MYSQL_PORT; + pgsql -> ?PGSQL_PORT + end), + DB = ejabberd_config:get_option({sql_database, Host}, + fun iolist_to_binary/1, + <<"ejabberd">>), + User = ejabberd_config:get_option({sql_username, Host}, + fun iolist_to_binary/1, + <<"ejabberd">>), + Pass = ejabberd_config:get_option({sql_password, Host}, + fun iolist_to_binary/1, + <<"">>), + case Type of + mssql -> + [mssql, <<"DSN=", Host/binary, ";UID=", User/binary, + ";PWD=", Pass/binary>>]; + _ -> + [Type, Server, Port, DB, User, Pass] + end + end. + +init_mssql(Host) -> + Server = ejabberd_config:get_option({sql_server, Host}, + fun iolist_to_binary/1, + <<"localhost">>), + Port = ejabberd_config:get_option( + {sql_port, Host}, + fun(P) when is_integer(P), P > 0, P < 65536 -> P end, + ?MSSQL_PORT), + DB = ejabberd_config:get_option({sql_database, Host}, + fun iolist_to_binary/1, + <<"ejabberd">>), + FreeTDS = io_lib:fwrite("[~s]~n" + "\thost = ~s~n" + "\tport = ~p~n" + "\ttds version = 7.1~n", + [Host, Server, Port]), + ODBCINST = io_lib:fwrite("[freetds]~n" + "Description = MSSQL connection~n" + "Driver = libtdsodbc.so~n" + "Setup = libtdsS.so~n" + "UsageCount = 1~n" + "FileUsage = 1~n", []), + ODBCINI = io_lib:fwrite("[~s]~n" + "Description = MS SQL~n" + "Driver = freetds~n" + "Servername = ~s~n" + "Database = ~s~n" + "Port = ~p~n", + [Host, Host, DB, Port]), + ?DEBUG("~s:~n~s", [freetds_config(), FreeTDS]), + ?DEBUG("~s:~n~s", [odbcinst_config(), ODBCINST]), + ?DEBUG("~s:~n~s", [odbc_config(), ODBCINI]), + case filelib:ensure_dir(freetds_config()) of + ok -> + try + ok = file:write_file(freetds_config(), FreeTDS, [append]), + ok = file:write_file(odbcinst_config(), ODBCINST), + ok = file:write_file(odbc_config(), ODBCINI, [append]), + os:putenv("ODBCSYSINI", tmp_dir()), + os:putenv("FREETDS", freetds_config()), + os:putenv("FREETDSCONF", freetds_config()), + ok + catch error:{badmatch, {error, Reason} = Err} -> + ?ERROR_MSG("failed to create temporary files in ~s: ~s", + [tmp_dir(), file:format_error(Reason)]), + Err + end; + {error, Reason} = Err -> + ?ERROR_MSG("failed to create temporary directory ~s: ~s", + [tmp_dir(), file:format_error(Reason)]), + Err + end. + +tmp_dir() -> + filename:join(["/tmp", "ejabberd"]). + +odbc_config() -> + filename:join(tmp_dir(), "odbc.ini"). + +freetds_config() -> + filename:join(tmp_dir(), "freetds.conf"). + +odbcinst_config() -> + filename:join(tmp_dir(), "odbcinst.ini"). + +max_fsm_queue() -> + ejabberd_config:get_option( + max_fsm_queue, + fun(N) when is_integer(N), N > 0 -> N end). + +fsm_limit_opts() -> + case max_fsm_queue() of + N when is_integer(N) -> [{max_queue, N}]; + _ -> [] + end. + +check_error({error, Why} = Err, #sql_query{} = Query) -> + ?ERROR_MSG("SQL query '~s' at ~p failed: ~p", + [Query#sql_query.hash, Query#sql_query.loc, Why]), + Err; +check_error({error, Why} = Err, Query) -> + case catch iolist_to_binary(Query) of + SQuery when is_binary(SQuery) -> + ?ERROR_MSG("SQL query '~s' failed: ~p", [SQuery, Why]); + _ -> + ?ERROR_MSG("SQL query ~p failed: ~p", [Query, Why]) + end, + Err; +check_error(Result, _Query) -> + Result. + +opt_type(max_fsm_queue) -> + fun (N) when is_integer(N), N > 0 -> N end; +opt_type(sql_database) -> fun iolist_to_binary/1; +opt_type(sql_keepalive_interval) -> + fun (I) when is_integer(I), I > 0 -> I end; +opt_type(sql_password) -> fun iolist_to_binary/1; +opt_type(sql_port) -> + fun (P) when is_integer(P), P > 0, P < 65536 -> P end; +opt_type(sql_server) -> fun iolist_to_binary/1; +opt_type(sql_type) -> + fun (mysql) -> mysql; + (pgsql) -> pgsql; + (sqlite) -> sqlite; + (mssql) -> mssql; + (odbc) -> odbc + end; +opt_type(sql_username) -> fun iolist_to_binary/1; +opt_type(_) -> + [max_fsm_queue, sql_database, sql_keepalive_interval, + sql_password, sql_port, sql_server, sql_type, + sql_username]. diff --git a/src/ejabberd_sql_pt.erl b/src/ejabberd_sql_pt.erl index 660eac19..47e4a07c 100644 --- a/src/ejabberd_sql_pt.erl +++ b/src/ejabberd_sql_pt.erl @@ -163,7 +163,7 @@ parse1([$@, $( | S], Acc, State) -> EVar; boolean -> erl_syntax:application( - erl_syntax:atom(ejabberd_odbc), + erl_syntax:atom(ejabberd_sql), erl_syntax:atom(to_bool), [EVar]) end, @@ -348,7 +348,7 @@ make_sql_upsert_generic(Table, ParseRes) -> InsertBranch = erl_syntax:case_expr( erl_syntax:application( - erl_syntax:atom(ejabberd_odbc), + erl_syntax:atom(ejabberd_sql), erl_syntax:atom(sql_query_t), [Insert]), [erl_syntax:clause( @@ -361,7 +361,7 @@ make_sql_upsert_generic(Table, ParseRes) -> [erl_syntax:variable("__UpdateRes")])]), erl_syntax:case_expr( erl_syntax:application( - erl_syntax:atom(ejabberd_odbc), + erl_syntax:atom(ejabberd_sql), erl_syntax:atom(sql_query_t), [Update]), [erl_syntax:clause( @@ -453,7 +453,7 @@ make_sql_upsert_pgsql901(Table, ParseRes) -> ]), Upsert = make_sql_query(State), erl_syntax:application( - erl_syntax:atom(ejabberd_odbc), + erl_syntax:atom(ejabberd_sql), erl_syntax:atom(sql_query_t), [Upsert]). diff --git a/src/ejabberd_sql_sup.erl b/src/ejabberd_sql_sup.erl new file mode 100644 index 00000000..68241455 --- /dev/null +++ b/src/ejabberd_sql_sup.erl @@ -0,0 +1,226 @@ +%%%---------------------------------------------------------------------- +%%% File : ejabberd_sql_sup.erl +%%% Author : Alexey Shchepin +%%% Purpose : SQL connections supervisor +%%% Created : 22 Dec 2004 by Alexey Shchepin +%%% +%%% +%%% 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. +%%% +%%%---------------------------------------------------------------------- + +-module(ejabberd_sql_sup). + +-behaviour(ejabberd_config). + +-author('alexey@process-one.net'). + +-export([start_link/1, init/1, add_pid/2, remove_pid/2, + get_pids/1, get_random_pid/1, transform_options/1, + opt_type/1]). + +-include("ejabberd.hrl"). +-include("logger.hrl"). + +-define(PGSQL_PORT, 5432). + +-define(MYSQL_PORT, 3306). + +-define(DEFAULT_POOL_SIZE, 10). + +-define(DEFAULT_SQL_START_INTERVAL, 30). + +-define(CONNECT_TIMEOUT, 500). + +-record(sql_pool, {host, pid}). + +start_link(Host) -> + mnesia:create_table(sql_pool, + [{ram_copies, [node()]}, {type, bag}, + {local_content, true}, + {attributes, record_info(fields, sql_pool)}]), + mnesia:add_table_copy(sql_pool, node(), ram_copies), + F = fun () -> mnesia:delete({sql_pool, Host}) end, + mnesia:ets(F), + supervisor:start_link({local, + gen_mod:get_module_proc(Host, ?MODULE)}, + ?MODULE, [Host]). + +init([Host]) -> + PoolSize = ejabberd_config:get_option( + {sql_pool_size, Host}, + fun(I) when is_integer(I), I>0 -> I end, + ?DEFAULT_POOL_SIZE), + StartInterval = ejabberd_config:get_option( + {sql_start_interval, Host}, + fun(I) when is_integer(I), I>0 -> I end, + ?DEFAULT_SQL_START_INTERVAL), + Type = ejabberd_config:get_option({sql_type, Host}, + fun(mysql) -> mysql; + (pgsql) -> pgsql; + (sqlite) -> sqlite; + (mssql) -> mssql; + (odbc) -> odbc + end, odbc), + case Type of + sqlite -> + check_sqlite_db(Host); + mssql -> + ejabberd_sql:init_mssql(Host); + _ -> + ok + end, + + {ok, + {{one_for_one, PoolSize * 10, 1}, + lists:map(fun (I) -> + {I, + {ejabberd_sql, start_link, + [Host, StartInterval * 1000]}, + transient, 2000, worker, [?MODULE]} + end, + lists:seq(1, PoolSize))}}. + +get_pids(Host) -> + Rs = mnesia:dirty_read(sql_pool, Host), + [R#sql_pool.pid || R <- Rs]. + +get_random_pid(Host) -> + case get_pids(Host) of + [] -> none; + Pids -> lists:nth(erlang:phash(p1_time_compat:unique_integer(), length(Pids)), Pids) + end. + +add_pid(Host, Pid) -> + F = fun () -> + mnesia:write(#sql_pool{host = Host, pid = Pid}) + end, + mnesia:ets(F). + +remove_pid(Host, Pid) -> + F = fun () -> + mnesia:delete_object(#sql_pool{host = Host, pid = Pid}) + end, + mnesia:ets(F). + +transform_options(Opts) -> + lists:foldl(fun transform_options/2, [], Opts). + +transform_options({odbc_server, {Type, Server, Port, DB, User, Pass}}, Opts) -> + [{sql_type, Type}, + {sql_server, Server}, + {sql_port, Port}, + {sql_database, DB}, + {sql_username, User}, + {sql_password, Pass}|Opts]; +transform_options({odbc_server, {mysql, Server, DB, User, Pass}}, Opts) -> + transform_options({odbc_server, {mysql, Server, ?MYSQL_PORT, DB, User, Pass}}, Opts); +transform_options({odbc_server, {pgsql, Server, DB, User, Pass}}, Opts) -> + transform_options({odbc_server, {pgsql, Server, ?PGSQL_PORT, DB, User, Pass}}, Opts); +transform_options({odbc_server, {sqlite, DB}}, Opts) -> + transform_options({odbc_server, {sqlite, DB}}, Opts); +transform_options(Opt, Opts) -> + [Opt|Opts]. + +check_sqlite_db(Host) -> + DB = ejabberd_sql:sqlite_db(Host), + File = ejabberd_sql:sqlite_file(Host), + Ret = case filelib:ensure_dir(File) of + ok -> + case sqlite3:open(DB, [{file, File}]) of + {ok, _Ref} -> ok; + {error, {already_started, _Ref}} -> ok; + {error, R} -> {error, R} + end; + Err -> + Err + end, + case Ret of + ok -> + sqlite3:sql_exec(DB, "pragma foreign_keys = on"), + case sqlite3:list_tables(DB) of + [] -> + create_sqlite_tables(DB), + sqlite3:close(DB), + ok; + [_H | _] -> + ok + end; + {error, Reason} -> + ?INFO_MSG("Failed open sqlite database, reason ~p", [Reason]) + end. + +create_sqlite_tables(DB) -> + SqlDir = case code:priv_dir(ejabberd) of + {error, _} -> + ?SQL_DIR; + PrivDir -> + filename:join(PrivDir, "sql") + end, + File = filename:join(SqlDir, "lite.sql"), + case file:open(File, [read, binary]) of + {ok, Fd} -> + Qs = read_lines(Fd, File, []), + ok = sqlite3:sql_exec(DB, "begin"), + [ok = sqlite3:sql_exec(DB, Q) || Q <- Qs], + ok = sqlite3:sql_exec(DB, "commit"); + {error, Reason} -> + ?INFO_MSG("Failed to read SQLite schema file: ~s", + [file:format_error(Reason)]) + end. + +read_lines(Fd, File, Acc) -> + case file:read_line(Fd) of + {ok, Line} -> + NewAcc = case str:strip(str:strip(Line, both, $\r), both, $\n) of + <<"--", _/binary>> -> + Acc; + <<>> -> + Acc; + _ -> + [Line|Acc] + end, + read_lines(Fd, File, NewAcc); + eof -> + QueryList = str:tokens(list_to_binary(lists:reverse(Acc)), <<";">>), + lists:flatmap( + fun(Query) -> + case str:strip(str:strip(Query, both, $\r), both, $\n) of + <<>> -> + []; + Q -> + [<>] + end + end, QueryList); + {error, _} = Err -> + ?ERROR_MSG("Failed read from lite.sql, reason: ~p", [Err]), + [] + end. + +opt_type(sql_pool_size) -> + fun (I) when is_integer(I), I > 0 -> I end; +opt_type(sql_start_interval) -> + fun (I) when is_integer(I), I > 0 -> I end; +opt_type(sql_type) -> + fun (mysql) -> mysql; + (pgsql) -> pgsql; + (sqlite) -> sqlite; + (mssql) -> mssql; + (odbc) -> odbc + end; +opt_type(_) -> + [sql_pool_size, sql_start_interval, sql_type]. diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl index f525a4d3..3281f643 100644 --- a/src/ejabberd_web_admin.erl +++ b/src/ejabberd_web_admin.erl @@ -2414,7 +2414,7 @@ node_backup_parse_query(Node, Query) -> lists:keysearch(<>, 1, Query), - ejabberd_cluster:call(Node, ejd2odbc, + ejabberd_cluster:call(Node, ejd2sql, export, [Host, Path]); <<"import_file">> -> ejabberd_cluster:call(Node, ejabberd_admin, diff --git a/src/ejd2odbc.erl b/src/ejd2odbc.erl deleted file mode 100644 index 1df88470..00000000 --- a/src/ejd2odbc.erl +++ /dev/null @@ -1,315 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : ejd2odbc.erl -%%% Author : Alexey Shchepin -%%% Purpose : Export some mnesia tables to SQL DB -%%% Created : 22 Aug 2005 by Alexey Shchepin -%%% -%%% -%%% 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. -%%% -%%%---------------------------------------------------------------------- - --module(ejd2odbc). - --author('alexey@process-one.net'). - --include("logger.hrl"). - --export([export/2, export/3, import_file/2, import/2, - import/3, delete/1]). - --define(MAX_RECORDS_PER_TRANSACTION, 100). - --record(dump, {fd, cont = start}). - -%%%---------------------------------------------------------------------- -%%% API -%%%---------------------------------------------------------------------- -%%% How to use: -%%% A table can be converted from Mnesia to an ODBC database by calling -%%% one of the API function with the following parameters: -%%% - Server is the server domain you want to convert -%%% - Output can be either odbc to export to the configured relational -%%% database or "Filename" to export to text file. - -modules() -> - [ejabberd_auth, - mod_announce, - mod_irc, - mod_last, - mod_muc, - mod_offline, - mod_privacy, - mod_private, - %% mod_pubsub, - mod_roster, - mod_shared_roster, - mod_vcard, - mod_vcard_xupdate]. - -export(Server, Output) -> - LServer = jid:nameprep(iolist_to_binary(Server)), - Modules = modules(), - IO = prepare_output(Output), - lists:foreach( - fun(Module) -> - export(LServer, IO, Module) - end, Modules), - close_output(Output, IO). - -export(Server, Output, Module) -> - LServer = jid:nameprep(iolist_to_binary(Server)), - IO = prepare_output(Output), - lists:foreach( - fun({Table, ConvertFun}) -> - export(LServer, Table, IO, ConvertFun) - end, Module:export(Server)), - close_output(Output, IO). - -delete(Server) -> - Modules = modules(), - lists:foreach( - fun(Module) -> - delete(Server, Module) - end, Modules). - -delete(Server, Module) -> - LServer = jid:nameprep(iolist_to_binary(Server)), - lists:foreach( - fun({Table, ConvertFun}) -> - delete(LServer, Table, ConvertFun) - end, Module:export(Server)). - -import_file(Server, FileName) when is_binary(FileName) -> - import(Server, binary_to_list(FileName)); -import_file(Server, FileName) -> - case disk_log:open([{name, make_ref()}, - {file, FileName}, - {mode, read_only}]) of - {ok, Fd} -> - LServer = jid:nameprep(Server), - Mods = [{Mod, gen_mod:db_type(LServer, Mod)} - || Mod <- modules(), gen_mod:is_loaded(LServer, Mod)], - AuthMods = case lists:member(ejabberd_auth_internal, - ejabberd_auth:auth_modules(LServer)) of - true -> - [{ejabberd_auth, mnesia}]; - false -> - [] - end, - import_dump(LServer, AuthMods ++ Mods, #dump{fd = Fd}); - Err -> - exit(Err) - end. - -import(Server, Output) -> - import(Server, Output, [{fast, true}]). - -import(Server, Output, Opts) -> - LServer = jid:nameprep(iolist_to_binary(Server)), - Modules = modules(), - IO = prepare_output(Output, disk_log), - lists:foreach( - fun(Module) -> - import(LServer, IO, Opts, Module) - end, Modules), - close_output(Output, IO). - -import(Server, Output, Opts, Module) -> - LServer = jid:nameprep(iolist_to_binary(Server)), - IO = prepare_output(Output, disk_log), - lists:foreach( - fun({SelectQuery, ConvertFun}) -> - import(LServer, SelectQuery, IO, ConvertFun, Opts) - end, Module:import(Server)), - close_output(Output, IO). - -%%%---------------------------------------------------------------------- -%%% Internal functions -%%%---------------------------------------------------------------------- -export(LServer, Table, IO, ConvertFun) -> - F = fun () -> - mnesia:read_lock_table(Table), - {_N, SQLs} = - mnesia:foldl( - fun(R, {N, SQLs} = Acc) -> - case ConvertFun(LServer, R) of - [] -> - Acc; - SQL -> - if N < (?MAX_RECORDS_PER_TRANSACTION) - 1 -> - {N + 1, [SQL | SQLs]}; - true -> - output(LServer, - Table, IO, - flatten([SQL | SQLs])), - {0, []} - end - end - end, - {0, []}, Table), - output(LServer, Table, IO, flatten(SQLs)) - end, - mnesia:transaction(F). - -output(_LServer, _Table, _IO, []) -> - ok; -output(LServer, _Table, odbc, SQLs) -> - ejabberd_odbc:sql_transaction(LServer, SQLs); -output(_LServer, Table, Fd, SQLs) -> - file:write(Fd, ["-- \n-- Mnesia table: ", atom_to_list(Table), - "\n--\n", SQLs]). - -delete(LServer, Table, ConvertFun) -> - F = fun () -> - mnesia:write_lock_table(Table), - {_N, SQLs} = - mnesia:foldl( - fun(R, {N, SQLs} = Acc) -> - case ConvertFun(LServer, R) of - [] -> - Acc; - _SQL -> - mnesia:delete_object(R), - Acc - end - end, - {0, []}, Table), - delete(LServer, Table, SQLs) - end, - mnesia:transaction(F). - -import(LServer, SelectQuery, IO, ConvertFun, Opts) -> - F = case proplists:get_bool(fast, Opts) of - true -> - fun() -> - case ejabberd_odbc:sql_query_t(SelectQuery) of - {selected, _, Rows} -> - lists:foldl(fun process_sql_row/2, - {IO, ConvertFun, undefined}, Rows); - Err -> - erlang:error(Err) - end - end; - false -> - fun() -> - ejabberd_odbc:sql_query_t( - [iolist_to_binary( - [<<"declare c cursor for ">>, SelectQuery])]), - fetch(IO, ConvertFun, undefined) - end - end, - ejabberd_odbc:sql_transaction(LServer, F). - -fetch(IO, ConvertFun, PrevRow) -> - case ejabberd_odbc:sql_query_t([<<"fetch c;">>]) of - {selected, _, [Row]} -> - process_sql_row(Row, {IO, ConvertFun, PrevRow}), - fetch(IO, ConvertFun, Row); - {selected, _, []} -> - ok; - Err -> - erlang:error(Err) - end. - -process_sql_row(Row, {IO, ConvertFun, PrevRow}) when Row == PrevRow -> - %% Avoid calling ConvertFun with the same input - {IO, ConvertFun, Row}; -process_sql_row(Row, {IO, ConvertFun, _PrevRow}) -> - case catch ConvertFun(Row) of - {'EXIT', _} = Err -> - ?ERROR_MSG("failed to convert ~p: ~p", [Row, Err]); - Term -> - ok = disk_log:log(IO#dump.fd, Term) - end, - {IO, ConvertFun, Row}. - -import_dump(LServer, Mods, #dump{fd = Fd, cont = Cont}) -> - case disk_log:chunk(Fd, Cont) of - {NewCont, Terms} -> - import_terms(LServer, Mods, Terms), - import_dump(LServer, Mods, #dump{fd = Fd, cont = NewCont}); - eof -> - ok; - Err -> - exit(Err) - end. - -import_terms(LServer, Mods, [Term|Terms]) -> - import_term(LServer, Mods, Term), - import_terms(LServer, Mods, Terms); -import_terms(_LServer, _Mods, []) -> - ok. - -import_term(LServer, [{Mod, DBType}|Mods], Term) -> - case catch Mod:import(LServer, DBType, Term) of - pass -> import_term(LServer, Mods, Term); - ok -> ok; - Err -> - ?ERROR_MSG("failed to import ~p for module ~p: ~p", - [Term, Mod, Err]) - end; -import_term(_LServer, [], _Term) -> - ok. - -prepare_output(FileName) -> - prepare_output(FileName, normal). - -prepare_output(FileName, Type) when is_binary(FileName) -> - prepare_output(binary_to_list(FileName), Type); -prepare_output(FileName, normal) when is_list(FileName) -> - case file:open(FileName, [write, raw]) of - {ok, Fd} -> - Fd; - Err -> - exit(Err) - end; -prepare_output(FileName, disk_log) when is_list(FileName) -> - case disk_log:open([{name, make_ref()}, - {repair, truncate}, - {file, FileName}]) of - {ok, Fd} -> - #dump{fd = Fd}; - Err -> - exit(Err) - end; -prepare_output(Output, _Type) -> - Output. - -close_output(FileName, Fd) when FileName /= Fd -> - case Fd of - #dump{} -> - disk_log:close(Fd#dump.fd); - _ -> - file:close(Fd) - end, - ok; -close_output(_, _) -> - ok. - -flatten(SQLs) -> - flatten(SQLs, []). - -flatten([L|Ls], Acc) -> - flatten(Ls, flatten1(lists:reverse(L), Acc)); -flatten([], Acc) -> - Acc. - -flatten1([H|T], Acc) -> - flatten1(T, [[H, $\n]|Acc]); -flatten1([], Acc) -> - Acc. diff --git a/src/ejd2sql.erl b/src/ejd2sql.erl new file mode 100644 index 00000000..aa74286e --- /dev/null +++ b/src/ejd2sql.erl @@ -0,0 +1,315 @@ +%%%---------------------------------------------------------------------- +%%% File : ejd2sql.erl +%%% Author : Alexey Shchepin +%%% Purpose : Export some mnesia tables to SQL DB +%%% Created : 22 Aug 2005 by Alexey Shchepin +%%% +%%% +%%% 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. +%%% +%%%---------------------------------------------------------------------- + +-module(ejd2sql). + +-author('alexey@process-one.net'). + +-include("logger.hrl"). + +-export([export/2, export/3, import_file/2, import/2, + import/3, delete/1]). + +-define(MAX_RECORDS_PER_TRANSACTION, 100). + +-record(dump, {fd, cont = start}). + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +%%% How to use: +%%% A table can be converted from Mnesia to an ODBC database by calling +%%% one of the API function with the following parameters: +%%% - Server is the server domain you want to convert +%%% - Output can be either sql to export to the configured relational +%%% database or "Filename" to export to text file. + +modules() -> + [ejabberd_auth, + mod_announce, + mod_irc, + mod_last, + mod_muc, + mod_offline, + mod_privacy, + mod_private, + %% mod_pubsub, + mod_roster, + mod_shared_roster, + mod_vcard, + mod_vcard_xupdate]. + +export(Server, Output) -> + LServer = jid:nameprep(iolist_to_binary(Server)), + Modules = modules(), + IO = prepare_output(Output), + lists:foreach( + fun(Module) -> + export(LServer, IO, Module) + end, Modules), + close_output(Output, IO). + +export(Server, Output, Module) -> + LServer = jid:nameprep(iolist_to_binary(Server)), + IO = prepare_output(Output), + lists:foreach( + fun({Table, ConvertFun}) -> + export(LServer, Table, IO, ConvertFun) + end, Module:export(Server)), + close_output(Output, IO). + +delete(Server) -> + Modules = modules(), + lists:foreach( + fun(Module) -> + delete(Server, Module) + end, Modules). + +delete(Server, Module) -> + LServer = jid:nameprep(iolist_to_binary(Server)), + lists:foreach( + fun({Table, ConvertFun}) -> + delete(LServer, Table, ConvertFun) + end, Module:export(Server)). + +import_file(Server, FileName) when is_binary(FileName) -> + import(Server, binary_to_list(FileName)); +import_file(Server, FileName) -> + case disk_log:open([{name, make_ref()}, + {file, FileName}, + {mode, read_only}]) of + {ok, Fd} -> + LServer = jid:nameprep(Server), + Mods = [{Mod, gen_mod:db_type(LServer, Mod)} + || Mod <- modules(), gen_mod:is_loaded(LServer, Mod)], + AuthMods = case lists:member(ejabberd_auth_internal, + ejabberd_auth:auth_modules(LServer)) of + true -> + [{ejabberd_auth, mnesia}]; + false -> + [] + end, + import_dump(LServer, AuthMods ++ Mods, #dump{fd = Fd}); + Err -> + exit(Err) + end. + +import(Server, Output) -> + import(Server, Output, [{fast, true}]). + +import(Server, Output, Opts) -> + LServer = jid:nameprep(iolist_to_binary(Server)), + Modules = modules(), + IO = prepare_output(Output, disk_log), + lists:foreach( + fun(Module) -> + import(LServer, IO, Opts, Module) + end, Modules), + close_output(Output, IO). + +import(Server, Output, Opts, Module) -> + LServer = jid:nameprep(iolist_to_binary(Server)), + IO = prepare_output(Output, disk_log), + lists:foreach( + fun({SelectQuery, ConvertFun}) -> + import(LServer, SelectQuery, IO, ConvertFun, Opts) + end, Module:import(Server)), + close_output(Output, IO). + +%%%---------------------------------------------------------------------- +%%% Internal functions +%%%---------------------------------------------------------------------- +export(LServer, Table, IO, ConvertFun) -> + F = fun () -> + mnesia:read_lock_table(Table), + {_N, SQLs} = + mnesia:foldl( + fun(R, {N, SQLs} = Acc) -> + case ConvertFun(LServer, R) of + [] -> + Acc; + SQL -> + if N < (?MAX_RECORDS_PER_TRANSACTION) - 1 -> + {N + 1, [SQL | SQLs]}; + true -> + output(LServer, + Table, IO, + flatten([SQL | SQLs])), + {0, []} + end + end + end, + {0, []}, Table), + output(LServer, Table, IO, flatten(SQLs)) + end, + mnesia:transaction(F). + +output(_LServer, _Table, _IO, []) -> + ok; +output(LServer, _Table, sql, SQLs) -> + ejabberd_sql:sql_transaction(LServer, SQLs); +output(_LServer, Table, Fd, SQLs) -> + file:write(Fd, ["-- \n-- Mnesia table: ", atom_to_list(Table), + "\n--\n", SQLs]). + +delete(LServer, Table, ConvertFun) -> + F = fun () -> + mnesia:write_lock_table(Table), + {_N, SQLs} = + mnesia:foldl( + fun(R, {N, SQLs} = Acc) -> + case ConvertFun(LServer, R) of + [] -> + Acc; + _SQL -> + mnesia:delete_object(R), + Acc + end + end, + {0, []}, Table), + delete(LServer, Table, SQLs) + end, + mnesia:transaction(F). + +import(LServer, SelectQuery, IO, ConvertFun, Opts) -> + F = case proplists:get_bool(fast, Opts) of + true -> + fun() -> + case ejabberd_sql:sql_query_t(SelectQuery) of + {selected, _, Rows} -> + lists:foldl(fun process_sql_row/2, + {IO, ConvertFun, undefined}, Rows); + Err -> + erlang:error(Err) + end + end; + false -> + fun() -> + ejabberd_sql:sql_query_t( + [iolist_to_binary( + [<<"declare c cursor for ">>, SelectQuery])]), + fetch(IO, ConvertFun, undefined) + end + end, + ejabberd_sql:sql_transaction(LServer, F). + +fetch(IO, ConvertFun, PrevRow) -> + case ejabberd_sql:sql_query_t([<<"fetch c;">>]) of + {selected, _, [Row]} -> + process_sql_row(Row, {IO, ConvertFun, PrevRow}), + fetch(IO, ConvertFun, Row); + {selected, _, []} -> + ok; + Err -> + erlang:error(Err) + end. + +process_sql_row(Row, {IO, ConvertFun, PrevRow}) when Row == PrevRow -> + %% Avoid calling ConvertFun with the same input + {IO, ConvertFun, Row}; +process_sql_row(Row, {IO, ConvertFun, _PrevRow}) -> + case catch ConvertFun(Row) of + {'EXIT', _} = Err -> + ?ERROR_MSG("failed to convert ~p: ~p", [Row, Err]); + Term -> + ok = disk_log:log(IO#dump.fd, Term) + end, + {IO, ConvertFun, Row}. + +import_dump(LServer, Mods, #dump{fd = Fd, cont = Cont}) -> + case disk_log:chunk(Fd, Cont) of + {NewCont, Terms} -> + import_terms(LServer, Mods, Terms), + import_dump(LServer, Mods, #dump{fd = Fd, cont = NewCont}); + eof -> + ok; + Err -> + exit(Err) + end. + +import_terms(LServer, Mods, [Term|Terms]) -> + import_term(LServer, Mods, Term), + import_terms(LServer, Mods, Terms); +import_terms(_LServer, _Mods, []) -> + ok. + +import_term(LServer, [{Mod, DBType}|Mods], Term) -> + case catch Mod:import(LServer, DBType, Term) of + pass -> import_term(LServer, Mods, Term); + ok -> ok; + Err -> + ?ERROR_MSG("failed to import ~p for module ~p: ~p", + [Term, Mod, Err]) + end; +import_term(_LServer, [], _Term) -> + ok. + +prepare_output(FileName) -> + prepare_output(FileName, normal). + +prepare_output(FileName, Type) when is_binary(FileName) -> + prepare_output(binary_to_list(FileName), Type); +prepare_output(FileName, normal) when is_list(FileName) -> + case file:open(FileName, [write, raw]) of + {ok, Fd} -> + Fd; + Err -> + exit(Err) + end; +prepare_output(FileName, disk_log) when is_list(FileName) -> + case disk_log:open([{name, make_ref()}, + {repair, truncate}, + {file, FileName}]) of + {ok, Fd} -> + #dump{fd = Fd}; + Err -> + exit(Err) + end; +prepare_output(Output, _Type) -> + Output. + +close_output(FileName, Fd) when FileName /= Fd -> + case Fd of + #dump{} -> + disk_log:close(Fd#dump.fd); + _ -> + file:close(Fd) + end, + ok; +close_output(_, _) -> + ok. + +flatten(SQLs) -> + flatten(SQLs, []). + +flatten([L|Ls], Acc) -> + flatten(Ls, flatten1(lists:reverse(L), Acc)); +flatten([], Acc) -> + Acc. + +flatten1([H|T], Acc) -> + flatten1(T, [[H, $\n]|Acc]); +flatten1([], Acc) -> + Acc. diff --git a/src/gen_mod.erl b/src/gen_mod.erl index a290aa20..26e662dc 100644 --- a/src/gen_mod.erl +++ b/src/gen_mod.erl @@ -48,7 +48,7 @@ opts = [] :: opts() | '_' | '$2'}). -type opts() :: [{atom(), any()}]. --type db_type() :: odbc | mnesia | riak. +-type db_type() :: sql | mnesia | riak. -callback start(binary(), opts()) -> any(). -callback stop(binary()) -> any(). @@ -297,7 +297,8 @@ validate_opts(Module, Opts) -> -spec v_db(db_type() | internal) -> db_type(). -v_db(odbc) -> odbc; +v_db(odbc) -> sql; +v_db(sql) -> sql; v_db(internal) -> mnesia; v_db(mnesia) -> mnesia; v_db(riak) -> riak. @@ -322,6 +323,7 @@ default_db(Host) -> -spec db_mod(binary() | global | db_type(), module()) -> module(). db_mod(odbc, Module) -> list_to_atom(atom_to_list(Module) ++ "_sql"); +db_mod(sql, Module) -> list_to_atom(atom_to_list(Module) ++ "_sql"); db_mod(mnesia, Module) -> list_to_atom(atom_to_list(Module) ++ "_mnesia"); db_mod(riak, Module) -> list_to_atom(atom_to_list(Module) ++ "_riak"); db_mod(Host, Module) when is_binary(Host) orelse Host == global -> diff --git a/src/mod_announce_sql.erl b/src/mod_announce_sql.erl index cf10ffd8..692c8fae 100644 --- a/src/mod_announce_sql.erl +++ b/src/mod_announce_sql.erl @@ -27,35 +27,35 @@ set_motd_users(LServer, USRs) -> F = fun() -> lists:foreach( fun({U, _S, _R}) -> - Username = ejabberd_odbc:escape(U), - odbc_queries:update_t( + Username = ejabberd_sql:escape(U), + sql_queries:update_t( <<"motd">>, [<<"username">>, <<"xml">>], [Username, <<"">>], [<<"username='">>, Username, <<"'">>]) end, USRs) end, - ejabberd_odbc:sql_transaction(LServer, F). + ejabberd_sql:sql_transaction(LServer, F). set_motd(LServer, Packet) -> - XML = ejabberd_odbc:escape(fxml:element_to_binary(Packet)), + XML = ejabberd_sql:escape(fxml:element_to_binary(Packet)), F = fun() -> - odbc_queries:update_t( + sql_queries:update_t( <<"motd">>, [<<"username">>, <<"xml">>], [<<"">>, XML], [<<"username=''">>]) end, - ejabberd_odbc:sql_transaction(LServer, F). + ejabberd_sql:sql_transaction(LServer, F). delete_motd(LServer) -> F = fun() -> - ejabberd_odbc:sql_query_t([<<"delete from motd;">>]) + ejabberd_sql:sql_query_t([<<"delete from motd;">>]) end, - ejabberd_odbc:sql_transaction(LServer, F). + ejabberd_sql:sql_transaction(LServer, F). get_motd(LServer) -> - case catch ejabberd_odbc:sql_query( + case catch ejabberd_sql:sql_query( LServer, [<<"select xml from motd where username='';">>]) of {selected, [<<"xml">>], [[XML]]} -> case fxml_stream:parse_element(XML) of @@ -69,8 +69,8 @@ get_motd(LServer) -> end. is_motd_user(LUser, LServer) -> - Username = ejabberd_odbc:escape(LUser), - case catch ejabberd_odbc:sql_query( + Username = ejabberd_sql:escape(LUser), + case catch ejabberd_sql:sql_query( LServer, [<<"select username from motd " "where username='">>, Username, <<"';">>]) of @@ -81,15 +81,15 @@ is_motd_user(LUser, LServer) -> end. set_motd_user(LUser, LServer) -> - Username = ejabberd_odbc:escape(LUser), + Username = ejabberd_sql:escape(LUser), F = fun() -> - odbc_queries:update_t( + sql_queries:update_t( <<"motd">>, [<<"username">>, <<"xml">>], [Username, <<"">>], [<<"username='">>, Username, <<"'">>]) end, - ejabberd_odbc:sql_transaction(LServer, F). + ejabberd_sql:sql_transaction(LServer, F). export(_Server) -> [{motd, @@ -97,7 +97,7 @@ export(_Server) -> when LServer == Host -> [[<<"delete from motd where username='';">>], [<<"insert into motd(username, xml) values ('', '">>, - ejabberd_odbc:escape(fxml:element_to_binary(El)), + ejabberd_sql:escape(fxml:element_to_binary(El)), <<"');">>]]; (_Host, _R) -> [] @@ -105,7 +105,7 @@ export(_Server) -> {motd_users, fun(Host, #motd_users{us = {LUser, LServer}}) when LServer == Host, LUser /= <<"">> -> - Username = ejabberd_odbc:escape(LUser), + Username = ejabberd_sql:escape(LUser), [[<<"delete from motd where username='">>, Username, <<"';">>], [<<"insert into motd(username, xml) values ('">>, Username, <<"', '');">>]]; diff --git a/src/mod_blocking_sql.erl b/src/mod_blocking_sql.erl index 8177adae..fb8380e2 100644 --- a/src/mod_blocking_sql.erl +++ b/src/mod_blocking_sql.erl @@ -44,7 +44,7 @@ process_blocklist_block(LUser, LServer, Filter) -> mod_privacy_sql:sql_set_privacy_list(ID, NewRItems), {ok, Default, NewList} end, - ejabberd_odbc:sql_transaction(LServer, F). + ejabberd_sql:sql_transaction(LServer, F). unblock_by_filter(LUser, LServer, Filter) -> F = fun () -> @@ -67,7 +67,7 @@ unblock_by_filter(LUser, LServer, Filter) -> _ -> ok end end, - ejabberd_odbc:sql_transaction(LServer, F). + ejabberd_sql:sql_transaction(LServer, F). process_blocklist_get(LUser, LServer) -> case catch mod_privacy_sql:sql_get_default_privacy_list(LUser, LServer) of diff --git a/src/mod_caps.erl b/src/mod_caps.erl index bd0f14f5..3d5c360a 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -609,7 +609,7 @@ import_start(LServer, DBType) -> Mod:init(LServer, []), ok. -import(_LServer, {odbc, _}, _DBType, <<"caps_features">>, +import(_LServer, {sql, _}, _DBType, <<"caps_features">>, [Node, SubNode, Feature, _TimeStamp]) -> Feature1 = case catch jlib:binary_to_integer(Feature) of I when is_integer(I), I>0 -> I; @@ -642,7 +642,7 @@ import_next(LServer, DBType, NodePair) -> ejabberd_riak:put( #caps_features{node_pair = NodePair, features = Features}, caps_features_schema()); - _ when DBType == odbc -> + _ when DBType == sql -> ok end, import_next(LServer, DBType, ets:next(caps_features_tmp, NodePair)). diff --git a/src/mod_caps_sql.erl b/src/mod_caps_sql.erl index 353b95b1..9f587db3 100644 --- a/src/mod_caps_sql.erl +++ b/src/mod_caps_sql.erl @@ -21,9 +21,9 @@ init(_Host, _Opts) -> ok. caps_read(LServer, {Node, SubNode}) -> - SNode = ejabberd_odbc:escape(Node), - SSubNode = ejabberd_odbc:escape(SubNode), - case ejabberd_odbc:sql_query( + SNode = ejabberd_sql:escape(Node), + SSubNode = ejabberd_sql:escape(SubNode), + case ejabberd_sql:sql_query( LServer, [<<"select feature from caps_features where ">>, <<"node='">>, SNode, <<"' and subnode='">>, SSubNode, <<"';">>]) of @@ -39,7 +39,7 @@ caps_read(LServer, {Node, SubNode}) -> end. caps_write(LServer, NodePair, Features) -> - ejabberd_odbc:sql_transaction( + ejabberd_sql:sql_transaction( LServer, sql_write_features_t(NodePair, Features)). @@ -56,8 +56,8 @@ export(_Server) -> %%% Internal functions %%%=================================================================== sql_write_features_t({Node, SubNode}, Features) -> - SNode = ejabberd_odbc:escape(Node), - SSubNode = ejabberd_odbc:escape(SubNode), + SNode = ejabberd_sql:escape(Node), + SSubNode = ejabberd_sql:escape(SubNode), NewFeatures = if is_integer(Features) -> [jlib:integer_to_binary(Features)]; true -> @@ -67,5 +67,5 @@ sql_write_features_t({Node, SubNode}, Features) -> SNode, <<"' and subnode='">>, SSubNode, <<"';">>]| [[<<"insert into caps_features(node, subnode, feature) ">>, <<"values ('">>, SNode, <<"', '">>, SSubNode, <<"', '">>, - ejabberd_odbc:escape(F), <<"');">>] || F <- NewFeatures]]. + ejabberd_sql:escape(F), <<"');">>] || F <- NewFeatures]]. diff --git a/src/mod_irc_sql.erl b/src/mod_irc_sql.erl index bf6dbb48..9a97d572 100644 --- a/src/mod_irc_sql.erl +++ b/src/mod_irc_sql.erl @@ -24,33 +24,33 @@ init(_Host, _Opts) -> get_data(LServer, Host, From) -> LJID = jid:tolower(jid:remove_resource(From)), - SJID = ejabberd_odbc:escape(jid:to_string(LJID)), - SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query( + SJID = ejabberd_sql:escape(jid:to_string(LJID)), + SHost = ejabberd_sql:escape(Host), + case catch ejabberd_sql:sql_query( LServer, [<<"select data from irc_custom where jid='">>, SJID, <<"' and host='">>, SHost, <<"';">>]) of {selected, [<<"data">>], [[SData]]} -> - mod_irc:data_to_binary(From, ejabberd_odbc:decode_term(SData)); + mod_irc:data_to_binary(From, ejabberd_sql:decode_term(SData)); {'EXIT', _} -> error; {selected, _, _} -> empty end. set_data(LServer, Host, From, Data) -> LJID = jid:tolower(jid:remove_resource(From)), - SJID = ejabberd_odbc:escape(jid:to_string(LJID)), - SHost = ejabberd_odbc:escape(Host), - SData = ejabberd_odbc:encode_term(Data), + SJID = ejabberd_sql:escape(jid:to_string(LJID)), + SHost = ejabberd_sql:escape(Host), + SData = ejabberd_sql:encode_term(Data), F = fun () -> - odbc_queries:update_t(<<"irc_custom">>, + sql_queries:update_t(<<"irc_custom">>, [<<"jid">>, <<"host">>, <<"data">>], [SJID, SHost, SData], [<<"jid='">>, SJID, <<"' and host='">>, SHost, <<"'">>]), ok end, - ejabberd_odbc:sql_transaction(LServer, F). + ejabberd_sql:sql_transaction(LServer, F). export(_Server) -> [{irc_custom, @@ -58,11 +58,11 @@ export(_Server) -> data = Data}) -> case str:suffix(Host, IRCHost) of true -> - SJID = ejabberd_odbc:escape( + SJID = ejabberd_sql:escape( jid:to_string( jid:make(U, S, <<"">>))), - SIRCHost = ejabberd_odbc:escape(IRCHost), - SData = ejabberd_odbc:encode_term(Data), + SIRCHost = ejabberd_sql:escape(IRCHost), + SData = ejabberd_sql:encode_term(Data), [[<<"delete from irc_custom where jid='">>, SJID, <<"' and host='">>, SIRCHost, <<"';">>], [<<"insert into irc_custom(jid, host, " @@ -78,7 +78,7 @@ import(_LServer) -> [{<<"select jid, host, data from irc_custom;">>, fun([SJID, IRCHost, SData]) -> #jid{luser = U, lserver = S} = jid:from_string(SJID), - Data = ejabberd_odbc:decode_term(SData), + Data = ejabberd_sql:decode_term(SData), #irc_custom{us_host = {{U, S}, IRCHost}, data = Data} end}]. diff --git a/src/mod_last_sql.erl b/src/mod_last_sql.erl index edfa3763..5f67b14f 100644 --- a/src/mod_last_sql.erl +++ b/src/mod_last_sql.erl @@ -23,7 +23,7 @@ init(_Host, _Opts) -> ok. get_last(LUser, LServer) -> - case catch odbc_queries:get_last(LServer, LUser) of + case catch sql_queries:get_last(LServer, LUser) of {selected, []} -> not_found; {selected, [{TimeStamp, Status}]} -> @@ -35,10 +35,10 @@ get_last(LUser, LServer) -> end. store_last_info(LUser, LServer, TimeStamp, Status) -> - odbc_queries:set_last_t(LServer, LUser, TimeStamp, Status). + sql_queries:set_last_t(LServer, LUser, TimeStamp, Status). remove_user(LUser, LServer) -> - odbc_queries:del_last(LServer, LUser). + sql_queries:del_last(LServer, LUser). import(_LServer, _LA) -> pass. @@ -48,10 +48,10 @@ export(_Server) -> fun(Host, #last_activity{us = {LUser, LServer}, timestamp = TimeStamp, status = Status}) when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), + Username = ejabberd_sql:escape(LUser), Seconds = - ejabberd_odbc:escape(jlib:integer_to_binary(TimeStamp)), - State = ejabberd_odbc:escape(Status), + ejabberd_sql:escape(jlib:integer_to_binary(TimeStamp)), + State = ejabberd_sql:escape(Status), [[<<"delete from last where username='">>, Username, <<"';">>], [<<"insert into last(username, seconds, " "state) values ('">>, diff --git a/src/mod_mam.erl b/src/mod_mam.erl index 098ee896..f84519a0 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -344,7 +344,7 @@ delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>; lists:map( fun(Host) -> case gen_mod:db_type(Host, ?MODULE) of - odbc -> {odbc, Host}; + sql -> {sql, Host}; Other -> {Other, global} end end, ?MYHOSTS)), @@ -1018,7 +1018,8 @@ mod_opt_type(cache_life_time) -> mod_opt_type(cache_size) -> fun (I) when is_integer(I), I > 0 -> I end; mod_opt_type(db_type) -> - fun(odbc) -> odbc; + fun(sql) -> sql; + (odbc) -> sql; (internal) -> mnesia; (mnesia) -> mnesia end; diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl index 1f24de31..69fdf323 100644 --- a/src/mod_mam_sql.erl +++ b/src/mod_mam_sql.erl @@ -26,11 +26,11 @@ init(_Host, _Opts) -> ok. remove_user(LUser, LServer) -> - SUser = ejabberd_odbc:escape(LUser), - ejabberd_odbc:sql_query( + SUser = ejabberd_sql:escape(LUser), + ejabberd_sql:sql_query( LServer, [<<"delete from archive where username='">>, SUser, <<"';">>]), - ejabberd_odbc:sql_query( + ejabberd_sql:sql_query( LServer, [<<"delete from archive_prefs where username='">>, SUser, <<"';">>]). @@ -43,7 +43,7 @@ delete_old_messages(ServerHost, TimeStamp, Type) -> true -> [<<" and kind='">>, jlib:atom_to_binary(Type), <<"'">>] end, TS = integer_to_binary(now_to_usec(TimeStamp)), - ejabberd_odbc:sql_query( + ejabberd_sql:sql_query( ServerHost, [<<"delete from archive where timestamp<">>, TS, TypeClause, <<";">>]), ok. @@ -67,18 +67,18 @@ store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) -> jid:tolower(Peer)), XML = fxml:element_to_binary(Pkt), Body = fxml:get_subtag_cdata(Pkt, <<"body">>), - case ejabberd_odbc:sql_query( + case ejabberd_sql:sql_query( LServer, [<<"insert into archive (username, timestamp, " "peer, bare_peer, xml, txt, kind, nick) values (">>, - <<"'">>, ejabberd_odbc:escape(SUser), <<"', ">>, + <<"'">>, ejabberd_sql:escape(SUser), <<"', ">>, <<"'">>, TS, <<"', ">>, - <<"'">>, ejabberd_odbc:escape(LPeer), <<"', ">>, - <<"'">>, ejabberd_odbc:escape(BarePeer), <<"', ">>, - <<"'">>, ejabberd_odbc:escape(XML), <<"', ">>, - <<"'">>, ejabberd_odbc:escape(Body), <<"', ">>, + <<"'">>, ejabberd_sql:escape(LPeer), <<"', ">>, + <<"'">>, ejabberd_sql:escape(BarePeer), <<"', ">>, + <<"'">>, ejabberd_sql:escape(XML), <<"', ">>, + <<"'">>, ejabberd_sql:escape(Body), <<"', ">>, <<"'">>, jlib:atom_to_binary(Type), <<"', ">>, - <<"'">>, ejabberd_odbc:escape(Nick), <<"');">>]) of + <<"'">>, ejabberd_sql:escape(Nick), <<"');">>]) of {updated, _} -> {ok, ID}; Err -> @@ -89,10 +89,10 @@ write_prefs(LUser, _LServer, #archive_prefs{default = Default, never = Never, always = Always}, ServerHost) -> - SUser = ejabberd_odbc:escape(LUser), + SUser = ejabberd_sql:escape(LUser), SDefault = erlang:atom_to_binary(Default, utf8), - SAlways = ejabberd_odbc:encode_term(Always), - SNever = ejabberd_odbc:encode_term(Never), + SAlways = ejabberd_sql:encode_term(Always), + SNever = ejabberd_sql:encode_term(Never), case update(ServerHost, <<"archive_prefs">>, [<<"username">>, <<"def">>, <<"always">>, <<"never">>], [SUser, SDefault, SAlways, SNever], @@ -104,15 +104,15 @@ write_prefs(LUser, _LServer, #archive_prefs{default = Default, end. get_prefs(LUser, LServer) -> - case ejabberd_odbc:sql_query( + case ejabberd_sql:sql_query( LServer, [<<"select def, always, never from archive_prefs ">>, <<"where username='">>, - ejabberd_odbc:escape(LUser), <<"';">>]) of + ejabberd_sql:escape(LUser), <<"';">>]) of {selected, _, [[SDefault, SAlways, SNever]]} -> Default = erlang:binary_to_existing_atom(SDefault, utf8), - Always = ejabberd_odbc:decode_term(SAlways), - Never = ejabberd_odbc:decode_term(SNever), + Always = ejabberd_sql:decode_term(SAlways), + Never = ejabberd_sql:decode_term(SNever), {ok, #archive_prefs{us = {LUser, LServer}, default = Default, always = Always, @@ -135,8 +135,8 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive, % and the client did not specify a limit using RSM then the server should % return a policy-violation error to the client." We currently don't do this % for v0.2 requests, but we do limit #rsm_in.max for v0.3 and newer. - case {ejabberd_odbc:sql_query(LServer, Query), - ejabberd_odbc:sql_query(LServer, CountQuery)} of + case {ejabberd_sql:sql_query(LServer, Query), + ejabberd_sql:sql_query(LServer, CountQuery)} of {{selected, _, Res}, {selected, _, [[Count]]}} -> {Max, Direction} = case RSM of #rsm_in{max = M, direction = D} -> {M, D}; @@ -206,8 +206,8 @@ make_sql_query(User, LServer, Start, End, With, RSM) -> {none, none, <<>>} end, ODBCType = ejabberd_config:get_option( - {odbc_type, LServer}, - ejabberd_odbc:opt_type(odbc_type)), + {sql_type, LServer}, + ejabberd_sql:opt_type(sql_type)), LimitClause = if is_integer(Max), Max >= 0, ODBCType /= mssql -> [<<" limit ">>, jlib:integer_to_binary(Max+1)]; true -> @@ -223,14 +223,14 @@ make_sql_query(User, LServer, Start, End, With, RSM) -> []; {text, Txt} -> [<<" and match (txt) against ('">>, - ejabberd_odbc:escape(Txt), <<"')">>]; + ejabberd_sql:escape(Txt), <<"')">>]; {_, _, <<>>} -> [<<" and bare_peer='">>, - ejabberd_odbc:escape(jid:to_string(With)), + ejabberd_sql:escape(jid:to_string(With)), <<"'">>]; {_, _, _} -> [<<" and peer='">>, - ejabberd_odbc:escape(jid:to_string(With)), + ejabberd_sql:escape(jid:to_string(With)), <<"'">>]; none -> [] @@ -262,7 +262,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) -> _ -> [] end, - SUser = ejabberd_odbc:escape(User), + SUser = ejabberd_sql:escape(User), Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick" " FROM archive WHERE username='">>, @@ -291,14 +291,14 @@ update(LServer, Table, Fields, Vals, Where) -> <> end, Fields, Vals), - case ejabberd_odbc:sql_query(LServer, + case ejabberd_sql:sql_query(LServer, [<<"update ">>, Table, <<" set ">>, join(UPairs, <<", ">>), <<" where ">>, Where, <<";">>]) of {updated, 1} -> {updated, 1}; _ -> - ejabberd_odbc:sql_query(LServer, + ejabberd_sql:sql_query(LServer, [<<"insert into ">>, Table, <<"(">>, join(Fields, <<", ">>), <<") values ('">>, join(Vals, <<"', '">>), <<"');">>]) diff --git a/src/mod_muc_sql.erl b/src/mod_muc_sql.erl index 9acd9f8d..55628d43 100644 --- a/src/mod_muc_sql.erl +++ b/src/mod_muc_sql.erl @@ -26,46 +26,46 @@ init(_Host, _Opts) -> ok. store_room(LServer, Host, Name, Opts) -> - SName = ejabberd_odbc:escape(Name), - SHost = ejabberd_odbc:escape(Host), - SOpts = ejabberd_odbc:encode_term(Opts), + SName = ejabberd_sql:escape(Name), + SHost = ejabberd_sql:escape(Host), + SOpts = ejabberd_sql:encode_term(Opts), F = fun () -> - odbc_queries:update_t(<<"muc_room">>, + sql_queries:update_t(<<"muc_room">>, [<<"name">>, <<"host">>, <<"opts">>], [SName, SHost, SOpts], [<<"name='">>, SName, <<"' and host='">>, SHost, <<"'">>]) end, - ejabberd_odbc:sql_transaction(LServer, F). + ejabberd_sql:sql_transaction(LServer, F). restore_room(LServer, Host, Name) -> - SName = ejabberd_odbc:escape(Name), - SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query(LServer, + SName = ejabberd_sql:escape(Name), + SHost = ejabberd_sql:escape(Host), + case catch ejabberd_sql:sql_query(LServer, [<<"select opts from muc_room where name='">>, SName, <<"' and host='">>, SHost, <<"';">>]) of {selected, [<<"opts">>], [[Opts]]} -> - mod_muc:opts_to_binary(ejabberd_odbc:decode_term(Opts)); + mod_muc:opts_to_binary(ejabberd_sql:decode_term(Opts)); _ -> error end. forget_room(LServer, Host, Name) -> - SName = ejabberd_odbc:escape(Name), - SHost = ejabberd_odbc:escape(Host), + SName = ejabberd_sql:escape(Name), + SHost = ejabberd_sql:escape(Host), F = fun () -> - ejabberd_odbc:sql_query_t([<<"delete from muc_room where name='">>, + ejabberd_sql:sql_query_t([<<"delete from muc_room where name='">>, SName, <<"' and host='">>, SHost, <<"';">>]) end, - ejabberd_odbc:sql_transaction(LServer, F). + ejabberd_sql:sql_transaction(LServer, F). can_use_nick(LServer, Host, JID, Nick) -> SJID = jid:to_string(jid:tolower(jid:remove_resource(JID))), - SNick = ejabberd_odbc:escape(Nick), - SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query(LServer, + SNick = ejabberd_sql:escape(Nick), + SHost = ejabberd_sql:escape(Host), + case catch ejabberd_sql:sql_query(LServer, [<<"select jid from muc_registered ">>, <<"where nick='">>, SNick, <<"' and host='">>, SHost, <<"';">>]) of @@ -74,8 +74,8 @@ can_use_nick(LServer, Host, JID, Nick) -> end. get_rooms(LServer, Host) -> - SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query(LServer, + SHost = ejabberd_sql:escape(Host), + case catch ejabberd_sql:sql_query(LServer, [<<"select name, opts from muc_room ">>, <<"where host='">>, SHost, <<"';">>]) of {selected, [<<"name">>, <<"opts">>], RoomOpts} -> @@ -83,7 +83,7 @@ get_rooms(LServer, Host) -> fun([Room, Opts]) -> #muc_room{name_host = {Room, Host}, opts = mod_muc:opts_to_binary( - ejabberd_odbc:decode_term(Opts))} + ejabberd_sql:decode_term(Opts))} end, RoomOpts); Err -> ?ERROR_MSG("failed to get rooms: ~p", [Err]), @@ -91,9 +91,9 @@ get_rooms(LServer, Host) -> end. get_nick(LServer, Host, From) -> - SJID = ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))), - SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query(LServer, + SJID = ejabberd_sql:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))), + SHost = ejabberd_sql:escape(Host), + case catch ejabberd_sql:sql_query(LServer, [<<"select nick from muc_registered where " "jid='">>, SJID, <<"' and host='">>, SHost, @@ -104,20 +104,20 @@ get_nick(LServer, Host, From) -> set_nick(LServer, Host, From, Nick) -> JID = jid:to_string(jid:tolower(jid:remove_resource(From))), - SJID = ejabberd_odbc:escape(JID), - SNick = ejabberd_odbc:escape(Nick), - SHost = ejabberd_odbc:escape(Host), + SJID = ejabberd_sql:escape(JID), + SNick = ejabberd_sql:escape(Nick), + SHost = ejabberd_sql:escape(Host), F = fun () -> case Nick of <<"">> -> - ejabberd_odbc:sql_query_t( + ejabberd_sql:sql_query_t( [<<"delete from muc_registered where ">>, <<"jid='">>, SJID, <<"' and host='">>, Host, <<"';">>]), ok; _ -> - Allow = case ejabberd_odbc:sql_query_t( + Allow = case ejabberd_sql:sql_query_t( [<<"select jid from muc_registered ">>, <<"where nick='">>, SNick, @@ -127,7 +127,7 @@ set_nick(LServer, Host, From, Nick) -> _ -> true end, if Allow -> - odbc_queries:update_t(<<"muc_registered">>, + sql_queries:update_t(<<"muc_registered">>, [<<"jid">>, <<"host">>, <<"nick">>], [SJID, SHost, SNick], @@ -140,16 +140,16 @@ set_nick(LServer, Host, From, Nick) -> end end end, - ejabberd_odbc:sql_transaction(LServer, F). + ejabberd_sql:sql_transaction(LServer, F). export(_Server) -> [{muc_room, fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) -> case str:suffix(Host, RoomHost) of true -> - SName = ejabberd_odbc:escape(Name), - SRoomHost = ejabberd_odbc:escape(RoomHost), - SOpts = ejabberd_odbc:encode_term(Opts), + SName = ejabberd_sql:escape(Name), + SRoomHost = ejabberd_sql:escape(RoomHost), + SOpts = ejabberd_sql:encode_term(Opts), [[<<"delete from muc_room where name='">>, SName, <<"' and host='">>, SRoomHost, <<"';">>], [<<"insert into muc_room(name, host, opts) ", @@ -165,11 +165,11 @@ export(_Server) -> nick = Nick}) -> case str:suffix(Host, RoomHost) of true -> - SJID = ejabberd_odbc:escape( + SJID = ejabberd_sql:escape( jid:to_string( jid:make(U, S, <<"">>))), - SNick = ejabberd_odbc:escape(Nick), - SRoomHost = ejabberd_odbc:escape(RoomHost), + SNick = ejabberd_sql:escape(Nick), + SRoomHost = ejabberd_sql:escape(RoomHost), [[<<"delete from muc_registered where jid='">>, SJID, <<"' and host='">>, SRoomHost, <<"';">>], [<<"insert into muc_registered(jid, host, " @@ -184,7 +184,7 @@ export(_Server) -> import(_LServer) -> [{<<"select name, host, opts from muc_room;">>, fun([Name, RoomHost, SOpts]) -> - Opts = mod_muc:opts_to_binary(ejabberd_odbc:decode_term(SOpts)), + Opts = mod_muc:opts_to_binary(ejabberd_sql:decode_term(SOpts)), #muc_room{name_host = {Name, RoomHost}, opts = Opts} end}, {<<"select jid, host, nick from muc_registered;">>, diff --git a/src/mod_offline.erl b/src/mod_offline.erl index 2cdd82ae..356d89a6 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -243,7 +243,7 @@ receive_all(US, Msgs, DBType) -> after 0 -> case DBType of mnesia -> Msgs; - odbc -> lists:reverse(Msgs); + sql -> lists:reverse(Msgs); riak -> Msgs end end. diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl index 37b90163..4d945557 100644 --- a/src/mod_offline_sql.erl +++ b/src/mod_offline_sql.erl @@ -39,7 +39,7 @@ store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) -> Query = lists:map( fun(M) -> Username = - ejabberd_odbc:escape((M#offline_msg.to)#jid.luser), + ejabberd_sql:escape((M#offline_msg.to)#jid.luser), From = M#offline_msg.from, To = M#offline_msg.to, Packet = @@ -50,15 +50,15 @@ store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) -> M#offline_msg.timestamp, <<"Offline Storage">>), XML = - ejabberd_odbc:escape(fxml:element_to_binary(NewPacket)), - odbc_queries:add_spool_sql(Username, XML) + ejabberd_sql:escape(fxml:element_to_binary(NewPacket)), + sql_queries:add_spool_sql(Username, XML) end, Msgs), - odbc_queries:add_spool(Host, Query) + sql_queries:add_spool(Host, Query) end. pop_messages(LUser, LServer) -> - case odbc_queries:get_and_del_spool_msg_t(LServer, LUser) of + case sql_queries:get_and_del_spool_msg_t(LServer, LUser) of {atomic, {selected, Rs}} -> {ok, lists:flatmap( fun({_, XML}) -> @@ -78,7 +78,7 @@ remove_expired_messages(_LServer) -> {atomic, ok}. remove_old_messages(Days, LServer) -> - case catch ejabberd_odbc:sql_query( + case catch ejabberd_sql:sql_query( LServer, [<<"DELETE FROM spool" " WHERE created_at < " @@ -92,11 +92,11 @@ remove_old_messages(Days, LServer) -> {atomic, ok}. remove_user(LUser, LServer) -> - odbc_queries:del_spool_msg(LServer, LUser). + sql_queries:del_spool_msg(LServer, LUser). read_message_headers(LUser, LServer) -> - Username = ejabberd_odbc:escape(LUser), - case catch ejabberd_odbc:sql_query( + Username = ejabberd_sql:escape(LUser), + case catch ejabberd_sql:sql_query( LServer, [<<"select xml, seq from spool where username ='">>, Username, <<"' order by seq;">>]) of {selected, [<<"xml">>, <<"seq">>], Rows} -> @@ -117,9 +117,9 @@ read_message_headers(LUser, LServer) -> end. read_message(LUser, LServer, Seq) -> - Username = ejabberd_odbc:escape(LUser), - SSeq = ejabberd_odbc:escape(integer_to_binary(Seq)), - case ejabberd_odbc:sql_query( + Username = ejabberd_sql:escape(LUser), + SSeq = ejabberd_sql:escape(integer_to_binary(Seq)), + case ejabberd_sql:sql_query( LServer, [<<"select xml from spool where username='">>, Username, <<"' and seq='">>, SSeq, <<"';">>]) of @@ -135,16 +135,16 @@ read_message(LUser, LServer, Seq) -> end. remove_message(LUser, LServer, Seq) -> - Username = ejabberd_odbc:escape(LUser), - SSeq = ejabberd_odbc:escape(integer_to_binary(Seq)), - ejabberd_odbc:sql_query( + Username = ejabberd_sql:escape(LUser), + SSeq = ejabberd_sql:escape(integer_to_binary(Seq)), + ejabberd_sql:sql_query( LServer, [<<"delete from spool where username='">>, Username, <<"' and seq='">>, SSeq, <<"';">>]), ok. read_all_messages(LUser, LServer) -> - case catch ejabberd_odbc:sql_query( + case catch ejabberd_sql:sql_query( LServer, ?SQL("select @(xml)s from spool where " "username=%(LUser)s order by seq")) of @@ -161,11 +161,11 @@ read_all_messages(LUser, LServer) -> end. remove_all_messages(LUser, LServer) -> - odbc_queries:del_spool_msg(LServer, LUser), + sql_queries:del_spool_msg(LServer, LUser), {atomic, ok}. count_messages(LUser, LServer) -> - case catch ejabberd_odbc:sql_query( + case catch ejabberd_sql:sql_query( LServer, ?SQL("select @(count(*))d from spool " "where username=%(LUser)s")) of @@ -180,11 +180,11 @@ export(_Server) -> timestamp = TimeStamp, from = From, to = To, packet = Packet}) when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), + Username = ejabberd_sql:escape(LUser), Packet1 = jlib:replace_from_to(From, To, Packet), Packet2 = jlib:add_delay_info(Packet1, LServer, TimeStamp, <<"Offline Storage">>), - XML = ejabberd_odbc:escape(fxml:element_to_binary(Packet2)), + XML = ejabberd_sql:escape(fxml:element_to_binary(Packet2)), [[<<"delete from spool where username='">>, Username, <<"';">>], [<<"insert into spool(username, xml) values ('">>, Username, <<"', '">>, XML, <<"');">>]]; diff --git a/src/mod_privacy_sql.erl b/src/mod_privacy_sql.erl index ffaf5518..6b996fa8 100644 --- a/src/mod_privacy_sql.erl +++ b/src/mod_privacy_sql.erl @@ -77,7 +77,7 @@ process_default_set(LUser, LServer, {value, Name}) -> end end end, - odbc_queries:sql_transaction(LServer, F); + sql_queries:sql_transaction(LServer, F); process_default_set(LUser, LServer, false) -> case catch sql_unset_default_privacy_list(LUser, LServer) @@ -110,7 +110,7 @@ remove_privacy_list(LUser, LServer, Name) -> end end end, - odbc_queries:sql_transaction(LServer, F). + sql_queries:sql_transaction(LServer, F). set_privacy_list(#privacy{us = {LUser, LServer}, default = Default, @@ -131,7 +131,7 @@ set_privacy_list(#privacy{us = {LUser, LServer}, end end, Lists) end, - odbc_queries:sql_transaction(LServer, F). + sql_queries:sql_transaction(LServer, F). set_privacy_list(LUser, LServer, Name, List) -> RItems = lists:map(fun item_to_raw/1, List), @@ -147,7 +147,7 @@ set_privacy_list(LUser, LServer, Name, List) -> sql_set_privacy_list(ID, RItems), ok end, - odbc_queries:sql_transaction(LServer, F). + sql_queries:sql_transaction(LServer, F). get_user_list(LUser, LServer) -> case catch sql_get_default_privacy_list(LUser, LServer) @@ -196,7 +196,7 @@ remove_user(LUser, LServer) -> sql_del_privacy_lists(LUser, LServer). export(Server) -> - case catch ejabberd_odbc:sql_query(jid:nameprep(Server), + case catch ejabberd_sql:sql_query(jid:nameprep(Server), [<<"select id from privacy_list order by " "id desc limit 1;">>]) of {selected, [<<"id">>], [[I]]} -> @@ -208,9 +208,9 @@ export(Server) -> fun(Host, #privacy{us = {LUser, LServer}, lists = Lists, default = Default}) when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), + Username = ejabberd_sql:escape(LUser), if Default /= none -> - SDefault = ejabberd_odbc:escape(Default), + SDefault = ejabberd_sql:escape(Default), [[<<"delete from privacy_default_list where ">>, <<"username='">>, Username, <<"';">>], [<<"insert into privacy_default_list(username, " @@ -222,7 +222,7 @@ export(Server) -> end ++ lists:flatmap( fun({Name, List}) -> - SName = ejabberd_odbc:escape(Name), + SName = ejabberd_sql:escape(Name), RItems = lists:map(fun item_to_raw/1, List), ID = jlib:integer_to_binary(get_id()), [[<<"delete from privacy_list where username='">>, @@ -329,8 +329,8 @@ item_to_raw(#listitem{type = Type, value = Value, none -> {<<"n">>, <<"">>}; jid -> {<<"j">>, - ejabberd_odbc:escape(jid:to_string(Value))}; - group -> {<<"g">>, ejabberd_odbc:escape(Value)}; + ejabberd_sql:escape(jid:to_string(Value))}; + group -> {<<"g">>, ejabberd_sql:escape(Value)}; subscription -> case Value of none -> {<<"s">>, <<"none">>}; @@ -347,51 +347,51 @@ item_to_raw(#listitem{type = Type, value = Value, MatchMessage, MatchPresenceIn, MatchPresenceOut}. sql_get_default_privacy_list(LUser, LServer) -> - odbc_queries:get_default_privacy_list(LServer, LUser). + sql_queries:get_default_privacy_list(LServer, LUser). sql_get_default_privacy_list_t(LUser) -> - odbc_queries:get_default_privacy_list_t(LUser). + sql_queries:get_default_privacy_list_t(LUser). sql_get_privacy_list_names(LUser, LServer) -> - odbc_queries:get_privacy_list_names(LServer, LUser). + sql_queries:get_privacy_list_names(LServer, LUser). sql_get_privacy_list_names_t(LUser) -> - odbc_queries:get_privacy_list_names_t(LUser). + sql_queries:get_privacy_list_names_t(LUser). sql_get_privacy_list_id(LUser, LServer, Name) -> - odbc_queries:get_privacy_list_id(LServer, LUser, Name). + sql_queries:get_privacy_list_id(LServer, LUser, Name). sql_get_privacy_list_id_t(LUser, Name) -> - odbc_queries:get_privacy_list_id_t(LUser, Name). + sql_queries:get_privacy_list_id_t(LUser, Name). sql_get_privacy_list_data(LUser, LServer, Name) -> - odbc_queries:get_privacy_list_data(LServer, LUser, Name). + sql_queries:get_privacy_list_data(LServer, LUser, Name). sql_get_privacy_list_data_t(LUser, Name) -> - Username = ejabberd_odbc:escape(LUser), - SName = ejabberd_odbc:escape(Name), - odbc_queries:get_privacy_list_data_t(Username, SName). + Username = ejabberd_sql:escape(LUser), + SName = ejabberd_sql:escape(Name), + sql_queries:get_privacy_list_data_t(Username, SName). sql_get_privacy_list_data_by_id(ID, LServer) -> - odbc_queries:get_privacy_list_data_by_id(LServer, ID). + sql_queries:get_privacy_list_data_by_id(LServer, ID). sql_get_privacy_list_data_by_id_t(ID) -> - odbc_queries:get_privacy_list_data_by_id_t(ID). + sql_queries:get_privacy_list_data_by_id_t(ID). sql_set_default_privacy_list(LUser, Name) -> - odbc_queries:set_default_privacy_list(LUser, Name). + sql_queries:set_default_privacy_list(LUser, Name). sql_unset_default_privacy_list(LUser, LServer) -> - odbc_queries:unset_default_privacy_list(LServer, LUser). + sql_queries:unset_default_privacy_list(LServer, LUser). sql_remove_privacy_list(LUser, Name) -> - odbc_queries:remove_privacy_list(LUser, Name). + sql_queries:remove_privacy_list(LUser, Name). sql_add_privacy_list(LUser, Name) -> - odbc_queries:add_privacy_list(LUser, Name). + sql_queries:add_privacy_list(LUser, Name). sql_set_privacy_list(ID, RItems) -> - odbc_queries:set_privacy_list(ID, RItems). + sql_queries:set_privacy_list(ID, RItems). sql_del_privacy_lists(LUser, LServer) -> - odbc_queries:del_privacy_lists(LServer, LUser). + sql_queries:del_privacy_lists(LServer, LUser). diff --git a/src/mod_private_sql.erl b/src/mod_private_sql.erl index 1b77c48b..6ec6c9df 100644 --- a/src/mod_private_sql.erl +++ b/src/mod_private_sql.erl @@ -28,14 +28,14 @@ set_data(LUser, LServer, Data) -> lists:foreach( fun({XMLNS, El}) -> SData = fxml:element_to_binary(El), - odbc_queries:set_private_data( + sql_queries:set_private_data( LServer, LUser, XMLNS, SData) end, Data) end, - ejabberd_odbc:sql_transaction(LServer, F). + ejabberd_sql:sql_transaction(LServer, F). get_data(LUser, LServer, XMLNS) -> - case catch odbc_queries:get_private_data(LServer, LUser, XMLNS) of + case catch sql_queries:get_private_data(LServer, LUser, XMLNS) of {selected, [{SData}]} -> case fxml_stream:parse_element(SData) of Data when is_record(Data, xmlel) -> @@ -48,7 +48,7 @@ get_data(LUser, LServer, XMLNS) -> end. get_all_data(LUser, LServer) -> - case catch odbc_queries:get_private_data(LServer, LUser) of + case catch sql_queries:get_private_data(LServer, LUser) of {selected, Res} -> lists:flatmap( fun({_, SData}) -> @@ -64,18 +64,18 @@ get_all_data(LUser, LServer) -> end. remove_user(LUser, LServer) -> - odbc_queries:del_user_private_storage(LServer, LUser). + sql_queries:del_user_private_storage(LServer, LUser). export(_Server) -> [{private_storage, fun(Host, #private_storage{usns = {LUser, LServer, XMLNS}, xml = Data}) when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), - LXMLNS = ejabberd_odbc:escape(XMLNS), + Username = ejabberd_sql:escape(LUser), + LXMLNS = ejabberd_sql:escape(XMLNS), SData = - ejabberd_odbc:escape(fxml:element_to_binary(Data)), - odbc_queries:set_private_data_sql(Username, LXMLNS, + ejabberd_sql:escape(fxml:element_to_binary(Data)), + sql_queries:set_private_data_sql(Username, LXMLNS, SData); (_Host, _R) -> [] diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 247b9f8c..e42d5c05 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -2527,7 +2527,7 @@ get_last_item(Host, Type, Nidx, LJID, mnesia) -> {result, {[LastItem|_], _}} -> LastItem; _ -> undefined end; -get_last_item(Host, Type, Nidx, LJID, odbc) -> +get_last_item(Host, Type, Nidx, LJID, sql) -> case node_action(Host, Type, get_last_items, [Nidx, LJID, 1]) of {result, [LastItem]} -> LastItem; _ -> undefined @@ -2542,7 +2542,7 @@ get_last_items(Host, Type, Nidx, LJID, Number, mnesia) -> {result, {Items, _}} -> lists:sublist(Items, Number); _ -> [] end; -get_last_items(Host, Type, Nidx, LJID, Number, odbc) -> +get_last_items(Host, Type, Nidx, LJID, Number, sql) -> case node_action(Host, Type, get_last_items, [Nidx, LJID, Number]) of {result, Items} -> Items; _ -> [] @@ -3012,7 +3012,7 @@ get_subscriptions_for_send_last(Host, PType, mnesia, JID, LJID, BJID) -> || {Node, Sub, SubId, SubJID} <- Subs, Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID), match_option(Node, send_last_published_item, on_sub_and_presence)]; -get_subscriptions_for_send_last(Host, PType, odbc, JID, LJID, BJID) -> +get_subscriptions_for_send_last(Host, PType, sql, JID, LJID, BJID) -> case catch node_action(Host, PType, get_entity_subscriptions_for_send_last, [Host, JID]) @@ -3688,7 +3688,7 @@ filter_node_options(Options) -> node_owners_action(Host, Type, Nidx, []) -> case gen_mod:db_type(serverhost(Host), ?MODULE) of - odbc -> + sql -> case node_action(Host, Type, get_node_affiliations, [Nidx]) of {result, Affs} -> [LJID || {LJID, Aff} <- Affs, Aff =:= owner]; _ -> [] @@ -3701,7 +3701,7 @@ node_owners_action(_Host, _Type, _Nidx, Owners) -> node_owners_call(Host, Type, Nidx, []) -> case gen_mod:db_type(serverhost(Host), ?MODULE) of - odbc -> + sql -> case node_call(Host, Type, get_node_affiliations, [Nidx]) of {result, Affs} -> [LJID || {LJID, Aff} <- Affs, Aff =:= owner]; _ -> [] @@ -4066,14 +4066,14 @@ tree(_Host, <<"virtual">>) -> tree(Host, Name) -> case gen_mod:db_type(serverhost(Host), ?MODULE) of mnesia -> jlib:binary_to_atom(<<"nodetree_", Name/binary>>); - odbc -> jlib:binary_to_atom(<<"nodetree_", Name/binary, "_odbc">>); + sql -> jlib:binary_to_atom(<<"nodetree_", Name/binary, "_sql">>); _ -> Name end. plugin(Host, Name) -> case gen_mod:db_type(serverhost(Host), ?MODULE) of mnesia -> jlib:binary_to_atom(<<"node_", Name/binary>>); - odbc -> jlib:binary_to_atom(<<"node_", Name/binary, "_odbc">>); + sql -> jlib:binary_to_atom(<<"node_", Name/binary, "_sql">>); _ -> Name end. @@ -4087,7 +4087,7 @@ plugins(Host) -> subscription_plugin(Host) -> case gen_mod:db_type(serverhost(Host), ?MODULE) of mnesia -> pubsub_subscription; - odbc -> pubsub_subscription_odbc; + sql -> pubsub_subscription_sql; _ -> none end. @@ -4184,8 +4184,8 @@ tree_action(Host, Function, Args) -> case gen_mod:db_type(ServerHost, ?MODULE) of mnesia -> catch mnesia:sync_dirty(Fun); - odbc -> - case catch ejabberd_odbc:sql_bloc(ServerHost, Fun) of + sql -> + case catch ejabberd_sql:sql_bloc(ServerHost, Fun) of {atomic, Result} -> Result; {aborted, Reason} -> @@ -4244,7 +4244,7 @@ transaction(Host, Fun, Trans) -> ServerHost = serverhost(Host), DBType = gen_mod:db_type(ServerHost, ?MODULE), Retry = case DBType of - odbc -> 2; + sql -> 2; _ -> 1 end, transaction_retry(Host, ServerHost, Fun, Trans, DBType, Retry). @@ -4255,12 +4255,12 @@ transaction_retry(Host, ServerHost, Fun, Trans, DBType, Count) -> Res = case DBType of mnesia -> catch mnesia:Trans(Fun); - odbc -> + sql -> SqlFun = case Trans of transaction -> sql_transaction; _ -> sql_bloc end, - catch ejabberd_odbc:SqlFun(ServerHost, Fun); + catch ejabberd_sql:SqlFun(ServerHost, Fun); _ -> {unsupported, DBType} end, diff --git a/src/mod_roster_sql.erl b/src/mod_roster_sql.erl index 82662865..616d0ec6 100644 --- a/src/mod_roster_sql.erl +++ b/src/mod_roster_sql.erl @@ -28,28 +28,28 @@ init(_Host, _Opts) -> ok. read_roster_version(LUser, LServer) -> - case odbc_queries:get_roster_version(LServer, LUser) of + case sql_queries:get_roster_version(LServer, LUser) of {selected, [{Version}]} -> Version; {selected, []} -> error end. write_roster_version(LUser, LServer, InTransaction, Ver) -> - Username = ejabberd_odbc:escape(LUser), - EVer = ejabberd_odbc:escape(Ver), + Username = ejabberd_sql:escape(LUser), + EVer = ejabberd_sql:escape(Ver), if InTransaction -> - odbc_queries:set_roster_version(Username, EVer); + sql_queries:set_roster_version(Username, EVer); true -> - odbc_queries:sql_transaction( + sql_queries:sql_transaction( LServer, fun () -> - odbc_queries:set_roster_version(Username, EVer) + sql_queries:set_roster_version(Username, EVer) end) end. get_roster(LUser, LServer) -> - case catch odbc_queries:get_roster(LServer, LUser) of + case catch sql_queries:get_roster(LServer, LUser) of {selected, Items} when is_list(Items) -> - JIDGroups = case catch odbc_queries:get_roster_jid_groups( + JIDGroups = case catch sql_queries:get_roster_jid_groups( LServer, LUser) of {selected, JGrps} when is_list(JGrps) -> JGrps; @@ -80,7 +80,7 @@ get_roster(LUser, LServer) -> get_roster_by_jid(LUser, LServer, LJID) -> {selected, Res} = - odbc_queries:get_roster_by_jid(LServer, LUser, jid:to_string(LJID)), + sql_queries:get_roster_by_jid(LServer, LUser, jid:to_string(LJID)), case Res of [] -> #roster{usj = {LUser, LServer, LJID}, @@ -99,7 +99,7 @@ get_roster_by_jid(LUser, LServer, LJID) -> end. get_only_items(LUser, LServer) -> - case catch odbc_queries:get_roster(LServer, LUser) of + case catch sql_queries:get_roster(LServer, LUser) of {selected, Is} when is_list(Is) -> lists:map(fun(I) -> raw_to_record(LServer, I) end, Is); _ -> [] @@ -107,18 +107,18 @@ get_only_items(LUser, LServer) -> roster_subscribe(_LUser, _LServer, _LJID, Item) -> ItemVals = record_to_row(Item), - odbc_queries:roster_subscribe(ItemVals). + sql_queries:roster_subscribe(ItemVals). transaction(LServer, F) -> - ejabberd_odbc:sql_transaction(LServer, F). + ejabberd_sql:sql_transaction(LServer, F). get_roster_by_jid_with_groups(LUser, LServer, LJID) -> SJID = jid:to_string(LJID), - case odbc_queries:get_roster_by_jid(LServer, LUser, SJID) of + case sql_queries:get_roster_by_jid(LServer, LUser, SJID) of {selected, [I]} -> R = raw_to_record(LServer, I), Groups = - case odbc_queries:get_roster_groups(LServer, LUser, SJID) of + case sql_queries:get_roster_groups(LServer, LUser, SJID) of {selected, JGrps} when is_list(JGrps) -> [JGrp || {JGrp} <- JGrps]; _ -> [] @@ -130,23 +130,23 @@ get_roster_by_jid_with_groups(LUser, LServer, LJID) -> end. remove_user(LUser, LServer) -> - odbc_queries:del_user_roster_t(LServer, LUser), + sql_queries:del_user_roster_t(LServer, LUser), {atomic, ok}. update_roster(LUser, LServer, LJID, Item) -> SJID = jid:to_string(LJID), ItemVals = record_to_row(Item), ItemGroups = Item#roster.groups, - odbc_queries:update_roster(LServer, LUser, SJID, ItemVals, + sql_queries:update_roster(LServer, LUser, SJID, ItemVals, ItemGroups). del_roster(LUser, LServer, LJID) -> SJID = jid:to_string(LJID), - odbc_queries:del_roster(LServer, LUser, SJID). + sql_queries:del_roster(LServer, LUser, SJID). read_subscription_and_groups(LUser, LServer, LJID) -> SJID = jid:to_string(LJID), - case catch odbc_queries:get_subscription(LServer, LUser, SJID) of + case catch sql_queries:get_subscription(LServer, LUser, SJID) of {selected, [{SSubscription}]} -> Subscription = case SSubscription of <<"B">> -> both; @@ -154,7 +154,7 @@ read_subscription_and_groups(LUser, LServer, LJID) -> <<"F">> -> from; _ -> none end, - Groups = case catch odbc_queries:get_rostergroup_by_jid( + Groups = case catch sql_queries:get_rostergroup_by_jid( LServer, LUser, SJID) of {selected, JGrps} when is_list(JGrps) -> [JGrp || {JGrp} <- JGrps]; @@ -169,11 +169,11 @@ export(_Server) -> [{roster, fun(Host, #roster{usj = {LUser, LServer, LJID}} = R) when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), - SJID = ejabberd_odbc:escape(jid:to_string(LJID)), + Username = ejabberd_sql:escape(LUser), + SJID = ejabberd_sql:escape(jid:to_string(LJID)), ItemVals = record_to_string(R), ItemGroups = groups_to_string(R), - odbc_queries:update_roster_sql(Username, SJID, + sql_queries:update_roster_sql(Username, SJID, ItemVals, ItemGroups); (_Host, _R) -> [] @@ -181,8 +181,8 @@ export(_Server) -> {roster_version, fun(Host, #roster_version{us = {LUser, LServer}, version = Ver}) when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), - SVer = ejabberd_odbc:escape(Ver), + Username = ejabberd_sql:escape(LUser), + SVer = ejabberd_sql:escape(Ver), [[<<"delete from roster_version where username='">>, Username, <<"';">>], [<<"insert into roster_version(username, version) values('">>, @@ -196,10 +196,10 @@ import(LServer) -> "ask, askmessage, server, subscribe, type from rosterusers;">>, fun([LUser, JID|_] = Row) -> Item = raw_to_record(LServer, Row), - Username = ejabberd_odbc:escape(LUser), - SJID = ejabberd_odbc:escape(JID), + Username = ejabberd_sql:escape(LUser), + SJID = ejabberd_sql:escape(JID), {selected, _, Rows} = - ejabberd_odbc:sql_query_t( + ejabberd_sql:sql_query_t( [<<"select grp from rostergroups where username='">>, Username, <<"' and jid='">>, SJID, <<"'">>]), Groups = [Grp || [Grp] <- Rows], @@ -252,10 +252,10 @@ raw_to_record(LServer, record_to_string(#roster{us = {User, _Server}, jid = JID, name = Name, subscription = Subscription, ask = Ask, askmessage = AskMessage}) -> - Username = ejabberd_odbc:escape(User), + Username = ejabberd_sql:escape(User), SJID = - ejabberd_odbc:escape(jid:to_string(jid:tolower(JID))), - Nick = ejabberd_odbc:escape(Name), + ejabberd_sql:escape(jid:to_string(jid:tolower(JID))), + Nick = ejabberd_sql:escape(Name), SSubscription = case Subscription of both -> <<"B">>; to -> <<"T">>; @@ -270,7 +270,7 @@ record_to_string(#roster{us = {User, _Server}, in -> <<"I">>; none -> <<"N">> end, - SAskMessage = ejabberd_odbc:escape(AskMessage), + SAskMessage = ejabberd_sql:escape(AskMessage), [Username, SJID, Nick, SSubscription, SAsk, SAskMessage, <<"N">>, <<"">>, <<"item">>]. @@ -297,12 +297,12 @@ record_to_row( groups_to_string(#roster{us = {User, _Server}, jid = JID, groups = Groups}) -> - Username = ejabberd_odbc:escape(User), + Username = ejabberd_sql:escape(User), SJID = - ejabberd_odbc:escape(jid:to_string(jid:tolower(JID))), + ejabberd_sql:escape(jid:to_string(jid:tolower(JID))), lists:foldl(fun (<<"">>, Acc) -> Acc; (Group, Acc) -> - G = ejabberd_odbc:escape(Group), + G = ejabberd_sql:escape(Group), [[Username, SJID, G] | Acc] end, [], Groups). diff --git a/src/mod_shared_roster_sql.erl b/src/mod_shared_roster_sql.erl index 21ea768a..bd123cd4 100644 --- a/src/mod_shared_roster_sql.erl +++ b/src/mod_shared_roster_sql.erl @@ -29,69 +29,69 @@ init(_Host, _Opts) -> ok. list_groups(Host) -> - case ejabberd_odbc:sql_query( + case ejabberd_sql:sql_query( Host, [<<"select name from sr_group;">>]) of {selected, [<<"name">>], Rs} -> [G || [G] <- Rs]; _ -> [] end. groups_with_opts(Host) -> - case ejabberd_odbc:sql_query(Host, + case ejabberd_sql:sql_query(Host, [<<"select name, opts from sr_group;">>]) of {selected, [<<"name">>, <<"opts">>], Rs} -> - [{G, mod_shared_roster:opts_to_binary(ejabberd_odbc:decode_term(Opts))} + [{G, mod_shared_roster:opts_to_binary(ejabberd_sql:decode_term(Opts))} || [G, Opts] <- Rs]; _ -> [] end. create_group(Host, Group, Opts) -> - SGroup = ejabberd_odbc:escape(Group), - SOpts = ejabberd_odbc:encode_term(Opts), + SGroup = ejabberd_sql:escape(Group), + SOpts = ejabberd_sql:encode_term(Opts), F = fun () -> - odbc_queries:update_t(<<"sr_group">>, + sql_queries:update_t(<<"sr_group">>, [<<"name">>, <<"opts">>], [SGroup, SOpts], [<<"name='">>, SGroup, <<"'">>]) end, - ejabberd_odbc:sql_transaction(Host, F). + ejabberd_sql:sql_transaction(Host, F). delete_group(Host, Group) -> - SGroup = ejabberd_odbc:escape(Group), + SGroup = ejabberd_sql:escape(Group), F = fun () -> - ejabberd_odbc:sql_query_t([<<"delete from sr_group where name='">>, + ejabberd_sql:sql_query_t([<<"delete from sr_group where name='">>, SGroup, <<"';">>]), - ejabberd_odbc:sql_query_t([<<"delete from sr_user where grp='">>, + ejabberd_sql:sql_query_t([<<"delete from sr_user where grp='">>, SGroup, <<"';">>]) end, - case ejabberd_odbc:sql_transaction(Host, F) of + case ejabberd_sql:sql_transaction(Host, F) of {atomic,{updated,_}} -> {atomic, ok}; Res -> Res end. get_group_opts(Host, Group) -> - SGroup = ejabberd_odbc:escape(Group), - case catch ejabberd_odbc:sql_query( + SGroup = ejabberd_sql:escape(Group), + case catch ejabberd_sql:sql_query( Host, [<<"select opts from sr_group where name='">>, SGroup, <<"';">>]) of {selected, [<<"opts">>], [[SOpts]]} -> - mod_shared_roster:opts_to_binary(ejabberd_odbc:decode_term(SOpts)); + mod_shared_roster:opts_to_binary(ejabberd_sql:decode_term(SOpts)); _ -> error end. set_group_opts(Host, Group, Opts) -> - SGroup = ejabberd_odbc:escape(Group), - SOpts = ejabberd_odbc:encode_term(Opts), + SGroup = ejabberd_sql:escape(Group), + SOpts = ejabberd_sql:encode_term(Opts), F = fun () -> - odbc_queries:update_t(<<"sr_group">>, + sql_queries:update_t(<<"sr_group">>, [<<"name">>, <<"opts">>], [SGroup, SOpts], [<<"name='">>, SGroup, <<"'">>]) end, - ejabberd_odbc:sql_transaction(Host, F). + ejabberd_sql:sql_transaction(Host, F). get_user_groups(US, Host) -> SJID = make_jid_s(US), - case catch ejabberd_odbc:sql_query( + case catch ejabberd_sql:sql_query( Host, [<<"select grp from sr_user where jid='">>, SJID, <<"';">>]) of @@ -100,8 +100,8 @@ get_user_groups(US, Host) -> end. get_group_explicit_users(Host, Group) -> - SGroup = ejabberd_odbc:escape(Group), - case catch ejabberd_odbc:sql_query( + SGroup = ejabberd_sql:escape(Group), + case catch ejabberd_sql:sql_query( Host, [<<"select jid from sr_user where grp='">>, SGroup, <<"';">>]) of @@ -117,7 +117,7 @@ get_group_explicit_users(Host, Group) -> get_user_displayed_groups(LUser, LServer, GroupsOpts) -> SJID = make_jid_s(LUser, LServer), - case catch ejabberd_odbc:sql_query( + case catch ejabberd_sql:sql_query( LServer, [<<"select grp from sr_user where jid='">>, SJID, <<"';">>]) of @@ -129,8 +129,8 @@ get_user_displayed_groups(LUser, LServer, GroupsOpts) -> is_user_in_group(US, Group, Host) -> SJID = make_jid_s(US), - SGroup = ejabberd_odbc:escape(Group), - case catch ejabberd_odbc:sql_query(Host, + SGroup = ejabberd_sql:escape(Group), + case catch ejabberd_sql:sql_query(Host, [<<"select * from sr_user where jid='">>, SJID, <<"' and grp='">>, SGroup, <<"';">>]) of @@ -140,32 +140,32 @@ is_user_in_group(US, Group, Host) -> add_user_to_group(Host, US, Group) -> SJID = make_jid_s(US), - SGroup = ejabberd_odbc:escape(Group), + SGroup = ejabberd_sql:escape(Group), F = fun () -> - odbc_queries:update_t(<<"sr_user">>, + sql_queries:update_t(<<"sr_user">>, [<<"jid">>, <<"grp">>], [SJID, SGroup], [<<"jid='">>, SJID, <<"' and grp='">>, SGroup, <<"'">>]) end, - ejabberd_odbc:sql_transaction(Host, F). + ejabberd_sql:sql_transaction(Host, F). remove_user_from_group(Host, US, Group) -> SJID = make_jid_s(US), - SGroup = ejabberd_odbc:escape(Group), + SGroup = ejabberd_sql:escape(Group), F = fun () -> - ejabberd_odbc:sql_query_t([<<"delete from sr_user where jid='">>, + ejabberd_sql:sql_query_t([<<"delete from sr_user where jid='">>, SJID, <<"' and grp='">>, SGroup, <<"';">>]), ok end, - ejabberd_odbc:sql_transaction(Host, F). + ejabberd_sql:sql_transaction(Host, F). export(_Server) -> [{sr_group, fun(Host, #sr_group{group_host = {Group, LServer}, opts = Opts}) when LServer == Host -> - SGroup = ejabberd_odbc:escape(Group), - SOpts = ejabberd_odbc:encode_term(Opts), + SGroup = ejabberd_sql:escape(Group), + SOpts = ejabberd_sql:encode_term(Opts), [[<<"delete from sr_group where name='">>, Group, <<"';">>], [<<"insert into sr_group(name, opts) values ('">>, SGroup, <<"', '">>, SOpts, <<"');">>]]; @@ -175,8 +175,8 @@ export(_Server) -> {sr_user, fun(Host, #sr_user{us = {U, S}, group_host = {Group, LServer}}) when LServer == Host -> - SGroup = ejabberd_odbc:escape(Group), - SJID = ejabberd_odbc:escape( + SGroup = ejabberd_sql:escape(Group), + SJID = ejabberd_sql:escape( jid:to_string( jid:tolower( jid:make(U, S, <<"">>)))), @@ -192,7 +192,7 @@ import(LServer) -> [{<<"select name, opts from sr_group;">>, fun([Group, SOpts]) -> #sr_group{group_host = {Group, LServer}, - opts = ejabberd_odbc:decode_term(SOpts)} + opts = ejabberd_sql:decode_term(SOpts)} end}, {<<"select jid, grp from sr_user;">>, fun([SJID, Group]) -> @@ -207,6 +207,6 @@ import(_, _) -> %%% Internal functions %%%=================================================================== make_jid_s(U, S) -> - ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:make(U, S, <<"">>)))). + ejabberd_sql:escape(jid:to_string(jid:tolower(jid:make(U, S, <<"">>)))). make_jid_s({U, S}) -> make_jid_s(U, S). diff --git a/src/mod_vcard_sql.erl b/src/mod_vcard_sql.erl index fff39091..9fcd5cd5 100644 --- a/src/mod_vcard_sql.erl +++ b/src/mod_vcard_sql.erl @@ -28,7 +28,7 @@ init(_Host, _Opts) -> ok. get_vcard(LUser, LServer) -> - case catch odbc_queries:get_vcard(LServer, LUser) of + case catch sql_queries:get_vcard(LServer, LUser) of {selected, [{SVCARD}]} -> case fxml_stream:parse_element(SVCARD) of {error, _Reason} -> error; @@ -63,7 +63,7 @@ set_vcard(LUser, LServer, VCARD, orgunit = OrgUnit, lorgunit = LOrgUnit}) -> SVCARD = fxml:element_to_binary(VCARD), - odbc_queries:set_vcard(LServer, LUser, BDay, CTRY, + sql_queries:set_vcard(LServer, LUser, BDay, CTRY, EMail, FN, Family, Given, LBDay, LCTRY, LEMail, LFN, LFamily, LGiven, LLocality, LMiddle, @@ -81,7 +81,7 @@ search(LServer, Data, AllowReturnAll, MaxMatch) -> Val -> [<<" LIMIT ">>, jlib:integer_to_binary(Val)] end, - case catch ejabberd_odbc:sql_query( + case catch ejabberd_sql:sql_query( LServer, [<<"select username, fn, family, given, " "middle, nickname, bday, ctry, " @@ -100,12 +100,12 @@ search(LServer, Data, AllowReturnAll, MaxMatch) -> end. remove_user(LUser, LServer) -> - ejabberd_odbc:sql_transaction( + ejabberd_sql:sql_transaction( LServer, fun() -> - ejabberd_odbc:sql_query_t( + ejabberd_sql:sql_query_t( ?SQL("delete from vcard where username=%(LUser)s")), - ejabberd_odbc:sql_query_t( + ejabberd_sql:sql_query_t( ?SQL("delete from vcard_search where lusername=%(LUser)s")) end). @@ -113,9 +113,9 @@ export(_Server) -> [{vcard, fun(Host, #vcard{us = {LUser, LServer}, vcard = VCARD}) when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), + Username = ejabberd_sql:escape(LUser), SVCARD = - ejabberd_odbc:escape(fxml:element_to_binary(VCARD)), + ejabberd_sql:escape(fxml:element_to_binary(VCARD)), [[<<"delete from vcard where username='">>, Username, <<"';">>], [<<"insert into vcard(username, vcard) values ('">>, Username, <<"', '">>, SVCARD, <<"');">>]]; @@ -135,30 +135,30 @@ export(_Server) -> orgname = OrgName, lorgname = LOrgName, orgunit = OrgUnit, lorgunit = LOrgUnit}) when LServer == Host -> - Username = ejabberd_odbc:escape(User), - LUsername = ejabberd_odbc:escape(LUser), - SFN = ejabberd_odbc:escape(FN), - SLFN = ejabberd_odbc:escape(LFN), - SFamily = ejabberd_odbc:escape(Family), - SLFamily = ejabberd_odbc:escape(LFamily), - SGiven = ejabberd_odbc:escape(Given), - SLGiven = ejabberd_odbc:escape(LGiven), - SMiddle = ejabberd_odbc:escape(Middle), - SLMiddle = ejabberd_odbc:escape(LMiddle), - SNickname = ejabberd_odbc:escape(Nickname), - SLNickname = ejabberd_odbc:escape(LNickname), - SBDay = ejabberd_odbc:escape(BDay), - SLBDay = ejabberd_odbc:escape(LBDay), - SCTRY = ejabberd_odbc:escape(CTRY), - SLCTRY = ejabberd_odbc:escape(LCTRY), - SLocality = ejabberd_odbc:escape(Locality), - SLLocality = ejabberd_odbc:escape(LLocality), - SEMail = ejabberd_odbc:escape(EMail), - SLEMail = ejabberd_odbc:escape(LEMail), - SOrgName = ejabberd_odbc:escape(OrgName), - SLOrgName = ejabberd_odbc:escape(LOrgName), - SOrgUnit = ejabberd_odbc:escape(OrgUnit), - SLOrgUnit = ejabberd_odbc:escape(LOrgUnit), + Username = ejabberd_sql:escape(User), + LUsername = ejabberd_sql:escape(LUser), + SFN = ejabberd_sql:escape(FN), + SLFN = ejabberd_sql:escape(LFN), + SFamily = ejabberd_sql:escape(Family), + SLFamily = ejabberd_sql:escape(LFamily), + SGiven = ejabberd_sql:escape(Given), + SLGiven = ejabberd_sql:escape(LGiven), + SMiddle = ejabberd_sql:escape(Middle), + SLMiddle = ejabberd_sql:escape(LMiddle), + SNickname = ejabberd_sql:escape(Nickname), + SLNickname = ejabberd_sql:escape(LNickname), + SBDay = ejabberd_sql:escape(BDay), + SLBDay = ejabberd_sql:escape(LBDay), + SCTRY = ejabberd_sql:escape(CTRY), + SLCTRY = ejabberd_sql:escape(LCTRY), + SLocality = ejabberd_sql:escape(Locality), + SLLocality = ejabberd_sql:escape(LLocality), + SEMail = ejabberd_sql:escape(EMail), + SLEMail = ejabberd_sql:escape(LEMail), + SOrgName = ejabberd_sql:escape(OrgName), + SLOrgName = ejabberd_sql:escape(LOrgName), + SOrgUnit = ejabberd_sql:escape(OrgUnit), + SLOrgUnit = ejabberd_sql:escape(LOrgUnit), [[<<"delete from vcard_search where lusername='">>, LUsername, <<"';">>], [<<"insert into vcard_search( username, " @@ -255,11 +255,11 @@ make_val(Match, Field, Val) -> Condition = case str:suffix(<<"*">>, Val) of true -> Val1 = str:substr(Val, 1, byte_size(Val) - 1), - SVal = <<(ejabberd_odbc:escape_like(Val1))/binary, + SVal = <<(ejabberd_sql:escape_like(Val1))/binary, "%">>, [Field, <<" LIKE '">>, SVal, <<"'">>]; _ -> - SVal = ejabberd_odbc:escape(Val), + SVal = ejabberd_sql:escape(Val), [Field, <<" = '">>, SVal, <<"'">>] end, case Match of diff --git a/src/mod_vcard_xupdate_sql.erl b/src/mod_vcard_xupdate_sql.erl index d580b151..7f0079dd 100644 --- a/src/mod_vcard_xupdate_sql.erl +++ b/src/mod_vcard_xupdate_sql.erl @@ -21,19 +21,19 @@ init(_Host, _Opts) -> ok. add_xupdate(LUser, LServer, Hash) -> - Username = ejabberd_odbc:escape(LUser), - SHash = ejabberd_odbc:escape(Hash), + Username = ejabberd_sql:escape(LUser), + SHash = ejabberd_sql:escape(Hash), F = fun () -> - odbc_queries:update_t(<<"vcard_xupdate">>, + sql_queries:update_t(<<"vcard_xupdate">>, [<<"username">>, <<"hash">>], [Username, SHash], [<<"username='">>, Username, <<"'">>]) end, - ejabberd_odbc:sql_transaction(LServer, F). + ejabberd_sql:sql_transaction(LServer, F). get_xupdate(LUser, LServer) -> - Username = ejabberd_odbc:escape(LUser), - case ejabberd_odbc:sql_query(LServer, + Username = ejabberd_sql:escape(LUser), + case ejabberd_sql:sql_query(LServer, [<<"select hash from vcard_xupdate where " "username='">>, Username, <<"';">>]) @@ -43,19 +43,19 @@ get_xupdate(LUser, LServer) -> end. remove_xupdate(LUser, LServer) -> - Username = ejabberd_odbc:escape(LUser), + Username = ejabberd_sql:escape(LUser), F = fun () -> - ejabberd_odbc:sql_query_t([<<"delete from vcard_xupdate where username='">>, + ejabberd_sql:sql_query_t([<<"delete from vcard_xupdate where username='">>, Username, <<"';">>]) end, - ejabberd_odbc:sql_transaction(LServer, F). + ejabberd_sql:sql_transaction(LServer, F). export(_Server) -> [{vcard_xupdate, fun(Host, #vcard_xupdate{us = {LUser, LServer}, hash = Hash}) when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), - SHash = ejabberd_odbc:escape(Hash), + Username = ejabberd_sql:escape(LUser), + SHash = ejabberd_sql:escape(Hash), [[<<"delete from vcard_xupdate where username='">>, Username, <<"';">>], [<<"insert into vcard_xupdate(username, " diff --git a/src/node_flat_odbc.erl b/src/node_flat_odbc.erl deleted file mode 100644 index 5dd52066..00000000 --- a/src/node_flat_odbc.erl +++ /dev/null @@ -1,1071 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : node_flat_odbc.erl -%%% Author : Christophe Romain -%%% Purpose : Standard PubSub node plugin with ODBC backend -%%% Created : 1 Dec 2007 by Christophe Romain -%%% -%%% -%%% 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 {@module} is the default PubSub plugin. -%%%

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 -%%% types.

-%%%

PubSub plugin nodes are using the {@link gen_node} behaviour.

- --module(node_flat_odbc). --behaviour(gen_pubsub_node). --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). --include("jlib.hrl"). - --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1, - get_entity_subscriptions_for_send_last/2, get_last_items/3]). - --export([decode_jid/1, encode_jid/1, - decode_affiliation/1, decode_subscriptions/1, - encode_affiliation/1, encode_subscriptions/1, - encode_host/1]). - -init(_Host, _ServerHost, _Opts) -> - %%pubsub_subscription_odbc:init(), - ok. - -terminate(_Host, _ServerHost) -> - ok. - -options() -> - [{odbc, true}, {rsm, true} | node_flat:options()]. - -features() -> - [<<"rsm">> | node_flat:features()]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). - -create_node(Nidx, Owner) -> - {_U, _S, _R} = OwnerKey = jid:tolower(jid:remove_resource(Owner)), - State = #pubsub_state{stateid = {OwnerKey, Nidx}, affiliation = owner}, - catch ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) " - "values(">>, state_to_raw(Nidx, State), <<");">>]), - {result, {default, broadcast}}. - -delete_node(Nodes) -> - Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) -> - Subscriptions = case catch - ejabberd_odbc:sql_query_t([<<"select jid, subscriptions " - "from pubsub_state where nodeid='">>, Nidx, <<"';">>]) - of - {selected, [<<"jid">>, <<"subscriptions">>], RItems} -> - [{decode_jid(SJID), decode_subscriptions(Subs)} || [SJID, Subs] <- RItems]; - _ -> - [] - end, - {PubsubNode, Subscriptions} - end, Nodes), - {result, {default, broadcast, Reply}}. - -subscribe_node(Nidx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, _Options) -> - SubKey = jid:tolower(Subscriber), - GenKey = jid:remove_resource(SubKey), - Authorized = jid:tolower(jid:remove_resource(Sender)) == GenKey, - {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey), - Whitelisted = lists:member(Affiliation, [member, publisher, owner]), - PendingSubscription = lists:any(fun - ({pending, _}) -> true; - (_) -> false - end, - Subscriptions), - Owner = Affiliation == owner, - if not Authorized -> - {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)}; - (Affiliation == outcast) or (Affiliation == publish_only) -> - {error, ?ERR_FORBIDDEN}; - PendingSubscription -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)}; - (AccessModel == presence) and (not PresenceSubscription) and (not Owner) -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)}; - (AccessModel == roster) and (not RosterGroup) and (not Owner) -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; - (AccessModel == whitelist) and (not Whitelisted) and (not Owner) -> - {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; - %%MustPay -> - %% % Payment is required for a subscription - %% {error, ?ERR_PAYMENT_REQUIRED}; - %%ForbiddenAnonymous -> - %% % Requesting entity is anonymous - %% {error, ?ERR_FORBIDDEN}; - true -> - %%{result, SubId} = pubsub_subscription_odbc:subscribe_node(Subscriber, Nidx, Options), - {NewSub, SubId} = case Subscriptions of - [{subscribed, Id}|_] -> - {subscribed, Id}; - [] -> - Id = pubsub_subscription_odbc:make_subid(), - Sub = case AccessModel of - authorize -> pending; - _ -> subscribed - end, - update_subscription(Nidx, SubKey, [{Sub, Id} | Subscriptions]), - {Sub, Id} - end, - case {NewSub, SendLast} of - {subscribed, never} -> - {result, {default, subscribed, SubId}}; - {subscribed, _} -> - {result, {default, subscribed, SubId, send_last}}; - {_, _} -> - {result, {default, pending, SubId}} - end - end. - -unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> - SubKey = jid:tolower(Subscriber), - GenKey = jid:remove_resource(SubKey), - Authorized = jid:tolower(jid:remove_resource(Sender)) == GenKey, - {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, SubKey), - SubIdExists = case SubId of - <<>> -> false; - Binary when is_binary(Binary) -> true; - _ -> false - end, - if - %% Requesting entity is prohibited from unsubscribing entity - not Authorized -> - {error, ?ERR_FORBIDDEN}; - %% Entity did not specify SubId - %%SubId == "", ?? -> - %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - %% Invalid subscription identifier - %%InvalidSubId -> - %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; - %% Requesting entity is not a subscriber - Subscriptions == [] -> - {error, - ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}; - %% Subid supplied, so use that. - SubIdExists -> - Sub = first_in_list(fun - ({_, S}) when S == SubId -> true; - (_) -> false - end, - Subscriptions), - case Sub of - {value, S} -> - delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions), - {result, default}; - false -> - {error, - ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)} - end; - %% Asking to remove all subscriptions to the given node - SubId == all -> - [delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions) - || S <- Subscriptions], - {result, default}; - %% No subid supplied, but there's only one matching subscription - length(Subscriptions) == 1 -> - delete_subscription(SubKey, Nidx, hd(Subscriptions), Affiliation, Subscriptions), - {result, default}; - %% No subid and more than one possible subscription match. - true -> - {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)} - end. - -delete_subscription(SubKey, Nidx, {Subscription, SubId}, Affiliation, Subscriptions) -> - NewSubs = Subscriptions -- [{Subscription, SubId}], - %%pubsub_subscription_odbc:unsubscribe_node(SubKey, Nidx, SubId), - case {Affiliation, NewSubs} of - {none, []} -> del_state(Nidx, SubKey); - _ -> update_subscription(Nidx, SubKey, NewSubs) - end. - -publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) -> - SubKey = jid:tolower(Publisher), - GenKey = jid:remove_resource(SubKey), - {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey), - Subscribed = case PublishModel of - subscribers -> node_flat:is_subscribed(Subscriptions); - _ -> undefined - end, - if not ((PublishModel == open) or - (PublishModel == publishers) and - ((Affiliation == owner) - or (Affiliation == publisher) - or (Affiliation == publish_only)) - or (Subscribed == true)) -> - {error, ?ERR_FORBIDDEN}; - true -> - if MaxItems > 0 -> - PubId = {p1_time_compat:timestamp(), SubKey}, - set_item(#pubsub_item{itemid = {ItemId, Nidx}, - creation = {p1_time_compat:timestamp(), GenKey}, - modification = PubId, - payload = Payload}), - Items = [ItemId | itemids(Nidx, GenKey) -- [ItemId]], - {result, {_, OI}} = remove_extra_items(Nidx, MaxItems, Items), - {result, {default, broadcast, OI}}; - true -> - {result, {default, broadcast, []}} - end - end. - -remove_extra_items(_Nidx, unlimited, ItemIds) -> - {result, {ItemIds, []}}; -remove_extra_items(Nidx, MaxItems, ItemIds) -> - NewItems = lists:sublist(ItemIds, MaxItems), - OldItems = lists:nthtail(length(NewItems), ItemIds), - del_items(Nidx, OldItems), - {result, {NewItems, OldItems}}. - -delete_item(Nidx, Publisher, PublishModel, ItemId) -> - SubKey = jid:tolower(Publisher), - GenKey = jid:remove_resource(SubKey), - {result, Affiliation} = get_affiliation(Nidx, GenKey), - Allowed = Affiliation == publisher orelse - Affiliation == owner orelse - PublishModel == open orelse - case get_item(Nidx, ItemId) of - {result, #pubsub_item{creation = {_, GenKey}}} -> true; - _ -> false - end, - if not Allowed -> - {error, ?ERR_FORBIDDEN}; - true -> - case del_item(Nidx, ItemId) of - {updated, 1} -> {result, {default, broadcast}}; - _ -> {error, ?ERR_ITEM_NOT_FOUND} - end - end. - -purge_node(Nidx, Owner) -> - SubKey = jid:tolower(Owner), - GenKey = jid:remove_resource(SubKey), - GenState = get_state(Nidx, GenKey), - case GenState of - #pubsub_state{affiliation = owner} -> - {result, States} = get_states(Nidx), - lists:foreach(fun - (#pubsub_state{items = []}) -> ok; - (#pubsub_state{items = Items}) -> del_items(Nidx, Items) - end, - States), - {result, {default, broadcast}}; - _ -> - {error, ?ERR_FORBIDDEN} - end. - -get_entity_affiliations(Host, Owner) -> - SubKey = jid:tolower(Owner), - GenKey = jid:remove_resource(SubKey), - H = encode_host(Host), - J = encode_jid(GenKey), - Reply = case catch - ejabberd_odbc:sql_query_t([<<"select node, type, i.nodeid, affiliation " - "from pubsub_state i, pubsub_node n where " - "i.nodeid = n.nodeid and jid='">>, J, <<"' and host='">>, H, <<"';">>]) - of - {selected, [<<"node">>, <<"type">>, <<"nodeid">>, <<"affiliation">>], RItems} -> - [{nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]), decode_affiliation(A)} - || [N, T, I, A] <- RItems]; - _ -> - [] - end, - {result, Reply}. - -get_node_affiliations(Nidx) -> - Reply = case catch - ejabberd_odbc:sql_query_t([<<"select jid, affiliation from pubsub_state " - "where nodeid='">>, Nidx, <<"';">>]) - of - {selected, [<<"jid">>, <<"affiliation">>], RItems} -> - [{decode_jid(J), decode_affiliation(A)} || [J, A] <- RItems]; - _ -> - [] - end, - {result, Reply}. - -get_affiliation(Nidx, Owner) -> - SubKey = jid:tolower(Owner), - GenKey = jid:remove_resource(SubKey), - J = encode_jid(GenKey), - Reply = case catch - ejabberd_odbc:sql_query_t([<<"select affiliation from pubsub_state " - "where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>]) - of - {selected, [<<"affiliation">>], [[A]]} -> - decode_affiliation(A); - _ -> - none - end, - {result, Reply}. - -set_affiliation(Nidx, Owner, Affiliation) -> - SubKey = jid:tolower(Owner), - GenKey = jid:remove_resource(SubKey), - {_, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey), - case {Affiliation, Subscriptions} of - {none, []} -> del_state(Nidx, GenKey); - _ -> update_affiliation(Nidx, GenKey, Affiliation) - end. - -get_entity_subscriptions(Host, Owner) -> - SubKey = jid:tolower(Owner), - GenKey = jid:remove_resource(SubKey), - H = encode_host(Host), - SJ = encode_jid(SubKey), - GJ = encode_jid(GenKey), - Query = case SubKey of - GenKey -> - [<<"select node, type, i.nodeid, jid, subscriptions " - "from pubsub_state i, pubsub_node n " - "where i.nodeid = n.nodeid and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>]; - _ -> - [<<"select node, type, i.nodeid, jid, subscriptions " - "from pubsub_state i, pubsub_node n " - "where i.nodeid = n.nodeid and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>] - end, - Reply = case catch ejabberd_odbc:sql_query_t(Query) of - {selected, - [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} -> - lists:foldl(fun ([N, T, I, J, S], Acc) -> - Node = nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]), - Jid = decode_jid(J), - case decode_subscriptions(S) of - [] -> - [{Node, none, Jid} | Acc]; - Subs -> - lists:foldl(fun ({Sub, SubId}, Acc2) -> - [{Node, Sub, SubId, Jid} | Acc2] - end, - Acc, Subs) - end - end, - [], RItems); - _ -> - [] - end, - {result, Reply}. - --spec(get_entity_subscriptions_for_send_last/2 :: - ( - Host :: mod_pubsub:hostPubsub(), - Owner :: jid()) - -> {result, - [{mod_pubsub:pubsubNode(), - mod_pubsub:subscription(), - mod_pubsub:subId(), - ljid()}] - } - ). -get_entity_subscriptions_for_send_last(Host, Owner) -> - SubKey = jid:tolower(Owner), - GenKey = jid:remove_resource(SubKey), - H = encode_host(Host), - SJ = encode_jid(SubKey), - GJ = encode_jid(GenKey), - Query = case SubKey of - GenKey -> - [<<"select node, type, i.nodeid, jid, subscriptions " - "from pubsub_state i, pubsub_node n, pubsub_node_option o " - "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' " - "and val='on_sub_and_presence' and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>]; - _ -> - [<<"select node, type, i.nodeid, jid, subscriptions " - "from pubsub_state i, pubsub_node n, pubsub_node_option o " - "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' " - "and val='on_sub_and_presence' and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>] - end, - Reply = case catch ejabberd_odbc:sql_query_t(Query) of - {selected, - [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} -> - lists:foldl(fun ([N, T, I, J, S], Acc) -> - Node = nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]), - Jid = decode_jid(J), - case decode_subscriptions(S) of - [] -> - [{Node, none, Jid} | Acc]; - Subs -> - lists:foldl(fun ({Sub, SubId}, Acc2) -> - [{Node, Sub, SubId, Jid}| Acc2] - end, - Acc, Subs) - end - end, - [], RItems); - _ -> - [] - end, - {result, Reply}. - -get_node_subscriptions(Nidx) -> - Reply = case catch - ejabberd_odbc:sql_query_t([<<"select jid, subscriptions from pubsub_state " - "where nodeid='">>, Nidx, <<"';">>]) - of - {selected, [<<"jid">>, <<"subscriptions">>], RItems} -> - lists:foldl(fun ([J, S], Acc) -> - Jid = decode_jid(J), - case decode_subscriptions(S) of - [] -> - [{Jid, none} | Acc]; - Subs -> - lists:foldl(fun ({Sub, SubId}, Acc2) -> - [{Jid, Sub, SubId} | Acc2] - end, - Acc, Subs) - end - end, - [], RItems); - _ -> - [] - end, - {result, Reply}. - -get_subscriptions(Nidx, Owner) -> - SubKey = jid:tolower(Owner), - J = encode_jid(SubKey), - Reply = case catch - ejabberd_odbc:sql_query_t([<<"select subscriptions from pubsub_state where " - "nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>]) - of - {selected, [<<"subscriptions">>], [[S]]} -> - decode_subscriptions(S); - _ -> - [] - end, - {result, Reply}. - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - SubKey = jid:tolower(Owner), - SubState = get_state_without_itemids(Nidx, SubKey), - case {SubId, SubState#pubsub_state.subscriptions} of - {_, []} -> - case Subscription of - none -> - {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)}; - _ -> - new_subscription(Nidx, Owner, Subscription, SubState) - end; - {<<>>, [{_, SID}]} -> - case Subscription of - none -> unsub_with_subid(Nidx, SID, SubState); - _ -> replace_subscription({Subscription, SID}, SubState) - end; - {<<>>, [_ | _]} -> - {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}; - _ -> - case Subscription of - none -> unsub_with_subid(Nidx, SubId, SubState); - _ -> replace_subscription({Subscription, SubId}, SubState) - end - end. - -replace_subscription(NewSub, SubState) -> - NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []), - set_state(SubState#pubsub_state{subscriptions = NewSubs}). - -replace_subscription(_, [], Acc) -> Acc; -replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) -> - replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]). - -new_subscription(_Nidx, _Owner, Subscription, SubState) -> - %%{result, SubId} = pubsub_subscription_odbc:subscribe_node(Owner, Nidx, []), - SubId = pubsub_subscription_odbc:make_subid(), - Subscriptions = [{Subscription, SubId} | SubState#pubsub_state.subscriptions], - set_state(SubState#pubsub_state{subscriptions = Subscriptions}), - {Subscription, SubId}. - -unsub_with_subid(Nidx, SubId, SubState) -> - %%pubsub_subscription_odbc:unsubscribe_node(SubState#pubsub_state.stateid, Nidx, SubId), - NewSubs = [{S, Sid} - || {S, Sid} <- SubState#pubsub_state.subscriptions, - SubId =/= Sid], - case {NewSubs, SubState#pubsub_state.affiliation} of - {[], none} -> del_state(Nidx, element(1, SubState#pubsub_state.stateid)); - _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs}) - end. - -get_pending_nodes(Host, Owner) -> - GenKey = jid:remove_resource(jid:tolower(Owner)), - States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, - affiliation = owner, _ = '_'}), - Nidxxs = [Nidx || #pubsub_state{stateid = {_, Nidx}} <- States], - NodeTree = mod_pubsub:tree(Host), - Reply = mnesia:foldl(fun (#pubsub_state{stateid = {_, Nidx}} = S, Acc) -> - case lists:member(Nidx, Nidxxs) of - true -> - case get_nodes_helper(NodeTree, S) of - {value, Node} -> [Node | Acc]; - false -> Acc - end; - false -> - Acc - end - end, - [], pubsub_state), - {result, Reply}. - -get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}) -> - HasPending = fun - ({pending, _}) -> true; - (pending) -> true; - (_) -> false - end, - case lists:any(HasPending, Subs) of - true -> - case NodeTree:get_node(N) of - #pubsub_node{nodeid = {_, Node}} -> {value, Node}; - _ -> false - end; - false -> - false - end. - -get_states(Nidx) -> - case catch - ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions " - "from pubsub_state where nodeid='">>, Nidx, <<"';">>]) - of - {selected, - [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], RItems} -> - {result, - lists:map(fun ([SJID, Aff, Subs]) -> - #pubsub_state{stateid = {decode_jid(SJID), Nidx}, - items = itemids(Nidx, SJID), - affiliation = decode_affiliation(Aff), - subscriptions = decode_subscriptions(Subs)} - end, - RItems)}; - _ -> - {result, []} - end. - -get_state(Nidx, JID) -> - State = get_state_without_itemids(Nidx, JID), - {SJID, _} = State#pubsub_state.stateid, - State#pubsub_state{items = itemids(Nidx, SJID)}. - --spec(get_state_without_itemids/2 :: - (Nidx :: mod_pubsub:nodeIdx(), - Key :: ljid()) -> - mod_pubsub:pubsubState() - ). -get_state_without_itemids(Nidx, JID) -> - J = encode_jid(JID), - case catch - ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions " - "from pubsub_state where jid='">>, J, <<"' and nodeid='">>, Nidx, <<"';">>]) - of - {selected, - [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], [[SJID, Aff, Subs]]} -> - #pubsub_state{stateid = {decode_jid(SJID), Nidx}, - affiliation = decode_affiliation(Aff), - subscriptions = decode_subscriptions(Subs)}; - _ -> - #pubsub_state{stateid = {JID, Nidx}} - end. - -set_state(State) -> - {_, Nidx} = State#pubsub_state.stateid, - set_state(Nidx, State). - -set_state(Nidx, State) -> - {JID, _} = State#pubsub_state.stateid, - J = encode_jid(JID), - S = encode_subscriptions(State#pubsub_state.subscriptions), - A = encode_affiliation(State#pubsub_state.affiliation), - case catch - ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, S, <<"', affiliation='">>, A, - <<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>]) - of - {updated, 1} -> - ok; - _ -> - catch - ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) " - "values('">>, - Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"');">>]) - end, - ok. - -del_state(Nidx, JID) -> - J = encode_jid(JID), - catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_state where jid='">>, - J, <<"' and nodeid='">>, Nidx, <<"';">>]), - ok. - -%get_items(Nidx, _From) -> -% case catch -% ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload " -% "from pubsub_item where nodeid='">>, Nidx, -% <<"' order by modification desc;">>]) -% of -% {selected, -% [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} -> -% {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]}; -% _ -> -% {result, []} -% end. - -get_items(Nidx, From, none) -> - MaxItems = case catch - ejabberd_odbc:sql_query_t([<<"select val from pubsub_node_option " - "where nodeid='">>, Nidx, <<"' and name='max_items';">>]) - of - {selected, [<<"val">>], [[Value]]} -> - Tokens = element(2, erl_scan:string(binary_to_list(<>))), - element(2, erl_parse:parse_term(Tokens)); - _ -> - ?MAXITEMS - end, - get_items(Nidx, From, #rsm_in{max = MaxItems}); -get_items(Nidx, _From, - #rsm_in{max = M, direction = Direction, id = I, index = IncIndex}) -> - Max = ejabberd_odbc:escape(jlib:i2l(M)), - {Way, Order} = case Direction of - % aft -> {<<"<">>, <<"desc">>}; - % before when I == <<>> -> {<<"is not">>, <<"asc">>}; - % before -> {<<">">>, <<"asc">>}; - % _ when IncIndex =/= undefined -> - % {<<"<">>, <<"desc">>}; % using index - _ -> - {<<"is not">>, <<"desc">>}% Can be better - end, - [AttrName, Id] = case I of - undefined when IncIndex =/= undefined -> - case catch - ejabberd_odbc:sql_query_t([<<"select modification from pubsub_item pi " - "where exists ( select count(*) as count1 " - "from pubsub_item where nodeid='">>, Nidx, - <<"' and modification > pi.modification having count1 = ">>, - ejabberd_odbc:escape(jlib:i2l(IncIndex)), <<" );">>]) - of - {selected, [_], [[O]]} -> - [<<"modification">>, <<"'", O/binary, "'">>]; - _ -> - [<<"modification">>, <<"null">>] - end; - undefined -> - [<<"modification">>, <<"null">>]; - <<>> -> - [<<"modification">>, <<"null">>]; - I -> - [A, B] = str:tokens(ejabberd_odbc:escape(jlib:i2l(I)), <<"@">>), - [A, <<"'", B/binary, "'">>] - end, - Count = case catch - ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, Nidx, <<"';">>]) - of - {selected, [_], [[C]]} -> C; - _ -> <<"0">> - end, - Query = fun(mssql, _) -> - ejabberd_odbc:sql_query_t( - [<<"select top ">>, jlib:i2l(Max), - <<" itemid, publisher, creation, modification, payload " - "from pubsub_item where nodeid='">>, Nidx, - <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>, - AttrName, <<" ">>, Order, <<";">>]); - (_, _) -> - ejabberd_odbc:sql_query_t( - [<<"select itemid, publisher, creation, modification, payload " - "from pubsub_item where nodeid='">>, Nidx, - <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>, - AttrName, <<" ">>, Order, <<" limit ">>, jlib:i2l(Max), <<" ;">>]) - end, - case catch ejabberd_odbc:sql_query_t(Query) of - {selected, - [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} -> - case RItems of - [[_, _, _, F, _]|_] -> - Index = case catch - ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item " - "where nodeid='">>, Nidx, <<"' and ">>, - AttrName, <<" > '">>, F, <<"';">>]) - of - %{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")]; - {selected, [_], [[In]]} -> In; - _ -> <<"0">> - end, - [_, _, _, L, _] = lists:last(RItems), - RsmOut = #rsm_out{count = Count, index = Index, - first = <<"modification@", F/binary>>, - last = <<"modification@", (jlib:i2l(L))/binary>>}, - {result, {[raw_to_item(Nidx, RItem) || RItem <- RItems], RsmOut}}; - [] -> - {result, {[], #rsm_out{count = Count}}} - end; - _ -> - {result, {[], none}} - end. - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) -> - SubKey = jid:tolower(JID), - GenKey = jid:remove_resource(SubKey), - {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey), - Whitelisted = node_flat:can_fetch_item(Affiliation, Subscriptions), - if %%SubId == "", ?? -> - %% Entity has multiple subscriptions to the node but does not specify a subscription ID - %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - %%InvalidSubId -> - %% Entity is subscribed but specifies an invalid subscription ID - %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; - (Affiliation == outcast) or (Affiliation == publish_only) -> - {error, ?ERR_FORBIDDEN}; - (AccessModel == presence) and not PresenceSubscription -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)}; - (AccessModel == roster) and not RosterGroup -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; - (AccessModel == whitelist) and not Whitelisted -> - {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; - (AccessModel == authorize) and not Whitelisted -> - {error, ?ERR_FORBIDDEN}; - %%MustPay -> - %% % Payment is required for a subscription - %% {error, ?ERR_PAYMENT_REQUIRED}; - true -> - get_items(Nidx, JID, RSM) - end. - -get_last_items(Nidx, _From, Count) -> - Limit = jlib:i2l(Count), - Query = fun(mssql, _) -> - ejabberd_odbc:sql_query_t( - [<<"select top ">>, Limit, - <<" itemid, publisher, creation, modification, payload " - "from pubsub_item where nodeid='">>, Nidx, - <<"' order by modification desc ;">>]); - (_, _) -> - ejabberd_odbc:sql_query_t( - [<<"select itemid, publisher, creation, modification, payload " - "from pubsub_item where nodeid='">>, Nidx, - <<"' order by modification desc limit ">>, Limit, <<";">>]) - end, - case catch ejabberd_odbc:sql_query_t(Query) of - {selected, - [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} -> - {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]}; - _ -> - {result, []} - end. - -get_item(Nidx, ItemId) -> - I = ejabberd_odbc:escape(ItemId), - case catch - ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, " - "modification, payload from pubsub_item " - "where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>]) - of - {selected, - [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], [RItem]} -> - {result, raw_to_item(Nidx, RItem)}; - {selected, _, []} -> - {error, ?ERR_ITEM_NOT_FOUND}; - {'EXIT', _} -> - {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)} - end. - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> - SubKey = jid:tolower(JID), - GenKey = jid:remove_resource(SubKey), - {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey), - Whitelisted = node_flat:can_fetch_item(Affiliation, Subscriptions), - if %%SubId == "", ?? -> - %% Entity has multiple subscriptions to the node but does not specify a subscription ID - %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - %%InvalidSubId -> - %% Entity is subscribed but specifies an invalid subscription ID - %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; - (Affiliation == outcast) or (Affiliation == publish_only) -> - {error, ?ERR_FORBIDDEN}; - (AccessModel == presence) and not PresenceSubscription -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)}; - (AccessModel == roster) and not RosterGroup -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; - (AccessModel == whitelist) and not Whitelisted -> - {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; - (AccessModel == authorize) and not Whitelisted -> - {error, ?ERR_FORBIDDEN}; - %%MustPay -> - %% % Payment is required for a subscription - %% {error, ?ERR_PAYMENT_REQUIRED}; - true -> - get_item(Nidx, ItemId) - end. - -set_item(Item) -> - {ItemId, Nidx} = Item#pubsub_item.itemid, - I = ejabberd_odbc:escape(ItemId), - {C, _} = Item#pubsub_item.creation, - {M, JID} = Item#pubsub_item.modification, - P = encode_jid(JID), - Payload = Item#pubsub_item.payload, - XML = ejabberd_odbc:escape(str:join([fxml:element_to_binary(X) || X<-Payload], <<>>)), - S = fun ({T1, T2, T3}) -> - str:join([jlib:i2l(T1, 6), jlib:i2l(T2, 6), jlib:i2l(T3, 6)], <<":">>) - end, - case catch - ejabberd_odbc:sql_query_t([<<"update pubsub_item set publisher='">>, P, - <<"', modification='">>, S(M), - <<"', payload='">>, XML, - <<"' where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>]) - of - {updated, 1} -> - ok; - _ -> - catch - ejabberd_odbc:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, " - "publisher, creation, modification, payload) " - "values('">>, Nidx, <<"', '">>, I, <<"', '">>, P, - <<"', '">>, S(C), <<"', '">>, S(M), - <<"', '">>, XML, <<"');">>]) - end, - ok. - -del_item(Nidx, ItemId) -> - I = ejabberd_odbc:escape(ItemId), - catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid='">>, - I, <<"' and nodeid='">>, Nidx, <<"';">>]). - -del_items(_, []) -> - ok; -del_items(Nidx, [ItemId]) -> - del_item(Nidx, ItemId); -del_items(Nidx, ItemIds) -> - I = str:join([[<<"'">>, ejabberd_odbc:escape(X), <<"'">>] || X <- ItemIds], <<",">>), - catch - ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid in (">>, - I, <<") and nodeid='">>, Nidx, <<"';">>]). - -get_item_name(_Host, _Node, Id) -> - Id. - -node_to_path(Node) -> - node_flat:node_to_path(Node). - -path_to_node(Path) -> - node_flat:path_to_node(Path). - - -first_in_list(_Pred, []) -> - false; -first_in_list(Pred, [H | T]) -> - case Pred(H) of - true -> {value, H}; - _ -> first_in_list(Pred, T) - end. - -itemids(Nidx, {U, S, R}) -> - itemids(Nidx, encode_jid({U, S, R})); -itemids(Nidx, SJID) -> - case catch - ejabberd_odbc:sql_query_t([<<"select itemid from pubsub_item where " - "nodeid='">>, Nidx, <<"' and publisher like '">>, SJID, - <<"%' order by modification desc;">>]) - of - {selected, [<<"itemid">>], RItems} -> - [ItemId || [ItemId] <- RItems]; - _ -> - [] - end. - -select_affiliation_subscriptions(Nidx, JID) -> - J = encode_jid(JID), - case catch - ejabberd_odbc:sql_query_t([<<"select affiliation,subscriptions from " - "pubsub_state where nodeid='">>, - Nidx, <<"' and jid='">>, J, <<"';">>]) - of - {selected, [<<"affiliation">>, <<"subscriptions">>], [[A, S]]} -> - {decode_affiliation(A), decode_subscriptions(S)}; - _ -> - {none, []} - end. - -select_affiliation_subscriptions(Nidx, JID, JID) -> - select_affiliation_subscriptions(Nidx, JID); -select_affiliation_subscriptions(Nidx, GenKey, SubKey) -> - {result, Affiliation} = get_affiliation(Nidx, GenKey), - {result, Subscriptions} = get_subscriptions(Nidx, SubKey), - {Affiliation, Subscriptions}. - -update_affiliation(Nidx, JID, Affiliation) -> - J = encode_jid(JID), - A = encode_affiliation(Affiliation), - case catch - ejabberd_odbc:sql_query_t([<<"update pubsub_state set affiliation='">>, - A, <<"' where nodeid='">>, Nidx, - <<"' and jid='">>, J, <<"';">>]) - of - {updated, 1} -> - ok; - _ -> - catch - ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) " - "values('">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '');">>]) - end. - -update_subscription(Nidx, JID, Subscription) -> - J = encode_jid(JID), - S = encode_subscriptions(Subscription), - case catch - ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, S, - <<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>]) - of - {updated, 1} -> - ok; - _ -> - catch - ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) " - "values('">>, Nidx, <<"', '">>, J, <<"', 'n', '">>, S, <<"');">>]) - end. - --spec(decode_jid/1 :: - ( SJID :: binary()) - -> ljid() - ). -decode_jid(SJID) -> - jid:tolower(jid:from_string(SJID)). - --spec(decode_affiliation/1 :: - ( Arg :: binary()) - -> atom() - ). -decode_affiliation(<<"o">>) -> owner; -decode_affiliation(<<"p">>) -> publisher; -decode_affiliation(<<"u">>) -> publish_only; -decode_affiliation(<<"m">>) -> member; -decode_affiliation(<<"c">>) -> outcast; -decode_affiliation(_) -> none. - --spec(decode_subscription/1 :: - ( Arg :: binary()) - -> atom() - ). -decode_subscription(<<"s">>) -> subscribed; -decode_subscription(<<"p">>) -> pending; -decode_subscription(<<"u">>) -> unconfigured; -decode_subscription(_) -> none. - --spec(decode_subscriptions/1 :: - ( Subscriptions :: binary()) - -> [] | [{atom(), binary()},...] - ). -decode_subscriptions(Subscriptions) -> - lists:foldl(fun (Subscription, Acc) -> - case str:tokens(Subscription, <<":">>) of - [S, SubId] -> [{decode_subscription(S), SubId} | Acc]; - _ -> Acc - end - end, - [], str:tokens(Subscriptions, <<",">>)). - --spec(encode_jid/1 :: - ( JID :: ljid()) - -> binary() - ). -encode_jid(JID) -> - ejabberd_odbc:escape(jid:to_string(JID)). - --spec(encode_host/1 :: - ( Host :: host()) - -> binary() - ). -encode_host({_U, _S, _R} = LJID) -> encode_jid(LJID); -encode_host(Host) -> ejabberd_odbc:escape(Host). - --spec(encode_affiliation/1 :: - ( Arg :: atom()) - -> binary() - ). -encode_affiliation(owner) -> <<"o">>; -encode_affiliation(publisher) -> <<"p">>; -encode_affiliation(publish_only) -> <<"u">>; -encode_affiliation(member) -> <<"m">>; -encode_affiliation(outcast) -> <<"c">>; -encode_affiliation(_) -> <<"n">>. - --spec(encode_subscription/1 :: - ( Arg :: atom()) - -> binary() - ). -encode_subscription(subscribed) -> <<"s">>; -encode_subscription(pending) -> <<"p">>; -encode_subscription(unconfigured) -> <<"u">>; -encode_subscription(_) -> <<"n">>. - --spec(encode_subscriptions/1 :: - ( Subscriptions :: [] | [{atom(), binary()},...]) - -> binary() - ). -encode_subscriptions(Subscriptions) -> - str:join([<<(encode_subscription(S))/binary, ":", SubId/binary>> - || {S, SubId} <- Subscriptions], <<",">>). - -%%% record getter/setter - -state_to_raw(Nidx, State) -> - {JID, _} = State#pubsub_state.stateid, - J = encode_jid(JID), - A = encode_affiliation(State#pubsub_state.affiliation), - S = encode_subscriptions(State#pubsub_state.subscriptions), - [<<"'">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"'">>]. - -raw_to_item(Nidx, [ItemId, SJID, Creation, Modification, XML]) -> - JID = decode_jid(SJID), - ToTime = fun (Str) -> - [T1, T2, T3] = str:tokens(Str, <<":">>), - {jlib:l2i(T1), jlib:l2i(T2), jlib:l2i(T3)} - end, - Payload = case fxml_stream:parse_element(XML) of - {error, _Reason} -> []; - El -> [El] - end, - #pubsub_item{itemid = {ItemId, Nidx}, - creation = {ToTime(Creation), JID}, - modification = {ToTime(Modification), JID}, - payload = Payload}. diff --git a/src/node_flat_sql.erl b/src/node_flat_sql.erl new file mode 100644 index 00000000..5baabcfc --- /dev/null +++ b/src/node_flat_sql.erl @@ -0,0 +1,1071 @@ +%%%---------------------------------------------------------------------- +%%% File : node_flat_sql.erl +%%% Author : Christophe Romain +%%% Purpose : Standard PubSub node plugin with ODBC backend +%%% Created : 1 Dec 2007 by Christophe Romain +%%% +%%% +%%% 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 {@module} is the default PubSub plugin. +%%%

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 +%%% types.

+%%%

PubSub plugin nodes are using the {@link gen_node} behaviour.

+ +-module(node_flat_sql). +-behaviour(gen_pubsub_node). +-author('christophe.romain@process-one.net'). + +-include("pubsub.hrl"). +-include("jlib.hrl"). + +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, + get_entity_subscriptions/2, get_node_subscriptions/1, + get_subscriptions/2, set_subscriptions/4, + get_pending_nodes/2, get_states/1, get_state/2, + set_state/1, get_items/7, get_items/3, get_item/7, + get_item/2, set_item/1, get_item_name/3, node_to_path/1, + path_to_node/1, + get_entity_subscriptions_for_send_last/2, get_last_items/3]). + +-export([decode_jid/1, encode_jid/1, + decode_affiliation/1, decode_subscriptions/1, + encode_affiliation/1, encode_subscriptions/1, + encode_host/1]). + +init(_Host, _ServerHost, _Opts) -> + %%pubsub_subscription_sql:init(), + ok. + +terminate(_Host, _ServerHost) -> + ok. + +options() -> + [{sql, true}, {rsm, true} | node_flat:options()]. + +features() -> + [<<"rsm">> | node_flat:features()]. + +create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> + node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). + +create_node(Nidx, Owner) -> + {_U, _S, _R} = OwnerKey = jid:tolower(jid:remove_resource(Owner)), + State = #pubsub_state{stateid = {OwnerKey, Nidx}, affiliation = owner}, + catch ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) " + "values(">>, state_to_raw(Nidx, State), <<");">>]), + {result, {default, broadcast}}. + +delete_node(Nodes) -> + Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) -> + Subscriptions = case catch + ejabberd_sql:sql_query_t([<<"select jid, subscriptions " + "from pubsub_state where nodeid='">>, Nidx, <<"';">>]) + of + {selected, [<<"jid">>, <<"subscriptions">>], RItems} -> + [{decode_jid(SJID), decode_subscriptions(Subs)} || [SJID, Subs] <- RItems]; + _ -> + [] + end, + {PubsubNode, Subscriptions} + end, Nodes), + {result, {default, broadcast, Reply}}. + +subscribe_node(Nidx, Sender, Subscriber, AccessModel, + SendLast, PresenceSubscription, RosterGroup, _Options) -> + SubKey = jid:tolower(Subscriber), + GenKey = jid:remove_resource(SubKey), + Authorized = jid:tolower(jid:remove_resource(Sender)) == GenKey, + {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey), + Whitelisted = lists:member(Affiliation, [member, publisher, owner]), + PendingSubscription = lists:any(fun + ({pending, _}) -> true; + (_) -> false + end, + Subscriptions), + Owner = Affiliation == owner, + if not Authorized -> + {error, + ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)}; + (Affiliation == outcast) or (Affiliation == publish_only) -> + {error, ?ERR_FORBIDDEN}; + PendingSubscription -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)}; + (AccessModel == presence) and (not PresenceSubscription) and (not Owner) -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)}; + (AccessModel == roster) and (not RosterGroup) and (not Owner) -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; + (AccessModel == whitelist) and (not Whitelisted) and (not Owner) -> + {error, + ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + %%MustPay -> + %% % Payment is required for a subscription + %% {error, ?ERR_PAYMENT_REQUIRED}; + %%ForbiddenAnonymous -> + %% % Requesting entity is anonymous + %% {error, ?ERR_FORBIDDEN}; + true -> + %%{result, SubId} = pubsub_subscription_sql:subscribe_node(Subscriber, Nidx, Options), + {NewSub, SubId} = case Subscriptions of + [{subscribed, Id}|_] -> + {subscribed, Id}; + [] -> + Id = pubsub_subscription_sql:make_subid(), + Sub = case AccessModel of + authorize -> pending; + _ -> subscribed + end, + update_subscription(Nidx, SubKey, [{Sub, Id} | Subscriptions]), + {Sub, Id} + end, + case {NewSub, SendLast} of + {subscribed, never} -> + {result, {default, subscribed, SubId}}; + {subscribed, _} -> + {result, {default, subscribed, SubId, send_last}}; + {_, _} -> + {result, {default, pending, SubId}} + end + end. + +unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> + SubKey = jid:tolower(Subscriber), + GenKey = jid:remove_resource(SubKey), + Authorized = jid:tolower(jid:remove_resource(Sender)) == GenKey, + {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, SubKey), + SubIdExists = case SubId of + <<>> -> false; + Binary when is_binary(Binary) -> true; + _ -> false + end, + if + %% Requesting entity is prohibited from unsubscribing entity + not Authorized -> + {error, ?ERR_FORBIDDEN}; + %% Entity did not specify SubId + %%SubId == "", ?? -> + %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; + %% Invalid subscription identifier + %%InvalidSubId -> + %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + %% Requesting entity is not a subscriber + Subscriptions == [] -> + {error, + ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}; + %% Subid supplied, so use that. + SubIdExists -> + Sub = first_in_list(fun + ({_, S}) when S == SubId -> true; + (_) -> false + end, + Subscriptions), + case Sub of + {value, S} -> + delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions), + {result, default}; + false -> + {error, + ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)} + end; + %% Asking to remove all subscriptions to the given node + SubId == all -> + [delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions) + || S <- Subscriptions], + {result, default}; + %% No subid supplied, but there's only one matching subscription + length(Subscriptions) == 1 -> + delete_subscription(SubKey, Nidx, hd(Subscriptions), Affiliation, Subscriptions), + {result, default}; + %% No subid and more than one possible subscription match. + true -> + {error, + ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)} + end. + +delete_subscription(SubKey, Nidx, {Subscription, SubId}, Affiliation, Subscriptions) -> + NewSubs = Subscriptions -- [{Subscription, SubId}], + %%pubsub_subscription_sql:unsubscribe_node(SubKey, Nidx, SubId), + case {Affiliation, NewSubs} of + {none, []} -> del_state(Nidx, SubKey); + _ -> update_subscription(Nidx, SubKey, NewSubs) + end. + +publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) -> + SubKey = jid:tolower(Publisher), + GenKey = jid:remove_resource(SubKey), + {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey), + Subscribed = case PublishModel of + subscribers -> node_flat:is_subscribed(Subscriptions); + _ -> undefined + end, + if not ((PublishModel == open) or + (PublishModel == publishers) and + ((Affiliation == owner) + or (Affiliation == publisher) + or (Affiliation == publish_only)) + or (Subscribed == true)) -> + {error, ?ERR_FORBIDDEN}; + true -> + if MaxItems > 0 -> + PubId = {p1_time_compat:timestamp(), SubKey}, + set_item(#pubsub_item{itemid = {ItemId, Nidx}, + creation = {p1_time_compat:timestamp(), GenKey}, + modification = PubId, + payload = Payload}), + Items = [ItemId | itemids(Nidx, GenKey) -- [ItemId]], + {result, {_, OI}} = remove_extra_items(Nidx, MaxItems, Items), + {result, {default, broadcast, OI}}; + true -> + {result, {default, broadcast, []}} + end + end. + +remove_extra_items(_Nidx, unlimited, ItemIds) -> + {result, {ItemIds, []}}; +remove_extra_items(Nidx, MaxItems, ItemIds) -> + NewItems = lists:sublist(ItemIds, MaxItems), + OldItems = lists:nthtail(length(NewItems), ItemIds), + del_items(Nidx, OldItems), + {result, {NewItems, OldItems}}. + +delete_item(Nidx, Publisher, PublishModel, ItemId) -> + SubKey = jid:tolower(Publisher), + GenKey = jid:remove_resource(SubKey), + {result, Affiliation} = get_affiliation(Nidx, GenKey), + Allowed = Affiliation == publisher orelse + Affiliation == owner orelse + PublishModel == open orelse + case get_item(Nidx, ItemId) of + {result, #pubsub_item{creation = {_, GenKey}}} -> true; + _ -> false + end, + if not Allowed -> + {error, ?ERR_FORBIDDEN}; + true -> + case del_item(Nidx, ItemId) of + {updated, 1} -> {result, {default, broadcast}}; + _ -> {error, ?ERR_ITEM_NOT_FOUND} + end + end. + +purge_node(Nidx, Owner) -> + SubKey = jid:tolower(Owner), + GenKey = jid:remove_resource(SubKey), + GenState = get_state(Nidx, GenKey), + case GenState of + #pubsub_state{affiliation = owner} -> + {result, States} = get_states(Nidx), + lists:foreach(fun + (#pubsub_state{items = []}) -> ok; + (#pubsub_state{items = Items}) -> del_items(Nidx, Items) + end, + States), + {result, {default, broadcast}}; + _ -> + {error, ?ERR_FORBIDDEN} + end. + +get_entity_affiliations(Host, Owner) -> + SubKey = jid:tolower(Owner), + GenKey = jid:remove_resource(SubKey), + H = encode_host(Host), + J = encode_jid(GenKey), + Reply = case catch + ejabberd_sql:sql_query_t([<<"select node, type, i.nodeid, affiliation " + "from pubsub_state i, pubsub_node n where " + "i.nodeid = n.nodeid and jid='">>, J, <<"' and host='">>, H, <<"';">>]) + of + {selected, [<<"node">>, <<"type">>, <<"nodeid">>, <<"affiliation">>], RItems} -> + [{nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]), decode_affiliation(A)} + || [N, T, I, A] <- RItems]; + _ -> + [] + end, + {result, Reply}. + +get_node_affiliations(Nidx) -> + Reply = case catch + ejabberd_sql:sql_query_t([<<"select jid, affiliation from pubsub_state " + "where nodeid='">>, Nidx, <<"';">>]) + of + {selected, [<<"jid">>, <<"affiliation">>], RItems} -> + [{decode_jid(J), decode_affiliation(A)} || [J, A] <- RItems]; + _ -> + [] + end, + {result, Reply}. + +get_affiliation(Nidx, Owner) -> + SubKey = jid:tolower(Owner), + GenKey = jid:remove_resource(SubKey), + J = encode_jid(GenKey), + Reply = case catch + ejabberd_sql:sql_query_t([<<"select affiliation from pubsub_state " + "where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>]) + of + {selected, [<<"affiliation">>], [[A]]} -> + decode_affiliation(A); + _ -> + none + end, + {result, Reply}. + +set_affiliation(Nidx, Owner, Affiliation) -> + SubKey = jid:tolower(Owner), + GenKey = jid:remove_resource(SubKey), + {_, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey), + case {Affiliation, Subscriptions} of + {none, []} -> del_state(Nidx, GenKey); + _ -> update_affiliation(Nidx, GenKey, Affiliation) + end. + +get_entity_subscriptions(Host, Owner) -> + SubKey = jid:tolower(Owner), + GenKey = jid:remove_resource(SubKey), + H = encode_host(Host), + SJ = encode_jid(SubKey), + GJ = encode_jid(GenKey), + Query = case SubKey of + GenKey -> + [<<"select node, type, i.nodeid, jid, subscriptions " + "from pubsub_state i, pubsub_node n " + "where i.nodeid = n.nodeid and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>]; + _ -> + [<<"select node, type, i.nodeid, jid, subscriptions " + "from pubsub_state i, pubsub_node n " + "where i.nodeid = n.nodeid and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>] + end, + Reply = case catch ejabberd_sql:sql_query_t(Query) of + {selected, + [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} -> + lists:foldl(fun ([N, T, I, J, S], Acc) -> + Node = nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]), + Jid = decode_jid(J), + case decode_subscriptions(S) of + [] -> + [{Node, none, Jid} | Acc]; + Subs -> + lists:foldl(fun ({Sub, SubId}, Acc2) -> + [{Node, Sub, SubId, Jid} | Acc2] + end, + Acc, Subs) + end + end, + [], RItems); + _ -> + [] + end, + {result, Reply}. + +-spec(get_entity_subscriptions_for_send_last/2 :: + ( + Host :: mod_pubsub:hostPubsub(), + Owner :: jid()) + -> {result, + [{mod_pubsub:pubsubNode(), + mod_pubsub:subscription(), + mod_pubsub:subId(), + ljid()}] + } + ). +get_entity_subscriptions_for_send_last(Host, Owner) -> + SubKey = jid:tolower(Owner), + GenKey = jid:remove_resource(SubKey), + H = encode_host(Host), + SJ = encode_jid(SubKey), + GJ = encode_jid(GenKey), + Query = case SubKey of + GenKey -> + [<<"select node, type, i.nodeid, jid, subscriptions " + "from pubsub_state i, pubsub_node n, pubsub_node_option o " + "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' " + "and val='on_sub_and_presence' and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>]; + _ -> + [<<"select node, type, i.nodeid, jid, subscriptions " + "from pubsub_state i, pubsub_node n, pubsub_node_option o " + "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' " + "and val='on_sub_and_presence' and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>] + end, + Reply = case catch ejabberd_sql:sql_query_t(Query) of + {selected, + [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} -> + lists:foldl(fun ([N, T, I, J, S], Acc) -> + Node = nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]), + Jid = decode_jid(J), + case decode_subscriptions(S) of + [] -> + [{Node, none, Jid} | Acc]; + Subs -> + lists:foldl(fun ({Sub, SubId}, Acc2) -> + [{Node, Sub, SubId, Jid}| Acc2] + end, + Acc, Subs) + end + end, + [], RItems); + _ -> + [] + end, + {result, Reply}. + +get_node_subscriptions(Nidx) -> + Reply = case catch + ejabberd_sql:sql_query_t([<<"select jid, subscriptions from pubsub_state " + "where nodeid='">>, Nidx, <<"';">>]) + of + {selected, [<<"jid">>, <<"subscriptions">>], RItems} -> + lists:foldl(fun ([J, S], Acc) -> + Jid = decode_jid(J), + case decode_subscriptions(S) of + [] -> + [{Jid, none} | Acc]; + Subs -> + lists:foldl(fun ({Sub, SubId}, Acc2) -> + [{Jid, Sub, SubId} | Acc2] + end, + Acc, Subs) + end + end, + [], RItems); + _ -> + [] + end, + {result, Reply}. + +get_subscriptions(Nidx, Owner) -> + SubKey = jid:tolower(Owner), + J = encode_jid(SubKey), + Reply = case catch + ejabberd_sql:sql_query_t([<<"select subscriptions from pubsub_state where " + "nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>]) + of + {selected, [<<"subscriptions">>], [[S]]} -> + decode_subscriptions(S); + _ -> + [] + end, + {result, Reply}. + +set_subscriptions(Nidx, Owner, Subscription, SubId) -> + SubKey = jid:tolower(Owner), + SubState = get_state_without_itemids(Nidx, SubKey), + case {SubId, SubState#pubsub_state.subscriptions} of + {_, []} -> + case Subscription of + none -> + {error, + ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)}; + _ -> + new_subscription(Nidx, Owner, Subscription, SubState) + end; + {<<>>, [{_, SID}]} -> + case Subscription of + none -> unsub_with_subid(Nidx, SID, SubState); + _ -> replace_subscription({Subscription, SID}, SubState) + end; + {<<>>, [_ | _]} -> + {error, + ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}; + _ -> + case Subscription of + none -> unsub_with_subid(Nidx, SubId, SubState); + _ -> replace_subscription({Subscription, SubId}, SubState) + end + end. + +replace_subscription(NewSub, SubState) -> + NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []), + set_state(SubState#pubsub_state{subscriptions = NewSubs}). + +replace_subscription(_, [], Acc) -> Acc; +replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) -> + replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]). + +new_subscription(_Nidx, _Owner, Subscription, SubState) -> + %%{result, SubId} = pubsub_subscription_sql:subscribe_node(Owner, Nidx, []), + SubId = pubsub_subscription_sql:make_subid(), + Subscriptions = [{Subscription, SubId} | SubState#pubsub_state.subscriptions], + set_state(SubState#pubsub_state{subscriptions = Subscriptions}), + {Subscription, SubId}. + +unsub_with_subid(Nidx, SubId, SubState) -> + %%pubsub_subscription_sql:unsubscribe_node(SubState#pubsub_state.stateid, Nidx, SubId), + NewSubs = [{S, Sid} + || {S, Sid} <- SubState#pubsub_state.subscriptions, + SubId =/= Sid], + case {NewSubs, SubState#pubsub_state.affiliation} of + {[], none} -> del_state(Nidx, element(1, SubState#pubsub_state.stateid)); + _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs}) + end. + +get_pending_nodes(Host, Owner) -> + GenKey = jid:remove_resource(jid:tolower(Owner)), + States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, + affiliation = owner, _ = '_'}), + Nidxxs = [Nidx || #pubsub_state{stateid = {_, Nidx}} <- States], + NodeTree = mod_pubsub:tree(Host), + Reply = mnesia:foldl(fun (#pubsub_state{stateid = {_, Nidx}} = S, Acc) -> + case lists:member(Nidx, Nidxxs) of + true -> + case get_nodes_helper(NodeTree, S) of + {value, Node} -> [Node | Acc]; + false -> Acc + end; + false -> + Acc + end + end, + [], pubsub_state), + {result, Reply}. + +get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}) -> + HasPending = fun + ({pending, _}) -> true; + (pending) -> true; + (_) -> false + end, + case lists:any(HasPending, Subs) of + true -> + case NodeTree:get_node(N) of + #pubsub_node{nodeid = {_, Node}} -> {value, Node}; + _ -> false + end; + false -> + false + end. + +get_states(Nidx) -> + case catch + ejabberd_sql:sql_query_t([<<"select jid, affiliation, subscriptions " + "from pubsub_state where nodeid='">>, Nidx, <<"';">>]) + of + {selected, + [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], RItems} -> + {result, + lists:map(fun ([SJID, Aff, Subs]) -> + #pubsub_state{stateid = {decode_jid(SJID), Nidx}, + items = itemids(Nidx, SJID), + affiliation = decode_affiliation(Aff), + subscriptions = decode_subscriptions(Subs)} + end, + RItems)}; + _ -> + {result, []} + end. + +get_state(Nidx, JID) -> + State = get_state_without_itemids(Nidx, JID), + {SJID, _} = State#pubsub_state.stateid, + State#pubsub_state{items = itemids(Nidx, SJID)}. + +-spec(get_state_without_itemids/2 :: + (Nidx :: mod_pubsub:nodeIdx(), + Key :: ljid()) -> + mod_pubsub:pubsubState() + ). +get_state_without_itemids(Nidx, JID) -> + J = encode_jid(JID), + case catch + ejabberd_sql:sql_query_t([<<"select jid, affiliation, subscriptions " + "from pubsub_state where jid='">>, J, <<"' and nodeid='">>, Nidx, <<"';">>]) + of + {selected, + [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], [[SJID, Aff, Subs]]} -> + #pubsub_state{stateid = {decode_jid(SJID), Nidx}, + affiliation = decode_affiliation(Aff), + subscriptions = decode_subscriptions(Subs)}; + _ -> + #pubsub_state{stateid = {JID, Nidx}} + end. + +set_state(State) -> + {_, Nidx} = State#pubsub_state.stateid, + set_state(Nidx, State). + +set_state(Nidx, State) -> + {JID, _} = State#pubsub_state.stateid, + J = encode_jid(JID), + S = encode_subscriptions(State#pubsub_state.subscriptions), + A = encode_affiliation(State#pubsub_state.affiliation), + case catch + ejabberd_sql:sql_query_t([<<"update pubsub_state set subscriptions='">>, S, <<"', affiliation='">>, A, + <<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>]) + of + {updated, 1} -> + ok; + _ -> + catch + ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) " + "values('">>, + Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"');">>]) + end, + ok. + +del_state(Nidx, JID) -> + J = encode_jid(JID), + catch ejabberd_sql:sql_query_t([<<"delete from pubsub_state where jid='">>, + J, <<"' and nodeid='">>, Nidx, <<"';">>]), + ok. + +%get_items(Nidx, _From) -> +% case catch +% ejabberd_sql:sql_query_t([<<"select itemid, publisher, creation, modification, payload " +% "from pubsub_item where nodeid='">>, Nidx, +% <<"' order by modification desc;">>]) +% of +% {selected, +% [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} -> +% {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]}; +% _ -> +% {result, []} +% end. + +get_items(Nidx, From, none) -> + MaxItems = case catch + ejabberd_sql:sql_query_t([<<"select val from pubsub_node_option " + "where nodeid='">>, Nidx, <<"' and name='max_items';">>]) + of + {selected, [<<"val">>], [[Value]]} -> + Tokens = element(2, erl_scan:string(binary_to_list(<>))), + element(2, erl_parse:parse_term(Tokens)); + _ -> + ?MAXITEMS + end, + get_items(Nidx, From, #rsm_in{max = MaxItems}); +get_items(Nidx, _From, + #rsm_in{max = M, direction = Direction, id = I, index = IncIndex}) -> + Max = ejabberd_sql:escape(jlib:i2l(M)), + {Way, Order} = case Direction of + % aft -> {<<"<">>, <<"desc">>}; + % before when I == <<>> -> {<<"is not">>, <<"asc">>}; + % before -> {<<">">>, <<"asc">>}; + % _ when IncIndex =/= undefined -> + % {<<"<">>, <<"desc">>}; % using index + _ -> + {<<"is not">>, <<"desc">>}% Can be better + end, + [AttrName, Id] = case I of + undefined when IncIndex =/= undefined -> + case catch + ejabberd_sql:sql_query_t([<<"select modification from pubsub_item pi " + "where exists ( select count(*) as count1 " + "from pubsub_item where nodeid='">>, Nidx, + <<"' and modification > pi.modification having count1 = ">>, + ejabberd_sql:escape(jlib:i2l(IncIndex)), <<" );">>]) + of + {selected, [_], [[O]]} -> + [<<"modification">>, <<"'", O/binary, "'">>]; + _ -> + [<<"modification">>, <<"null">>] + end; + undefined -> + [<<"modification">>, <<"null">>]; + <<>> -> + [<<"modification">>, <<"null">>]; + I -> + [A, B] = str:tokens(ejabberd_sql:escape(jlib:i2l(I)), <<"@">>), + [A, <<"'", B/binary, "'">>] + end, + Count = case catch + ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, Nidx, <<"';">>]) + of + {selected, [_], [[C]]} -> C; + _ -> <<"0">> + end, + Query = fun(mssql, _) -> + ejabberd_sql:sql_query_t( + [<<"select top ">>, jlib:i2l(Max), + <<" itemid, publisher, creation, modification, payload " + "from pubsub_item where nodeid='">>, Nidx, + <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>, + AttrName, <<" ">>, Order, <<";">>]); + (_, _) -> + ejabberd_sql:sql_query_t( + [<<"select itemid, publisher, creation, modification, payload " + "from pubsub_item where nodeid='">>, Nidx, + <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>, + AttrName, <<" ">>, Order, <<" limit ">>, jlib:i2l(Max), <<" ;">>]) + end, + case catch ejabberd_sql:sql_query_t(Query) of + {selected, + [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} -> + case RItems of + [[_, _, _, F, _]|_] -> + Index = case catch + ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item " + "where nodeid='">>, Nidx, <<"' and ">>, + AttrName, <<" > '">>, F, <<"';">>]) + of + %{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")]; + {selected, [_], [[In]]} -> In; + _ -> <<"0">> + end, + [_, _, _, L, _] = lists:last(RItems), + RsmOut = #rsm_out{count = Count, index = Index, + first = <<"modification@", F/binary>>, + last = <<"modification@", (jlib:i2l(L))/binary>>}, + {result, {[raw_to_item(Nidx, RItem) || RItem <- RItems], RsmOut}}; + [] -> + {result, {[], #rsm_out{count = Count}}} + end; + _ -> + {result, {[], none}} + end. + +get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) -> + SubKey = jid:tolower(JID), + GenKey = jid:remove_resource(SubKey), + {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey), + Whitelisted = node_flat:can_fetch_item(Affiliation, Subscriptions), + if %%SubId == "", ?? -> + %% Entity has multiple subscriptions to the node but does not specify a subscription ID + %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; + %%InvalidSubId -> + %% Entity is subscribed but specifies an invalid subscription ID + %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + (Affiliation == outcast) or (Affiliation == publish_only) -> + {error, ?ERR_FORBIDDEN}; + (AccessModel == presence) and not PresenceSubscription -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)}; + (AccessModel == roster) and not RosterGroup -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; + (AccessModel == whitelist) and not Whitelisted -> + {error, + ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + (AccessModel == authorize) and not Whitelisted -> + {error, ?ERR_FORBIDDEN}; + %%MustPay -> + %% % Payment is required for a subscription + %% {error, ?ERR_PAYMENT_REQUIRED}; + true -> + get_items(Nidx, JID, RSM) + end. + +get_last_items(Nidx, _From, Count) -> + Limit = jlib:i2l(Count), + Query = fun(mssql, _) -> + ejabberd_sql:sql_query_t( + [<<"select top ">>, Limit, + <<" itemid, publisher, creation, modification, payload " + "from pubsub_item where nodeid='">>, Nidx, + <<"' order by modification desc ;">>]); + (_, _) -> + ejabberd_sql:sql_query_t( + [<<"select itemid, publisher, creation, modification, payload " + "from pubsub_item where nodeid='">>, Nidx, + <<"' order by modification desc limit ">>, Limit, <<";">>]) + end, + case catch ejabberd_sql:sql_query_t(Query) of + {selected, + [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} -> + {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]}; + _ -> + {result, []} + end. + +get_item(Nidx, ItemId) -> + I = ejabberd_sql:escape(ItemId), + case catch + ejabberd_sql:sql_query_t([<<"select itemid, publisher, creation, " + "modification, payload from pubsub_item " + "where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>]) + of + {selected, + [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], [RItem]} -> + {result, raw_to_item(Nidx, RItem)}; + {selected, _, []} -> + {error, ?ERR_ITEM_NOT_FOUND}; + {'EXIT', _} -> + {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)} + end. + +get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> + SubKey = jid:tolower(JID), + GenKey = jid:remove_resource(SubKey), + {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey), + Whitelisted = node_flat:can_fetch_item(Affiliation, Subscriptions), + if %%SubId == "", ?? -> + %% Entity has multiple subscriptions to the node but does not specify a subscription ID + %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; + %%InvalidSubId -> + %% Entity is subscribed but specifies an invalid subscription ID + %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + (Affiliation == outcast) or (Affiliation == publish_only) -> + {error, ?ERR_FORBIDDEN}; + (AccessModel == presence) and not PresenceSubscription -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)}; + (AccessModel == roster) and not RosterGroup -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; + (AccessModel == whitelist) and not Whitelisted -> + {error, + ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + (AccessModel == authorize) and not Whitelisted -> + {error, ?ERR_FORBIDDEN}; + %%MustPay -> + %% % Payment is required for a subscription + %% {error, ?ERR_PAYMENT_REQUIRED}; + true -> + get_item(Nidx, ItemId) + end. + +set_item(Item) -> + {ItemId, Nidx} = Item#pubsub_item.itemid, + I = ejabberd_sql:escape(ItemId), + {C, _} = Item#pubsub_item.creation, + {M, JID} = Item#pubsub_item.modification, + P = encode_jid(JID), + Payload = Item#pubsub_item.payload, + XML = ejabberd_sql:escape(str:join([fxml:element_to_binary(X) || X<-Payload], <<>>)), + S = fun ({T1, T2, T3}) -> + str:join([jlib:i2l(T1, 6), jlib:i2l(T2, 6), jlib:i2l(T3, 6)], <<":">>) + end, + case catch + ejabberd_sql:sql_query_t([<<"update pubsub_item set publisher='">>, P, + <<"', modification='">>, S(M), + <<"', payload='">>, XML, + <<"' where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>]) + of + {updated, 1} -> + ok; + _ -> + catch + ejabberd_sql:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, " + "publisher, creation, modification, payload) " + "values('">>, Nidx, <<"', '">>, I, <<"', '">>, P, + <<"', '">>, S(C), <<"', '">>, S(M), + <<"', '">>, XML, <<"');">>]) + end, + ok. + +del_item(Nidx, ItemId) -> + I = ejabberd_sql:escape(ItemId), + catch ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid='">>, + I, <<"' and nodeid='">>, Nidx, <<"';">>]). + +del_items(_, []) -> + ok; +del_items(Nidx, [ItemId]) -> + del_item(Nidx, ItemId); +del_items(Nidx, ItemIds) -> + I = str:join([[<<"'">>, ejabberd_sql:escape(X), <<"'">>] || X <- ItemIds], <<",">>), + catch + ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid in (">>, + I, <<") and nodeid='">>, Nidx, <<"';">>]). + +get_item_name(_Host, _Node, Id) -> + Id. + +node_to_path(Node) -> + node_flat:node_to_path(Node). + +path_to_node(Path) -> + node_flat:path_to_node(Path). + + +first_in_list(_Pred, []) -> + false; +first_in_list(Pred, [H | T]) -> + case Pred(H) of + true -> {value, H}; + _ -> first_in_list(Pred, T) + end. + +itemids(Nidx, {U, S, R}) -> + itemids(Nidx, encode_jid({U, S, R})); +itemids(Nidx, SJID) -> + case catch + ejabberd_sql:sql_query_t([<<"select itemid from pubsub_item where " + "nodeid='">>, Nidx, <<"' and publisher like '">>, SJID, + <<"%' order by modification desc;">>]) + of + {selected, [<<"itemid">>], RItems} -> + [ItemId || [ItemId] <- RItems]; + _ -> + [] + end. + +select_affiliation_subscriptions(Nidx, JID) -> + J = encode_jid(JID), + case catch + ejabberd_sql:sql_query_t([<<"select affiliation,subscriptions from " + "pubsub_state where nodeid='">>, + Nidx, <<"' and jid='">>, J, <<"';">>]) + of + {selected, [<<"affiliation">>, <<"subscriptions">>], [[A, S]]} -> + {decode_affiliation(A), decode_subscriptions(S)}; + _ -> + {none, []} + end. + +select_affiliation_subscriptions(Nidx, JID, JID) -> + select_affiliation_subscriptions(Nidx, JID); +select_affiliation_subscriptions(Nidx, GenKey, SubKey) -> + {result, Affiliation} = get_affiliation(Nidx, GenKey), + {result, Subscriptions} = get_subscriptions(Nidx, SubKey), + {Affiliation, Subscriptions}. + +update_affiliation(Nidx, JID, Affiliation) -> + J = encode_jid(JID), + A = encode_affiliation(Affiliation), + case catch + ejabberd_sql:sql_query_t([<<"update pubsub_state set affiliation='">>, + A, <<"' where nodeid='">>, Nidx, + <<"' and jid='">>, J, <<"';">>]) + of + {updated, 1} -> + ok; + _ -> + catch + ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) " + "values('">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '');">>]) + end. + +update_subscription(Nidx, JID, Subscription) -> + J = encode_jid(JID), + S = encode_subscriptions(Subscription), + case catch + ejabberd_sql:sql_query_t([<<"update pubsub_state set subscriptions='">>, S, + <<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>]) + of + {updated, 1} -> + ok; + _ -> + catch + ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) " + "values('">>, Nidx, <<"', '">>, J, <<"', 'n', '">>, S, <<"');">>]) + end. + +-spec(decode_jid/1 :: + ( SJID :: binary()) + -> ljid() + ). +decode_jid(SJID) -> + jid:tolower(jid:from_string(SJID)). + +-spec(decode_affiliation/1 :: + ( Arg :: binary()) + -> atom() + ). +decode_affiliation(<<"o">>) -> owner; +decode_affiliation(<<"p">>) -> publisher; +decode_affiliation(<<"u">>) -> publish_only; +decode_affiliation(<<"m">>) -> member; +decode_affiliation(<<"c">>) -> outcast; +decode_affiliation(_) -> none. + +-spec(decode_subscription/1 :: + ( Arg :: binary()) + -> atom() + ). +decode_subscription(<<"s">>) -> subscribed; +decode_subscription(<<"p">>) -> pending; +decode_subscription(<<"u">>) -> unconfigured; +decode_subscription(_) -> none. + +-spec(decode_subscriptions/1 :: + ( Subscriptions :: binary()) + -> [] | [{atom(), binary()},...] + ). +decode_subscriptions(Subscriptions) -> + lists:foldl(fun (Subscription, Acc) -> + case str:tokens(Subscription, <<":">>) of + [S, SubId] -> [{decode_subscription(S), SubId} | Acc]; + _ -> Acc + end + end, + [], str:tokens(Subscriptions, <<",">>)). + +-spec(encode_jid/1 :: + ( JID :: ljid()) + -> binary() + ). +encode_jid(JID) -> + ejabberd_sql:escape(jid:to_string(JID)). + +-spec(encode_host/1 :: + ( Host :: host()) + -> binary() + ). +encode_host({_U, _S, _R} = LJID) -> encode_jid(LJID); +encode_host(Host) -> ejabberd_sql:escape(Host). + +-spec(encode_affiliation/1 :: + ( Arg :: atom()) + -> binary() + ). +encode_affiliation(owner) -> <<"o">>; +encode_affiliation(publisher) -> <<"p">>; +encode_affiliation(publish_only) -> <<"u">>; +encode_affiliation(member) -> <<"m">>; +encode_affiliation(outcast) -> <<"c">>; +encode_affiliation(_) -> <<"n">>. + +-spec(encode_subscription/1 :: + ( Arg :: atom()) + -> binary() + ). +encode_subscription(subscribed) -> <<"s">>; +encode_subscription(pending) -> <<"p">>; +encode_subscription(unconfigured) -> <<"u">>; +encode_subscription(_) -> <<"n">>. + +-spec(encode_subscriptions/1 :: + ( Subscriptions :: [] | [{atom(), binary()},...]) + -> binary() + ). +encode_subscriptions(Subscriptions) -> + str:join([<<(encode_subscription(S))/binary, ":", SubId/binary>> + || {S, SubId} <- Subscriptions], <<",">>). + +%%% record getter/setter + +state_to_raw(Nidx, State) -> + {JID, _} = State#pubsub_state.stateid, + J = encode_jid(JID), + A = encode_affiliation(State#pubsub_state.affiliation), + S = encode_subscriptions(State#pubsub_state.subscriptions), + [<<"'">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"'">>]. + +raw_to_item(Nidx, [ItemId, SJID, Creation, Modification, XML]) -> + JID = decode_jid(SJID), + ToTime = fun (Str) -> + [T1, T2, T3] = str:tokens(Str, <<":">>), + {jlib:l2i(T1), jlib:l2i(T2), jlib:l2i(T3)} + end, + Payload = case fxml_stream:parse_element(XML) of + {error, _Reason} -> []; + El -> [El] + end, + #pubsub_item{itemid = {ItemId, Nidx}, + creation = {ToTime(Creation), JID}, + modification = {ToTime(Modification), JID}, + payload = Payload}. diff --git a/src/node_hometree_odbc.erl b/src/node_hometree_odbc.erl deleted file mode 100644 index 6ac5c37b..00000000 --- a/src/node_hometree_odbc.erl +++ /dev/null @@ -1,159 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : node_hometree_odbc.erl -%%% Author : Christophe Romain -%%% Purpose : Standard tree ordered node plugin with ODBC backend -%%% Created : 1 Dec 2007 by Christophe Romain -%%% -%%% -%%% 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. -%%% -%%%---------------------------------------------------------------------- - --module(node_hometree_odbc). --behaviour(gen_pubsub_node). --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). --include("jlib.hrl"). - --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1, - get_entity_subscriptions_for_send_last/2, get_last_items/3]). - -init(Host, ServerHost, Opts) -> - node_flat_odbc:init(Host, ServerHost, Opts), - Owner = mod_pubsub:service_jid(Host), - mod_pubsub:create_node(Host, ServerHost, <<"/home">>, Owner, <<"hometree">>), - mod_pubsub:create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, Owner, <<"hometree">>), - ok. - -terminate(Host, ServerHost) -> - node_flat_odbc:terminate(Host, ServerHost). - -options() -> - [{odbc, true}, {rsm, true} | node_hometree:options()]. - -features() -> - [<<"rsm">> | node_hometree:features()]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). - -create_node(Nidx, Owner) -> - node_flat_odbc:create_node(Nidx, Owner). - -delete_node(Nodes) -> - node_flat_odbc:delete_node(Nodes). - -subscribe_node(Nidx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_flat_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). - -unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> - node_flat_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId). - -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_flat_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). - -remove_extra_items(Nidx, MaxItems, ItemIds) -> - node_flat_odbc:remove_extra_items(Nidx, MaxItems, ItemIds). - -delete_item(Nidx, Publisher, PublishModel, ItemId) -> - node_flat_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId). - -purge_node(Nidx, Owner) -> - node_flat_odbc:purge_node(Nidx, Owner). - -get_entity_affiliations(Host, Owner) -> - node_flat_odbc:get_entity_affiliations(Host, Owner). - -get_node_affiliations(Nidx) -> - node_flat_odbc:get_node_affiliations(Nidx). - -get_affiliation(Nidx, Owner) -> - node_flat_odbc:get_affiliation(Nidx, Owner). - -set_affiliation(Nidx, Owner, Affiliation) -> - node_flat_odbc:set_affiliation(Nidx, Owner, Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_flat_odbc:get_entity_subscriptions(Host, Owner). - -get_entity_subscriptions_for_send_last(Host, Owner) -> - node_flat_odbc:get_entity_subscriptions_for_send_last(Host, Owner). - -get_node_subscriptions(Nidx) -> - node_flat_odbc:get_node_subscriptions(Nidx). - -get_subscriptions(Nidx, Owner) -> - node_flat_odbc:get_subscriptions(Nidx, Owner). - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - node_flat_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_flat_odbc:get_pending_nodes(Host, Owner). - -get_states(Nidx) -> - node_flat_odbc:get_states(Nidx). - -get_state(Nidx, JID) -> - node_flat_odbc:get_state(Nidx, JID). - -set_state(State) -> - node_flat_odbc:set_state(State). - -get_items(Nidx, From, RSM) -> - node_flat_odbc:get_items(Nidx, From, RSM). - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_flat_odbc:get_items(Nidx, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM). - -get_item(Nidx, ItemId) -> - node_flat_odbc:get_item(Nidx, ItemId). - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_flat_odbc:get_item(Nidx, ItemId, JID, - AccessModel, PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_flat_odbc:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_flat_odbc:get_item_name(Host, Node, Id). - -get_last_items(Nidx, From, Count) -> - node_flat_odbc:get_last_items(Nidx, From, Count). - -node_to_path(Node) -> - node_hometree:node_to_path(Node). - -path_to_node(Path) -> - node_hometree:path_to_node(Path). - diff --git a/src/node_hometree_sql.erl b/src/node_hometree_sql.erl new file mode 100644 index 00000000..4ce6f855 --- /dev/null +++ b/src/node_hometree_sql.erl @@ -0,0 +1,159 @@ +%%%---------------------------------------------------------------------- +%%% File : node_hometree_sql.erl +%%% Author : Christophe Romain +%%% Purpose : Standard tree ordered node plugin with ODBC backend +%%% Created : 1 Dec 2007 by Christophe Romain +%%% +%%% +%%% 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. +%%% +%%%---------------------------------------------------------------------- + +-module(node_hometree_sql). +-behaviour(gen_pubsub_node). +-author('christophe.romain@process-one.net'). + +-include("pubsub.hrl"). +-include("jlib.hrl"). + +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, + get_entity_subscriptions/2, get_node_subscriptions/1, + get_subscriptions/2, set_subscriptions/4, + get_pending_nodes/2, get_states/1, get_state/2, + set_state/1, get_items/7, get_items/3, get_item/7, + get_item/2, set_item/1, get_item_name/3, node_to_path/1, + path_to_node/1, + get_entity_subscriptions_for_send_last/2, get_last_items/3]). + +init(Host, ServerHost, Opts) -> + node_flat_sql:init(Host, ServerHost, Opts), + Owner = mod_pubsub:service_jid(Host), + mod_pubsub:create_node(Host, ServerHost, <<"/home">>, Owner, <<"hometree">>), + mod_pubsub:create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, Owner, <<"hometree">>), + ok. + +terminate(Host, ServerHost) -> + node_flat_sql:terminate(Host, ServerHost). + +options() -> + [{sql, true}, {rsm, true} | node_hometree:options()]. + +features() -> + [<<"rsm">> | node_hometree:features()]. + +create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> + node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). + +create_node(Nidx, Owner) -> + node_flat_sql:create_node(Nidx, Owner). + +delete_node(Nodes) -> + node_flat_sql:delete_node(Nodes). + +subscribe_node(Nidx, Sender, Subscriber, AccessModel, + SendLast, PresenceSubscription, RosterGroup, Options) -> + node_flat_sql:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, + PresenceSubscription, RosterGroup, Options). + +unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> + node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId). + +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> + node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). + +remove_extra_items(Nidx, MaxItems, ItemIds) -> + node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds). + +delete_item(Nidx, Publisher, PublishModel, ItemId) -> + node_flat_sql:delete_item(Nidx, Publisher, PublishModel, ItemId). + +purge_node(Nidx, Owner) -> + node_flat_sql:purge_node(Nidx, Owner). + +get_entity_affiliations(Host, Owner) -> + node_flat_sql:get_entity_affiliations(Host, Owner). + +get_node_affiliations(Nidx) -> + node_flat_sql:get_node_affiliations(Nidx). + +get_affiliation(Nidx, Owner) -> + node_flat_sql:get_affiliation(Nidx, Owner). + +set_affiliation(Nidx, Owner, Affiliation) -> + node_flat_sql:set_affiliation(Nidx, Owner, Affiliation). + +get_entity_subscriptions(Host, Owner) -> + node_flat_sql:get_entity_subscriptions(Host, Owner). + +get_entity_subscriptions_for_send_last(Host, Owner) -> + node_flat_sql:get_entity_subscriptions_for_send_last(Host, Owner). + +get_node_subscriptions(Nidx) -> + node_flat_sql:get_node_subscriptions(Nidx). + +get_subscriptions(Nidx, Owner) -> + node_flat_sql:get_subscriptions(Nidx, Owner). + +set_subscriptions(Nidx, Owner, Subscription, SubId) -> + node_flat_sql:set_subscriptions(Nidx, Owner, Subscription, SubId). + +get_pending_nodes(Host, Owner) -> + node_flat_sql:get_pending_nodes(Host, Owner). + +get_states(Nidx) -> + node_flat_sql:get_states(Nidx). + +get_state(Nidx, JID) -> + node_flat_sql:get_state(Nidx, JID). + +set_state(State) -> + node_flat_sql:set_state(State). + +get_items(Nidx, From, RSM) -> + node_flat_sql:get_items(Nidx, From, RSM). + +get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> + node_flat_sql:get_items(Nidx, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId, RSM). + +get_item(Nidx, ItemId) -> + node_flat_sql:get_item(Nidx, ItemId). + +get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_flat_sql:get_item(Nidx, ItemId, JID, + AccessModel, PresenceSubscription, RosterGroup, SubId). + +set_item(Item) -> + node_flat_sql:set_item(Item). + +get_item_name(Host, Node, Id) -> + node_flat_sql:get_item_name(Host, Node, Id). + +get_last_items(Nidx, From, Count) -> + node_flat_sql:get_last_items(Nidx, From, Count). + +node_to_path(Node) -> + node_hometree:node_to_path(Node). + +path_to_node(Path) -> + node_hometree:path_to_node(Path). + diff --git a/src/node_mix_odbc.erl b/src/node_mix_odbc.erl deleted file mode 100644 index e7cc6883..00000000 --- a/src/node_mix_odbc.erl +++ /dev/null @@ -1,170 +0,0 @@ -%%%------------------------------------------------------------------- -%%% @author Evgeny Khramtsov -%%% @copyright (C) 2016, Evgeny Khramtsov -%%% @doc -%%% -%%% @end -%%% Created : 8 Mar 2016 by Evgeny Khramtsov -%%%------------------------------------------------------------------- --module(node_mix_odbc). - --behaviour(gen_pubsub_node). - -%% API --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1, get_entity_subscriptions_for_send_last/2]). - --include("pubsub.hrl"). - -%%%=================================================================== -%%% API -%%%=================================================================== -init(Host, ServerHost, Opts) -> - node_flat_odbc:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_flat_odbc:terminate(Host, ServerHost). - -options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, open}, - {roster_groups_allowed, []}, - {publish_model, open}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, never}, - {deliver_notifications, true}, - {broadcast_all_resources, true}, - {presence_based_delivery, false}]. - -features() -> - [<<"create-nodes">>, - <<"delete-nodes">>, - <<"delete-items">>, - <<"instant-nodes">>, - <<"item-ids">>, - <<"outcast-affiliation">>, - <<"persistent-items">>, - <<"publish">>, - <<"purge-nodes">>, - <<"retract-items">>, - <<"retrieve-affiliations">>, - <<"retrieve-items">>, - <<"retrieve-subscriptions">>, - <<"subscribe">>, - <<"subscription-notifications">>]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_flat_odbc:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). - -create_node(Nidx, Owner) -> - node_flat_odbc:create_node(Nidx, Owner). - -delete_node(Removed) -> - node_flat_odbc:delete_node(Removed). - -subscribe_node(Nidx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_flat_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). - -unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> - node_flat_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId). - -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_flat_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). - -remove_extra_items(Nidx, MaxItems, ItemIds) -> - node_flat_odbc:remove_extra_items(Nidx, MaxItems, ItemIds). - -delete_item(Nidx, Publisher, PublishModel, ItemId) -> - node_flat_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId). - -purge_node(Nidx, Owner) -> - node_flat_odbc:purge_node(Nidx, Owner). - -get_entity_affiliations(Host, Owner) -> - node_flat_odbc:get_entity_affiliations(Host, Owner). - -get_node_affiliations(Nidx) -> - node_flat_odbc:get_node_affiliations(Nidx). - -get_affiliation(Nidx, Owner) -> - node_flat_odbc:get_affiliation(Nidx, Owner). - -set_affiliation(Nidx, Owner, Affiliation) -> - node_flat_odbc:set_affiliation(Nidx, Owner, Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_flat_odbc:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(Nidx) -> - node_flat_odbc:get_node_subscriptions(Nidx). - -get_subscriptions(Nidx, Owner) -> - node_flat_odbc:get_subscriptions(Nidx, Owner). - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - node_flat_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_flat_odbc:get_pending_nodes(Host, Owner). - -get_states(Nidx) -> - node_flat_odbc:get_states(Nidx). - -get_state(Nidx, JID) -> - node_flat_odbc:get_state(Nidx, JID). - -set_state(State) -> - node_flat_odbc:set_state(State). - -get_items(Nidx, From, RSM) -> - node_flat_odbc:get_items(Nidx, From, RSM). - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_flat_odbc:get_items(Nidx, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM). - -get_item(Nidx, ItemId) -> - node_flat_odbc:get_item(Nidx, ItemId). - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_flat_odbc:get_item(Nidx, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_flat_odbc:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_flat_odbc:get_item_name(Host, Node, Id). - -node_to_path(Node) -> - node_flat_odbc:node_to_path(Node). - -path_to_node(Path) -> - node_flat_odbc:path_to_node(Path). - -get_entity_subscriptions_for_send_last(Host, Owner) -> - node_flat_odbc:get_entity_subscriptions_for_send_last(Host, Owner). - -%%%=================================================================== -%%% Internal functions -%%%=================================================================== diff --git a/src/node_mix_sql.erl b/src/node_mix_sql.erl new file mode 100644 index 00000000..45d471d0 --- /dev/null +++ b/src/node_mix_sql.erl @@ -0,0 +1,170 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 8 Mar 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(node_mix_sql). + +-behaviour(gen_pubsub_node). + +%% API +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, + get_entity_subscriptions/2, get_node_subscriptions/1, + get_subscriptions/2, set_subscriptions/4, + get_pending_nodes/2, get_states/1, get_state/2, + set_state/1, get_items/7, get_items/3, get_item/7, + get_item/2, set_item/1, get_item_name/3, node_to_path/1, + path_to_node/1, get_entity_subscriptions_for_send_last/2]). + +-include("pubsub.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(Host, ServerHost, Opts) -> + node_flat_sql:init(Host, ServerHost, Opts). + +terminate(Host, ServerHost) -> + node_flat_sql:terminate(Host, ServerHost). + +options() -> + [{deliver_payloads, true}, + {notify_config, false}, + {notify_delete, false}, + {notify_retract, true}, + {purge_offline, false}, + {persist_items, true}, + {max_items, ?MAXITEMS}, + {subscribe, true}, + {access_model, open}, + {roster_groups_allowed, []}, + {publish_model, open}, + {notification_type, headline}, + {max_payload_size, ?MAX_PAYLOAD_SIZE}, + {send_last_published_item, never}, + {deliver_notifications, true}, + {broadcast_all_resources, true}, + {presence_based_delivery, false}]. + +features() -> + [<<"create-nodes">>, + <<"delete-nodes">>, + <<"delete-items">>, + <<"instant-nodes">>, + <<"item-ids">>, + <<"outcast-affiliation">>, + <<"persistent-items">>, + <<"publish">>, + <<"purge-nodes">>, + <<"retract-items">>, + <<"retrieve-affiliations">>, + <<"retrieve-items">>, + <<"retrieve-subscriptions">>, + <<"subscribe">>, + <<"subscription-notifications">>]. + +create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> + node_flat_sql:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). + +create_node(Nidx, Owner) -> + node_flat_sql:create_node(Nidx, Owner). + +delete_node(Removed) -> + node_flat_sql:delete_node(Removed). + +subscribe_node(Nidx, Sender, Subscriber, AccessModel, + SendLast, PresenceSubscription, RosterGroup, Options) -> + node_flat_sql:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, + PresenceSubscription, RosterGroup, Options). + +unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> + node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId). + +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> + node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). + +remove_extra_items(Nidx, MaxItems, ItemIds) -> + node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds). + +delete_item(Nidx, Publisher, PublishModel, ItemId) -> + node_flat_sql:delete_item(Nidx, Publisher, PublishModel, ItemId). + +purge_node(Nidx, Owner) -> + node_flat_sql:purge_node(Nidx, Owner). + +get_entity_affiliations(Host, Owner) -> + node_flat_sql:get_entity_affiliations(Host, Owner). + +get_node_affiliations(Nidx) -> + node_flat_sql:get_node_affiliations(Nidx). + +get_affiliation(Nidx, Owner) -> + node_flat_sql:get_affiliation(Nidx, Owner). + +set_affiliation(Nidx, Owner, Affiliation) -> + node_flat_sql:set_affiliation(Nidx, Owner, Affiliation). + +get_entity_subscriptions(Host, Owner) -> + node_flat_sql:get_entity_subscriptions(Host, Owner). + +get_node_subscriptions(Nidx) -> + node_flat_sql:get_node_subscriptions(Nidx). + +get_subscriptions(Nidx, Owner) -> + node_flat_sql:get_subscriptions(Nidx, Owner). + +set_subscriptions(Nidx, Owner, Subscription, SubId) -> + node_flat_sql:set_subscriptions(Nidx, Owner, Subscription, SubId). + +get_pending_nodes(Host, Owner) -> + node_flat_sql:get_pending_nodes(Host, Owner). + +get_states(Nidx) -> + node_flat_sql:get_states(Nidx). + +get_state(Nidx, JID) -> + node_flat_sql:get_state(Nidx, JID). + +set_state(State) -> + node_flat_sql:set_state(State). + +get_items(Nidx, From, RSM) -> + node_flat_sql:get_items(Nidx, From, RSM). + +get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> + node_flat_sql:get_items(Nidx, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId, RSM). + +get_item(Nidx, ItemId) -> + node_flat_sql:get_item(Nidx, ItemId). + +get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_flat_sql:get_item(Nidx, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). + +set_item(Item) -> + node_flat_sql:set_item(Item). + +get_item_name(Host, Node, Id) -> + node_flat_sql:get_item_name(Host, Node, Id). + +node_to_path(Node) -> + node_flat_sql:node_to_path(Node). + +path_to_node(Path) -> + node_flat_sql:path_to_node(Path). + +get_entity_subscriptions_for_send_last(Host, Owner) -> + node_flat_sql:get_entity_subscriptions_for_send_last(Host, Owner). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/src/node_pep_odbc.erl b/src/node_pep_odbc.erl deleted file mode 100644 index 6eb0de04..00000000 --- a/src/node_pep_odbc.erl +++ /dev/null @@ -1,254 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : node_pep_odbc.erl -%%% Author : Christophe Romain -%%% Purpose : Standard PubSub PEP plugin with ODBC backend -%%% Created : 1 Dec 2007 by Christophe Romain -%%% -%%% -%%% 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 {@module} is the pep PubSub plugin. -%%%

PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.

- --module(node_pep_odbc). --behaviour(gen_pubsub_node). --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). --include("jlib.hrl"). --include("logger.hrl"). - --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/6, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1, - get_entity_subscriptions_for_send_last/2, get_last_items/3]). - -init(Host, ServerHost, Opts) -> - node_flat_odbc:init(Host, ServerHost, Opts), - complain_if_modcaps_disabled(ServerHost), - ok. - -terminate(Host, ServerHost) -> - node_flat_odbc:terminate(Host, ServerHost), - ok. - -options() -> - [{odbc, true}, {rsm, true} | node_pep:options()]. - -features() -> - [<<"rsm">> | node_pep:features()]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_pep:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). - -create_node(Nidx, Owner) -> - node_flat_odbc:create_node(Nidx, Owner), - {result, {default, broadcast}}. - -delete_node(Nodes) -> - {result, {_, _, Result}} = node_flat_odbc:delete_node(Nodes), - {result, {[], Result}}. - -subscribe_node(Nidx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_flat_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). - - -unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> - case node_flat_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of - {error, Error} -> {error, Error}; - {result, _} -> {result, []} - end. - -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> - node_flat_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). - -remove_extra_items(Nidx, MaxItems, ItemIds) -> - node_flat_odbc:remove_extra_items(Nidx, MaxItems, ItemIds). - -delete_item(Nidx, Publisher, PublishModel, ItemId) -> - node_flat_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId). - -purge_node(Nidx, Owner) -> - node_flat_odbc:purge_node(Nidx, Owner). - -get_entity_affiliations(_Host, Owner) -> - OwnerKey = jid:tolower(jid:remove_resource(Owner)), - node_flat_odbc:get_entity_affiliations(OwnerKey, Owner). - -get_node_affiliations(Nidx) -> - node_flat_odbc:get_node_affiliations(Nidx). - -get_affiliation(Nidx, Owner) -> - node_flat_odbc:get_affiliation(Nidx, Owner). - -set_affiliation(Nidx, Owner, Affiliation) -> - node_flat_odbc:set_affiliation(Nidx, Owner, Affiliation). - -get_entity_subscriptions(_Host, Owner) -> - SubKey = jid:tolower(Owner), - GenKey = jid:remove_resource(SubKey), - Host = node_flat_odbc:encode_host(element(2, SubKey)), - SJ = node_flat_odbc:encode_jid(SubKey), - GJ = node_flat_odbc:encode_jid(GenKey), - Query = case SubKey of - GenKey -> - [<<"select host, node, type, i.nodeid, jid, " - "subscriptions from pubsub_state i, pubsub_node n " - "where i.nodeid = n.nodeid and jid " - "like '">>, GJ, <<"%' and host like '%@">>, Host, <<"';">>]; - _ -> - [<<"select host, node, type, i.nodeid, jid, " - "subscriptions from pubsub_state i, pubsub_node n " - "where i.nodeid = n.nodeid and jid " - "in ('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, <<"';">>] - end, - Reply = case catch ejabberd_odbc:sql_query_t(Query) of - {selected, - [<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], - RItems} -> - lists:map(fun ([H, N, T, I, J, S]) -> - O = node_flat_odbc:decode_jid(H), - Node = nodetree_tree_odbc:raw_to_node(O, [N, <<"">>, T, I]), - {Node, - node_flat_odbc:decode_subscriptions(S), - node_flat_odbc:decode_jid(J)} - end, - RItems); - _ -> - [] - end, - {result, Reply}. - -get_entity_subscriptions_for_send_last(_Host, Owner) -> - SubKey = jid:tolower(Owner), - GenKey = jid:remove_resource(SubKey), - Host = node_flat_odbc:encode_host(element(2, SubKey)), - SJ = node_flat_odbc:encode_jid(SubKey), - GJ = node_flat_odbc:encode_jid(GenKey), - Query = case SubKey of - GenKey -> - [<<"select host, node, type, i.nodeid, jid, " - "subscriptions from pubsub_state i, pubsub_node n, " - "pubsub_node_option o where i.nodeid = n.nodeid " - "and n.nodeid = o.nodeid and name='send_last_published_item' and " - "val='on_sub_and_presence' and jid like '">>, - GJ, <<"%' and host like '%@">>, Host, <<"';">>]; - _ -> - [<<"select host, node, type, i.nodeid, jid, " - "subscriptions from pubsub_state i, pubsub_node n, " - "pubsub_node_option o where i.nodeid = n.nodeid " - "and n.nodeid = o.nodeid and name='send_last_published_item' and " - "val='on_sub_and_presence' and jid in ", - "('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, <<"';">>] - end, - Reply = case catch ejabberd_odbc:sql_query_t(Query) of - {selected, - [<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], - RItems} -> - lists:map(fun ([H, N, T, I, J, S]) -> - O = node_flat_odbc:decode_jid(H), - Node = nodetree_tree_odbc:raw_to_node(O, [N, <<"">>, T, I]), - {Node, - node_flat_odbc:decode_subscriptions(S), - node_flat_odbc:decode_jid(J)} - end, - RItems); - _ -> - [] - end, - {result, Reply}. - -get_node_subscriptions(Nidx) -> - node_flat_odbc:get_node_subscriptions(Nidx). - -get_subscriptions(Nidx, Owner) -> - node_flat_odbc:get_subscriptions(Nidx, Owner). - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - node_flat_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_flat_odbc:get_pending_nodes(Host, Owner). - -get_states(Nidx) -> - node_flat_odbc:get_states(Nidx). - -get_state(Nidx, JID) -> - node_flat_odbc:get_state(Nidx, JID). - -set_state(State) -> - node_flat_odbc:set_state(State). - -get_items(Nidx, From, RSM) -> - node_flat_odbc:get_items(Nidx, From, RSM). - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_flat_odbc:get_items(Nidx, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM). - -get_last_items(Nidx, JID, Count) -> - node_flat_odbc:get_last_items(Nidx, JID, Count). - -get_item(Nidx, ItemId) -> - node_flat_odbc:get_item(Nidx, ItemId). - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_flat_odbc:get_item(Nidx, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_flat_odbc:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_flat_odbc:get_item_name(Host, Node, Id). - -node_to_path(Node) -> - node_flat_odbc:node_to_path(Node). - -path_to_node(Path) -> - node_flat_odbc:path_to_node(Path). - -%%% -%%% Internal -%%% - -%% @doc Check mod_caps is enabled, otherwise show warning. -%% The PEP plugin for mod_pubsub requires mod_caps to be enabled in the host. -%% Check that the mod_caps module is enabled in that Jabber Host -%% If not, show a warning message in the ejabberd log file. -complain_if_modcaps_disabled(ServerHost) -> - case gen_mod:is_loaded(ServerHost, mod_caps) of - false -> - ?WARNING_MSG("The PEP plugin is enabled in mod_pubsub " - "of host ~p. This plugin requires mod_caps " - "to be enabled, but it isn't.", - [ServerHost]); - true -> ok - end. diff --git a/src/node_pep_sql.erl b/src/node_pep_sql.erl new file mode 100644 index 00000000..c5b31d15 --- /dev/null +++ b/src/node_pep_sql.erl @@ -0,0 +1,254 @@ +%%%---------------------------------------------------------------------- +%%% File : node_pep_sql.erl +%%% Author : Christophe Romain +%%% Purpose : Standard PubSub PEP plugin with ODBC backend +%%% Created : 1 Dec 2007 by Christophe Romain +%%% +%%% +%%% 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 {@module} is the pep PubSub plugin. +%%%

PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.

+ +-module(node_pep_sql). +-behaviour(gen_pubsub_node). +-author('christophe.romain@process-one.net'). + +-include("pubsub.hrl"). +-include("jlib.hrl"). +-include("logger.hrl"). + +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, + get_entity_subscriptions/2, get_node_subscriptions/1, + get_subscriptions/2, set_subscriptions/4, + get_pending_nodes/2, get_states/1, get_state/2, + set_state/1, get_items/7, get_items/3, get_item/7, + get_item/2, set_item/1, get_item_name/3, node_to_path/1, + path_to_node/1, + get_entity_subscriptions_for_send_last/2, get_last_items/3]). + +init(Host, ServerHost, Opts) -> + node_flat_sql:init(Host, ServerHost, Opts), + complain_if_modcaps_disabled(ServerHost), + ok. + +terminate(Host, ServerHost) -> + node_flat_sql:terminate(Host, ServerHost), + ok. + +options() -> + [{sql, true}, {rsm, true} | node_pep:options()]. + +features() -> + [<<"rsm">> | node_pep:features()]. + +create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> + node_pep:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). + +create_node(Nidx, Owner) -> + node_flat_sql:create_node(Nidx, Owner), + {result, {default, broadcast}}. + +delete_node(Nodes) -> + {result, {_, _, Result}} = node_flat_sql:delete_node(Nodes), + {result, {[], Result}}. + +subscribe_node(Nidx, Sender, Subscriber, AccessModel, + SendLast, PresenceSubscription, RosterGroup, Options) -> + node_flat_sql:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, + PresenceSubscription, RosterGroup, Options). + + +unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> + case node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of + {error, Error} -> {error, Error}; + {result, _} -> {result, []} + end. + +publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) -> + node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload). + +remove_extra_items(Nidx, MaxItems, ItemIds) -> + node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds). + +delete_item(Nidx, Publisher, PublishModel, ItemId) -> + node_flat_sql:delete_item(Nidx, Publisher, PublishModel, ItemId). + +purge_node(Nidx, Owner) -> + node_flat_sql:purge_node(Nidx, Owner). + +get_entity_affiliations(_Host, Owner) -> + OwnerKey = jid:tolower(jid:remove_resource(Owner)), + node_flat_sql:get_entity_affiliations(OwnerKey, Owner). + +get_node_affiliations(Nidx) -> + node_flat_sql:get_node_affiliations(Nidx). + +get_affiliation(Nidx, Owner) -> + node_flat_sql:get_affiliation(Nidx, Owner). + +set_affiliation(Nidx, Owner, Affiliation) -> + node_flat_sql:set_affiliation(Nidx, Owner, Affiliation). + +get_entity_subscriptions(_Host, Owner) -> + SubKey = jid:tolower(Owner), + GenKey = jid:remove_resource(SubKey), + Host = node_flat_sql:encode_host(element(2, SubKey)), + SJ = node_flat_sql:encode_jid(SubKey), + GJ = node_flat_sql:encode_jid(GenKey), + Query = case SubKey of + GenKey -> + [<<"select host, node, type, i.nodeid, jid, " + "subscriptions from pubsub_state i, pubsub_node n " + "where i.nodeid = n.nodeid and jid " + "like '">>, GJ, <<"%' and host like '%@">>, Host, <<"';">>]; + _ -> + [<<"select host, node, type, i.nodeid, jid, " + "subscriptions from pubsub_state i, pubsub_node n " + "where i.nodeid = n.nodeid and jid " + "in ('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, <<"';">>] + end, + Reply = case catch ejabberd_sql:sql_query_t(Query) of + {selected, + [<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], + RItems} -> + lists:map(fun ([H, N, T, I, J, S]) -> + O = node_flat_sql:decode_jid(H), + Node = nodetree_tree_sql:raw_to_node(O, [N, <<"">>, T, I]), + {Node, + node_flat_sql:decode_subscriptions(S), + node_flat_sql:decode_jid(J)} + end, + RItems); + _ -> + [] + end, + {result, Reply}. + +get_entity_subscriptions_for_send_last(_Host, Owner) -> + SubKey = jid:tolower(Owner), + GenKey = jid:remove_resource(SubKey), + Host = node_flat_sql:encode_host(element(2, SubKey)), + SJ = node_flat_sql:encode_jid(SubKey), + GJ = node_flat_sql:encode_jid(GenKey), + Query = case SubKey of + GenKey -> + [<<"select host, node, type, i.nodeid, jid, " + "subscriptions from pubsub_state i, pubsub_node n, " + "pubsub_node_option o where i.nodeid = n.nodeid " + "and n.nodeid = o.nodeid and name='send_last_published_item' and " + "val='on_sub_and_presence' and jid like '">>, + GJ, <<"%' and host like '%@">>, Host, <<"';">>]; + _ -> + [<<"select host, node, type, i.nodeid, jid, " + "subscriptions from pubsub_state i, pubsub_node n, " + "pubsub_node_option o where i.nodeid = n.nodeid " + "and n.nodeid = o.nodeid and name='send_last_published_item' and " + "val='on_sub_and_presence' and jid in ", + "('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, <<"';">>] + end, + Reply = case catch ejabberd_sql:sql_query_t(Query) of + {selected, + [<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], + RItems} -> + lists:map(fun ([H, N, T, I, J, S]) -> + O = node_flat_sql:decode_jid(H), + Node = nodetree_tree_sql:raw_to_node(O, [N, <<"">>, T, I]), + {Node, + node_flat_sql:decode_subscriptions(S), + node_flat_sql:decode_jid(J)} + end, + RItems); + _ -> + [] + end, + {result, Reply}. + +get_node_subscriptions(Nidx) -> + node_flat_sql:get_node_subscriptions(Nidx). + +get_subscriptions(Nidx, Owner) -> + node_flat_sql:get_subscriptions(Nidx, Owner). + +set_subscriptions(Nidx, Owner, Subscription, SubId) -> + node_flat_sql:set_subscriptions(Nidx, Owner, Subscription, SubId). + +get_pending_nodes(Host, Owner) -> + node_flat_sql:get_pending_nodes(Host, Owner). + +get_states(Nidx) -> + node_flat_sql:get_states(Nidx). + +get_state(Nidx, JID) -> + node_flat_sql:get_state(Nidx, JID). + +set_state(State) -> + node_flat_sql:set_state(State). + +get_items(Nidx, From, RSM) -> + node_flat_sql:get_items(Nidx, From, RSM). + +get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> + node_flat_sql:get_items(Nidx, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId, RSM). + +get_last_items(Nidx, JID, Count) -> + node_flat_sql:get_last_items(Nidx, JID, Count). + +get_item(Nidx, ItemId) -> + node_flat_sql:get_item(Nidx, ItemId). + +get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_flat_sql:get_item(Nidx, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). + +set_item(Item) -> + node_flat_sql:set_item(Item). + +get_item_name(Host, Node, Id) -> + node_flat_sql:get_item_name(Host, Node, Id). + +node_to_path(Node) -> + node_flat_sql:node_to_path(Node). + +path_to_node(Path) -> + node_flat_sql:path_to_node(Path). + +%%% +%%% Internal +%%% + +%% @doc Check mod_caps is enabled, otherwise show warning. +%% The PEP plugin for mod_pubsub requires mod_caps to be enabled in the host. +%% Check that the mod_caps module is enabled in that Jabber Host +%% If not, show a warning message in the ejabberd log file. +complain_if_modcaps_disabled(ServerHost) -> + case gen_mod:is_loaded(ServerHost, mod_caps) of + false -> + ?WARNING_MSG("The PEP plugin is enabled in mod_pubsub " + "of host ~p. This plugin requires mod_caps " + "to be enabled, but it isn't.", + [ServerHost]); + true -> ok + end. diff --git a/src/nodetree_tree_odbc.erl b/src/nodetree_tree_odbc.erl deleted file mode 100644 index 6c139ad6..00000000 --- a/src/nodetree_tree_odbc.erl +++ /dev/null @@ -1,314 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : nodetree_tree_odbc.erl -%%% Author : Christophe Romain -%%% Purpose : Standard node tree plugin with ODBC backend -%%% Created : 1 Dec 2007 by Christophe Romain -%%% -%%% -%%% 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 {@module} is the default PubSub node tree plugin. -%%%

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.

-%%%

PubSub node tree plugins are using the {@link gen_nodetree} behaviour.

-%%%

The API isn't stabilized yet. 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.

- --module(nodetree_tree_odbc). --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() -> - [{odbc, 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_odbc:encode_host(Host), - N = ejabberd_odbc:escape(Node), - P = ejabberd_odbc:escape(Parent), - Nidx = case nodeidx(Host, Node) of - {result, OldNidx} -> - catch - ejabberd_odbc:sql_query_t([<<"delete from pubsub_node_option where " - "nodeid='">>, OldNidx, <<"';">>]), - catch - ejabberd_odbc:sql_query_t([<<"update pubsub_node set host='">>, - H, <<"' node='">>, N, - <<"' parent='">>, P, - <<"' type='">>, Type, - <<"' where nodeid='">>, - OldNidx, <<"';">>]), - OldNidx; - _ -> - catch - ejabberd_odbc: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_odbc:escape( - list_to_binary( - lists:flatten(io_lib:fwrite("~p", [Value])))), - catch - ejabberd_odbc: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_odbc:encode_host(Host), - N = ejabberd_odbc:escape(Node), - case catch - ejabberd_odbc: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_odbc: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_odbc:encode_host(Host), - case catch - ejabberd_odbc: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

Default node tree does not handle parents, return a list -%% containing just this node.

-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_odbc:encode_host(Host), - N = ejabberd_odbc:escape(Node), - case catch - ejabberd_odbc: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_odbc:encode_host(Host), - N = ejabberd_odbc:escape(Node), - case catch - ejabberd_odbc: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_odbc:encode_host(Host), - N = ejabberd_odbc:escape(Node), - Removed = get_subnodes_tree(Host, Node), - catch ejabberd_odbc: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_odbc: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(<>))), - RValue = element(2, erl_parse:parse_term(Tokens)), - {RKey, RValue} - end, - ROptions), - Module = jlib:binary_to_atom(<<"node_", Type/binary, "_odbc">>), - 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_odbc:encode_host(Host), - N = ejabberd_odbc:escape(Node), - case catch - ejabberd_odbc: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_odbc:get_node_affiliations(Nidx), - [LJID || {LJID, Aff} <- Res, Aff =:= owner]. diff --git a/src/nodetree_tree_sql.erl b/src/nodetree_tree_sql.erl new file mode 100644 index 00000000..b5654339 --- /dev/null +++ b/src/nodetree_tree_sql.erl @@ -0,0 +1,314 @@ +%%%---------------------------------------------------------------------- +%%% File : nodetree_tree_sql.erl +%%% Author : Christophe Romain +%%% Purpose : Standard node tree plugin with ODBC backend +%%% Created : 1 Dec 2007 by Christophe Romain +%%% +%%% +%%% 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 {@module} is the default PubSub node tree plugin. +%%%

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.

+%%%

PubSub node tree plugins are using the {@link gen_nodetree} behaviour.

+%%%

The API isn't stabilized yet. 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.

+ +-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

Default node tree does not handle parents, return a list +%% containing just this node.

+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(<>))), + 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]. diff --git a/src/odbc_queries.erl b/src/odbc_queries.erl deleted file mode 100644 index c12931c6..00000000 --- a/src/odbc_queries.erl +++ /dev/null @@ -1,645 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : odbc_queries.erl -%%% Author : Mickael Remond -%%% Purpose : ODBC queries dependind on back-end -%%% Created : by Mickael Remond -%%% -%%% -%%% 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. -%%% -%%%---------------------------------------------------------------------- - --module(odbc_queries). - --compile([{parse_transform, ejabberd_sql_pt}]). - --behaviour(ejabberd_config). - --author("mremond@process-one.net"). - --export([get_db_type/0, update/5, update_t/4, - sql_transaction/2, get_last/2, set_last_t/4, del_last/2, - get_password/2, get_password_scram/2, set_password_t/3, - set_password_scram_t/6, add_user/3, add_user_scram/6, - del_user/2, del_user_return_password/3, list_users/1, - list_users/2, users_number/1, users_number/2, - add_spool_sql/2, add_spool/2, get_and_del_spool_msg_t/2, - del_spool_msg/2, get_roster/2, get_roster_jid_groups/2, - get_roster_groups/3, del_user_roster_t/2, - get_roster_by_jid/3, get_rostergroup_by_jid/3, - del_roster/3, del_roster_sql/2, update_roster/5, - update_roster_sql/4, roster_subscribe/1, - get_subscription/3, set_private_data/4, - set_private_data_sql/3, get_private_data/3, - get_private_data/2, del_user_private_storage/2, - get_default_privacy_list/2, - get_default_privacy_list_t/1, get_privacy_list_names/2, - get_privacy_list_names_t/1, get_privacy_list_id/3, - get_privacy_list_id_t/2, get_privacy_list_data/3, - get_privacy_list_data_by_id/2, - get_privacy_list_data_t/2, - get_privacy_list_data_by_id_t/1, - set_default_privacy_list/2, - unset_default_privacy_list/2, remove_privacy_list/2, - add_privacy_list/2, set_privacy_list/2, - del_privacy_lists/2, set_vcard/26, get_vcard/2, - escape/1, count_records_where/3, get_roster_version/2, - set_roster_version/2, opt_type/1]). - --include("ejabberd.hrl"). --include("logger.hrl"). --include("ejabberd_sql_pt.hrl"). - -%% Almost a copy of string:join/2. -%% We use this version because string:join/2 is relatively -%% new function (introduced in R12B-0). -join([], _Sep) -> []; -join([H | T], Sep) -> [H, [[Sep, X] || X <- T]]. - -get_db_type() -> generic. - -%% Safe atomic update. -update_t(Table, Fields, Vals, Where) -> - UPairs = lists:zipwith(fun (A, B) -> - <
> - end, - Fields, Vals), - case ejabberd_odbc:sql_query_t([<<"update ">>, Table, - <<" set ">>, join(UPairs, <<", ">>), - <<" where ">>, Where, <<";">>]) - of - {updated, 1} -> ok; - _ -> - Res = ejabberd_odbc:sql_query_t([<<"insert into ">>, Table, - <<"(">>, join(Fields, <<", ">>), - <<") values ('">>, join(Vals, <<"', '">>), - <<"');">>]), - case Res of - {updated,1} -> ok; - _ -> Res - end - end. - -update(LServer, Table, Fields, Vals, Where) -> - UPairs = lists:zipwith(fun (A, B) -> - <> - end, - Fields, Vals), - case ejabberd_odbc:sql_query(LServer, - [<<"update ">>, Table, <<" set ">>, - join(UPairs, <<", ">>), <<" where ">>, Where, - <<";">>]) - of - {updated, 1} -> ok; - _ -> - Res = ejabberd_odbc:sql_query(LServer, - [<<"insert into ">>, Table, <<"(">>, - join(Fields, <<", ">>), <<") values ('">>, - join(Vals, <<"', '">>), <<"');">>]), - case Res of - {updated,1} -> ok; - _ -> Res - end - end. - -%% F can be either a fun or a list of queries -%% TODO: We should probably move the list of queries transaction -%% wrapper from the ejabberd_odbc module to this one (odbc_queries) -sql_transaction(LServer, F) -> - ejabberd_odbc:sql_transaction(LServer, F). - -get_last(LServer, LUser) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(seconds)d, @(state)s from last" - " where username=%(LUser)s")). - -set_last_t(LServer, LUser, TimeStamp, Status) -> - ?SQL_UPSERT(LServer, "last", - ["!username=%(LUser)s", - "seconds=%(TimeStamp)d", - "state=%(Status)s"]). - -del_last(LServer, LUser) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("delete from last where username=%(LUser)s")). - -get_password(LServer, LUser) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(password)s from users where username=%(LUser)s")). - -get_password_scram(LServer, LUser) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(password)s, @(serverkey)s, @(salt)s, @(iterationcount)d" - " from users" - " where username=%(LUser)s")). - -set_password_t(LServer, LUser, Password) -> - ejabberd_odbc:sql_transaction( - LServer, - fun () -> - ?SQL_UPSERT_T( - "users", - ["!username=%(LUser)s", - "password=%(Password)s"]) - end). - -set_password_scram_t(LServer, LUser, - StoredKey, ServerKey, Salt, IterationCount) -> - ejabberd_odbc:sql_transaction( - LServer, - fun () -> - ?SQL_UPSERT_T( - "users", - ["!username=%(LUser)s", - "password=%(StoredKey)s", - "serverkey=%(ServerKey)s", - "salt=%(Salt)s", - "iterationcount=%(IterationCount)d"]) - end). - -add_user(LServer, LUser, Password) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("insert into users(username, password) " - "values (%(LUser)s, %(Password)s)")). - -add_user_scram(LServer, LUser, - StoredKey, ServerKey, Salt, IterationCount) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("insert into users(username, password, serverkey, salt, " - "iterationcount) " - "values (%(LUser)s, %(StoredKey)s, %(ServerKey)s," - " %(Salt)s, %(IterationCount)d)")). - -del_user(LServer, LUser) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("delete from users where username=%(LUser)s")). - -del_user_return_password(_LServer, LUser, Password) -> - P = - ejabberd_odbc:sql_query_t( - ?SQL("select @(password)s from users where username=%(LUser)s")), - ejabberd_odbc:sql_query_t( - ?SQL("delete from users" - " where username=%(LUser)s and password=%(Password)s")), - P. - -list_users(LServer) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(username)s from users")). - -list_users(LServer, [{from, Start}, {to, End}]) - when is_integer(Start) and is_integer(End) -> - list_users(LServer, - [{limit, End - Start + 1}, {offset, Start - 1}]); -list_users(LServer, - [{prefix, Prefix}, {from, Start}, {to, End}]) - when is_binary(Prefix) and is_integer(Start) and - is_integer(End) -> - list_users(LServer, - [{prefix, Prefix}, {limit, End - Start + 1}, - {offset, Start - 1}]); -list_users(LServer, [{limit, Limit}, {offset, Offset}]) - when is_integer(Limit) and is_integer(Offset) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(username)s from users " - "order by username " - "limit %(Limit)d offset %(Offset)d")); -list_users(LServer, - [{prefix, Prefix}, {limit, Limit}, {offset, Offset}]) - when is_binary(Prefix) and is_integer(Limit) and - is_integer(Offset) -> - SPrefix = ejabberd_odbc:escape_like_arg(Prefix), - SPrefix2 = <>, - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(username)s from users " - "where username like %(SPrefix2)s " - "order by username " - "limit %(Limit)d offset %(Offset)d")). - -users_number(LServer) -> - ejabberd_odbc:sql_query( - LServer, - fun(pgsql, _) -> - case - ejabberd_config:get_option( - {pgsql_users_number_estimate, LServer}, - fun(V) when is_boolean(V) -> V end, - false) of - true -> - ejabberd_odbc:sql_query_t( - ?SQL("select @(reltuples :: bigint)d from pg_class" - " where oid = 'users'::regclass::oid")); - _ -> - ejabberd_odbc:sql_query_t( - ?SQL("select @(count(*))d from users")) - end; - (_Type, _) -> - ejabberd_odbc:sql_query_t( - ?SQL("select @(count(*))d from users")) - end). - -users_number(LServer, [{prefix, Prefix}]) - when is_binary(Prefix) -> - SPrefix = ejabberd_odbc:escape_like_arg(Prefix), - SPrefix2 = <>, - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(count(*))d from users " - "where username like %(SPrefix2)s")); -users_number(LServer, []) -> - users_number(LServer). - - -add_spool_sql(Username, XML) -> - [<<"insert into spool(username, xml) values ('">>, - Username, <<"', '">>, XML, <<"');">>]. - -add_spool(LServer, Queries) -> - ejabberd_odbc:sql_transaction(LServer, Queries). - -get_and_del_spool_msg_t(LServer, LUser) -> - F = fun () -> - Result = - ejabberd_odbc:sql_query_t( - ?SQL("select @(username)s, @(xml)s from spool where " - "username=%(LUser)s order by seq;")), - ejabberd_odbc:sql_query_t( - ?SQL("delete from spool where username=%(LUser)s;")), - Result - end, - ejabberd_odbc:sql_transaction(LServer, F). - -del_spool_msg(LServer, LUser) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("delete from spool where username=%(LUser)s")). - -get_roster(LServer, LUser) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s, " - "@(ask)s, @(askmessage)s, @(server)s, @(subscribe)s, " - "@(type)s from rosterusers where username=%(LUser)s")). - -get_roster_jid_groups(LServer, LUser) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(jid)s, @(grp)s from rostergroups where " - "username=%(LUser)s")). - -get_roster_groups(_LServer, LUser, SJID) -> - ejabberd_odbc:sql_query_t( - ?SQL("select @(grp)s from rostergroups" - " where username=%(LUser)s and jid=%(SJID)s")). - -del_user_roster_t(LServer, LUser) -> - ejabberd_odbc:sql_transaction( - LServer, - fun () -> - ejabberd_odbc:sql_query_t( - ?SQL("delete from rosterusers where username=%(LUser)s")), - ejabberd_odbc:sql_query_t( - ?SQL("delete from rostergroups where username=%(LUser)s")) - end). - -get_roster_by_jid(_LServer, LUser, SJID) -> - ejabberd_odbc:sql_query_t( - ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s," - " @(ask)s, @(askmessage)s, @(server)s, @(subscribe)s," - " @(type)s from rosterusers" - " where username=%(LUser)s and jid=%(SJID)s")). - -get_rostergroup_by_jid(LServer, LUser, SJID) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(grp)s from rostergroups" - " where username=%(LUser)s and jid=%(SJID)s")). - -del_roster(_LServer, LUser, SJID) -> - ejabberd_odbc:sql_query_t( - ?SQL("delete from rosterusers" - " where username=%(LUser)s and jid=%(SJID)s")), - ejabberd_odbc:sql_query_t( - ?SQL("delete from rostergroups" - " where username=%(LUser)s and jid=%(SJID)s")). - -del_roster_sql(Username, SJID) -> - [[<<"delete from rosterusers where " - "username='">>, - Username, <<"' and jid='">>, SJID, <<"';">>], - [<<"delete from rostergroups where " - "username='">>, - Username, <<"' and jid='">>, SJID, <<"';">>]]. - -update_roster(_LServer, LUser, SJID, ItemVals, - ItemGroups) -> - roster_subscribe(ItemVals), - ejabberd_odbc:sql_query_t( - ?SQL("delete from rostergroups" - " where username=%(LUser)s and jid=%(SJID)s")), - lists:foreach( - fun(ItemGroup) -> - ejabberd_odbc:sql_query_t( - ?SQL("insert into rostergroups(username, jid, grp) " - "values (%(LUser)s, %(SJID)s, %(ItemGroup)s)")) - end, - ItemGroups). - -update_roster_sql(Username, SJID, ItemVals, - ItemGroups) -> - [[<<"delete from rosterusers where " - "username='">>, - Username, <<"' and jid='">>, SJID, <<"';">>], - [<<"insert into rosterusers( " - " username, jid, nick, " - " subscription, ask, askmessage, " - " server, subscribe, type) " - "values ('">>, - join(ItemVals, <<"', '">>), <<"');">>], - [<<"delete from rostergroups where " - "username='">>, - Username, <<"' and jid='">>, SJID, <<"';">>]] - ++ - [[<<"insert into rostergroups( " - " username, jid, grp) values ('">>, - join(ItemGroup, <<"', '">>), <<"');">>] - || ItemGroup <- ItemGroups]. - -roster_subscribe({LUser, SJID, Name, SSubscription, SAsk, AskMessage}) -> - ?SQL_UPSERT_T( - "rosterusers", - ["!username=%(LUser)s", - "!jid=%(SJID)s", - "nick=%(Name)s", - "subscription=%(SSubscription)s", - "ask=%(SAsk)s", - "askmessage=%(AskMessage)s", - "server='N'", - "subscribe=''", - "type='item'"]). - -get_subscription(LServer, LUser, SJID) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(subscription)s from rosterusers " - "where username=%(LUser)s and jid=%(SJID)s")). - -set_private_data(_LServer, LUser, XMLNS, SData) -> - ?SQL_UPSERT_T( - "private_storage", - ["!username=%(LUser)s", - "!namespace=%(XMLNS)s", - "data=%(SData)s"]). - -set_private_data_sql(Username, LXMLNS, SData) -> - [[<<"delete from private_storage where username='">>, - Username, <<"' and namespace='">>, LXMLNS, <<"';">>], - [<<"insert into private_storage(username, " - "namespace, data) values ('">>, - Username, <<"', '">>, LXMLNS, <<"', '">>, SData, - <<"');">>]]. - -get_private_data(LServer, LUser, XMLNS) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(data)s from private_storage" - " where username=%(LUser)s and namespace=%(XMLNS)s")). - -get_private_data(LServer, LUser) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(namespace)s, @(data)s from private_storage" - " where username=%(LUser)s")). - -del_user_private_storage(LServer, LUser) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("delete from private_storage" - " where username=%(LUser)s")). - -set_vcard(LServer, LUser, BDay, CTRY, EMail, FN, - Family, Given, LBDay, LCTRY, LEMail, LFN, - LFamily, LGiven, LLocality, LMiddle, LNickname, - LOrgName, LOrgUnit, Locality, Middle, Nickname, - OrgName, OrgUnit, SVCARD, User) -> - ejabberd_odbc:sql_transaction( - LServer, - fun() -> - ?SQL_UPSERT(LServer, "vcard", - ["!username=%(LUser)s", - "vcard=%(SVCARD)s"]), - ?SQL_UPSERT(LServer, "vcard_search", - ["username=%(User)s", - "!lusername=%(LUser)s", - "fn=%(FN)s", - "lfn=%(LFN)s", - "family=%(Family)s", - "lfamily=%(LFamily)s", - "given=%(Given)s", - "lgiven=%(LGiven)s", - "middle=%(Middle)s", - "lmiddle=%(LMiddle)s", - "nickname=%(Nickname)s", - "lnickname=%(LNickname)s", - "bday=%(BDay)s", - "lbday=%(LBDay)s", - "ctry=%(CTRY)s", - "lctry=%(LCTRY)s", - "locality=%(Locality)s", - "llocality=%(LLocality)s", - "email=%(EMail)s", - "lemail=%(LEMail)s", - "orgname=%(OrgName)s", - "lorgname=%(LOrgName)s", - "orgunit=%(OrgUnit)s", - "lorgunit=%(LOrgUnit)s"]) - end). - -get_vcard(LServer, LUser) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(vcard)s from vcard where username=%(LUser)s")). - -get_default_privacy_list(LServer, LUser) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(name)s from privacy_default_list " - "where username=%(LUser)s")). - -get_default_privacy_list_t(LUser) -> - ejabberd_odbc:sql_query_t( - ?SQL("select @(name)s from privacy_default_list " - "where username=%(LUser)s")). - -get_privacy_list_names(LServer, LUser) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(name)s from privacy_list" - " where username=%(LUser)s")). - -get_privacy_list_names_t(LUser) -> - ejabberd_odbc:sql_query_t( - ?SQL("select @(name)s from privacy_list" - " where username=%(LUser)s")). - -get_privacy_list_id(LServer, LUser, Name) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(id)d from privacy_list" - " where username=%(LUser)s and name=%(Name)s")). - -get_privacy_list_id_t(LUser, Name) -> - ejabberd_odbc:sql_query_t( - ?SQL("select @(id)d from privacy_list" - " where username=%(LUser)s and name=%(Name)s")). - -get_privacy_list_data(LServer, LUser, Name) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, " - "@(match_iq)b, @(match_message)b, @(match_presence_in)b, " - "@(match_presence_out)b from privacy_list_data " - "where id =" - " (select id from privacy_list" - " where username=%(LUser)s and name=%(Name)s) " - "order by ord")). - -%% Not used? -get_privacy_list_data_t(LUser, Name) -> - ejabberd_odbc:sql_query_t( - ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, " - "@(match_iq)b, @(match_message)b, @(match_presence_in)b, " - "@(match_presence_out)b from privacy_list_data " - "where id =" - " (select id from privacy_list" - " where username=%(LUser)s and name=%(Name)s) " - "order by ord")). - -get_privacy_list_data_by_id(LServer, ID) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, " - "@(match_iq)b, @(match_message)b, @(match_presence_in)b, " - "@(match_presence_out)b from privacy_list_data " - "where id=%(ID)d order by ord")). - -get_privacy_list_data_by_id_t(ID) -> - ejabberd_odbc:sql_query_t( - ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, " - "@(match_iq)b, @(match_message)b, @(match_presence_in)b, " - "@(match_presence_out)b from privacy_list_data " - "where id=%(ID)d order by ord")). - -set_default_privacy_list(LUser, Name) -> - ?SQL_UPSERT_T( - "privacy_default_list", - ["!username=%(LUser)s", - "name=%(Name)s"]). - -unset_default_privacy_list(LServer, LUser) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("delete from privacy_default_list" - " where username=%(LUser)s")). - -remove_privacy_list(LUser, Name) -> - ejabberd_odbc:sql_query_t( - ?SQL("delete from privacy_list where" - " username=%(LUser)s and name=%(Name)s")). - -add_privacy_list(LUser, Name) -> - ejabberd_odbc:sql_query_t( - ?SQL("insert into privacy_list(username, name) " - "values (%(LUser)s, %(Name)s)")). - -set_privacy_list(ID, RItems) -> - ejabberd_odbc:sql_query_t( - ?SQL("delete from privacy_list_data where id=%(ID)d")), - lists:foreach( - fun({SType, SValue, SAction, Order, MatchAll, MatchIQ, - MatchMessage, MatchPresenceIn, MatchPresenceOut}) -> - ejabberd_odbc:sql_query_t( - ?SQL("insert into privacy_list_data(id, t, " - "value, action, ord, match_all, match_iq, " - "match_message, match_presence_in, match_presence_out) " - "values (%(ID)d, %(SType)s, %(SValue)s, %(SAction)s," - " %(Order)d, %(MatchAll)b, %(MatchIQ)b," - " %(MatchMessage)b, %(MatchPresenceIn)b," - " %(MatchPresenceOut)b)")) - end, - RItems). - -del_privacy_lists(LServer, LUser) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("delete from privacy_list where username=%(LUser)s")), - %US = <>, - %ejabberd_odbc:sql_query( - % LServer, - % ?SQL("delete from privacy_list_data where value=%(US)s")), - ejabberd_odbc:sql_query( - LServer, - ?SQL("delete from privacy_default_list where username=%(LUser)s")). - -%% Characters to escape -escape($\000) -> <<"\\0">>; -escape($\n) -> <<"\\n">>; -escape($\t) -> <<"\\t">>; -escape($\b) -> <<"\\b">>; -escape($\r) -> <<"\\r">>; -escape($') -> <<"''">>; -escape($") -> <<"\\\"">>; -escape($\\) -> <<"\\\\">>; -escape(C) -> <>. - -%% Count number of records in a table given a where clause -count_records_where(LServer, Table, WhereClause) -> - ejabberd_odbc:sql_query(LServer, - [<<"select count(*) from ">>, Table, <<" ">>, - WhereClause, <<";">>]). - -get_roster_version(LServer, LUser) -> - ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(version)s from roster_version" - " where username = %(LUser)s")). - -set_roster_version(LUser, Version) -> - update_t(<<"roster_version">>, - [<<"username">>, <<"version">>], [LUser, Version], - [<<"username = '">>, LUser, <<"'">>]). - -opt_type(odbc_type) -> - fun (pgsql) -> pgsql; - (mysql) -> mysql; - (sqlite) -> sqlite; - (mssql) -> mssql; - (odbc) -> odbc - end; -opt_type(pgsql_users_number_estimate) -> - fun (V) when is_boolean(V) -> V end; -opt_type(_) -> [odbc_type, pgsql_users_number_estimate]. diff --git a/src/pubsub_db_odbc.erl b/src/pubsub_db_odbc.erl deleted file mode 100644 index cfdeda1e..00000000 --- a/src/pubsub_db_odbc.erl +++ /dev/null @@ -1,141 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : pubsub_db_odbc.erl -%%% Author : Pablo Polvorin -%%% Purpose : Provide helpers for PubSub ODBC backend -%%% Created : 7 Aug 2009 by Pablo Polvorin -%%% -%%% -%%% 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. -%%% -%%%---------------------------------------------------------------------- - --module(pubsub_db_odbc). - --author("pablo.polvorin@process-one.net"). - --include("pubsub.hrl"). - --export([add_subscription/1, read_subscription/1, - delete_subscription/1, update_subscription/1]). - -%% TODO: Those -spec lines produce errors in old Erlang versions. -%% They can be enabled again in ejabberd 3.0 because it uses R12B or higher. -%% -spec read_subscription(SubID :: string()) -> {ok, #pubsub_subscription{}} | notfound. -read_subscription(SubID) -> - case - ejabberd_odbc:sql_query_t([<<"select opt_name, opt_value from pubsub_subscr" - "iption_opt where subid = '">>, - ejabberd_odbc:escape(SubID), <<"'">>]) - of - {selected, [<<"opt_name">>, <<"opt_value">>], []} -> - notfound; - {selected, [<<"opt_name">>, <<"opt_value">>], Options} -> - {ok, - #pubsub_subscription{subid = SubID, - options = lists:map(fun subscription_opt_from_odbc/1, Options)}} - end. - -%% -spec delete_subscription(SubID :: string()) -> ok. -delete_subscription(SubID) -> - %% -spec update_subscription(#pubsub_subscription{}) -> ok . - %% -spec add_subscription(#pubsub_subscription{}) -> ok. - %% -------------- Internal utilities ----------------------- - ejabberd_odbc:sql_query_t([<<"delete from pubsub_subscription_opt " - "where subid = '">>, - ejabberd_odbc:escape(SubID), <<"'">>]), - ok. - -update_subscription(#pubsub_subscription{subid = SubId} = Sub) -> - delete_subscription(SubId), add_subscription(Sub). - -add_subscription(#pubsub_subscription{subid = SubId, options = Opts}) -> - EscapedSubId = ejabberd_odbc:escape(SubId), - lists:foreach(fun (Opt) -> - {OdbcOptName, OdbcOptValue} = subscription_opt_to_odbc(Opt), - ejabberd_odbc:sql_query_t([<<"insert into pubsub_subscription_opt(subid, " - "opt_name, opt_value)values ('">>, - EscapedSubId, <<"','">>, - OdbcOptName, <<"','">>, - OdbcOptValue, <<"')">>]) - end, - Opts), - ok. - -subscription_opt_from_odbc({<<"DELIVER">>, Value}) -> - {deliver, odbc_to_boolean(Value)}; -subscription_opt_from_odbc({<<"DIGEST">>, Value}) -> - {digest, odbc_to_boolean(Value)}; -subscription_opt_from_odbc({<<"DIGEST_FREQUENCY">>, Value}) -> - {digest_frequency, odbc_to_integer(Value)}; -subscription_opt_from_odbc({<<"EXPIRE">>, Value}) -> - {expire, odbc_to_timestamp(Value)}; -subscription_opt_from_odbc({<<"INCLUDE_BODY">>, Value}) -> - {include_body, odbc_to_boolean(Value)}; -%%TODO: might be > than 1 show_values value??. -%% need to use compact all in only 1 opt. -subscription_opt_from_odbc({<<"SHOW_VALUES">>, Value}) -> - {show_values, Value}; -subscription_opt_from_odbc({<<"SUBSCRIPTION_TYPE">>, Value}) -> - {subscription_type, - case Value of - <<"items">> -> items; - <<"nodes">> -> nodes - end}; -subscription_opt_from_odbc({<<"SUBSCRIPTION_DEPTH">>, Value}) -> - {subscription_depth, - case Value of - <<"all">> -> all; - N -> odbc_to_integer(N) - end}. - -subscription_opt_to_odbc({deliver, Bool}) -> - {<<"DELIVER">>, boolean_to_odbc(Bool)}; -subscription_opt_to_odbc({digest, Bool}) -> - {<<"DIGEST">>, boolean_to_odbc(Bool)}; -subscription_opt_to_odbc({digest_frequency, Int}) -> - {<<"DIGEST_FREQUENCY">>, integer_to_odbc(Int)}; -subscription_opt_to_odbc({expire, Timestamp}) -> - {<<"EXPIRE">>, timestamp_to_odbc(Timestamp)}; -subscription_opt_to_odbc({include_body, Bool}) -> - {<<"INCLUDE_BODY">>, boolean_to_odbc(Bool)}; -subscription_opt_to_odbc({show_values, Values}) -> - {<<"SHOW_VALUES">>, Values}; -subscription_opt_to_odbc({subscription_type, Type}) -> - {<<"SUBSCRIPTION_TYPE">>, - case Type of - items -> <<"items">>; - nodes -> <<"nodes">> - end}; -subscription_opt_to_odbc({subscription_depth, Depth}) -> - {<<"SUBSCRIPTION_DEPTH">>, - case Depth of - all -> <<"all">>; - N -> integer_to_odbc(N) - end}. - -integer_to_odbc(N) -> iolist_to_binary(integer_to_list(N)). - -boolean_to_odbc(true) -> <<"1">>; -boolean_to_odbc(false) -> <<"0">>. - -timestamp_to_odbc(T) -> jlib:now_to_utc_string(T). - -odbc_to_integer(N) -> jlib:binary_to_integer(N). - -odbc_to_boolean(B) -> B == <<"1">>. - -odbc_to_timestamp(T) -> jlib:datetime_string_to_timestamp(T). diff --git a/src/pubsub_db_sql.erl b/src/pubsub_db_sql.erl new file mode 100644 index 00000000..b910a5e7 --- /dev/null +++ b/src/pubsub_db_sql.erl @@ -0,0 +1,141 @@ +%%%---------------------------------------------------------------------- +%%% File : pubsub_db_sql.erl +%%% Author : Pablo Polvorin +%%% Purpose : Provide helpers for PubSub ODBC backend +%%% Created : 7 Aug 2009 by Pablo Polvorin +%%% +%%% +%%% 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. +%%% +%%%---------------------------------------------------------------------- + +-module(pubsub_db_sql). + +-author("pablo.polvorin@process-one.net"). + +-include("pubsub.hrl"). + +-export([add_subscription/1, read_subscription/1, + delete_subscription/1, update_subscription/1]). + +%% TODO: Those -spec lines produce errors in old Erlang versions. +%% They can be enabled again in ejabberd 3.0 because it uses R12B or higher. +%% -spec read_subscription(SubID :: string()) -> {ok, #pubsub_subscription{}} | notfound. +read_subscription(SubID) -> + case + ejabberd_sql:sql_query_t([<<"select opt_name, opt_value from pubsub_subscr" + "iption_opt where subid = '">>, + ejabberd_sql:escape(SubID), <<"'">>]) + of + {selected, [<<"opt_name">>, <<"opt_value">>], []} -> + notfound; + {selected, [<<"opt_name">>, <<"opt_value">>], Options} -> + {ok, + #pubsub_subscription{subid = SubID, + options = lists:map(fun subscription_opt_from_sql/1, Options)}} + end. + +%% -spec delete_subscription(SubID :: string()) -> ok. +delete_subscription(SubID) -> + %% -spec update_subscription(#pubsub_subscription{}) -> ok . + %% -spec add_subscription(#pubsub_subscription{}) -> ok. + %% -------------- Internal utilities ----------------------- + ejabberd_sql:sql_query_t([<<"delete from pubsub_subscription_opt " + "where subid = '">>, + ejabberd_sql:escape(SubID), <<"'">>]), + ok. + +update_subscription(#pubsub_subscription{subid = SubId} = Sub) -> + delete_subscription(SubId), add_subscription(Sub). + +add_subscription(#pubsub_subscription{subid = SubId, options = Opts}) -> + EscapedSubId = ejabberd_sql:escape(SubId), + lists:foreach(fun (Opt) -> + {OdbcOptName, OdbcOptValue} = subscription_opt_to_sql(Opt), + ejabberd_sql:sql_query_t([<<"insert into pubsub_subscription_opt(subid, " + "opt_name, opt_value)values ('">>, + EscapedSubId, <<"','">>, + OdbcOptName, <<"','">>, + OdbcOptValue, <<"')">>]) + end, + Opts), + ok. + +subscription_opt_from_sql({<<"DELIVER">>, Value}) -> + {deliver, sql_to_boolean(Value)}; +subscription_opt_from_sql({<<"DIGEST">>, Value}) -> + {digest, sql_to_boolean(Value)}; +subscription_opt_from_sql({<<"DIGEST_FREQUENCY">>, Value}) -> + {digest_frequency, sql_to_integer(Value)}; +subscription_opt_from_sql({<<"EXPIRE">>, Value}) -> + {expire, sql_to_timestamp(Value)}; +subscription_opt_from_sql({<<"INCLUDE_BODY">>, Value}) -> + {include_body, sql_to_boolean(Value)}; +%%TODO: might be > than 1 show_values value??. +%% need to use compact all in only 1 opt. +subscription_opt_from_sql({<<"SHOW_VALUES">>, Value}) -> + {show_values, Value}; +subscription_opt_from_sql({<<"SUBSCRIPTION_TYPE">>, Value}) -> + {subscription_type, + case Value of + <<"items">> -> items; + <<"nodes">> -> nodes + end}; +subscription_opt_from_sql({<<"SUBSCRIPTION_DEPTH">>, Value}) -> + {subscription_depth, + case Value of + <<"all">> -> all; + N -> sql_to_integer(N) + end}. + +subscription_opt_to_sql({deliver, Bool}) -> + {<<"DELIVER">>, boolean_to_sql(Bool)}; +subscription_opt_to_sql({digest, Bool}) -> + {<<"DIGEST">>, boolean_to_sql(Bool)}; +subscription_opt_to_sql({digest_frequency, Int}) -> + {<<"DIGEST_FREQUENCY">>, integer_to_sql(Int)}; +subscription_opt_to_sql({expire, Timestamp}) -> + {<<"EXPIRE">>, timestamp_to_sql(Timestamp)}; +subscription_opt_to_sql({include_body, Bool}) -> + {<<"INCLUDE_BODY">>, boolean_to_sql(Bool)}; +subscription_opt_to_sql({show_values, Values}) -> + {<<"SHOW_VALUES">>, Values}; +subscription_opt_to_sql({subscription_type, Type}) -> + {<<"SUBSCRIPTION_TYPE">>, + case Type of + items -> <<"items">>; + nodes -> <<"nodes">> + end}; +subscription_opt_to_sql({subscription_depth, Depth}) -> + {<<"SUBSCRIPTION_DEPTH">>, + case Depth of + all -> <<"all">>; + N -> integer_to_sql(N) + end}. + +integer_to_sql(N) -> iolist_to_binary(integer_to_list(N)). + +boolean_to_sql(true) -> <<"1">>; +boolean_to_sql(false) -> <<"0">>. + +timestamp_to_sql(T) -> jlib:now_to_utc_string(T). + +sql_to_integer(N) -> jlib:binary_to_integer(N). + +sql_to_boolean(B) -> B == <<"1">>. + +sql_to_timestamp(T) -> jlib:datetime_string_to_timestamp(T). diff --git a/src/pubsub_subscription_odbc.erl b/src/pubsub_subscription_odbc.erl deleted file mode 100644 index afd31c81..00000000 --- a/src/pubsub_subscription_odbc.erl +++ /dev/null @@ -1,331 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : pubsub_subscription_odbc.erl -%%% Author : Pablo Polvorin -%%% Purpose : Handle pubsub subscriptions options with ODBC backend -%%% based on pubsub_subscription.erl by Brian Cully -%%% Created : 7 Aug 2009 by Pablo Polvorin -%%% -%%% -%%% 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. -%%% -%%%---------------------------------------------------------------------- - --module(pubsub_subscription_odbc). --author("pablo.polvorin@process-one.net"). - -%% API --export([init/0, subscribe_node/3, unsubscribe_node/3, - get_subscription/3, set_subscription/4, - make_subid/0, - get_options_xform/2, parse_options_xform/1]). - --include("pubsub.hrl"). - --include("jlib.hrl"). - --define(PUBSUB_DELIVER, <<"pubsub#deliver">>). --define(PUBSUB_DIGEST, <<"pubsub#digest">>). --define(PUBSUB_DIGEST_FREQUENCY, <<"pubsub#digest_frequency">>). --define(PUBSUB_EXPIRE, <<"pubsub#expire">>). --define(PUBSUB_INCLUDE_BODY, <<"pubsub#include_body">>). --define(PUBSUB_SHOW_VALUES, <<"pubsub#show-values">>). --define(PUBSUB_SUBSCRIPTION_TYPE, <<"pubsub#subscription_type">>). --define(PUBSUB_SUBSCRIPTION_DEPTH, <<"pubsub#subscription_depth">>). --define(DELIVER_LABEL, <<"Whether an entity wants to receive or disable notifications">>). --define(DIGEST_LABEL, <<"Whether an entity wants to receive digests " - "(aggregations) of notifications or all notifications individually">>). --define(DIGEST_FREQUENCY_LABEL, <<"The minimum number of milliseconds between " - "sending any two notification digests">>). --define(EXPIRE_LABEL, <<"The DateTime at which a leased subscription will end or has ended">>). --define(INCLUDE_BODY_LABEL, <<"Whether an entity wants to receive an " - "XMPP message body in addition to the payload format">>). --define(SHOW_VALUES_LABEL, <<"The presence states for which an entity wants to receive notifications">>). --define(SUBSCRIPTION_TYPE_LABEL, <<"Type of notification to receive">>). --define(SUBSCRIPTION_DEPTH_LABEL, <<"Depth from subscription for which to receive notifications">>). --define(SHOW_VALUE_AWAY_LABEL, <<"XMPP Show Value of Away">>). --define(SHOW_VALUE_CHAT_LABEL, <<"XMPP Show Value of Chat">>). --define(SHOW_VALUE_DND_LABEL, <<"XMPP Show Value of DND (Do Not Disturb)">>). --define(SHOW_VALUE_ONLINE_LABEL, <<"Mere Availability in XMPP (No Show Value)">>). --define(SHOW_VALUE_XA_LABEL, <<"XMPP Show Value of XA (Extended Away)">>). --define(SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL, <<"Receive notification of new items only">>). --define(SUBSCRIPTION_TYPE_VALUE_NODES_LABEL, <<"Receive notification of new nodes only">>). --define(SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL, <<"Receive notification from direct child nodes only">>). --define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL, <<"Receive notification from all descendent nodes">>). - --define(DB_MOD, pubsub_db_odbc). -%%==================================================================== -%% API -%%==================================================================== - -init() -> ok = create_table(). - --spec(subscribe_node/3 :: - ( - _JID :: _, - _NodeId :: _, - Options :: [] | mod_pubsub:subOptions()) - -> {result, mod_pubsub:subId()} - ). -subscribe_node(_JID, _NodeId, Options) -> - SubID = make_subid(), - (?DB_MOD):add_subscription(#pubsub_subscription{subid = SubID, options = Options}), - {result, SubID}. - --spec(unsubscribe_node/3 :: - ( - _JID :: _, - _NodeId :: _, - SubID :: mod_pubsub:subId()) - -> {result, mod_pubsub:subscription()} - | {error, notfound} - ). -unsubscribe_node(_JID, _NodeId, SubID) -> - case (?DB_MOD):read_subscription(SubID) of - {ok, Sub} -> (?DB_MOD):delete_subscription(SubID), {result, Sub}; - notfound -> {error, notfound} - end. - --spec(get_subscription/3 :: - ( - _JID :: _, - _NodeId :: _, - SubId :: mod_pubsub:subId()) - -> {result, mod_pubsub:subscription()} - | {error, notfound} - ). -get_subscription(_JID, _NodeId, SubID) -> - case (?DB_MOD):read_subscription(SubID) of - {ok, Sub} -> {result, Sub}; - notfound -> {error, notfound} - end. - --spec(set_subscription/4 :: - ( - _JID :: _, - _NodeId :: _, - SubId :: mod_pubsub:subId(), - Options :: mod_pubsub:subOptions()) - -> {result, ok} - ). -set_subscription(_JID, _NodeId, SubID, Options) -> - case (?DB_MOD):read_subscription(SubID) of - {ok, _} -> - (?DB_MOD):update_subscription(#pubsub_subscription{subid = SubID, - options = Options}), - {result, ok}; - notfound -> - (?DB_MOD):add_subscription(#pubsub_subscription{subid = SubID, - options = Options}), - {result, ok} - end. - -get_options_xform(Lang, Options) -> - Keys = [deliver, show_values, subscription_type, subscription_depth], - XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys], - {result, - #xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [#xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"FORM_TYPE">>}, - {<<"type">>, <<"hidden">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}] - ++ XFields}}. - -parse_options_xform(XFields) -> - case fxml:remove_cdata(XFields) of - [#xmlel{name = <<"x">>} = XEl] -> - case jlib:parse_xdata_submit(XEl) of - XData when is_list(XData) -> - Opts = set_xoption(XData, []), - {result, Opts}; - Other -> Other - end; - _ -> {result, []} - end. - -%%==================================================================== -%% Internal functions -%%==================================================================== -create_table() -> ok. - --spec(make_subid/0 :: () -> mod_pubsub:subId()). -make_subid() -> - {T1, T2, T3} = p1_time_compat:timestamp(), - iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])). - -%% -%% Subscription XForm processing. -%% - -%% Return processed options, with types converted and so forth, using -%% Opts as defaults. -set_xoption([], Opts) -> Opts; -set_xoption([{Var, Value} | T], Opts) -> - NewOpts = case var_xfield(Var) of - {error, _} -> Opts; - Key -> - Val = val_xfield(Key, Value), - lists:keystore(Key, 1, Opts, {Key, Val}) - end, - set_xoption(T, NewOpts). - -%% Return the options list's key for an XForm var. -%% Convert Values for option list's Key. -var_xfield(?PUBSUB_DELIVER) -> deliver; -var_xfield(?PUBSUB_DIGEST) -> digest; -var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> digest_frequency; -var_xfield(?PUBSUB_EXPIRE) -> expire; -var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body; -var_xfield(?PUBSUB_SHOW_VALUES) -> show_values; -var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> subscription_type; -var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> subscription_depth; -var_xfield(_) -> {error, badarg}. - -val_xfield(deliver = Opt, [Val]) -> xopt_to_bool(Opt, Val); -val_xfield(digest = Opt, [Val]) -> xopt_to_bool(Opt, Val); -val_xfield(digest_frequency = Opt, [Val]) -> - case catch jlib:binary_to_integer(Val) of - N when is_integer(N) -> N; - _ -> - Txt = <<"Value of '~s' should be integer">>, - ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), - {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)} - end; -val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val); -val_xfield(include_body = Opt, [Val]) -> xopt_to_bool(Opt, Val); -val_xfield(show_values, Vals) -> Vals; -val_xfield(subscription_type, [<<"items">>]) -> items; -val_xfield(subscription_type, [<<"nodes">>]) -> nodes; -val_xfield(subscription_depth, [<<"all">>]) -> all; -val_xfield(subscription_depth = Opt, [Depth]) -> - case catch jlib:binary_to_integer(Depth) of - N when is_integer(N) -> N; - _ -> - Txt = <<"Value of '~s' should be integer">>, - ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), - {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)} - end. - -%% Convert XForm booleans to Erlang booleans. -xopt_to_bool(_, <<"0">>) -> false; -xopt_to_bool(_, <<"1">>) -> true; -xopt_to_bool(_, <<"false">>) -> false; -xopt_to_bool(_, <<"true">>) -> true; -xopt_to_bool(Option, _) -> - Txt = <<"Value of '~s' should be boolean">>, - ErrTxt = iolist_to_binary(io_lib:format(Txt, [Option])), - {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}. - -%% Return a field for an XForm for Key, with data filled in, if -%% applicable, from Options. -get_option_xfield(Lang, Key, Options) -> - Var = xfield_var(Key), - Label = xfield_label(Key), - {Type, OptEls} = type_and_options(xfield_type(Key), Lang), - Vals = case lists:keysearch(Key, 1, Options) of - {value, {_, Val}} -> - [tr_xfield_values(Vals) - || Vals <- xfield_val(Key, Val)]; - false -> [] - end, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, Var}, {<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}], - children = OptEls ++ Vals}. - -type_and_options({Type, Options}, Lang) -> - {Type, [tr_xfield_options(O, Lang) || O <- Options]}; -type_and_options(Type, _Lang) -> {Type, []}. - -tr_xfield_options({Value, Label}, Lang) -> - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, translate:translate(Lang, Label)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Value}]}]}. - -tr_xfield_values(Value) -> - %% Return the XForm variable name for a subscription option key. - %% Return the XForm variable type for a subscription option key. - #xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Value}]}. - -xfield_var(deliver) -> ?PUBSUB_DELIVER; -%xfield_var(digest) -> ?PUBSUB_DIGEST; -%xfield_var(digest_frequency) -> ?PUBSUB_DIGEST_FREQUENCY; -%xfield_var(expire) -> ?PUBSUB_EXPIRE; -%xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY; -xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES; -xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE; -xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH. - -xfield_type(deliver) -> <<"boolean">>; -%xfield_type(digest) -> <<"boolean">>; -%xfield_type(digest_frequency) -> <<"text-single">>; -%xfield_type(expire) -> <<"text-single">>; -%xfield_type(include_body) -> <<"boolean">>; -xfield_type(show_values) -> - {<<"list-multi">>, - [{<<"away">>, ?SHOW_VALUE_AWAY_LABEL}, - {<<"chat">>, ?SHOW_VALUE_CHAT_LABEL}, - {<<"dnd">>, ?SHOW_VALUE_DND_LABEL}, - {<<"online">>, ?SHOW_VALUE_ONLINE_LABEL}, - {<<"xa">>, ?SHOW_VALUE_XA_LABEL}]}; -xfield_type(subscription_type) -> - {<<"list-single">>, - [{<<"items">>, ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL}, - {<<"nodes">>, ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]}; -xfield_type(subscription_depth) -> - {<<"list-single">>, - [{<<"1">>, ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL}, - {<<"all">>, ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}. - -%% Return the XForm variable label for a subscription option key. -xfield_label(deliver) -> ?DELIVER_LABEL; -%xfield_label(digest) -> ?DIGEST_LABEL; -%xfield_label(digest_frequency) -> ?DIGEST_FREQUENCY_LABEL; -%xfield_label(expire) -> ?EXPIRE_LABEL; -%xfield_label(include_body) -> ?INCLUDE_BODY_LABEL; -xfield_label(show_values) -> ?SHOW_VALUES_LABEL; -%% Return the XForm value for a subscription option key. -%% Convert erlang booleans to XForms. -xfield_label(subscription_type) -> ?SUBSCRIPTION_TYPE_LABEL; -xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL. - -xfield_val(deliver, Val) -> [bool_to_xopt(Val)]; -%xfield_val(digest, Val) -> [bool_to_xopt(Val)]; -%xfield_val(digest_frequency, Val) -> -% [iolist_to_binary(integer_to_list(Val))]; -%xfield_val(expire, Val) -> -% [jlib:now_to_utc_string(Val)]; -%xfield_val(include_body, Val) -> [bool_to_xopt(Val)]; -xfield_val(show_values, Val) -> Val; -xfield_val(subscription_type, items) -> [<<"items">>]; -xfield_val(subscription_type, nodes) -> [<<"nodes">>]; -xfield_val(subscription_depth, all) -> [<<"all">>]; -xfield_val(subscription_depth, N) -> - [iolist_to_binary(integer_to_list(N))]. - -bool_to_xopt(false) -> <<"false">>; -bool_to_xopt(true) -> <<"true">>. diff --git a/src/pubsub_subscription_sql.erl b/src/pubsub_subscription_sql.erl new file mode 100644 index 00000000..1f82aa00 --- /dev/null +++ b/src/pubsub_subscription_sql.erl @@ -0,0 +1,331 @@ +%%%---------------------------------------------------------------------- +%%% File : pubsub_subscription_sql.erl +%%% Author : Pablo Polvorin +%%% Purpose : Handle pubsub subscriptions options with ODBC backend +%%% based on pubsub_subscription.erl by Brian Cully +%%% Created : 7 Aug 2009 by Pablo Polvorin +%%% +%%% +%%% 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. +%%% +%%%---------------------------------------------------------------------- + +-module(pubsub_subscription_sql). +-author("pablo.polvorin@process-one.net"). + +%% API +-export([init/0, subscribe_node/3, unsubscribe_node/3, + get_subscription/3, set_subscription/4, + make_subid/0, + get_options_xform/2, parse_options_xform/1]). + +-include("pubsub.hrl"). + +-include("jlib.hrl"). + +-define(PUBSUB_DELIVER, <<"pubsub#deliver">>). +-define(PUBSUB_DIGEST, <<"pubsub#digest">>). +-define(PUBSUB_DIGEST_FREQUENCY, <<"pubsub#digest_frequency">>). +-define(PUBSUB_EXPIRE, <<"pubsub#expire">>). +-define(PUBSUB_INCLUDE_BODY, <<"pubsub#include_body">>). +-define(PUBSUB_SHOW_VALUES, <<"pubsub#show-values">>). +-define(PUBSUB_SUBSCRIPTION_TYPE, <<"pubsub#subscription_type">>). +-define(PUBSUB_SUBSCRIPTION_DEPTH, <<"pubsub#subscription_depth">>). +-define(DELIVER_LABEL, <<"Whether an entity wants to receive or disable notifications">>). +-define(DIGEST_LABEL, <<"Whether an entity wants to receive digests " + "(aggregations) of notifications or all notifications individually">>). +-define(DIGEST_FREQUENCY_LABEL, <<"The minimum number of milliseconds between " + "sending any two notification digests">>). +-define(EXPIRE_LABEL, <<"The DateTime at which a leased subscription will end or has ended">>). +-define(INCLUDE_BODY_LABEL, <<"Whether an entity wants to receive an " + "XMPP message body in addition to the payload format">>). +-define(SHOW_VALUES_LABEL, <<"The presence states for which an entity wants to receive notifications">>). +-define(SUBSCRIPTION_TYPE_LABEL, <<"Type of notification to receive">>). +-define(SUBSCRIPTION_DEPTH_LABEL, <<"Depth from subscription for which to receive notifications">>). +-define(SHOW_VALUE_AWAY_LABEL, <<"XMPP Show Value of Away">>). +-define(SHOW_VALUE_CHAT_LABEL, <<"XMPP Show Value of Chat">>). +-define(SHOW_VALUE_DND_LABEL, <<"XMPP Show Value of DND (Do Not Disturb)">>). +-define(SHOW_VALUE_ONLINE_LABEL, <<"Mere Availability in XMPP (No Show Value)">>). +-define(SHOW_VALUE_XA_LABEL, <<"XMPP Show Value of XA (Extended Away)">>). +-define(SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL, <<"Receive notification of new items only">>). +-define(SUBSCRIPTION_TYPE_VALUE_NODES_LABEL, <<"Receive notification of new nodes only">>). +-define(SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL, <<"Receive notification from direct child nodes only">>). +-define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL, <<"Receive notification from all descendent nodes">>). + +-define(DB_MOD, pubsub_db_sql). +%%==================================================================== +%% API +%%==================================================================== + +init() -> ok = create_table(). + +-spec(subscribe_node/3 :: + ( + _JID :: _, + _NodeId :: _, + Options :: [] | mod_pubsub:subOptions()) + -> {result, mod_pubsub:subId()} + ). +subscribe_node(_JID, _NodeId, Options) -> + SubID = make_subid(), + (?DB_MOD):add_subscription(#pubsub_subscription{subid = SubID, options = Options}), + {result, SubID}. + +-spec(unsubscribe_node/3 :: + ( + _JID :: _, + _NodeId :: _, + SubID :: mod_pubsub:subId()) + -> {result, mod_pubsub:subscription()} + | {error, notfound} + ). +unsubscribe_node(_JID, _NodeId, SubID) -> + case (?DB_MOD):read_subscription(SubID) of + {ok, Sub} -> (?DB_MOD):delete_subscription(SubID), {result, Sub}; + notfound -> {error, notfound} + end. + +-spec(get_subscription/3 :: + ( + _JID :: _, + _NodeId :: _, + SubId :: mod_pubsub:subId()) + -> {result, mod_pubsub:subscription()} + | {error, notfound} + ). +get_subscription(_JID, _NodeId, SubID) -> + case (?DB_MOD):read_subscription(SubID) of + {ok, Sub} -> {result, Sub}; + notfound -> {error, notfound} + end. + +-spec(set_subscription/4 :: + ( + _JID :: _, + _NodeId :: _, + SubId :: mod_pubsub:subId(), + Options :: mod_pubsub:subOptions()) + -> {result, ok} + ). +set_subscription(_JID, _NodeId, SubID, Options) -> + case (?DB_MOD):read_subscription(SubID) of + {ok, _} -> + (?DB_MOD):update_subscription(#pubsub_subscription{subid = SubID, + options = Options}), + {result, ok}; + notfound -> + (?DB_MOD):add_subscription(#pubsub_subscription{subid = SubID, + options = Options}), + {result, ok} + end. + +get_options_xform(Lang, Options) -> + Keys = [deliver, show_values, subscription_type, subscription_depth], + XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys], + {result, + #xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [#xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"FORM_TYPE">>}, + {<<"type">>, <<"hidden">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}] + ++ XFields}}. + +parse_options_xform(XFields) -> + case fxml:remove_cdata(XFields) of + [#xmlel{name = <<"x">>} = XEl] -> + case jlib:parse_xdata_submit(XEl) of + XData when is_list(XData) -> + Opts = set_xoption(XData, []), + {result, Opts}; + Other -> Other + end; + _ -> {result, []} + end. + +%%==================================================================== +%% Internal functions +%%==================================================================== +create_table() -> ok. + +-spec(make_subid/0 :: () -> mod_pubsub:subId()). +make_subid() -> + {T1, T2, T3} = p1_time_compat:timestamp(), + iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])). + +%% +%% Subscription XForm processing. +%% + +%% Return processed options, with types converted and so forth, using +%% Opts as defaults. +set_xoption([], Opts) -> Opts; +set_xoption([{Var, Value} | T], Opts) -> + NewOpts = case var_xfield(Var) of + {error, _} -> Opts; + Key -> + Val = val_xfield(Key, Value), + lists:keystore(Key, 1, Opts, {Key, Val}) + end, + set_xoption(T, NewOpts). + +%% Return the options list's key for an XForm var. +%% Convert Values for option list's Key. +var_xfield(?PUBSUB_DELIVER) -> deliver; +var_xfield(?PUBSUB_DIGEST) -> digest; +var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> digest_frequency; +var_xfield(?PUBSUB_EXPIRE) -> expire; +var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body; +var_xfield(?PUBSUB_SHOW_VALUES) -> show_values; +var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> subscription_type; +var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> subscription_depth; +var_xfield(_) -> {error, badarg}. + +val_xfield(deliver = Opt, [Val]) -> xopt_to_bool(Opt, Val); +val_xfield(digest = Opt, [Val]) -> xopt_to_bool(Opt, Val); +val_xfield(digest_frequency = Opt, [Val]) -> + case catch jlib:binary_to_integer(Val) of + N when is_integer(N) -> N; + _ -> + Txt = <<"Value of '~s' should be integer">>, + ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), + {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)} + end; +val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val); +val_xfield(include_body = Opt, [Val]) -> xopt_to_bool(Opt, Val); +val_xfield(show_values, Vals) -> Vals; +val_xfield(subscription_type, [<<"items">>]) -> items; +val_xfield(subscription_type, [<<"nodes">>]) -> nodes; +val_xfield(subscription_depth, [<<"all">>]) -> all; +val_xfield(subscription_depth = Opt, [Depth]) -> + case catch jlib:binary_to_integer(Depth) of + N when is_integer(N) -> N; + _ -> + Txt = <<"Value of '~s' should be integer">>, + ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), + {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)} + end. + +%% Convert XForm booleans to Erlang booleans. +xopt_to_bool(_, <<"0">>) -> false; +xopt_to_bool(_, <<"1">>) -> true; +xopt_to_bool(_, <<"false">>) -> false; +xopt_to_bool(_, <<"true">>) -> true; +xopt_to_bool(Option, _) -> + Txt = <<"Value of '~s' should be boolean">>, + ErrTxt = iolist_to_binary(io_lib:format(Txt, [Option])), + {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}. + +%% Return a field for an XForm for Key, with data filled in, if +%% applicable, from Options. +get_option_xfield(Lang, Key, Options) -> + Var = xfield_var(Key), + Label = xfield_label(Key), + {Type, OptEls} = type_and_options(xfield_type(Key), Lang), + Vals = case lists:keysearch(Key, 1, Options) of + {value, {_, Val}} -> + [tr_xfield_values(Vals) + || Vals <- xfield_val(Key, Val)]; + false -> [] + end, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, Var}, {<<"type">>, Type}, + {<<"label">>, translate:translate(Lang, Label)}], + children = OptEls ++ Vals}. + +type_and_options({Type, Options}, Lang) -> + {Type, [tr_xfield_options(O, Lang) || O <- Options]}; +type_and_options(Type, _Lang) -> {Type, []}. + +tr_xfield_options({Value, Label}, Lang) -> + #xmlel{name = <<"option">>, + attrs = + [{<<"label">>, translate:translate(Lang, Label)}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Value}]}]}. + +tr_xfield_values(Value) -> + %% Return the XForm variable name for a subscription option key. + %% Return the XForm variable type for a subscription option key. + #xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Value}]}. + +xfield_var(deliver) -> ?PUBSUB_DELIVER; +%xfield_var(digest) -> ?PUBSUB_DIGEST; +%xfield_var(digest_frequency) -> ?PUBSUB_DIGEST_FREQUENCY; +%xfield_var(expire) -> ?PUBSUB_EXPIRE; +%xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY; +xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES; +xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE; +xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH. + +xfield_type(deliver) -> <<"boolean">>; +%xfield_type(digest) -> <<"boolean">>; +%xfield_type(digest_frequency) -> <<"text-single">>; +%xfield_type(expire) -> <<"text-single">>; +%xfield_type(include_body) -> <<"boolean">>; +xfield_type(show_values) -> + {<<"list-multi">>, + [{<<"away">>, ?SHOW_VALUE_AWAY_LABEL}, + {<<"chat">>, ?SHOW_VALUE_CHAT_LABEL}, + {<<"dnd">>, ?SHOW_VALUE_DND_LABEL}, + {<<"online">>, ?SHOW_VALUE_ONLINE_LABEL}, + {<<"xa">>, ?SHOW_VALUE_XA_LABEL}]}; +xfield_type(subscription_type) -> + {<<"list-single">>, + [{<<"items">>, ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL}, + {<<"nodes">>, ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]}; +xfield_type(subscription_depth) -> + {<<"list-single">>, + [{<<"1">>, ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL}, + {<<"all">>, ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}. + +%% Return the XForm variable label for a subscription option key. +xfield_label(deliver) -> ?DELIVER_LABEL; +%xfield_label(digest) -> ?DIGEST_LABEL; +%xfield_label(digest_frequency) -> ?DIGEST_FREQUENCY_LABEL; +%xfield_label(expire) -> ?EXPIRE_LABEL; +%xfield_label(include_body) -> ?INCLUDE_BODY_LABEL; +xfield_label(show_values) -> ?SHOW_VALUES_LABEL; +%% Return the XForm value for a subscription option key. +%% Convert erlang booleans to XForms. +xfield_label(subscription_type) -> ?SUBSCRIPTION_TYPE_LABEL; +xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL. + +xfield_val(deliver, Val) -> [bool_to_xopt(Val)]; +%xfield_val(digest, Val) -> [bool_to_xopt(Val)]; +%xfield_val(digest_frequency, Val) -> +% [iolist_to_binary(integer_to_list(Val))]; +%xfield_val(expire, Val) -> +% [jlib:now_to_utc_string(Val)]; +%xfield_val(include_body, Val) -> [bool_to_xopt(Val)]; +xfield_val(show_values, Val) -> Val; +xfield_val(subscription_type, items) -> [<<"items">>]; +xfield_val(subscription_type, nodes) -> [<<"nodes">>]; +xfield_val(subscription_depth, all) -> [<<"all">>]; +xfield_val(subscription_depth, N) -> + [iolist_to_binary(integer_to_list(N))]. + +bool_to_xopt(false) -> <<"false">>; +bool_to_xopt(true) -> <<"true">>. diff --git a/src/sql_queries.erl b/src/sql_queries.erl new file mode 100644 index 00000000..98530a4c --- /dev/null +++ b/src/sql_queries.erl @@ -0,0 +1,644 @@ +%%%---------------------------------------------------------------------- +%%% File : sql_queries.erl +%%% Author : Mickael Remond +%%% Purpose : ODBC queries dependind on back-end +%%% Created : by Mickael Remond +%%% +%%% +%%% 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. +%%% +%%%---------------------------------------------------------------------- + +-module(sql_queries). + +-compile([{parse_transform, ejabberd_sql_pt}]). + +-behaviour(ejabberd_config). + +-author("mremond@process-one.net"). + +-export([get_db_type/0, update/5, update_t/4, + sql_transaction/2, get_last/2, set_last_t/4, del_last/2, + get_password/2, get_password_scram/2, set_password_t/3, + set_password_scram_t/6, add_user/3, add_user_scram/6, + del_user/2, del_user_return_password/3, list_users/1, + list_users/2, users_number/1, users_number/2, + add_spool_sql/2, add_spool/2, get_and_del_spool_msg_t/2, + del_spool_msg/2, get_roster/2, get_roster_jid_groups/2, + get_roster_groups/3, del_user_roster_t/2, + get_roster_by_jid/3, get_rostergroup_by_jid/3, + del_roster/3, del_roster_sql/2, update_roster/5, + update_roster_sql/4, roster_subscribe/1, + get_subscription/3, set_private_data/4, + set_private_data_sql/3, get_private_data/3, + get_private_data/2, del_user_private_storage/2, + get_default_privacy_list/2, + get_default_privacy_list_t/1, get_privacy_list_names/2, + get_privacy_list_names_t/1, get_privacy_list_id/3, + get_privacy_list_id_t/2, get_privacy_list_data/3, + get_privacy_list_data_by_id/2, + get_privacy_list_data_t/2, + get_privacy_list_data_by_id_t/1, + set_default_privacy_list/2, + unset_default_privacy_list/2, remove_privacy_list/2, + add_privacy_list/2, set_privacy_list/2, + del_privacy_lists/2, set_vcard/26, get_vcard/2, + escape/1, count_records_where/3, get_roster_version/2, + set_roster_version/2, opt_type/1]). + +-include("ejabberd.hrl"). +-include("logger.hrl"). +-include("ejabberd_sql_pt.hrl"). + +%% Almost a copy of string:join/2. +%% We use this version because string:join/2 is relatively +%% new function (introduced in R12B-0). +join([], _Sep) -> []; +join([H | T], Sep) -> [H, [[Sep, X] || X <- T]]. + +get_db_type() -> generic. + +%% Safe atomic update. +update_t(Table, Fields, Vals, Where) -> + UPairs = lists:zipwith(fun (A, B) -> + <> + end, + Fields, Vals), + case ejabberd_sql:sql_query_t([<<"update ">>, Table, + <<" set ">>, join(UPairs, <<", ">>), + <<" where ">>, Where, <<";">>]) + of + {updated, 1} -> ok; + _ -> + Res = ejabberd_sql:sql_query_t([<<"insert into ">>, Table, + <<"(">>, join(Fields, <<", ">>), + <<") values ('">>, join(Vals, <<"', '">>), + <<"');">>]), + case Res of + {updated,1} -> ok; + _ -> Res + end + end. + +update(LServer, Table, Fields, Vals, Where) -> + UPairs = lists:zipwith(fun (A, B) -> + <> + end, + Fields, Vals), + case ejabberd_sql:sql_query(LServer, + [<<"update ">>, Table, <<" set ">>, + join(UPairs, <<", ">>), <<" where ">>, Where, + <<";">>]) + of + {updated, 1} -> ok; + _ -> + Res = ejabberd_sql:sql_query(LServer, + [<<"insert into ">>, Table, <<"(">>, + join(Fields, <<", ">>), <<") values ('">>, + join(Vals, <<"', '">>), <<"');">>]), + case Res of + {updated,1} -> ok; + _ -> Res + end + end. + +%% F can be either a fun or a list of queries +%% TODO: We should probably move the list of queries transaction +%% wrapper from the ejabberd_sql module to this one (sql_queries) +sql_transaction(LServer, F) -> + ejabberd_sql:sql_transaction(LServer, F). + +get_last(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(seconds)d, @(state)s from last" + " where username=%(LUser)s")). + +set_last_t(LServer, LUser, TimeStamp, Status) -> + ?SQL_UPSERT(LServer, "last", + ["!username=%(LUser)s", + "seconds=%(TimeStamp)d", + "state=%(Status)s"]). + +del_last(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("delete from last where username=%(LUser)s")). + +get_password(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(password)s from users where username=%(LUser)s")). + +get_password_scram(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(password)s, @(serverkey)s, @(salt)s, @(iterationcount)d" + " from users" + " where username=%(LUser)s")). + +set_password_t(LServer, LUser, Password) -> + ejabberd_sql:sql_transaction( + LServer, + fun () -> + ?SQL_UPSERT_T( + "users", + ["!username=%(LUser)s", + "password=%(Password)s"]) + end). + +set_password_scram_t(LServer, LUser, + StoredKey, ServerKey, Salt, IterationCount) -> + ejabberd_sql:sql_transaction( + LServer, + fun () -> + ?SQL_UPSERT_T( + "users", + ["!username=%(LUser)s", + "password=%(StoredKey)s", + "serverkey=%(ServerKey)s", + "salt=%(Salt)s", + "iterationcount=%(IterationCount)d"]) + end). + +add_user(LServer, LUser, Password) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("insert into users(username, password) " + "values (%(LUser)s, %(Password)s)")). + +add_user_scram(LServer, LUser, + StoredKey, ServerKey, Salt, IterationCount) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("insert into users(username, password, serverkey, salt, " + "iterationcount) " + "values (%(LUser)s, %(StoredKey)s, %(ServerKey)s," + " %(Salt)s, %(IterationCount)d)")). + +del_user(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("delete from users where username=%(LUser)s")). + +del_user_return_password(_LServer, LUser, Password) -> + P = + ejabberd_sql:sql_query_t( + ?SQL("select @(password)s from users where username=%(LUser)s")), + ejabberd_sql:sql_query_t( + ?SQL("delete from users" + " where username=%(LUser)s and password=%(Password)s")), + P. + +list_users(LServer) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(username)s from users")). + +list_users(LServer, [{from, Start}, {to, End}]) + when is_integer(Start) and is_integer(End) -> + list_users(LServer, + [{limit, End - Start + 1}, {offset, Start - 1}]); +list_users(LServer, + [{prefix, Prefix}, {from, Start}, {to, End}]) + when is_binary(Prefix) and is_integer(Start) and + is_integer(End) -> + list_users(LServer, + [{prefix, Prefix}, {limit, End - Start + 1}, + {offset, Start - 1}]); +list_users(LServer, [{limit, Limit}, {offset, Offset}]) + when is_integer(Limit) and is_integer(Offset) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(username)s from users " + "order by username " + "limit %(Limit)d offset %(Offset)d")); +list_users(LServer, + [{prefix, Prefix}, {limit, Limit}, {offset, Offset}]) + when is_binary(Prefix) and is_integer(Limit) and + is_integer(Offset) -> + SPrefix = ejabberd_sql:escape_like_arg(Prefix), + SPrefix2 = <>, + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(username)s from users " + "where username like %(SPrefix2)s " + "order by username " + "limit %(Limit)d offset %(Offset)d")). + +users_number(LServer) -> + ejabberd_sql:sql_query( + LServer, + fun(pgsql, _) -> + case + ejabberd_config:get_option( + {pgsql_users_number_estimate, LServer}, + fun(V) when is_boolean(V) -> V end, + false) of + true -> + ejabberd_sql:sql_query_t( + ?SQL("select @(reltuples :: bigint)d from pg_class" + " where oid = 'users'::regclass::oid")); + _ -> + ejabberd_sql:sql_query_t( + ?SQL("select @(count(*))d from users")) + end; + (_Type, _) -> + ejabberd_sql:sql_query_t( + ?SQL("select @(count(*))d from users")) + end). + +users_number(LServer, [{prefix, Prefix}]) + when is_binary(Prefix) -> + SPrefix = ejabberd_sql:escape_like_arg(Prefix), + SPrefix2 = <>, + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(count(*))d from users " + "where username like %(SPrefix2)s")); +users_number(LServer, []) -> + users_number(LServer). + +add_spool_sql(Username, XML) -> + [<<"insert into spool(username, xml) values ('">>, + Username, <<"', '">>, XML, <<"');">>]. + +add_spool(LServer, Queries) -> + ejabberd_sql:sql_transaction(LServer, Queries). + +get_and_del_spool_msg_t(LServer, LUser) -> + F = fun () -> + Result = + ejabberd_sql:sql_query_t( + ?SQL("select @(username)s, @(xml)s from spool where " + "username=%(LUser)s order by seq;")), + ejabberd_sql:sql_query_t( + ?SQL("delete from spool where username=%(LUser)s;")), + Result + end, + ejabberd_sql:sql_transaction(LServer, F). + +del_spool_msg(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("delete from spool where username=%(LUser)s")). + +get_roster(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s, " + "@(ask)s, @(askmessage)s, @(server)s, @(subscribe)s, " + "@(type)s from rosterusers where username=%(LUser)s")). + +get_roster_jid_groups(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(jid)s, @(grp)s from rostergroups where " + "username=%(LUser)s")). + +get_roster_groups(_LServer, LUser, SJID) -> + ejabberd_sql:sql_query_t( + ?SQL("select @(grp)s from rostergroups" + " where username=%(LUser)s and jid=%(SJID)s")). + +del_user_roster_t(LServer, LUser) -> + ejabberd_sql:sql_transaction( + LServer, + fun () -> + ejabberd_sql:sql_query_t( + ?SQL("delete from rosterusers where username=%(LUser)s")), + ejabberd_sql:sql_query_t( + ?SQL("delete from rostergroups where username=%(LUser)s")) + end). + +get_roster_by_jid(_LServer, LUser, SJID) -> + ejabberd_sql:sql_query_t( + ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s," + " @(ask)s, @(askmessage)s, @(server)s, @(subscribe)s," + " @(type)s from rosterusers" + " where username=%(LUser)s and jid=%(SJID)s")). + +get_rostergroup_by_jid(LServer, LUser, SJID) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(grp)s from rostergroups" + " where username=%(LUser)s and jid=%(SJID)s")). + +del_roster(_LServer, LUser, SJID) -> + ejabberd_sql:sql_query_t( + ?SQL("delete from rosterusers" + " where username=%(LUser)s and jid=%(SJID)s")), + ejabberd_sql:sql_query_t( + ?SQL("delete from rostergroups" + " where username=%(LUser)s and jid=%(SJID)s")). + +del_roster_sql(Username, SJID) -> + [[<<"delete from rosterusers where " + "username='">>, + Username, <<"' and jid='">>, SJID, <<"';">>], + [<<"delete from rostergroups where " + "username='">>, + Username, <<"' and jid='">>, SJID, <<"';">>]]. + +update_roster(_LServer, LUser, SJID, ItemVals, + ItemGroups) -> + roster_subscribe(ItemVals), + ejabberd_sql:sql_query_t( + ?SQL("delete from rostergroups" + " where username=%(LUser)s and jid=%(SJID)s")), + lists:foreach( + fun(ItemGroup) -> + ejabberd_sql:sql_query_t( + ?SQL("insert into rostergroups(username, jid, grp) " + "values (%(LUser)s, %(SJID)s, %(ItemGroup)s)")) + end, + ItemGroups). + +update_roster_sql(Username, SJID, ItemVals, + ItemGroups) -> + [[<<"delete from rosterusers where " + "username='">>, + Username, <<"' and jid='">>, SJID, <<"';">>], + [<<"insert into rosterusers( " + " username, jid, nick, " + " subscription, ask, askmessage, " + " server, subscribe, type) " + "values ('">>, + join(ItemVals, <<"', '">>), <<"');">>], + [<<"delete from rostergroups where " + "username='">>, + Username, <<"' and jid='">>, SJID, <<"';">>]] + ++ + [[<<"insert into rostergroups( " + " username, jid, grp) values ('">>, + join(ItemGroup, <<"', '">>), <<"');">>] + || ItemGroup <- ItemGroups]. + +roster_subscribe({LUser, SJID, Name, SSubscription, SAsk, AskMessage}) -> + ?SQL_UPSERT_T( + "rosterusers", + ["!username=%(LUser)s", + "!jid=%(SJID)s", + "nick=%(Name)s", + "subscription=%(SSubscription)s", + "ask=%(SAsk)s", + "askmessage=%(AskMessage)s", + "server='N'", + "subscribe=''", + "type='item'"]). + +get_subscription(LServer, LUser, SJID) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(subscription)s from rosterusers " + "where username=%(LUser)s and jid=%(SJID)s")). + +set_private_data(_LServer, LUser, XMLNS, SData) -> + ?SQL_UPSERT_T( + "private_storage", + ["!username=%(LUser)s", + "!namespace=%(XMLNS)s", + "data=%(SData)s"]). + +set_private_data_sql(Username, LXMLNS, SData) -> + [[<<"delete from private_storage where username='">>, + Username, <<"' and namespace='">>, LXMLNS, <<"';">>], + [<<"insert into private_storage(username, " + "namespace, data) values ('">>, + Username, <<"', '">>, LXMLNS, <<"', '">>, SData, + <<"');">>]]. + +get_private_data(LServer, LUser, XMLNS) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(data)s from private_storage" + " where username=%(LUser)s and namespace=%(XMLNS)s")). + +get_private_data(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(namespace)s, @(data)s from private_storage" + " where username=%(LUser)s")). + +del_user_private_storage(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("delete from private_storage" + " where username=%(LUser)s")). + +set_vcard(LServer, LUser, BDay, CTRY, EMail, FN, + Family, Given, LBDay, LCTRY, LEMail, LFN, + LFamily, LGiven, LLocality, LMiddle, LNickname, + LOrgName, LOrgUnit, Locality, Middle, Nickname, + OrgName, OrgUnit, SVCARD, User) -> + ejabberd_sql:sql_transaction( + LServer, + fun() -> + ?SQL_UPSERT(LServer, "vcard", + ["!username=%(LUser)s", + "vcard=%(SVCARD)s"]), + ?SQL_UPSERT(LServer, "vcard_search", + ["username=%(User)s", + "!lusername=%(LUser)s", + "fn=%(FN)s", + "lfn=%(LFN)s", + "family=%(Family)s", + "lfamily=%(LFamily)s", + "given=%(Given)s", + "lgiven=%(LGiven)s", + "middle=%(Middle)s", + "lmiddle=%(LMiddle)s", + "nickname=%(Nickname)s", + "lnickname=%(LNickname)s", + "bday=%(BDay)s", + "lbday=%(LBDay)s", + "ctry=%(CTRY)s", + "lctry=%(LCTRY)s", + "locality=%(Locality)s", + "llocality=%(LLocality)s", + "email=%(EMail)s", + "lemail=%(LEMail)s", + "orgname=%(OrgName)s", + "lorgname=%(LOrgName)s", + "orgunit=%(OrgUnit)s", + "lorgunit=%(LOrgUnit)s"]) + end). + +get_vcard(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(vcard)s from vcard where username=%(LUser)s")). + +get_default_privacy_list(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(name)s from privacy_default_list " + "where username=%(LUser)s")). + +get_default_privacy_list_t(LUser) -> + ejabberd_sql:sql_query_t( + ?SQL("select @(name)s from privacy_default_list " + "where username=%(LUser)s")). + +get_privacy_list_names(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(name)s from privacy_list" + " where username=%(LUser)s")). + +get_privacy_list_names_t(LUser) -> + ejabberd_sql:sql_query_t( + ?SQL("select @(name)s from privacy_list" + " where username=%(LUser)s")). + +get_privacy_list_id(LServer, LUser, Name) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(id)d from privacy_list" + " where username=%(LUser)s and name=%(Name)s")). + +get_privacy_list_id_t(LUser, Name) -> + ejabberd_sql:sql_query_t( + ?SQL("select @(id)d from privacy_list" + " where username=%(LUser)s and name=%(Name)s")). + +get_privacy_list_data(LServer, LUser, Name) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, " + "@(match_iq)b, @(match_message)b, @(match_presence_in)b, " + "@(match_presence_out)b from privacy_list_data " + "where id =" + " (select id from privacy_list" + " where username=%(LUser)s and name=%(Name)s) " + "order by ord")). + +%% Not used? +get_privacy_list_data_t(LUser, Name) -> + ejabberd_sql:sql_query_t( + ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, " + "@(match_iq)b, @(match_message)b, @(match_presence_in)b, " + "@(match_presence_out)b from privacy_list_data " + "where id =" + " (select id from privacy_list" + " where username=%(LUser)s and name=%(Name)s) " + "order by ord")). + +get_privacy_list_data_by_id(LServer, ID) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, " + "@(match_iq)b, @(match_message)b, @(match_presence_in)b, " + "@(match_presence_out)b from privacy_list_data " + "where id=%(ID)d order by ord")). + +get_privacy_list_data_by_id_t(ID) -> + ejabberd_sql:sql_query_t( + ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, " + "@(match_iq)b, @(match_message)b, @(match_presence_in)b, " + "@(match_presence_out)b from privacy_list_data " + "where id=%(ID)d order by ord")). + +set_default_privacy_list(LUser, Name) -> + ?SQL_UPSERT_T( + "privacy_default_list", + ["!username=%(LUser)s", + "name=%(Name)s"]). + +unset_default_privacy_list(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("delete from privacy_default_list" + " where username=%(LUser)s")). + +remove_privacy_list(LUser, Name) -> + ejabberd_sql:sql_query_t( + ?SQL("delete from privacy_list where" + " username=%(LUser)s and name=%(Name)s")). + +add_privacy_list(LUser, Name) -> + ejabberd_sql:sql_query_t( + ?SQL("insert into privacy_list(username, name) " + "values (%(LUser)s, %(Name)s)")). + +set_privacy_list(ID, RItems) -> + ejabberd_sql:sql_query_t( + ?SQL("delete from privacy_list_data where id=%(ID)d")), + lists:foreach( + fun({SType, SValue, SAction, Order, MatchAll, MatchIQ, + MatchMessage, MatchPresenceIn, MatchPresenceOut}) -> + ejabberd_sql:sql_query_t( + ?SQL("insert into privacy_list_data(id, t, " + "value, action, ord, match_all, match_iq, " + "match_message, match_presence_in, match_presence_out) " + "values (%(ID)d, %(SType)s, %(SValue)s, %(SAction)s," + " %(Order)d, %(MatchAll)b, %(MatchIQ)b," + " %(MatchMessage)b, %(MatchPresenceIn)b," + " %(MatchPresenceOut)b)")) + end, + RItems). + +del_privacy_lists(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("delete from privacy_list where username=%(LUser)s")), + %US = <>, + %ejabberd_sql:sql_query( + % LServer, + % ?SQL("delete from privacy_list_data where value=%(US)s")), + ejabberd_sql:sql_query( + LServer, + ?SQL("delete from privacy_default_list where username=%(LUser)s")). + +%% Characters to escape +escape($\000) -> <<"\\0">>; +escape($\n) -> <<"\\n">>; +escape($\t) -> <<"\\t">>; +escape($\b) -> <<"\\b">>; +escape($\r) -> <<"\\r">>; +escape($') -> <<"''">>; +escape($") -> <<"\\\"">>; +escape($\\) -> <<"\\\\">>; +escape(C) -> <>. + +%% Count number of records in a table given a where clause +count_records_where(LServer, Table, WhereClause) -> + ejabberd_sql:sql_query(LServer, + [<<"select count(*) from ">>, Table, <<" ">>, + WhereClause, <<";">>]). + +get_roster_version(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(version)s from roster_version" + " where username = %(LUser)s")). + +set_roster_version(LUser, Version) -> + update_t(<<"roster_version">>, + [<<"username">>, <<"version">>], [LUser, Version], + [<<"username = '">>, LUser, <<"'">>]). + +opt_type(sql_type) -> + fun (pgsql) -> pgsql; + (mysql) -> mysql; + (sqlite) -> sqlite; + (mssql) -> mssql; + (odbc) -> odbc + end; +opt_type(pgsql_users_number_estimate) -> + fun (V) when is_boolean(V) -> V end; +opt_type(_) -> [sql_type, pgsql_users_number_estimate]. diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index 8cc1fd68..e2f26807 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -86,7 +86,7 @@ do_init_per_group(redis, Config) -> mod_muc:shutdown_rooms(?REDIS_VHOST), set_opt(server, ?REDIS_VHOST, Config); do_init_per_group(mysql, Config) -> - case catch ejabberd_odbc:sql_query(?MYSQL_VHOST, [<<"select 1;">>]) of + case catch ejabberd_sql:sql_query(?MYSQL_VHOST, [<<"select 1;">>]) of {selected, _, _} -> mod_muc:shutdown_rooms(?MYSQL_VHOST), create_sql_tables(mysql, ?config(base_dir, Config)), @@ -95,7 +95,7 @@ do_init_per_group(mysql, Config) -> {skip, {mysql_not_available, Err}} end; do_init_per_group(pgsql, Config) -> - case catch ejabberd_odbc:sql_query(?PGSQL_VHOST, [<<"select 1;">>]) of + case catch ejabberd_sql:sql_query(?PGSQL_VHOST, [<<"select 1;">>]) of {selected, _, _} -> mod_muc:shutdown_rooms(?PGSQL_VHOST), create_sql_tables(pgsql, ?config(base_dir, Config)), @@ -104,7 +104,7 @@ do_init_per_group(pgsql, Config) -> {skip, {pgsql_not_available, Err}} end; do_init_per_group(sqlite, Config) -> - case catch ejabberd_odbc:sql_query(?SQLITE_VHOST, [<<"select 1;">>]) of + case catch ejabberd_sql:sql_query(?SQLITE_VHOST, [<<"select 1;">>]) of {selected, _, _} -> mod_muc:shutdown_rooms(?SQLITE_VHOST), set_opt(server, ?SQLITE_VHOST, Config); @@ -2248,7 +2248,7 @@ create_sql_tables(Type, BaseDir) -> SQLFile = filename:join([BaseDir, "sql", File]), CreationQueries = read_sql_queries(SQLFile), DropTableQueries = drop_table_queries(CreationQueries), - case ejabberd_odbc:sql_transaction( + case ejabberd_sql:sql_transaction( VHost, DropTableQueries ++ CreationQueries) of {atomic, ok} -> ok; diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml index 91e6caa6..bd7f958d 100644 --- a/test/ejabberd_SUITE_data/ejabberd.yml +++ b/test/ejabberd_SUITE_data/ejabberd.yml @@ -1,33 +1,33 @@ host_config: "pgsql.localhost": - odbc_username: "@@pgsql_user@@" - odbc_type: pgsql - odbc_server: "@@pgsql_server@@" - odbc_port: @@pgsql_port@@ - odbc_pool_size: 1 - odbc_password: "@@pgsql_pass@@" - odbc_database: "@@pgsql_db@@" - auth_method: odbc - sm_db_type: odbc + sql_username: "@@pgsql_user@@" + sql_type: pgsql + sql_server: "@@pgsql_server@@" + sql_port: @@pgsql_port@@ + sql_pool_size: 1 + sql_password: "@@pgsql_pass@@" + sql_database: "@@pgsql_db@@" + auth_method: sql + sm_db_type: sql modules: mod_announce: - db_type: odbc + db_type: sql access: local mod_blocking: [] mod_caps: - db_type: odbc + db_type: sql mod_last: - db_type: odbc + db_type: sql mod_muc: - db_type: odbc + db_type: sql mod_offline: - db_type: odbc + db_type: sql mod_privacy: - db_type: odbc + db_type: sql mod_private: - db_type: odbc + db_type: sql mod_pubsub: - db_type: odbc + db_type: sql access_createnode: pubsub_createnode ignore_pep_from_offline: true last_item_cache: false @@ -39,13 +39,13 @@ host_config: mod_roster: versioning: true store_current_id: true - db_type: odbc + db_type: sql mod_mam: - db_type: odbc + db_type: sql mod_vcard: - db_type: odbc + db_type: sql mod_vcard_xupdate: - db_type: odbc + db_type: sql mod_adhoc: [] mod_configure: [] mod_disco: [] @@ -60,28 +60,28 @@ Welcome to this XMPP server." mod_time: [] mod_version: [] "sqlite.localhost": - odbc_type: sqlite - auth_method: odbc - sm_db_type: odbc + sql_type: sqlite + auth_method: sql + sm_db_type: sql modules: mod_announce: - db_type: odbc + db_type: sql access: local mod_blocking: [] mod_caps: - db_type: odbc + db_type: sql mod_last: - db_type: odbc + db_type: sql mod_muc: - db_type: odbc + db_type: sql mod_offline: - db_type: odbc + db_type: sql mod_privacy: - db_type: odbc + db_type: sql mod_private: - db_type: odbc + db_type: sql mod_pubsub: - db_type: odbc + db_type: sql access_createnode: pubsub_createnode ignore_pep_from_offline: true last_item_cache: false @@ -93,13 +93,13 @@ Welcome to this XMPP server." mod_roster: versioning: true store_current_id: true - db_type: odbc + db_type: sql mod_mam: - db_type: odbc + db_type: sql mod_vcard: - db_type: odbc + db_type: sql mod_vcard_xupdate: - db_type: odbc + db_type: sql mod_adhoc: [] mod_configure: [] mod_disco: [] @@ -114,34 +114,34 @@ Welcome to this XMPP server." mod_time: [] mod_version: [] "mysql.localhost": - odbc_username: "@@mysql_user@@" - odbc_type: mysql - odbc_server: "@@mysql_server@@" - odbc_port: @@mysql_port@@ - odbc_pool_size: 1 - odbc_password: "@@mysql_pass@@" - odbc_database: "@@mysql_db@@" - auth_method: odbc - sm_db_type: odbc + sql_username: "@@mysql_user@@" + sql_type: mysql + sql_server: "@@mysql_server@@" + sql_port: @@mysql_port@@ + sql_pool_size: 1 + sql_password: "@@mysql_pass@@" + sql_database: "@@mysql_db@@" + auth_method: sql + sm_db_type: sql modules: mod_announce: - db_type: odbc + db_type: sql access: local mod_blocking: [] mod_caps: - db_type: odbc + db_type: sql mod_last: - db_type: odbc + db_type: sql mod_muc: - db_type: odbc + db_type: sql mod_offline: - db_type: odbc + db_type: sql mod_privacy: - db_type: odbc + db_type: sql mod_private: - db_type: odbc + db_type: sql mod_pubsub: - db_type: odbc + db_type: sql access_createnode: pubsub_createnode ignore_pep_from_offline: true last_item_cache: false @@ -153,13 +153,13 @@ Welcome to this XMPP server." mod_roster: versioning: true store_current_id: true - db_type: odbc + db_type: sql mod_mam: - db_type: odbc + db_type: sql mod_vcard: - db_type: odbc + db_type: sql mod_vcard_xupdate: - db_type: odbc + db_type: sql mod_adhoc: [] mod_configure: [] mod_disco: [] -- cgit v1.2.3