1 -- Based on mod_mam_sql
2 -- Copyright (C) 2011-2012 Kim Alvefur
4 -- This file is MIT/X11 licensed.
6 local st
= require
"util.stanza";
7 local jid_bare
= require
"util.jid".bare
;
8 local jid_split
= require
"util.jid".split
;
10 local serialize
= require
"util.json".encode
, require
"util.json".decode
;
11 local tostring = tostring;
12 local time_now
= os
.time
;
15 local table_name
= module
:get_option("message_log_sql_table", pcall(require
, "util.cache") and "messages" or "prosodyarchive");
17 local sql
, setsql
, getsql
= {};
20 local resolve_relative_path
= require
"core.configmanager".resolve_relative_path
;
21 local params
= module
:get_option("message_log_sql", module
:get_option("sql"));
24 local function test_connection()
25 if not connection
then return nil; end
26 if connection
:ping() then
29 module
:log("debug", "Database connection closed");
33 local function connect()
34 if not test_connection() then
35 prosody
.unlock_globals();
36 local dbh
, err
= DBI
.Connect(
37 params
.driver
, params
.database
,
38 params
.username
, params
.password
,
39 params
.host
, params
.port
41 prosody
.lock_globals();
43 module
:log("debug", "Database connection failed: %s", tostring(err
));
46 module
:log("debug", "Successfully connected to database");
47 dbh
:autocommit(false); -- don't commit automatically
55 do -- process options to get a db connection
57 prosody
.unlock_globals();
58 ok
, DBI
= pcall(require
, "DBI");
60 package
.loaded
["DBI"] = {};
61 module
:log("error", "Failed to load the LuaDBI library for accessing SQL databases: %s", DBI
);
62 module
:log("error", "More information on installing LuaDBI can be found at http://prosody.im/doc/depends#luadbi");
64 prosody
.lock_globals();
65 if not ok
or not DBI
.Connect
then
66 return; -- Halt loading of this module
69 params
= params
or { driver
= "SQLite3" };
71 if params
.driver
== "SQLite3" then
72 params
.database
= resolve_relative_path(prosody
.paths
.data
or ".", params
.database
or "prosody.sqlite");
75 assert(params
.driver
and params
.database
, "Both the SQL driver and the database need to be specified");
81 function getsql(sql
, ...)
82 if params
.driver
== "PostgreSQL" then
83 sql
= sql
:gsub("`", "\"");
85 -- do prepared statement stuff
86 local stmt
, err
= connection
:prepare(sql
);
87 if not stmt
and not test_connection() then error("connection failed"); end
88 if not stmt
then module
:log("error", "QUERY FAILED: %s %s", err
, debug
.traceback()); return nil, err
; end
90 local ok
, err
= stmt
:execute(...);
91 if not ok
and not test_connection() then error("connection failed"); end
92 if not ok
then return nil, err
; end
96 function setsql(sql
, ...)
97 local stmt
, err
= getsql(sql
, ...);
98 if not stmt
then return stmt
, err
; end
99 return stmt
:affected();
101 function sql
.rollback(...)
102 if connection
then connection
:rollback(); end -- FIXME check for rollback error?
105 function sql
.commit(...)
106 local ok
, err
= connection
:commit();
108 module
:log("error", "SQL commit failed: %s", tostring(err
));
109 return nil, "SQL commit failed: "..tostring(err
);
117 local function message_handler(event
, c2s
)
118 local origin
, stanza
= event
.origin
, event
.stanza
;
119 local orig_type
= stanza
.attr
.type or "normal";
120 local orig_to
= stanza
.attr
.to
;
121 local orig_from
= stanza
.attr
.from
;
123 if not orig_from
and c2s
then
124 orig_from
= origin
.full_jid
;
126 orig_to
= orig_to
or orig_from
; -- Weird corner cases
128 -- Don't store messages of these types
129 if orig_type
== "error"
130 or orig_type
== "headline"
131 or orig_type
== "groupchat"
132 or not stanza
:get_child("body") then
134 -- TODO Maybe headlines should be configurable?
137 local store_user
, store_host
= jid_split(c2s
and orig_from
or orig_to
);
138 local target_jid
= c2s
and orig_to
or orig_from
;
139 local target_bare
= jid_bare(target_jid
);
140 local _
, _
, target_resource
= jid_split(target_jid
);
143 local when
= time_now();
145 local ok
, err
= setsql([[
146 INSERT INTO `]]..table_name
..[[`
147 (`host`, `user`, `store`, `when`, `with`, `resource`, `stanza`)
148 VALUES (?, ?, ?, ?, ?, ?, ?);
149 ]], store_host
, store_user
, "message_log", when
, target_bare
, target_resource
, serialize(st
.preserialize(stanza
)))
153 module
:log("error", "SQL error: %s", err
);
158 local function c2s_message_handler(event
)
159 return message_handler(event
, true);
162 -- Stanzas sent by local clients
163 module
:hook("pre-message/bare", c2s_message_handler
, 2);
164 module
:hook("pre-message/full", c2s_message_handler
, 2);
165 -- Stanszas to local clients
166 module
:hook("message/bare", message_handler
, 2);
167 module
:hook("message/full", message_handler
, 2);
169 -- In the telnet console, run:
170 -- >hosts["this host"].modules.mam_sql.environment.create_sql()
171 function create_sql()
172 local stm
= assert(getsql([[
173 CREATE TABLE `]]..table_name
..[[` (
177 `id` INTEGER PRIMARY KEY AUTOINCREMENT,
183 CREATE INDEX `hus` ON `]]..table_name
..[[` (`host`, `user`, `store`);
184 CREATE INDEX `with` ON `]]..table_name
..[[` (`with`);
185 CREATE INDEX `thetime` ON `]]..table_name
..[[` (`when`);