From 5ae01e8bb40915371052b7f46eeb2f92b95c2e27 Mon Sep 17 00:00:00 2001 From: Sergey Abramyan Date: Mon, 16 Mar 2015 21:53:19 +0300 Subject: [PATCH] Add SQLite support --- .gitignore | 3 + .travis.yml | 2 +- Makefile.in | 11 ++ configure.ac | 15 +- ejabberd.yml.example | 6 + include/ejabberd.hrl | 6 + rebar.config.script | 2 + sql/lite.sql | 272 ++++++++++++++++++++++++++ src/ejabberd_odbc.erl | 48 ++++- src/ejabberd_odbc_sup.erl | 86 ++++++++ src/ejabberd_rdbms.erl | 2 + src/odbc_queries.erl | 1 + test/ejabberd_SUITE.erl | 15 ++ test/ejabberd_SUITE_data/ejabberd.cfg | 20 ++ test/ejabberd_SUITE_data/ejabberd.yml | 52 +++++ test/suite.hrl | 1 + vars.config.in | 1 + 17 files changed, 534 insertions(+), 9 deletions(-) create mode 100644 sql/lite.sql diff --git a/.gitignore b/.gitignore index c72fcdc36..8964ea72d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ *~ \#*# .#* +.edts +*.dump /Makefile /config.log /config.status @@ -38,3 +40,4 @@ /dialyzer/ /test/*.beam /logs/ +/priv/sql diff --git a/.travis.yml b/.travis.yml index 1dd745a77..f68795459 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ before_install: - sudo apt-get -qq update install: - - sudo apt-get -qq install libexpat1-dev libyaml-dev libpam0g-dev + - sudo apt-get -qq install libexpat1-dev libyaml-dev libpam0g-dev libsqlite3-dev before_script: - mysql -u root -e "CREATE USER 'ejabberd_test'@'localhost' IDENTIFIED BY 'ejabberd_test';" diff --git a/Makefile.in b/Makefile.in index 90a116ac9..87ace381a 100644 --- a/Makefile.in +++ b/Makefile.in @@ -43,6 +43,9 @@ SODIR = $(PRIVDIR)/lib # /usr/lib/ejabberd/priv/msgs MSGSDIR = $(PRIVDIR)/msgs +# /usr/lib/ejabberd/priv/sql +SQLDIR = $(PRIVDIR)/sql + # /var/lib/ejabberd/ SPOOLDIR = $(DESTDIR)@localstatedir@/lib/ejabberd @@ -176,11 +179,16 @@ install: all $(INSTALL) -d $(SODIR) $(INSTALL) -m 644 $(DLLs) $(SODIR) -[ -f $(SODIR)/jiffy.so ] && (cd $(PRIVDIR); ln -s lib/jiffy.so; true) + -[ -f $(SODIR)/sqlite3_drv.so ] && (cd $(PRIVDIR); ln -s lib/sqlite3_drv.so; true) # # Translated strings $(INSTALL) -d $(MSGSDIR) $(INSTALL) -m 644 priv/msgs/*.msg $(MSGSDIR) # + # Copy lite.sql + -[ -d deps/sqlite3 ] && $(INSTALL) -d $(SQLDIR) + -[ -d deps/sqlite3 ] && $(INSTALL) -m 644 sql/lite.sql $(SQLDIR) + # # Spool directory $(INSTALL) -d -m 750 $(O_USER) $(SPOOLDIR) $(CHOWN_COMMAND) -R @INSTALLUSER@ $(SPOOLDIR) >$(CHOWN_OUTPUT) @@ -229,6 +237,8 @@ uninstall-binary: rm -fr $(SODIR) rm -f $(MSGSDIR)/*.msgs rm -fr $(MSGSDIR) + rm -f $(SQLDIR)/*.sql + rm -fr $(SQLDIR) rm -fr $(PRIVDIR) rm -fr $(EJABBERDDIR) @@ -310,6 +320,7 @@ test: @echo "************************** NOTICE ***************************************" @cat test/README @echo "*************************************************************************" + @cd priv && ln -sf ../sql $(REBAR) skip_deps=true ct quicktest: diff --git a/configure.ac b/configure.ac index 6bef15864..a70530990 100644 --- a/configure.ac +++ b/configure.ac @@ -106,10 +106,10 @@ AC_ARG_ENABLE(mssql, esac],[db_type=generic]) AC_ARG_ENABLE(all, -[AC_HELP_STRING([--enable-all], [same as --enable-nif --enable-odbc --enable-mysql --enable-pgsql --enable-pam --enable-zlib --enable-riak --enable-redis --enable-json --enable-elixir --enable-iconv --enable-debug --enable-lager --enable-tools (useful for Dialyzer checks, default: no)])], +[AC_HELP_STRING([--enable-all], [same as --enable-nif --enable-odbc --enable-mysql --enable-pgsql --enable-sqlite --enable-pam --enable-zlib --enable-riak --enable-redis --enable-json --enable-elixir --enable-iconv --enable-debug --enable-lager --enable-tools (useful for Dialyzer checks, default: no)])], [case "${enableval}" in - yes) nif=true odbc=true mysql=true pgsql=true pam=true zlib=true riak=true redis=true json=true elixir=true iconv=true debug=true lager=true tools=true ;; - no) nif=false odbc=false mysql=false pgsql=false pam=false zlib=false riak=false redis=false json=false elixir=false iconv=false debug=false lager=false tools=false ;; + yes) nif=true odbc=true mysql=true pgsql=true sqlite=true pam=true zlib=true riak=true redis=true json=true elixir=true iconv=true debug=true lager=true tools=true ;; + no) nif=false odbc=false mysql=false pgsql=false sqlite=false pam=false zlib=false riak=false redis=false json=false elixir=false iconv=false debug=false lager=false tools=false ;; *) AC_MSG_ERROR(bad value ${enableval} for --enable-all) ;; esac],[]) @@ -153,6 +153,14 @@ AC_ARG_ENABLE(pgsql, *) AC_MSG_ERROR(bad value ${enableval} for --enable-pgsql) ;; esac],[if test "x$pgsql" = "x"; then pgsql=false; fi]) +AC_ARG_ENABLE(sqlite, +[AC_HELP_STRING([--enable-sqlite], [enable SQLite support (default: no)])], +[case "${enableval}" in + yes) sqlite=true ;; + no) sqlite=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-sqlite) ;; +esac],[if test "x$sqlite" = "x"; then sqlite=false; fi]) + AC_ARG_ENABLE(pam, [AC_HELP_STRING([--enable-pam], [enable PAM support (default: no)])], [case "${enableval}" in @@ -254,6 +262,7 @@ AC_SUBST(db_type) AC_SUBST(odbc) AC_SUBST(mysql) AC_SUBST(pgsql) +AC_SUBST(sqlite) AC_SUBST(pam) AC_SUBST(zlib) AC_SUBST(riak) diff --git a/ejabberd.yml.example b/ejabberd.yml.example index 3d69df236..a35bb1a51 100644 --- a/ejabberd.yml.example +++ b/ejabberd.yml.example @@ -343,6 +343,12 @@ auth_method: internal ## ## pgsql_users_number_estimate: true +## +## SQLite: +## +## odbc_type: sqlite +## odbc_database: "/path/to/database.db" + ## ## ODBC compatible or MSSQL server: ## diff --git a/include/ejabberd.hrl b/include/ejabberd.hrl index 0e5ba1b20..c5f8dffe3 100644 --- a/include/ejabberd.hrl +++ b/include/ejabberd.hrl @@ -31,6 +31,12 @@ -define(MSGS_DIR, filename:join(["priv", "msgs"])). +-define(SQL_DIR, filename:join(["priv", "sql"])). + +-define(SQLITE_DB, ejabberd_sqlite). + +-define(DEFAULT_SQLITE_DB_PATH, <<"/tmp/ejabberd.db">>). + -define(CONFIG_PATH, <<"ejabberd.cfg">>). -define(LOG_PATH, <<"ejabberd.log">>). diff --git a/rebar.config.script b/rebar.config.script index 69964852b..a9754b061 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -87,6 +87,8 @@ CfgDeps = lists:flatmap( [{p1_mysql, ".*", {git, "git://github.com/processone/mysql"}}]; ({pgsql, true}) -> [{p1_pgsql, ".*", {git, "git://github.com/processone/pgsql"}}]; + ({sqlite, true}) -> + [{sqlite3, ".*", {git, "git://github.com/alexeyr/erlang-sqlite3"}}]; ({pam, true}) -> [{p1_pam, ".*", {git, "git://github.com/processone/epam"}}]; ({zlib, true}) -> diff --git a/sql/lite.sql b/sql/lite.sql new file mode 100644 index 000000000..c11f7524b --- /dev/null +++ b/sql/lite.sql @@ -0,0 +1,272 @@ +-- +-- ejabberd, Copyright (C) 2002-2015 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. +-- + +CREATE TABLE users ( + username text PRIMARY KEY, + password text NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + + +CREATE TABLE last ( + username text PRIMARY KEY, + seconds text NOT NULL, + state text NOT NULL +); + + +CREATE TABLE rosterusers ( + username text NOT NULL, + jid text NOT NULL, + nick text NOT NULL, + subscription character(1) NOT NULL, + ask character(1) NOT NULL, + askmessage text NOT NULL, + server character(1) NOT NULL, + subscribe text, + type text, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE UNIQUE INDEX i_rosteru_user_jid ON rosterusers (username, jid); +CREATE INDEX i_rosteru_username ON rosterusers (username); +CREATE INDEX i_rosteru_jid ON rosterusers (jid); + + +CREATE TABLE rostergroups ( + username text NOT NULL, + jid text NOT NULL, + grp text NOT NULL +); + +CREATE INDEX pk_rosterg_user_jid ON rostergroups (username, jid); + +CREATE TABLE sr_group ( + name text NOT NULL, + opts text NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE sr_user ( + jid text NOT NULL, + grp text NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE UNIQUE INDEX i_sr_user_jid_grp ON sr_user (jid, grp); +CREATE INDEX i_sr_user_jid ON sr_user (jid); +CREATE INDEX i_sr_user_grp ON sr_user (grp); + +CREATE TABLE spool ( + username text NOT NULL, + xml text NOT NULL, + seq SERIAL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX i_despool ON spool (username); + + +CREATE TABLE vcard ( + username text PRIMARY KEY, + vcard text NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE vcard_xupdate ( + username text PRIMARY KEY, + hash text NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE vcard_search ( + username text NOT NULL, + lusername text PRIMARY KEY, + fn text NOT NULL, + lfn text NOT NULL, + family text NOT NULL, + lfamily text NOT NULL, + given text NOT NULL, + lgiven text NOT NULL, + middle text NOT NULL, + lmiddle text NOT NULL, + nickname text NOT NULL, + lnickname text NOT NULL, + bday text NOT NULL, + lbday text NOT NULL, + ctry text NOT NULL, + lctry text NOT NULL, + locality text NOT NULL, + llocality text NOT NULL, + email text NOT NULL, + lemail text NOT NULL, + orgname text NOT NULL, + lorgname text NOT NULL, + orgunit text NOT NULL, + lorgunit text NOT NULL +); + +CREATE INDEX i_vcard_search_lfn ON vcard_search(lfn); +CREATE INDEX i_vcard_search_lfamily ON vcard_search(lfamily); +CREATE INDEX i_vcard_search_lgiven ON vcard_search(lgiven); +CREATE INDEX i_vcard_search_lmiddle ON vcard_search(lmiddle); +CREATE INDEX i_vcard_search_lnickname ON vcard_search(lnickname); +CREATE INDEX i_vcard_search_lbday ON vcard_search(lbday); +CREATE INDEX i_vcard_search_lctry ON vcard_search(lctry); +CREATE INDEX i_vcard_search_llocality ON vcard_search(llocality); +CREATE INDEX i_vcard_search_lemail ON vcard_search(lemail); +CREATE INDEX i_vcard_search_lorgname ON vcard_search(lorgname); +CREATE INDEX i_vcard_search_lorgunit ON vcard_search(lorgunit); + +CREATE TABLE privacy_default_list ( + username text PRIMARY KEY, + name text NOT NULL +); + +CREATE TABLE privacy_list ( + username text NOT NULL, + name text NOT NULL, + id SERIAL UNIQUE, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX i_privacy_list_username ON privacy_list (username); +CREATE UNIQUE INDEX i_privacy_list_username_name ON privacy_list (username, name); + +CREATE TABLE privacy_list_data ( + id bigint REFERENCES privacy_list(id) ON DELETE CASCADE, + t character(1) NOT NULL, + value text NOT NULL, + action character(1) NOT NULL, + ord NUMERIC NOT NULL, + match_all boolean NOT NULL, + match_iq boolean NOT NULL, + match_message boolean NOT NULL, + match_presence_in boolean NOT NULL, + match_presence_out boolean NOT NULL +); + +CREATE TABLE private_storage ( + username text NOT NULL, + namespace text NOT NULL, + data text NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX i_private_storage_username ON private_storage (username); +CREATE UNIQUE INDEX i_private_storage_username_namespace ON private_storage (username, namespace); + + +CREATE TABLE roster_version ( + username text PRIMARY KEY, + version text NOT NULL +); + +CREATE TABLE pubsub_node ( + host text, + node text, + parent text, + type text, + nodeid SERIAL UNIQUE +); +CREATE INDEX i_pubsub_node_parent ON pubsub_node (parent); +CREATE UNIQUE INDEX i_pubsub_node_tuple ON pubsub_node (host, node); + +CREATE TABLE pubsub_node_option ( + nodeid bigint REFERENCES pubsub_node(nodeid) ON DELETE CASCADE, + name text, + val text +); +CREATE INDEX i_pubsub_node_option_nodeid ON pubsub_node_option (nodeid); + +CREATE TABLE pubsub_node_owner ( + nodeid bigint REFERENCES pubsub_node(nodeid) ON DELETE CASCADE, + owner text +); +CREATE INDEX i_pubsub_node_owner_nodeid ON pubsub_node_owner (nodeid); + +CREATE TABLE pubsub_state ( + nodeid bigint REFERENCES pubsub_node(nodeid) ON DELETE CASCADE, + jid text, + affiliation character(1), + subscriptions text, + stateid SERIAL UNIQUE +); +CREATE INDEX i_pubsub_state_jid ON pubsub_state (jid); +CREATE UNIQUE INDEX i_pubsub_state_tuple ON pubsub_state (nodeid, jid); + +CREATE TABLE pubsub_item ( + nodeid bigint REFERENCES pubsub_node(nodeid) ON DELETE CASCADE, + itemid text, + publisher text, + creation text, + modification text, + payload text +); +CREATE INDEX i_pubsub_item_itemid ON pubsub_item (itemid); +CREATE UNIQUE INDEX i_pubsub_item_tuple ON pubsub_item (nodeid, itemid); + + CREATE TABLE pubsub_subscription_opt ( + subid text, + opt_name varchar(32), + opt_value text +); +CREATE UNIQUE INDEX i_pubsub_subscription_opt ON pubsub_subscription_opt (subid, opt_name); + +CREATE TABLE muc_room ( + name text NOT NULL, + host text NOT NULL, + opts text NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE UNIQUE INDEX i_muc_room_name_host ON muc_room (name, host); + +CREATE TABLE muc_registered ( + jid text NOT NULL, + host text NOT NULL, + nick text NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX i_muc_registered_nick ON muc_registered (nick); +CREATE UNIQUE INDEX i_muc_registered_jid_host ON muc_registered (jid, host); + +CREATE TABLE irc_custom ( + jid text NOT NULL, + host text NOT NULL, + data text NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE UNIQUE INDEX i_irc_custom_jid_host ON irc_custom (jid, host); + +CREATE TABLE motd ( + username text PRIMARY KEY, + xml text, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE caps_features ( + node text NOT NULL, + subnode text NOT NULL, + feature text, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX i_caps_features_node_subnode ON caps_features (node, subnode); diff --git a/src/ejabberd_odbc.erl b/src/ejabberd_odbc.erl index 5828912d5..278bb760f 100644 --- a/src/ejabberd_odbc.erl +++ b/src/ejabberd_odbc.erl @@ -58,7 +58,7 @@ -record(state, {db_ref = self() :: pid(), - db_type = odbc :: pgsql | mysql | odbc, + db_type = odbc :: pgsql | mysql | sqlite | odbc, start_interval = 0 :: non_neg_integer(), host = <<"">> :: binary(), max_pending_requests_len :: non_neg_integer(), @@ -224,7 +224,8 @@ init([Host, 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); + [pgsql | Args] -> apply(fun pgsql_connect/5, Args); + [sqlite | Args] -> apply(fun sqlite_connect/1, Args); [odbc | Args] -> apply(fun odbc_connect/1, Args) end, {_, PendingRequests} = State#state.pending_requests, @@ -327,8 +328,9 @@ handle_info(Info, 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); - _ -> ok + mysql -> catch p1_mysql_conn:stop(State#state.db_ref); + sqlite -> catch sqlite3:close(?SQLITE_DB); + _ -> ok end, ok. @@ -456,7 +458,9 @@ sql_query_internal(Query) -> [{timeout, (?TRANSACTION_TIMEOUT) - 1000}, {result_type, binary}])), %% ?INFO_MSG("MySQL, Received result~n~p~n", [R]), - R + R; + sqlite -> + sqlite_to_odbc(sqlite3:sql_exec(?SQLITE_DB, Query)) end, case Res of {error, <<"No SQL-driver information available.">>} -> @@ -488,6 +492,34 @@ odbc_connect(SQLServer) -> ejabberd:start_app(odbc), odbc:connect(binary_to_list(SQLServer), [{scrollable_cursors, off}]). +%% == Native SQLite code + +%% part of init/1 +%% Open a database connection to SQLite + +sqlite_connect(DB) -> + process_flag(trap_exit, true), + case sqlite3:open(?SQLITE_DB, [{file, binary_to_list(DB)}]) of + {ok, Ref} -> + {ok, Ref}; + {error, {already_started, Ref}} -> + {ok, Ref}; + {error, Reason} -> + {error, Reason} + end. + +%% Convert SQLite query result to Erlang ODBC result formalism +sqlite_to_odbc(ok) -> + {updated, sqlite3:changes(?SQLITE_DB)}; +sqlite_to_odbc({rowid, _}) -> + {updated, sqlite3:changes(?SQLITE_DB)}; +sqlite_to_odbc([{columns, Columns}, {rows, Rows}]) -> + {selected, [list_to_binary(C) || C <- Columns], [tuple_to_list(Row) || Row <- Rows]}; +sqlite_to_odbc({error, _Code, Reason}) -> + {error, Reason}; +sqlite_to_odbc(_) -> + {updated, undefined}. + %% == Native PostgreSQL code %% part of init/1 @@ -584,6 +616,7 @@ db_opts(Host) -> Type = ejabberd_config:get_option({odbc_type, Host}, fun(mysql) -> mysql; (pgsql) -> pgsql; + (sqlite) -> sqlite; (odbc) -> odbc end, odbc), Server = ejabberd_config:get_option({odbc_server, Host}, @@ -592,6 +625,11 @@ db_opts(Host) -> case Type of odbc -> [odbc, Server]; + sqlite -> + DB = ejabberd_config:get_option({odbc_database, Host}, + fun iolist_to_binary/1, + ?DEFAULT_SQLITE_DB_PATH), + [sqlite, DB]; _ -> Port = ejabberd_config:get_option( {odbc_port, Host}, diff --git a/src/ejabberd_odbc_sup.erl b/src/ejabberd_odbc_sup.erl index 602e7e03b..95e8fa300 100644 --- a/src/ejabberd_odbc_sup.erl +++ b/src/ejabberd_odbc_sup.erl @@ -67,6 +67,22 @@ init([Host]) -> {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; + (odbc) -> odbc + end, odbc), + case Type of + sqlite -> + DB = ejabberd_config:get_option({odbc_database, Host}, + fun iolist_to_binary/1, + ?DEFAULT_SQLITE_DB_PATH), + check_sqlite_db(DB); + _ -> + ok + end, + {ok, {{one_for_one, PoolSize * 10, 1}, lists:map(fun (I) -> @@ -113,5 +129,75 @@ 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(DB) -> + process_flag(trap_exit, true), + Ret = case sqlite3:open(?SQLITE_DB, [{file, binary_to_list(DB)}]) of + {ok, _Ref} -> ok; + {error, {already_started, _Ref}} -> ok; + {error, R} -> {error, R} + end, + case Ret of + ok -> + case sqlite3:list_tables(?SQLITE_DB) of + [] -> + create_sqlite_tables(), + sqlite3:close(?SQLITE_DB), + ok; + [_H | _] -> + ok + end; + {error, Reason} -> + ?INFO_MSG("Failed open sqlite database, reason ~p", [Reason]) + end. + +create_sqlite_tables() -> + 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(?SQLITE_DB, "begin"), + [ok = sqlite3:sql_exec(?SQLITE_DB, Q) || Q <- Qs], + ok = sqlite3:sql_exec(?SQLITE_DB, "commit"); + {error, Reason} -> + ?INFO_MSG("Not found sqlite database schema, reason: ~p", [Reason]), + ok + 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. diff --git a/src/ejabberd_rdbms.erl b/src/ejabberd_rdbms.erl index e71728da5..93bd9fc49 100644 --- a/src/ejabberd_rdbms.erl +++ b/src/ejabberd_rdbms.erl @@ -74,10 +74,12 @@ needs_odbc(Host) -> case ejabberd_config:get_option({odbc_type, LHost}, fun(mysql) -> mysql; (pgsql) -> pgsql; + (sqlite) -> sqlite; (odbc) -> odbc end, undefined) of mysql -> {true, p1_mysql}; pgsql -> {true, p1_pgsql}; + sqlite -> {true, sqlite3}; odbc -> {true, odbc}; undefined -> false end. diff --git a/src/odbc_queries.erl b/src/odbc_queries.erl index f2771e52f..86c3af6d0 100644 --- a/src/odbc_queries.erl +++ b/src/odbc_queries.erl @@ -229,6 +229,7 @@ users_number(LServer) -> Type = ejabberd_config:get_option({odbc_type, LServer}, fun(pgsql) -> pgsql; (mysql) -> mysql; + (sqlite) -> sqlite; (odbc) -> odbc end, odbc), case Type of diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index 9ab9c247e..ec60b728d 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -35,6 +35,7 @@ init_per_suite(Config) -> LDIFFile = filename:join([DataDir, "ejabberd.ldif"]), {ok, _} = file:copy(ExtAuthScript, filename:join([CWD, "extauth.py"])), {ok, _} = ldap_srv:start(LDIFFile), + file:delete("/tmp/ejabberd_test.db"), ok = application:start(ejabberd), NewConfig. @@ -65,6 +66,14 @@ init_per_group(pgsql, Config) -> Err -> {skip, {pgsql_not_available, Err}} end; +init_per_group(sqlite, Config) -> + case catch ejabberd_odbc:sql_query(?SQLITE_VHOST, [<<"select 1;">>]) of + {selected, _, _} -> + mod_muc:shutdown_rooms(?SQLITE_VHOST), + set_opt(server, ?SQLITE_VHOST, Config); + Err -> + {skip, {sqlite_not_available, Err}} + end; init_per_group(ldap, Config) -> set_opt(server, ?LDAP_VHOST, Config); init_per_group(extauth, Config) -> @@ -88,6 +97,8 @@ end_per_group(mysql, _Config) -> ok; end_per_group(pgsql, _Config) -> ok; +end_per_group(sqlite, _Config) -> + ok; end_per_group(no_db, _Config) -> ok; end_per_group(ldap, _Config) -> @@ -300,6 +311,7 @@ groups() -> {mnesia, [sequence], db_tests(mnesia)}, {mysql, [sequence], db_tests(mysql)}, {pgsql, [sequence], db_tests(pgsql)}, + {sqlite, [sequence], db_tests(sqlite)}, {riak, [sequence], db_tests(riak)}]. all() -> @@ -308,6 +320,7 @@ all() -> {group, mnesia}, {group, mysql}, {group, pgsql}, + {group, sqlite}, {group, extauth}, {group, riak}, stop_ejabberd]. @@ -1631,6 +1644,8 @@ socks5_recv(Sock, Data) -> %%%=================================================================== %%% SQL stuff %%%=================================================================== +create_sql_tables(sqlite, _BaseDir) -> + ok; create_sql_tables(Type, BaseDir) -> {VHost, File} = case Type of mysql -> diff --git a/test/ejabberd_SUITE_data/ejabberd.cfg b/test/ejabberd_SUITE_data/ejabberd.cfg index 458de2c7d..cb321661e 100644 --- a/test/ejabberd_SUITE_data/ejabberd.cfg +++ b/test/ejabberd_SUITE_data/ejabberd.cfg @@ -3,6 +3,7 @@ "mnesia.localhost", "mysql.localhost", "pgsql.localhost", + "sqlite.localhost", "extauth.localhost", "ldap.localhost"]}. {define_macro, 'CERTFILE', "cert.pem"}. @@ -120,6 +121,25 @@ {mod_roster, [{db_type, odbc}]}, {mod_vcard, [{db_type, odbc}]}]} ]}. +{host_config, "sqlite.localhost", + [{auth_method, odbc}, + {odbc_pool_size, 1}, + {odbc_server, {sqlite, "/tmp/ejabberd_test.db"}}, + {{add, modules}, [{mod_announce, [{db_type, odbc}]}, + {mod_blocking, [{db_type, odbc}]}, + {mod_caps, [{db_type, odbc}]}, + {mod_last, [{db_type, odbc}]}, + {mod_muc, [{db_type, odbc}]}, + {mod_offline, [{db_type, odbc}]}, + {mod_privacy, [{db_type, odbc}]}, + {mod_private, [{db_type, odbc}]}, + {mod_pubsub_odbc, [{access_createnode, pubsub_createnode}, + {ignore_pep_from_offline, true}, + {last_item_cache, false}, + {plugins, ["flat", "hometree", "pep"]}]}, + {mod_roster, [{db_type, odbc}]}, + {mod_vcard, [{db_type, odbc}]}]} + ]}. {host_config, "ldap.localhost", [{auth_method, ldap}, {ldap_servers, ["localhost"]}, diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml index 9bd8a8b0a..1714464d5 100644 --- a/test/ejabberd_SUITE_data/ejabberd.yml +++ b/test/ejabberd_SUITE_data/ejabberd.yml @@ -51,6 +51,57 @@ host_config: welcome_message: subject: "Welcome!" body: "Hi. +Welcome to this XMPP server." + mod_stats: [] + mod_time: [] + mod_version: [] + "sqlite.localhost": + odbc_type: sqlite + odbc_database: "/tmp/ejabberd_test.db" + auth_method: odbc + modules: + mod_announce: + db_type: odbc + access: local + mod_blocking: + db_type: odbc + mod_caps: + db_type: odbc + mod_last: + db_type: odbc + mod_muc: + db_type: odbc + mod_offline: + db_type: odbc + mod_privacy: + db_type: odbc + mod_private: + db_type: odbc + mod_pubsub_odbc: + access_createnode: pubsub_createnode + ignore_pep_from_offline: true + last_item_cache: false + plugins: + - "flat" + - "hometree" + - "pep" + mod_roster: + versioning: true + store_current_id: true + db_type: odbc + mod_vcard: + db_type: odbc + mod_vcard_xupdate: + db_type: odbc + mod_adhoc: [] + mod_configure: [] + mod_disco: [] + mod_ping: [] + mod_proxy65: [] + mod_register: + welcome_message: + subject: "Welcome!" + body: "Hi. Welcome to this XMPP server." mod_stats: [] mod_time: [] @@ -242,6 +293,7 @@ hosts: - "extauth.localhost" - "ldap.localhost" - "riak.localhost" + - "sqlite.localhost" access: announce: admin: allow diff --git a/test/suite.hrl b/test/suite.hrl index 49bfff2d9..269620179 100644 --- a/test/suite.hrl +++ b/test/suite.hrl @@ -59,6 +59,7 @@ -define(MNESIA_VHOST, <<"mnesia.localhost">>). -define(MYSQL_VHOST, <<"mysql.localhost">>). -define(PGSQL_VHOST, <<"pgsql.localhost">>). +-define(SQLITE_VHOST, <<"sqlite.localhost">>). -define(LDAP_VHOST, <<"ldap.localhost">>). -define(EXTAUTH_VHOST, <<"extauth.localhost">>). -define(RIAK_VHOST, <<"riak.localhost">>). diff --git a/vars.config.in b/vars.config.in index cb82775aa..2ccd5c76f 100644 --- a/vars.config.in +++ b/vars.config.in @@ -23,6 +23,7 @@ {odbc, @odbc@}. {mysql, @mysql@}. {pgsql, @pgsql@}. +{sqlite, @sqlite@}. {pam, @pam@}. {zlib, @zlib@}. {riak, @riak@}.