Automatically create and update SQL schema

This commit is contained in:
Alexey Shchepin 2023-09-28 03:37:36 +03:00
parent f6e8eb52f0
commit c1af36ac20
28 changed files with 1733 additions and 19 deletions

View File

@ -50,3 +50,20 @@
boolean :: fun((boolean()) -> binary()),
in_array_string :: fun((binary()) -> binary()),
like_escape :: fun(() -> binary())}).
-record(sql_index, {columns,
unique = false :: boolean()}).
-record(sql_column, {name :: binary(),
type,
default = false,
opts = []}).
-record(sql_table, {name :: binary(),
columns :: [#sql_column{}],
indices = [] :: [#sql_index{}],
post_create}).
-record(sql_schema, {version :: integer(),
tables :: [#sql_table{}],
update = []}).
-record(sql_references, {table :: binary(),
column :: binary()}).

View File

@ -45,7 +45,31 @@
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(_Host) -> ok.
start(Host) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"users">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"password">>, type = text},
#sql_column{name = <<"serverkey">>, type = {text, 128},
default = true},
#sql_column{name = <<"salt">>, type = {text, 128},
default = true},
#sql_column{name = <<"iterationcount">>, type = integer,
default = true},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>],
unique = true}]}]}].
stop(_Host) -> ok.

View File

@ -41,8 +41,35 @@
-include("logger.hrl").
init() ->
ejabberd_sql_schema:update_schema(
ejabberd_config:get_myname(), ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"oauth_token">>,
columns =
[#sql_column{name = <<"token">>, type = text},
#sql_column{name = <<"jid">>, type = text},
#sql_column{name = <<"scope">>, type = text},
#sql_column{name = <<"expire">>, type = bigint}],
indices = [#sql_index{
columns = [<<"token">>],
unique = true}]},
#sql_table{
name = <<"oauth_client">>,
columns =
[#sql_column{name = <<"client_id">>, type = text},
#sql_column{name = <<"client_name">>, type = text},
#sql_column{name = <<"grant_type">>, type = text},
#sql_column{name = <<"options">>, type = text}],
indices = [#sql_index{
columns = [<<"client_id">>],
unique = true}]}]}].
store(R) ->
Token = R#oauth_token.token,
{User, Server} = R#oauth_token.us,

View File

@ -162,6 +162,7 @@
-export([sql_type/0, sql_type/1]).
-export([sql_username/0, sql_username/1]).
-export([trusted_proxies/0]).
-export([update_sql_schema/0]).
-export([use_cache/0, use_cache/1]).
-export([validate_stream/0]).
-export([version/0]).
@ -1096,6 +1097,10 @@ sql_username(Host) ->
trusted_proxies() ->
ejabberd_config:get_option({trusted_proxies, global}).
-spec update_sql_schema() -> boolean().
update_sql_schema() ->
ejabberd_config:get_option({update_sql_schema, global}).
-spec use_cache() -> boolean().
use_cache() ->
use_cache(global).

View File

@ -260,6 +260,8 @@ opt_type(net_ticktime) ->
econf:timeout(second);
opt_type(new_sql_schema) ->
econf:bool();
opt_type(update_sql_schema) ->
econf:bool();
opt_type(oauth_access) ->
econf:acl();
opt_type(oauth_cache_life_time) ->
@ -607,6 +609,7 @@ options() ->
{negotiation_timeout, timer:seconds(30)},
{net_ticktime, timer:seconds(60)},
{new_sql_schema, ?USE_NEW_SQL_SCHEMA_DEFAULT},
{update_sql_schema, true},
{oauth_access, none},
{oauth_cache_life_time,
fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end},
@ -756,6 +759,7 @@ globals() ->
negotiation_timeout,
net_ticktime,
new_sql_schema,
update_sql_schema,
node_start,
oauth_cache_life_time,
oauth_cache_missed,

View File

@ -911,6 +911,11 @@ doc() ->
"configuration flag '--enable-new-sql-schema' which is set "
"at compile time."),
[binary:part(ejabberd_config:version(), {0,5})]}}},
{update_sql_schema,
#{value => "true | false",
desc =>
?T("Allow ejabberd to update SQL schema. "
"The default value is 'true'.")}},
{oauth_access,
#{value => ?T("AccessName"),
desc => ?T("By default creating OAuth tokens is not allowed. "

View File

@ -37,6 +37,8 @@
%%% API
%%%===================================================================
init() ->
ejabberd_sql_schema:update_schema(
ejabberd_config:get_myname(), ?MODULE, schemas()),
Node = erlang:atom_to_binary(node(), latin1),
?DEBUG("Cleaning SQL 'route' table...", []),
case ejabberd_sql:sql_query(
@ -48,6 +50,23 @@ init() ->
Err
end.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"route">>,
columns =
[#sql_column{name = <<"domain">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"node">>, type = text},
#sql_column{name = <<"pid">>, type = text},
#sql_column{name = <<"local_hint">>, type = text}],
indices = [#sql_index{
columns = [<<"domain">>, <<"server_host">>,
<<"node">>, <<"pid">>],
unique = true}]}]}].
register_route(Domain, ServerHost, LocalHint, _, Pid) ->
PidS = misc:encode_pid(Pid),
LocalHintS = enc_local_hint(LocalHint),

View File

@ -48,6 +48,7 @@ init() ->
?DEBUG("Cleaning SQL SM table...", []),
lists:foldl(
fun(Host, ok) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
case ejabberd_sql:sql_query(
Host, ?SQL("delete from sm where node=%(Node)s")) of
{updated, _} ->
@ -60,6 +61,29 @@ init() ->
Err
end, ok, ejabberd_sm:get_vh_by_backend(?MODULE)).
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"sm">>,
columns =
[#sql_column{name = <<"usec">>, type = bigint},
#sql_column{name = <<"pid">>, type = text},
#sql_column{name = <<"node">>, type = text},
#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"resource">>, type = text},
#sql_column{name = <<"priority">>, type = text},
#sql_column{name = <<"info">>, type = text}],
indices = [#sql_index{
columns = [<<"usec">>, <<"pid">>],
unique = true},
#sql_index{
columns = [<<"node">>]},
#sql_index{
columns = [<<"server_host">>, <<"username">>]}]}]}].
set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R},
priority = Priority, info = Info}) ->
InfoS = misc:term_to_expr(Info),

936
src/ejabberd_sql_schema.erl Normal file
View File

@ -0,0 +1,936 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_sql.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : SQL schema versioning
%%% Created : 15 Aug 2023 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2023 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_schema).
-author('alexey@process-one.net').
-export([start/1, update_schema/3,
get_table_schema/2, get_table_indices/2, test/0]).
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
start(Host) ->
case should_update_schema(Host) of
true ->
case table_exists(Host, <<"schema_version">>) of
true ->
ok;
false ->
Table = filter_table_sh(schema_table()),
Res = create_table(Host, Table),
case Res of
{error, Error} ->
?ERROR_MSG("Failed to create table ~s: ~p",
[Table#sql_table.name, Error]),
{error, Error};
_ ->
ok
end
end;
false ->
ok
end.
schema_table() ->
#sql_table{
name = <<"schema_version">>,
columns = [#sql_column{name = <<"module">>, type = text},
#sql_column{name = <<"version">>, type = bigint}],
indices = [#sql_index{
columns = [<<"module">>],
unique = true}]}.
get_table_schema(Host, Table) ->
ejabberd_sql:sql_query(
Host,
fun(pgsql, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select "
" @(a.attname)s, "
" @(pg_catalog.format_type(a.atttypid, a.atttypmod))s "
" from "
" pg_class t, "
" pg_attribute a "
" where "
" a.attrelid = t.oid and "
" a.attnum > 0 and "
" a.atttypid > 0 and "
" t.relkind = 'r' and "
" t.relname=%(Table)s"))
of
{selected, Cols} ->
[{Col, string_to_type(SType)} || {Col, SType} <- Cols]
end;
(sqlite, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select @(i.name)s, @(i.type)s"
" from pragma_table_info(%(Table)s) as i"))
of
{selected, Cols} ->
[{Col, string_to_type(SType)} || {Col, SType} <- Cols]
end;
(mysql, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select @(column_name)s, @(column_type)s"
" from information_schema.columns"
" where table_name=%(Table)s and"
" table_schema=schema()"
" order by ordinal_position"))
of
{selected, Cols} ->
[{Col, string_to_type(SType)} || {Col, SType} <- Cols]
end
end).
get_table_indices(Host, Table) ->
ejabberd_sql:sql_query(
Host,
fun(pgsql, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select "
" @(i.relname)s, "
" @(a.attname)s "
" from "
" pg_class t, "
" pg_class i, "
" pg_index ix, "
" pg_attribute a "
" where "
" t.oid = ix.indrelid and "
" i.oid = ix.indexrelid and "
" a.attrelid = t.oid and "
" a.attnum = ANY(ix.indkey) and "
" t.relkind = 'r' and "
" t.relname=%(Table)s "
" order by "
" i.relname, "
" array_position(ix.indkey, a.attnum)"))
of
{selected, Cols} ->
Indices =
lists:foldr(
fun({IdxName, ColName}, Acc) ->
maps:update_with(
IdxName,
fun(Cs) -> [ColName | Cs] end,
[ColName],
Acc)
end, #{}, Cols),
maps:to_list(Indices)
end;
(sqlite, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select @(i.name)s, @(c.name)s "
" from pragma_index_list(%(Table)s) as i,"
" pragma_index_xinfo(i.name) as c"
" where c.cid >= 0"
" order by i.name, c.seqno"))
of
{selected, Cols} ->
Indices =
lists:foldr(
fun({IdxName, ColName}, Acc) ->
maps:update_with(
IdxName,
fun(Cs) -> [ColName | Cs] end,
[ColName],
Acc)
end, #{}, Cols),
maps:to_list(Indices)
end;
(mysql, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select @(index_name)s, @(column_name)s"
" from information_schema.statistics"
" where table_name=%(Table)s and"
" table_schema=schema()"
" order by index_name, seq_in_index"))
of
{selected, Cols} ->
Indices =
lists:foldr(
fun({IdxName, ColName}, Acc) ->
maps:update_with(
IdxName,
fun(Cs) -> [ColName | Cs] end,
[ColName],
Acc)
end, #{}, Cols),
maps:to_list(Indices)
end
end).
find_index_name(Host, Table, Columns) ->
Indices = get_table_indices(Host, Table),
case lists:keyfind(Columns, 2, Indices) of
false ->
false;
{Name, _} ->
{ok, Name}
end.
get_version(Host, Module) ->
SModule = misc:atom_to_binary(Module),
ejabberd_sql:sql_query(
Host,
?SQL("select @(version)d"
" from schema_version"
" where module=%(SModule)s")).
store_version(Host, Module, Version) ->
SModule = misc:atom_to_binary(Module),
?SQL_UPSERT(
Host,
"schema_version",
["!module=%(SModule)s",
"version=%(Version)d"]).
table_exists(Host, Table) ->
ejabberd_sql:sql_query(
Host,
fun(pgsql, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select @()b exists (select * from pg_tables "
" where tablename=%(Table)s)"))
of
{selected, [{Res}]} ->
Res
end;
(sqlite, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select @()b exists"
" (select 0 from pragma_table_info(%(Table)s))"))
of
{selected, [{Res}]} ->
Res
end;
(mysql, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select @()b exists"
" (select 0 from information_schema.tables"
" where table_name=%(Table)s and"
" table_schema=schema())"))
of
{selected, [{Res}]} ->
Res
end
end).
filter_table_sh(Table) ->
case {ejabberd_sql:use_new_schema(), Table#sql_table.name} of
{true, _} ->
Table;
{_, <<"route">>} ->
Table;
{false, _} ->
Table#sql_table{
columns =
lists:keydelete(
<<"server_host">>, #sql_column.name, Table#sql_table.columns),
indices =
lists:map(
fun(Idx) ->
Idx#sql_index{
columns =
lists:delete(
<<"server_host">>, Idx#sql_index.columns)
}
end, Table#sql_table.indices)
}
end.
string_to_type(SType) ->
case string:lowercase(SType) of
<<"text">> -> text;
<<"mediumtext">> -> text;
<<"bigint">> -> bigint;
<<"bigint ", _/binary>> -> bigint;
<<"bigint(", _/binary>> -> bigint;
<<"integer">> -> integer;
<<"int">> -> integer;
<<"int(", _/binary>> -> integer;
<<"smallint">> -> smallint;
<<"smallint(", _/binary>> -> smallint;
<<"numeric">> -> numeric;
<<"decimal", _/binary>> -> numeric;
<<"bigserial">> -> bigserial;
<<"boolean">> -> boolean;
<<"tinyint(1)">> -> boolean;
<<"bytea">> -> blob;
<<"blob">> -> blob;
<<"timestamp", _/binary>> -> timestamp;
<<"character(", R/binary>> ->
{ok, [N], []} = io_lib:fread("~d)", binary_to_list(R)),
{char, N};
<<"char(", R/binary>> ->
{ok, [N], []} = io_lib:fread("~d)", binary_to_list(R)),
{char, N};
<<"varchar(", _/binary>> -> text;
<<"character varying(", _/binary>> -> text;
T ->
?ERROR_MSG("Unknown SQL type '~s'", [T]),
{undefined, T}
end.
check_columns_compatibility(RequiredColumns, Columns) ->
lists:all(
fun(#sql_column{name = Name, type = Type}) ->
%io:format("col ~p~n", [{Name, Type}]),
case lists:keyfind(Name, 1, Columns) of
false ->
false;
{_, Type2} ->
%io:format("tt ~p~n", [{Type, Type2}]),
case {Type, Type2} of
{T, T} -> true;
{text, blob} -> true;
{{text, _}, blob} -> true;
{{text, _}, text} -> true;
{{text, _}, {varchar, _}} -> true;
{text, {varchar, _}} -> true;
{{char, _}, text} -> true;
{{varchar, _}, text} -> true;
{smallint, integer} -> true;
{smallint, bigint} -> true;
{smallint, numeric} -> true;
{integer, bigint} -> true;
{integer, numeric} -> true;
{bigint, numeric} -> true;
{bigserial, integer} -> true;
{bigserial, bigint} -> true;
{bigserial, numeric} -> true;
_ -> false
end
end
end, RequiredColumns).
guess_version(Host, Schemas) ->
LastSchema = lists:max(Schemas),
SomeTablesExist =
lists:any(
fun(Table) ->
table_exists(Host, Table#sql_table.name)
end, LastSchema#sql_schema.tables),
if
SomeTablesExist ->
CompatibleSchemas =
lists:filter(
fun(Schema) ->
lists:all(
fun(Table) ->
Table2 = filter_table_sh(Table),
CurrentColumns =
get_table_schema(
Host, Table2#sql_table.name),
check_columns_compatibility(
Table2#sql_table.columns,
CurrentColumns)
end, Schema#sql_schema.tables)
end, Schemas),
case CompatibleSchemas of
[] -> -1;
_ ->
(lists:max(CompatibleSchemas))#sql_schema.version
end;
true ->
0
end.
get_current_version(Host, Module, Schemas) ->
case get_version(Host, Module) of
{selected, [{Version}]} ->
Version;
{selected, []} ->
Version = guess_version(Host, Schemas),
if
Version > 0 ->
store_version(Host, Module, Version);
true ->
ok
end,
Version
end.
format_type(pgsql, _DBVersion, Column) ->
case Column#sql_column.type of
text -> <<"text">>;
{text, _} -> <<"text">>;
bigint -> <<"bigint">>;
integer -> <<"integer">>;
smallint -> <<"smallint">>;
numeric -> <<"numeric">>;
boolean -> <<"boolean">>;
blob -> <<"bytea">>;
timestamp -> <<"timestamp">>;
{char, N} -> [<<"character(">>, integer_to_binary(N), <<")">>];
bigserial -> <<"bigserial">>
end;
format_type(sqlite, _DBVersion, Column) ->
case Column#sql_column.type of
text -> <<"text">>;
{text, _} -> <<"text">>;
bigint -> <<"bigint">>;
integer -> <<"integer">>;
smallint -> <<"smallint">>;
numeric -> <<"numeric">>;
boolean -> <<"boolean">>;
blob -> <<"blob">>;
timestamp -> <<"timestamp">>;
{char, N} -> [<<"character(">>, integer_to_binary(N), <<")">>];
bigserial -> <<"integer primary key autoincrement">>
end;
format_type(mysql, _DBVersion, Column) ->
case Column#sql_column.type of
text -> <<"text">>;
{text, big} -> <<"mediumtext">>;
{text, N} when is_integer(N), N < 191 ->
[<<"varchar(">>, integer_to_binary(N), <<")">>];
{text, _} -> <<"text">>;
bigint -> <<"bigint">>;
integer -> <<"integer">>;
smallint -> <<"smallint">>;
numeric -> <<"numeric">>;
boolean -> <<"boolean">>;
blob -> <<"blob">>;
timestamp -> <<"timestamp">>;
{char, N} -> [<<"character(">>, integer_to_binary(N), <<")">>];
bigserial -> <<"bigint auto_increment primary key">>
end.
format_default(pgsql, _DBVersion, Column) ->
case Column#sql_column.type of
text -> <<"''">>;
{text, _} -> <<"''">>;
bigint -> <<"0">>;
integer -> <<"0">>;
smallint -> <<"0">>;
numeric -> <<"0">>;
boolean -> <<"false">>;
blob -> <<"''">>;
timestamp -> <<"now()">>
%{char, N} -> <<"''">>;
%bigserial -> <<"0">>
end;
format_default(sqlite, _DBVersion, Column) ->
case Column#sql_column.type of
text -> <<"''">>;
{text, _} -> <<"''">>;
bigint -> <<"0">>;
integer -> <<"0">>;
smallint -> <<"0">>;
numeric -> <<"0">>;
boolean -> <<"false">>;
blob -> <<"''">>;
timestamp -> <<"CURRENT_TIMESTAMP">>
%{char, N} -> <<"''">>;
%bigserial -> <<"0">>
end;
format_default(mysql, _DBVersion, Column) ->
case Column#sql_column.type of
text -> <<"('')">>;
{text, _} -> <<"('')">>;
bigint -> <<"0">>;
integer -> <<"0">>;
smallint -> <<"0">>;
numeric -> <<"0">>;
boolean -> <<"false">>;
blob -> <<"('')">>;
timestamp -> <<"CURRENT_TIMESTAMP">>
%{char, N} -> <<"''">>;
%bigserial -> <<"0">>
end.
escape_name(pgsql, _DBVersion, <<"type">>) ->
<<"\"type\"">>;
escape_name(_DBType, _DBVersion, ColumnName) ->
ColumnName.
format_column_def(DBType, DBVersion, Column) ->
[<<" ">>,
escape_name(DBType, DBVersion, Column#sql_column.name), <<" ">>,
format_type(DBType, DBVersion, Column),
<<" NOT NULL">>,
case Column#sql_column.default of
false -> [];
true ->
[<<" DEFAULT ">>, format_default(DBType, DBVersion, Column)]
end,
case lists:keyfind(sql_references, 1, Column#sql_column.opts) of
false -> [];
#sql_references{table = T, column = C} ->
[<<" REFERENCES ">>, T, <<"(">>, C, <<") ON DELETE CASCADE">>]
end].
format_mysql_index_column(Table, ColumnName) ->
{value, Column} =
lists:keysearch(
ColumnName, #sql_column.name, Table#sql_table.columns),
NeedsSizeLimit =
case Column#sql_column.type of
{text, N} when is_integer(N), N < 191 -> false;
{text, _} -> true;
text -> true;
_ -> false
end,
if
NeedsSizeLimit ->
[ColumnName, <<"(191)">>];
true ->
ColumnName
end.
format_create_index(pgsql, _DBVersion, Table, Index) ->
TableName = Table#sql_table.name,
Unique =
case Index#sql_index.unique of
true -> <<"UNIQUE ">>;
false -> <<"">>
end,
Name = [<<"i_">>, TableName, <<"_">>,
lists:join(
<<"_">>,
Index#sql_index.columns)],
[<<"CREATE ">>, Unique, <<"INDEX ">>, Name, <<" ON ">>, TableName,
<<" USING btree (">>,
lists:join(
<<", ">>,
Index#sql_index.columns),
<<");">>];
format_create_index(sqlite, _DBVersion, Table, Index) ->
TableName = Table#sql_table.name,
Unique =
case Index#sql_index.unique of
true -> <<"UNIQUE ">>;
false -> <<"">>
end,
Name = [<<"i_">>, TableName, <<"_">>,
lists:join(
<<"_">>,
Index#sql_index.columns)],
[<<"CREATE ">>, Unique, <<"INDEX ">>, Name, <<" ON ">>, TableName,
<<"(">>,
lists:join(
<<", ">>,
Index#sql_index.columns),
<<");">>];
format_create_index(mysql, _DBVersion, Table, Index) ->
TableName = Table#sql_table.name,
Unique =
case Index#sql_index.unique of
true -> <<"UNIQUE ">>;
false -> <<"">>
end,
Name = [<<"i_">>, TableName, <<"_">>,
lists:join(
<<"_">>,
Index#sql_index.columns)],
[<<"CREATE ">>, Unique, <<"INDEX ">>, Name,
<<" USING BTREE ON ">>, TableName,
<<"(">>,
lists:join(
<<", ">>,
lists:map(
fun(Col) ->
format_mysql_index_column(Table, Col)
end, Index#sql_index.columns)),
<<");">>].
format_create_table(pgsql = DBType, DBVersion, Table) ->
TableName = Table#sql_table.name,
[iolist_to_binary(
[<<"CREATE TABLE ">>, TableName, <<" (\n">>,
lists:join(
<<",\n">>,
lists:map(
fun(C) -> format_column_def(DBType, DBVersion, C) end,
Table#sql_table.columns)),
<<"\n);\n">>])] ++
lists:map(
fun(I) ->
iolist_to_binary(
[format_create_index(DBType, DBVersion, Table, I),
<<"\n">>])
end,
Table#sql_table.indices);
format_create_table(sqlite = DBType, DBVersion, Table) ->
TableName = Table#sql_table.name,
[iolist_to_binary(
[<<"CREATE TABLE ">>, TableName, <<" (\n">>,
lists:join(
<<",\n">>,
lists:map(
fun(C) -> format_column_def(DBType, DBVersion, C) end,
Table#sql_table.columns)),
<<"\n);\n">>])] ++
lists:map(
fun(I) ->
iolist_to_binary(
[format_create_index(DBType, DBVersion, Table, I),
<<"\n">>])
end,
Table#sql_table.indices);
format_create_table(mysql = DBType, DBVersion, Table) ->
TableName = Table#sql_table.name,
[iolist_to_binary(
[<<"CREATE TABLE ">>, TableName, <<" (\n">>,
lists:join(
<<",\n">>,
lists:map(
fun(C) -> format_column_def(DBType, DBVersion, C) end,
Table#sql_table.columns)),
<<"\n) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\n">>])] ++
lists:map(
fun(I) ->
iolist_to_binary(
[format_create_index(DBType, DBVersion, Table, I),
<<"\n">>])
end,
Table#sql_table.indices).
%format_create_table(DBType, _DBVersion, Table) ->
% ?ERROR_MSG("Can't create SQL table ~p on ~p",
% [Table#sql_table.name, DBType]),
% error.
create_table(Host, Table) ->
ejabberd_sql:sql_query(
Host,
fun(DBType, DBVersion) ->
SQLs = format_create_table(DBType, DBVersion, Table),
?INFO_MSG("Creating table ~s:~n~s~n",
[Table#sql_table.name, SQLs]),
lists:foreach(
fun(SQL) -> ejabberd_sql:sql_query_t(SQL) end, SQLs),
case Table#sql_table.post_create of
undefined ->
ok;
F ->
F(DBType, DBVersion)
end
end).
create_tables(Host, Module, Schema) ->
lists:foreach(
fun(Table) ->
Table2 = filter_table_sh(Table),
Res = create_table(Host, Table2),
case Res of
{error, Error} ->
?ERROR_MSG("Failed to create table ~s: ~p",
[Table2#sql_table.name, Error]),
error(Error);
_ ->
ok
end
end, Schema#sql_schema.tables),
store_version(Host, Module, Schema#sql_schema.version).
should_update_schema(Host) ->
SupportedDB =
case ejabberd_option:sql_type(Host) of
pgsql -> true;
sqlite -> true;
mysql -> true;
_ -> false
end,
case ejabberd_option:update_sql_schema() andalso SupportedDB of
true ->
case ejabberd_sql:use_new_schema() of
true ->
Host == ejabberd_config:get_myname();
false ->
true
end;
false ->
false
end.
update_schema(Host, Module, Schemas) ->
case should_update_schema(Host) of
true ->
Version = get_current_version(Host, Module, Schemas),
LastSchema = lists:max(Schemas),
LastVersion = LastSchema#sql_schema.version,
case Version of
_ when Version < 0 ->
?ERROR_MSG("Can't update SQL schema for module ~p, please do it manually", [Module]);
0 ->
create_tables(Host, Module, LastSchema);
LastVersion ->
ok;
_ when LastVersion < Version ->
?ERROR_MSG("The current SQL schema for module ~p is ~p, but the latest known schema in the module is ~p", [Module, Version, LastVersion]);
_ ->
lists:foreach(
fun(Schema) ->
if
Schema#sql_schema.version > Version ->
do_update_schema(Host, Module, Schema);
true ->
ok
end
end, lists:sort(Schemas))
end;
false ->
ok
end.
do_update_schema(Host, Module, Schema) ->
lists:foreach(
fun({add_column, TableName, ColumnName}) ->
{value, Table} =
lists:keysearch(
TableName, #sql_table.name, Schema#sql_schema.tables),
{value, Column} =
lists:keysearch(
ColumnName, #sql_column.name, Table#sql_table.columns),
Res =
ejabberd_sql:sql_query(
Host,
fun(DBType, DBVersion) ->
Def = format_column_def(DBType, DBVersion, Column),
Default = format_default(DBType, DBVersion, Column),
SQLs =
[[<<"ALTER TABLE ">>,
TableName,
<<" ADD COLUMN\n">>,
Def,
<<" DEFAULT ">>,
Default, <<";\n">>]] ++
case Column#sql_column.default of
false ->
[[<<"ALTER TABLE ">>,
TableName,
<<" ALTER COLUMN ">>,
ColumnName,
<<" DROP DEFAULT;">>]];
_ ->
[]
end,
?INFO_MSG("Add column ~s/~s:~n~s~n",
[TableName,
ColumnName,
SQLs]),
lists:foreach(
fun(SQL) -> ejabberd_sql:sql_query_t(SQL) end,
SQLs)
end),
case Res of
{error, Error} ->
?ERROR_MSG("Failed to update table ~s: ~p",
[TableName, Error]),
error(Error);
_ ->
ok
end;
({drop_column, TableName, ColumnName}) ->
Res =
ejabberd_sql:sql_query(
Host,
fun(_DBType, _DBVersion) ->
SQL = [<<"ALTER TABLE ">>,
TableName,
<<" DROP COLUMN ">>,
ColumnName,
<<";">>],
?INFO_MSG("Drop column ~s/~s:~n~s~n",
[TableName,
ColumnName,
SQL]),
ejabberd_sql:sql_query_t(SQL)
end),
case Res of
{error, Error} ->
?ERROR_MSG("Failed to update table ~s: ~p",
[TableName, Error]),
error(Error);
_ ->
ok
end;
({create_index, TableName, Columns}) ->
{value, Table1} =
lists:keysearch(
TableName, #sql_table.name, Schema#sql_schema.tables),
{value, Index1} =
lists:keysearch(
Columns, #sql_index.columns, Table1#sql_table.indices),
Table = filter_table_sh(Table1),
Index =
case ejabberd_sql:use_new_schema() of
true ->
Index1;
false ->
Index1#sql_index{
columns =
lists:delete(
<<"server_host">>, Index1#sql_index.columns)
}
end,
Res =
ejabberd_sql:sql_query(
Host,
fun(DBType, DBVersion) ->
SQL1 = format_create_index(
DBType, DBVersion, Table, Index),
SQL = iolist_to_binary(SQL1),
?INFO_MSG("Create index ~s/~p:~n~s~n",
[Table#sql_table.name,
Index#sql_index.columns,
SQL]),
ejabberd_sql:sql_query_t(SQL)
end),
case Res of
{error, Error} ->
?ERROR_MSG("Failed to update table ~s: ~p",
[TableName, Error]),
error(Error);
_ ->
ok
end;
({drop_index, TableName, Columns1}) ->
Columns =
case ejabberd_sql:use_new_schema() of
true ->
Columns1;
false ->
lists:delete(
<<"server_host">>, Columns1)
end,
case find_index_name(Host, TableName, Columns) of
false ->
?ERROR_MSG("Can't find an index to drop for ~s/~p",
[TableName, Columns]);
{ok, IndexName} ->
Res =
ejabberd_sql:sql_query(
Host,
fun(DBType, _DBVersion) ->
SQL =
case DBType of
mysql ->
[<<"DROP INDEX ">>,
IndexName,
<<" ON ">>,
TableName,
<<";">>];
_ ->
[<<"DROP INDEX ">>,
IndexName, <<";">>]
end,
?INFO_MSG("Drop index ~s/~p:~n~s~n",
[TableName,
Columns,
SQL]),
ejabberd_sql:sql_query_t(SQL)
end),
case Res of
{error, Error} ->
?ERROR_MSG("Failed to update table ~s: ~p",
[TableName, Error]),
error(Error);
_ ->
ok
end
end
end, Schema#sql_schema.update),
store_version(Host, Module, Schema#sql_schema.version).
test() ->
Schemas =
[#sql_schema{
version = 2,
tables =
[#sql_table{
name = <<"archive2">>,
columns = [#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"timestamp">>, type = bigint},
#sql_column{name = <<"peer">>, type = text},
#sql_column{name = <<"bare_peer">>, type = text},
#sql_column{name = <<"xml">>, type = {text, big}},
#sql_column{name = <<"txt">>, type = {text, big}},
#sql_column{name = <<"id">>, type = bigserial},
#sql_column{name = <<"kind">>, type = text},
#sql_column{name = <<"nick">>, type = text},
#sql_column{name = <<"origin_id">>, type = text},
#sql_column{name = <<"type">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"timestamp">>]},
#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"peer">>]},
#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"bare_peer">>]},
#sql_index{
columns = [<<"server_host">>, <<"origin_id">>]},
#sql_index{
columns = [<<"server_host">>, <<"timestamp">>]}
]}],
update =
[{add_column, <<"archive2">>, <<"origin_id">>},
{create_index, <<"archive2">>,
[<<"server_host">>, <<"origin_id">>]},
{drop_index, <<"archive2">>,
[<<"server_host">>, <<"origin_id">>]},
{drop_column, <<"archive2">>, <<"origin_id">>}
]},
#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"archive2">>,
columns = [#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"timestamp">>, type = bigint},
#sql_column{name = <<"peer">>, type = text},
#sql_column{name = <<"bare_peer">>, type = text},
#sql_column{name = <<"xml">>, type = {text, big}},
#sql_column{name = <<"txt">>, type = {text, big}},
#sql_column{name = <<"id">>, type = bigserial},
#sql_column{name = <<"kind">>, type = {text, 10}},
#sql_column{name = <<"nick">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"timestamp">>]},
#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"peer">>]},
#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"bare_peer">>]},
#sql_index{
columns = [<<"server_host">>, <<"timestamp">>]}
]}]}],
update_schema(<<"localhost">>, mod_foo, Schemas).

View File

@ -51,7 +51,9 @@ start(Host) ->
type => supervisor,
modules => [?MODULE]},
case supervisor:start_child(ejabberd_db_sup, Spec) of
{ok, _} -> ok;
{ok, _} ->
ejabberd_sql_schema:start(Host),
ok;
{error, {already_started, Pid}} ->
%% Wait for the supervisor to fully start
_ = supervisor:count_children(Pid),

View File

@ -40,9 +40,26 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"motd">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"xml">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>],
unique = true}]}]}].
set_motd_users(LServer, USRs) ->
F = fun() ->
lists:foreach(

View File

@ -37,6 +37,8 @@
%%% API
%%%===================================================================
init() ->
ejabberd_sql_schema:update_schema(
ejabberd_config:get_myname(), ?MODULE, schemas()),
Node = erlang:atom_to_binary(node(), latin1),
?DEBUG("Cleaning SQL 'bosh' table...", []),
case ejabberd_sql:sql_query(
@ -48,6 +50,20 @@ init() ->
Err
end.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"bosh">>,
columns =
[#sql_column{name = <<"sid">>, type = text},
#sql_column{name = <<"node">>, type = text},
#sql_column{name = <<"pid">>, type = text}],
indices = [#sql_index{
columns = [<<"sid">>],
unique = true}]}]}].
open_session(SID, Pid) ->
PidS = misc:encode_pid(Pid),
Node = erlang:atom_to_binary(node(Pid), latin1),

View File

@ -37,9 +37,25 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"caps_features">>,
columns =
[#sql_column{name = <<"node">>, type = text},
#sql_column{name = <<"subnode">>, type = text},
#sql_column{name = <<"feature">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"node">>, <<"subnode">>]}]}]}].
caps_read(LServer, {Node, SubNode}) ->
case ejabberd_sql:sql_query(
LServer,

View File

@ -38,9 +38,25 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"last">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"seconds">>, type = text},
#sql_column{name = <<"state">>, type = text}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>],
unique = true}]}]}].
get_last(LUser, LServer) ->
case ejabberd_sql:sql_query(
LServer,

View File

@ -43,9 +43,59 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"archive">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"timestamp">>, type = bigint},
#sql_column{name = <<"peer">>, type = text},
#sql_column{name = <<"bare_peer">>, type = text},
#sql_column{name = <<"xml">>, type = {text, big}},
#sql_column{name = <<"txt">>, type = {text, big}},
#sql_column{name = <<"id">>, type = bigserial},
#sql_column{name = <<"kind">>, type = {text, 10}},
#sql_column{name = <<"nick">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"timestamp">>]},
#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"peer">>]},
#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"bare_peer">>]},
#sql_index{
columns = [<<"server_host">>, <<"timestamp">>]}
],
post_create =
fun(mysql, _) ->
ejabberd_sql:sql_query_t(
<<"CREATE FULLTEXT INDEX i_archive_txt ON archive(txt);">>);
(_, _) ->
ok
end},
#sql_table{
name = <<"archive_prefs">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"def">>, type = text},
#sql_column{name = <<"always">>, type = text},
#sql_column{name = <<"never">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>],
unique = true}]}]}].
remove_user(LUser, LServer) ->
ejabberd_sql:sql_query(
LServer,

View File

@ -33,10 +33,29 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
%% TODO
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"mix_pam">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"channel">>, type = text},
#sql_column{name = <<"service">>, type = text},
#sql_column{name = <<"id">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"username">>, <<"server_host">>,
<<"channel">>, <<"service">>],
unique = true}]}]}].
add_channel(User, Channel, ID) ->
{LUser, LServer, _} = jid:tolower(User),
{Chan, Service, _} = jid:tolower(Channel),

View File

@ -34,10 +34,65 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
%% TODO
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"mix_channel">>,
columns =
[#sql_column{name = <<"channel">>, type = text},
#sql_column{name = <<"service">>, type = text},
#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"domain">>, type = text},
#sql_column{name = <<"jid">>, type = text},
#sql_column{name = <<"hidden">>, type = boolean},
#sql_column{name = <<"hmac_key">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"channel">>, <<"service">>],
unique = true},
#sql_index{
columns = [<<"service">>]}]},
#sql_table{
name = <<"mix_participant">>,
columns =
[#sql_column{name = <<"channel">>, type = text},
#sql_column{name = <<"service">>, type = text},
#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"domain">>, type = text},
#sql_column{name = <<"jid">>, type = text},
#sql_column{name = <<"id">>, type = text},
#sql_column{name = <<"nick">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"channel">>, <<"service">>,
<<"username">>, <<"domain">>],
unique = true}]},
#sql_table{
name = <<"mix_subscription">>,
columns =
[#sql_column{name = <<"channel">>, type = text},
#sql_column{name = <<"service">>, type = {text, 75}},
#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"domain">>, type = {text, 75}},
#sql_column{name = <<"node">>, type = text},
#sql_column{name = <<"jid">>, type = text}],
indices = [#sql_index{
columns = [<<"channel">>, <<"service">>,
<<"username">>, <<"domain">>,
<<"node">>],
unique = true},
#sql_index{
columns = [<<"channel">>, <<"service">>,
<<"node">>]}]}]}].
set_channel(LServer, Channel, Service, CreatorJID, Hidden, Key) ->
{User, Domain, _} = jid:tolower(CreatorJID),
RawJID = jid:encode(jid:remove_resource(CreatorJID)),

View File

@ -36,9 +36,33 @@ init() ->
?ERROR_MSG("Backend 'sql' is only supported for db_type", []),
{error, db_failure}.
init(_Host, _Opts) ->
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"mqtt_pub">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"resource">>, type = text},
#sql_column{name = <<"topic">>, type = text},
#sql_column{name = <<"qos">>, type = smallint},
#sql_column{name = <<"payload">>, type = blob},
#sql_column{name = <<"payload_format">>, type = smallint},
#sql_column{name = <<"content_type">>, type = text},
#sql_column{name = <<"response_topic">>, type = text},
#sql_column{name = <<"correlation_data">>, type = blob},
#sql_column{name = <<"user_property">>, type = blob},
#sql_column{name = <<"expiry">>, type = bigint}],
indices = [#sql_index{
columns = [<<"topic">>, <<"server_host">>],
unique = true}]}]}].
publish({U, LServer, R}, Topic, Payload, QoS, Props, ExpiryTime) ->
PayloadFormat = encode_pfi(maps:get(payload_format_indicator, Props, binary)),
ResponseTopic = maps:get(response_topic, Props, <<"">>),

View File

@ -52,6 +52,7 @@
%%% API
%%%===================================================================
init(Host, Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
case gen_mod:ram_db_mod(Opts, mod_muc) of
?MODULE ->
clean_tables(Host);
@ -59,6 +60,82 @@ init(Host, Opts) ->
ok
end.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"muc_room">>,
columns =
[#sql_column{name = <<"name">>, type = text},
#sql_column{name = <<"host">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"opts">>, type = {text, big}},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"name">>, <<"host">>],
unique = true},
#sql_index{
columns = [<<"host">>, <<"created_at">>]}]},
#sql_table{
name = <<"muc_registered">>,
columns =
[#sql_column{name = <<"jid">>, type = text},
#sql_column{name = <<"host">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"nick">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"jid">>, <<"host">>],
unique = true},
#sql_index{
columns = [<<"nick">>]}]},
#sql_table{
name = <<"muc_online_room">>,
columns =
[#sql_column{name = <<"name">>, type = text},
#sql_column{name = <<"host">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"node">>, type = text},
#sql_column{name = <<"pid">>, type = text}],
indices = [#sql_index{
columns = [<<"name">>, <<"host">>],
unique = true}]},
#sql_table{
name = <<"muc_online_users">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server">>, type = {text, 75}},
#sql_column{name = <<"resource">>, type = text},
#sql_column{name = <<"name">>, type = text},
#sql_column{name = <<"host">>, type = {text, 75}},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"node">>, type = text}],
indices = [#sql_index{
columns = [<<"username">>, <<"server">>,
<<"resource">>, <<"name">>,
<<"host">>],
unique = true}]},
#sql_table{
name = <<"muc_room_subscribers">>,
columns =
[#sql_column{name = <<"room">>, type = text},
#sql_column{name = <<"host">>, type = text},
#sql_column{name = <<"jid">>, type = text},
#sql_column{name = <<"nick">>, type = text},
#sql_column{name = <<"nodes">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"host">>, <<"room">>, <<"jid">>],
unique = true},
#sql_index{
columns = [<<"host">>, <<"jid">>]},
#sql_index{
columns = [<<"jid">>]}]}]}].
store_room(LServer, Host, Name, Opts, ChangesHints) ->
{Subs, Opts2} = case lists:keytake(subscribers, 1, Opts) of
{value, {subscribers, S}, OptN} -> {S, OptN};

View File

@ -40,9 +40,28 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"spool">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"xml">>, type = {text, big}},
#sql_column{name = <<"seq">>, type = bigserial},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>]},
#sql_index{
columns = [<<"created_at">>]}]}]}].
store_message(#offline_msg{us = {LUser, LServer}} = M) ->
From = M#offline_msg.from,
To = M#offline_msg.to,

View File

@ -42,9 +42,57 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"privacy_default_list">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"name">>, type = text}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>],
unique = true}]},
#sql_table{
name = <<"privacy_list">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"name">>, type = text},
#sql_column{name = <<"id">>, type = bigserial},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"id">>],
unique = true},
#sql_index{
columns = [<<"server_host">>, <<"username">>,
<<"name">>],
unique = true}]},
#sql_table{
name = <<"privacy_list_data">>,
columns =
[#sql_column{name = <<"id">>, type = bigint,
opts = [#sql_references{
table = <<"privacy_list">>,
column = <<"id">>}]},
#sql_column{name = <<"t">>, type = {char, 1}},
#sql_column{name = <<"value">>, type = text},
#sql_column{name = <<"action">>, type = {char, 1}},
#sql_column{name = <<"ord">>, type = numeric},
#sql_column{name = <<"match_all">>, type = boolean},
#sql_column{name = <<"match_iq">>, type = boolean},
#sql_column{name = <<"match_message">>, type = boolean},
#sql_column{name = <<"match_presence_in">>, type = boolean},
#sql_column{name = <<"match_presence_out">>, type = boolean}],
indices = [#sql_index{columns = [<<"id">>]}]}]}].
unset_default(LUser, LServer) ->
case unset_default_privacy_list(LUser, LServer) of
ok ->

View File

@ -37,9 +37,28 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"private_storage">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"namespace">>, type = text},
#sql_column{name = <<"data">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>,
<<"namespace">>],
unique = true}]}]}].
set_data(LUser, LServer, Data) ->
F = fun() ->
lists:foreach(

View File

@ -34,6 +34,8 @@
%%% API
%%%===================================================================
init() ->
ejabberd_sql_schema:update_schema(
ejabberd_config:get_myname(), ?MODULE, schemas()),
NodeS = erlang:atom_to_binary(node(), latin1),
?DEBUG("Cleaning SQL 'proxy65' table...", []),
case ejabberd_sql:sql_query(
@ -47,6 +49,25 @@ init() ->
Err
end.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"proxy65">>,
columns =
[#sql_column{name = <<"sid">>, type = text},
#sql_column{name = <<"pid_t">>, type = text},
#sql_column{name = <<"pid_i">>, type = text},
#sql_column{name = <<"node_t">>, type = text},
#sql_column{name = <<"node_i">>, type = text},
#sql_column{name = <<"jid_i">>, type = text}],
indices = [#sql_index{
columns = [<<"sid">>],
unique = true},
#sql_index{
columns = [<<"jid_i">>]}]}]}].
register_stream(SID, Pid) ->
PidS = misc:encode_pid(Pid),
NodeS = erlang:atom_to_binary(node(Pid), latin1),

View File

@ -21,12 +21,96 @@
%% API
-export([init/3]).
-include("ejabberd_sql_pt.hrl").
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _ServerHost, _Opts) ->
init(_Host, ServerHost, _Opts) ->
ejabberd_sql_schema:update_schema(ServerHost, ?MODULE, schemas()),
ok.
%%%===================================================================
%%% Internal functions
%%%===================================================================
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"pubsub_node">>,
columns =
[#sql_column{name = <<"host">>, type = text},
#sql_column{name = <<"node">>, type = text},
#sql_column{name = <<"parent">>, type = text,
default = true},
#sql_column{name = <<"plugin">>, type = text},
#sql_column{name = <<"nodeid">>, type = bigserial}],
indices = [#sql_index{
columns = [<<"nodeid">>],
unique = true},
#sql_index{
columns = [<<"parent">>]},
#sql_index{
columns = [<<"host">>, <<"node">>],
unique = true}]},
#sql_table{
name = <<"pubsub_node_option">>,
columns =
[#sql_column{name = <<"nodeid">>, type = bigint,
opts = [#sql_references{
table = <<"pubsub_node">>,
column = <<"nodeid">>}]},
#sql_column{name = <<"name">>, type = text},
#sql_column{name = <<"val">>, type = text}],
indices = [#sql_index{columns = [<<"nodeid">>]}]},
#sql_table{
name = <<"pubsub_node_owner">>,
columns =
[#sql_column{name = <<"nodeid">>, type = bigint,
opts = [#sql_references{
table = <<"pubsub_node">>,
column = <<"nodeid">>}]},
#sql_column{name = <<"owner">>, type = text}],
indices = [#sql_index{columns = [<<"nodeid">>]}]},
#sql_table{
name = <<"pubsub_state">>,
columns =
[#sql_column{name = <<"nodeid">>, type = bigint,
opts = [#sql_references{
table = <<"pubsub_node">>,
column = <<"nodeid">>}]},
#sql_column{name = <<"jid">>, type = text},
#sql_column{name = <<"affiliation">>, type = {char, 1}},
#sql_column{name = <<"subscriptions">>, type = text,
default = true},
#sql_column{name = <<"stateid">>, type = bigserial}],
indices = [#sql_index{columns = [<<"stateid">>],
unique = true},
#sql_index{columns = [<<"jid">>]},
#sql_index{columns = [<<"nodeid">>, <<"jid">>],
unique = true}]},
#sql_table{
name = <<"pubsub_item">>,
columns =
[#sql_column{name = <<"nodeid">>, type = bigint,
opts = [#sql_references{
table = <<"pubsub_node">>,
column = <<"nodeid">>}]},
#sql_column{name = <<"itemid">>, type = text},
#sql_column{name = <<"publisher">>, type = text},
#sql_column{name = <<"creation">>, type = {text, 32}},
#sql_column{name = <<"modification">>, type = {text, 32}},
#sql_column{name = <<"payload">>, type = {text, big},
default = true}],
indices = [#sql_index{columns = [<<"nodeid">>, <<"itemid">>],
unique = true},
#sql_index{columns = [<<"itemid">>]}]},
#sql_table{
name = <<"pubsub_subscription_opt">>,
columns =
[#sql_column{name = <<"subid">>, type = text},
#sql_column{name = <<"opt_name">>, type = {text, 32}},
#sql_column{name = <<"opt_value">>, type = text}],
indices = [#sql_index{columns = [<<"subid">>, <<"opt_name">>],
unique = true}]}]}].

View File

@ -39,9 +39,32 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"push_session">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"timestamp">>, type = bigint},
#sql_column{name = <<"service">>, type = text},
#sql_column{name = <<"node">>, type = text},
#sql_column{name = <<"xml">>, type = text}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>,
<<"timestamp">>],
unique = true},
#sql_index{
columns = [<<"server_host">>, <<"username">>,
<<"service">>, <<"node">>],
unique = true}]}]}].
store_session(LUser, LServer, NowTS, PushJID, Node, XData) ->
XML = encode_xdata(XData),
TS = misc:now_to_usec(NowTS),

View File

@ -43,9 +43,55 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"rosterusers">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"jid">>, type = text},
#sql_column{name = <<"nick">>, type = text},
#sql_column{name = <<"subscription">>, type = {char, 1}},
#sql_column{name = <<"ask">>, type = {char, 1}},
#sql_column{name = <<"askmessage">>, type = text},
#sql_column{name = <<"server">>, type = {char, 1}},
#sql_column{name = <<"subscribe">>, type = text},
#sql_column{name = <<"type">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>,
<<"jid">>],
unique = true},
#sql_index{
columns = [<<"server_host">>, <<"jid">>]}]},
#sql_table{
name = <<"rostergroups">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"jid">>, type = text},
#sql_column{name = <<"grp">>, type = text}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>,
<<"jid">>]}]},
#sql_table{
name = <<"roster_version">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"version">>, type = text}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>],
unique = true}]}]}].
read_roster_version(LUser, LServer) ->
case ejabberd_sql:sql_query(
LServer,

View File

@ -43,9 +43,40 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"sr_group">>,
columns =
[#sql_column{name = <<"name">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"opts">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"name">>],
unique = true}]},
#sql_table{
name = <<"sr_user">>,
columns =
[#sql_column{name = <<"jid">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"grp">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>,
<<"jid">>, <<"grp">>],
unique = true},
#sql_index{
columns = [<<"server_host">>, <<"grp">>]}]}]}].
list_groups(Host) ->
case ejabberd_sql:sql_query(
Host,

View File

@ -41,9 +41,79 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"vcard">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"vcard">>, type = {text, big}},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>],
unique = true}]},
#sql_table{
name = <<"vcard_search">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"lusername">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"fn">>, type = text},
#sql_column{name = <<"lfn">>, type = text},
#sql_column{name = <<"family">>, type = text},
#sql_column{name = <<"lfamily">>, type = text},
#sql_column{name = <<"given">>, type = text},
#sql_column{name = <<"lgiven">>, type = text},
#sql_column{name = <<"middle">>, type = text},
#sql_column{name = <<"lmiddle">>, type = text},
#sql_column{name = <<"nickname">>, type = text},
#sql_column{name = <<"lnickname">>, type = text},
#sql_column{name = <<"bday">>, type = text},
#sql_column{name = <<"lbday">>, type = text},
#sql_column{name = <<"ctry">>, type = text},
#sql_column{name = <<"lctry">>, type = text},
#sql_column{name = <<"locality">>, type = text},
#sql_column{name = <<"llocality">>, type = text},
#sql_column{name = <<"email">>, type = text},
#sql_column{name = <<"lemail">>, type = text},
#sql_column{name = <<"orgname">>, type = text},
#sql_column{name = <<"lorgname">>, type = text},
#sql_column{name = <<"orgunit">>, type = text},
#sql_column{name = <<"lorgunit">>, type = text}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"lusername">>],
unique = true},
#sql_index{
columns = [<<"server_host">>, <<"lfn">>]},
#sql_index{
columns = [<<"server_host">>, <<"lfamily">>]},
#sql_index{
columns = [<<"server_host">>, <<"lgiven">>]},
#sql_index{
columns = [<<"server_host">>, <<"lmiddle">>]},
#sql_index{
columns = [<<"server_host">>, <<"lnickname">>]},
#sql_index{
columns = [<<"server_host">>, <<"lbday">>]},
#sql_index{
columns = [<<"server_host">>, <<"lctry">>]},
#sql_index{
columns = [<<"server_host">>, <<"llocality">>]},
#sql_index{
columns = [<<"server_host">>, <<"lemail">>]},
#sql_index{
columns = [<<"server_host">>, <<"lorgname">>]},
#sql_index{
columns = [<<"server_host">>, <<"lorgunit">>]}]}]}].
stop(_Host) ->
ok.