Comparison

mod_log_messages_sql/mod_log_messages_sql.lua @ 953:2c38d7d8b332

mod_log_messages_sql: Fork of mod_mam_sql without the protocol bits
author Kim Alvefur <zash@zash.se>
date Wed, 03 Apr 2013 20:29:48 +0200
child 1343:7dbde05b48a9
comparison
equal deleted inserted replaced
952:ef2253a7858d 953:2c38d7d8b332
1 -- Based on mod_mam_sql
2 -- Copyright (C) 2011-2012 Kim Alvefur
3 --
4 -- This file is MIT/X11 licensed.
5
6 local st = require "util.stanza";
7 local jid_bare = require "util.jid".bare;
8 local jid_split = require "util.jid".split;
9
10 local serialize = require"util.json".encode, require"util.json".decode;
11 local tostring = tostring;
12 local time_now = os.time;
13
14 local sql, setsql, getsql = {};
15 do -- SQL stuff
16 local connection;
17 local resolve_relative_path = require "core.configmanager".resolve_relative_path;
18 local params = module:get_option("message_log_sql", module:get_option("sql"));
19
20 local function test_connection()
21 if not connection then return nil; end
22 if connection:ping() then
23 return true;
24 else
25 module:log("debug", "Database connection closed");
26 connection = nil;
27 end
28 end
29 local function connect()
30 if not test_connection() then
31 prosody.unlock_globals();
32 local dbh, err = DBI.Connect(
33 params.driver, params.database,
34 params.username, params.password,
35 params.host, params.port
36 );
37 prosody.lock_globals();
38 if not dbh then
39 module:log("debug", "Database connection failed: %s", tostring(err));
40 return nil, err;
41 end
42 module:log("debug", "Successfully connected to database");
43 dbh:autocommit(false); -- don't commit automatically
44 connection = dbh;
45
46 end
47 return connection;
48 end
49
50 do -- process options to get a db connection
51 local ok;
52 prosody.unlock_globals();
53 ok, DBI = pcall(require, "DBI");
54 if not ok then
55 package.loaded["DBI"] = {};
56 module:log("error", "Failed to load the LuaDBI library for accessing SQL databases: %s", DBI);
57 module:log("error", "More information on installing LuaDBI can be found at http://prosody.im/doc/depends#luadbi");
58 end
59 prosody.lock_globals();
60 if not ok or not DBI.Connect then
61 return; -- Halt loading of this module
62 end
63
64 params = params or { driver = "SQLite3" };
65
66 if params.driver == "SQLite3" then
67 params.database = resolve_relative_path(prosody.paths.data or ".", params.database or "prosody.sqlite");
68 end
69
70 assert(params.driver and params.database, "Both the SQL driver and the database need to be specified");
71
72 assert(connect());
73
74 end
75
76 function getsql(sql, ...)
77 if params.driver == "PostgreSQL" then
78 sql = sql:gsub("`", "\"");
79 end
80 -- do prepared statement stuff
81 local stmt, err = connection:prepare(sql);
82 if not stmt and not test_connection() then error("connection failed"); end
83 if not stmt then module:log("error", "QUERY FAILED: %s %s", err, debug.traceback()); return nil, err; end
84 -- run query
85 local ok, err = stmt:execute(...);
86 if not ok and not test_connection() then error("connection failed"); end
87 if not ok then return nil, err; end
88
89 return stmt;
90 end
91 function setsql(sql, ...)
92 local stmt, err = getsql(sql, ...);
93 if not stmt then return stmt, err; end
94 return stmt:affected();
95 end
96 function sql.rollback(...)
97 if connection then connection:rollback(); end -- FIXME check for rollback error?
98 return ...;
99 end
100 function sql.commit(...)
101 if not connection:commit() then return nil, "SQL commit failed"; end
102 return ...;
103 end
104
105 end
106
107 -- Handle messages
108 local function message_handler(event, c2s)
109 local origin, stanza = event.origin, event.stanza;
110 local orig_type = stanza.attr.type or "normal";
111 local orig_to = stanza.attr.to;
112 local orig_from = stanza.attr.from;
113
114 if not orig_from and c2s then
115 orig_from = origin.full_jid;
116 end
117 orig_to = orig_to or orig_from; -- Weird corner cases
118
119 -- Don't store messages of these types
120 if orig_type == "error"
121 or orig_type == "headline"
122 or orig_type == "groupchat"
123 or not stanza:get_child("body") then
124 return;
125 -- TODO Maybe headlines should be configurable?
126 end
127
128 local store_user, store_host = jid_split(c2s and orig_from or orig_to);
129 local target_jid = c2s and orig_to or orig_from;
130 local target_bare = jid_bare(target_jid);
131 local _, _, target_resource = jid_split(target_jid);
132
133 --local id = uuid();
134 local when = time_now();
135 -- And stash it
136 local ok, err = setsql([[
137 INSERT INTO `prosodyarchive`
138 (`host`, `user`, `store`, `when`, `with`, `resource`, `stanza`)
139 VALUES (?, ?, ?, ?, ?, ?, ?);
140 ]], store_host, store_user, "message_log", when, target_bare, target_resource, serialize(st.preserialize(stanza)))
141 if ok then
142 sql.commit();
143 else
144 module:log("error", "SQL error: %s", err);
145 sql.rollback();
146 end
147 end
148
149 local function c2s_message_handler(event)
150 return message_handler(event, true);
151 end
152
153 -- Stanzas sent by local clients
154 module:hook("pre-message/bare", c2s_message_handler, 2);
155 module:hook("pre-message/full", c2s_message_handler, 2);
156 -- Stanszas to local clients
157 module:hook("message/bare", message_handler, 2);
158 module:hook("message/full", message_handler, 2);
159
160 -- In the telnet console, run:
161 -- >hosts["this host"].modules.mam_sql.environment.create_sql()
162 function create_sql()
163 local stm = getsql[[
164 CREATE TABLE `prosodyarchive` (
165 `host` TEXT,
166 `user` TEXT,
167 `store` TEXT,
168 `id` INTEGER PRIMARY KEY AUTOINCREMENT,
169 `when` INTEGER,
170 `with` TEXT,
171 `resource` TEXT,
172 `stanza` TEXT
173 );
174 CREATE INDEX `hus` ON `prosodyarchive` (`host`, `user`, `store`);
175 CREATE INDEX `with` ON `prosodyarchive` (`with`);
176 CREATE INDEX `thetime` ON `prosodyarchive` (`when`);
177 ]];
178 stm:execute();
179 sql.commit();
180 end