From 5ae01e8bb40915371052b7f46eeb2f92b95c2e27 Mon Sep 17 00:00:00 2001 From: Sergey Abramyan Date: Mon, 16 Mar 2015 21:53:19 +0300 Subject: Add SQLite support --- src/ejabberd_odbc.erl | 48 +++++++++++++++++++++++--- src/ejabberd_odbc_sup.erl | 86 +++++++++++++++++++++++++++++++++++++++++++++++ src/ejabberd_rdbms.erl | 2 ++ src/odbc_queries.erl | 1 + 4 files changed, 132 insertions(+), 5 deletions(-) (limited to 'src') 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 -- cgit v1.2.3