Merge 0.10->trunk
[prosody.git] / plugins / mod_storage_sql.lua
index adf9f9b6a50d5282b6c0d6e717d472419e7827ff..60870a01afb6bcc5664eda54a5e88e0f24d7937e 100644 (file)
@@ -1,4 +1,6 @@
 
+-- luacheck: ignore 212/self
+
 local json = require "util.json";
 local sql = require "util.sql";
 local xml_parse = require "util.xml".parse;
@@ -80,16 +82,14 @@ local function keyval_store_set(data)
                local extradata = {};
                for key, value in pairs(data) do
                        if type(key) == "string" and key ~= "" then
-                               local t, value = serialize(value);
-                               assert(t, value);
+                               local t, value = assert(serialize(value));
                                engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, user or "", store, key, t, value);
                        else
                                extradata[key] = value;
                        end
                end
                if next(extradata) ~= nil then
-                       local t, extradata = serialize(extradata);
-                       assert(t, extradata);
+                       local t, extradata = assert(serialize(extradata));
                        engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, user or "", store, "", t, extradata);
                end
        end
@@ -125,32 +125,53 @@ end
 
 --- Archive store API
 
+-- luacheck: ignore 512 431/user 431/store
 local map_store = {};
 map_store.__index = map_store;
+map_store.remove = {};
 function map_store:get(username, key)
        local ok, result = engine:transaction(function()
+               local data;
                if type(key) == "string" and key ~= "" then
-                       for row in engine:select("SELECT `type`, `value` FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", host, username or "", self.store, key) do
-                               return deserialize(row[1], row[2]);
+                       for row in engine:select("SELECT `type`, `value` FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=? LIMIT 1", host, username or "", self.store, key) do
+                               data = deserialize(row[1], row[2]);
                        end
+                       return data;
                else
-                       error("TODO: non-string keys");
+                       for row in engine:select("SELECT `type`, `value` FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=? LIMIT 1", host, username or "", self.store, "") do
+                               data = deserialize(row[1], row[2]);
+                       end
+                       return data and data[key] or nil;
                end
        end);
        if not ok then return nil, result; end
        return result;
 end
 function map_store:set(username, key, data)
+       if data == nil then data = self.remove; end
+       return self:set_keys(username, { [key] = data });
+end
+function map_store:set_keys(username, keydatas)
        local ok, result = engine:transaction(function()
-               if type(key) == "string" and key ~= "" then
-                       engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?",
-                               host, username or "", self.store, key);
-                       if data ~= nil then
-                               local t, value = assert(serialize(data));
-                               engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, username or "", self.store, key, t, value);
+               for key, data in pairs(keydatas) do
+                       if type(key) == "string" and key ~= "" then
+                               engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?",
+                                       host, username or "", self.store, key);
+                               if data ~= self.remove then
+                                       local t, value = assert(serialize(data));
+                                       engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, username or "", self.store, key, t, value);
+                               end
+                       else
+                               local extradata = {};
+                               for row in engine:select("SELECT `type`, `value` FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=? LIMIT 1", host, username or "", self.store, "") do
+                                       extradata = deserialize(row[1], row[2]);
+                               end
+                               engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?",
+                                       host, username or "", self.store, "");
+                               extradata[key] = data;
+                               local t, value = assert(serialize(extradata));
+                               engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, username or "", self.store, "", t, value);
                        end
-               else
-                       error("TODO: non-string keys");
                end
                return true;
        end);
@@ -174,7 +195,7 @@ function archive_store:append(username, key, value, when, with)
                else
                        key = uuid.generate();
                end
-               local t, value = serialize(value);
+               local t, value = assert(serialize(value));
                engine:insert("INSERT INTO `prosodyarchive` (`host`, `user`, `store`, `when`, `with`, `key`, `type`, `value`) VALUES (?,?,?,?,?,?,?,?)", host, user or "", store, when, with, key, t, value);
                return key;
        end);
@@ -213,12 +234,12 @@ local function archive_where_id_range(query, args, where)
        local args_len = #args
        -- Before or after specific item, exclusive
        if query.after then  -- keys better be unique!
-               where[#where+1] = "`sort_id` > (SELECT `sort_id` FROM `prosodyarchive` WHERE `key` = ? AND `host` = ? AND `user` = ? AND `store` = ? LIMIT 1)"
+               where[#where+1] = "`sort_id` > COALESCE((SELECT `sort_id` FROM `prosodyarchive` WHERE `key` = ? AND `host` = ? AND `user` = ? AND `store` = ? LIMIT 1), 0)"
                args[args_len+1], args[args_len+2], args[args_len+3], args[args_len+4] = query.after, args[1], args[2], args[3];
                args_len = args_len + 4
        end
        if query.before then
-               where[#where+1] = "`sort_id` < (SELECT `sort_id` FROM `prosodyarchive` WHERE `key` = ? AND `host` = ? AND `user` = ? AND `store` = ? LIMIT 1)"
+               where[#where+1] = "`sort_id` < COALESCE((SELECT `sort_id` FROM `prosodyarchive` WHERE `key` = ? AND `host` = ? AND `user` = ? AND `store` = ? LIMIT 1), (SELECT MAX(`sort_id`)+1 FROM `prosodyarchive`))"
                args[args_len+1], args[args_len+2], args[args_len+3], args[args_len+4] = query.before, args[1], args[2], args[3];
        end
 end
@@ -238,8 +259,9 @@ function archive_store:find(username, query)
                if query.total then
                        local stats = engine:select("SELECT COUNT(*) FROM `prosodyarchive` WHERE " .. t_concat(where, " AND "), unpack(args));
                        if stats then
-                               local _total = stats()
-                               total = _total and _total[1];
+                               for row in stats do
+                                       total = row[1];
+                               end
                        end
                        if query.limit == 0 then -- Skip the real query
                                return noop, total;
@@ -253,7 +275,6 @@ function archive_store:find(username, query)
                end
 
                sql_query = sql_query:format(t_concat(where, " AND "), query.reverse and "DESC" or "ASC", query.limit and " LIMIT ?" or "");
-               module:log("debug", sql_query);
                return engine:select(sql_query, unpack(args));
        end);
        if not ok then return ok, result end
@@ -279,7 +300,6 @@ function archive_store:delete(username, query)
                archive_where(query, args, where);
                archive_where_id_range(query, args, where);
                sql_query = sql_query:format(t_concat(where, " AND "));
-               module:log("debug", sql_query);
                return engine:delete(sql_query, unpack(args));
        end);
 end
@@ -379,7 +399,7 @@ local function upgrade_table(params, apply_changes)
                end);
                if not success then
                        module:log("error", "Failed to check/upgrade database schema (%s), please see "
-                               .."http://prosody.im/doc/mysql for help",
+                               .."https://prosody.im/doc/mysql for help",
                                err or "unknown error");
                        return false;
                end
@@ -417,7 +437,9 @@ end
 
 local function normalize_params(params)
        if params.driver == "SQLite3" then
-               params.database = resolve_relative_path(prosody.paths.data or ".", params.database or "prosody.sqlite");
+               if params.database ~= ":memory:" then
+                       params.database = resolve_relative_path(prosody.paths.data or ".", params.database or "prosody.sqlite");
+               end
        end
        assert(params.driver and params.database, "Configuration error: Both the SQL driver and the database need to be specified");
        return params;
@@ -425,19 +447,25 @@ end
 
 function module.load()
        if prosody.prosodyctl then return; end
+       local engines = module:shared("/*/sql/connections");
        local params = normalize_params(module:get_option("sql", default_params));
-       engine = sql:create_engine(params, function (engine)
-               if module:get_option("sql_manage_tables", true) then
-                       -- Automatically create table, ignore failure (table probably already exists)
-                       -- FIXME: we should check in information_schema, etc.
-                       create_table();
-                       -- Check whether the table needs upgrading
-                       if upgrade_table(params, false) then
-                               module:log("error", "Old database format detected. Please run: prosodyctl mod_%s upgrade", module.name);
-                               return false, "database upgrade needed";
+       engine = engines[sql.db2uri(params)];
+       if not engine then
+               module:log("debug", "Creating new engine");
+               engine = sql:create_engine(params, function (engine)
+                       if module:get_option("sql_manage_tables", true) then
+                               -- Automatically create table, ignore failure (table probably already exists)
+                               -- FIXME: we should check in information_schema, etc.
+                               create_table();
+                               -- Check whether the table needs upgrading
+                               if upgrade_table(params, false) then
+                                       module:log("error", "Old database format detected. Please run: prosodyctl mod_%s upgrade", module.name);
+                                       return false, "database upgrade needed";
+                               end
                        end
-               end
-       end);
+               end);
+               engines[sql.db2uri(params)] = engine;
+       end
 
        module:provides("storage", driver);
 end