mod_admin_telnet: Don't rely on getpeerchain returning an empty list
[prosody.git] / plugins / mod_storage_sql.lua
index dd1487044113d11ae965759f1a9192d58a7f265a..eed3fec900784518a8860b1d5ed79199a85e397b 100644 (file)
@@ -27,12 +27,28 @@ local next = next;
 local setmetatable = setmetatable;
 local xpcall = xpcall;
 local json = require "util.json";
+local build_url = require"socket.url".build;
 
 local DBI;
 local connection;
 local host,user,store = module.host;
 local params = module:get_option("sql");
 
+local dburi;
+local connections = module:shared "/*/sql/connection-cache";
+
+local function db2uri(params)
+       return build_url{
+               scheme = params.driver,
+               user = params.username,
+               password = params.password,
+               host = params.host,
+               port = params.port,
+               path = params.database,
+       };
+end
+
+
 local resolve_relative_path = require "core.configmanager".resolve_relative_path;
 
 local function test_connection()
@@ -42,6 +58,7 @@ local function test_connection()
        else
                module:log("debug", "Database connection closed");
                connection = nil;
+               connections[dburi] = nil;
        end
 end
 local function connect()
@@ -60,11 +77,16 @@ local function connect()
                module:log("debug", "Successfully connected to database");
                dbh:autocommit(false); -- don't commit automatically
                connection = dbh;
-               return connection;
+
+               connections[dburi] = dbh;
        end
+       return connection;
 end
 
 local function create_table()
+       if not module:get_option("sql_manage_tables", true) then
+               return;
+       end
        local create_sql = "CREATE TABLE `prosody` (`host` TEXT, `user` TEXT, `store` TEXT, `key` TEXT, `type` TEXT, `value` TEXT);";
        if params.driver == "PostgreSQL" then
                create_sql = create_sql:gsub("`", "\"");
@@ -72,7 +94,7 @@ local function create_table()
                create_sql = create_sql:gsub("`value` TEXT", "`value` MEDIUMTEXT");
        end
        
-       local stmt = connection:prepare(create_sql);
+       local stmt, err = connection:prepare(create_sql);
        if stmt then
                local ok = stmt:execute();
                local commit_ok = connection:commit();
@@ -93,23 +115,32 @@ local function create_table()
                        if not(ok and commit_ok) then
                                module:log("warn", "Failed to create index (%s), lookups may not be optimised", err or commit_err);
                        end
-               else -- COMPAT: Upgrade tables from 0.8.0
+               elseif params.driver == "MySQL" then  -- COMPAT: Upgrade tables from 0.8.0
                        -- Failed to create, but check existing MySQL table here
                        local stmt = connection:prepare("SHOW COLUMNS FROM prosody WHERE Field='value' and Type='text'");
                        local ok = stmt:execute();
                        local commit_ok = connection:commit();
                        if ok and commit_ok then
                                if stmt:rowcount() > 0 then
+                                       module:log("info", "Upgrading database schema...");
                                        local stmt = connection:prepare("ALTER TABLE prosody MODIFY COLUMN `value` MEDIUMTEXT");
-                                       local ok = stmt:execute();
+                                       local ok, err = stmt:execute();
                                        local commit_ok = connection:commit();
                                        if ok and commit_ok then
                                                module:log("info", "Database table automatically upgraded");
+                                       else
+                                               module:log("error", "Failed to upgrade database schema (%s), please see "
+                                                       .."http://prosody.im/doc/mysql for help",
+                                                       err or "unknown error");
                                        end
                                end
                                repeat until not stmt:fetch();
                        end
                end
+       elseif params.driver ~= "SQLite3" then -- SQLite normally fails to prepare for existing table
+               module:log("warn", "Prosody was not able to automatically check/create the database table (%s), "
+                       .."see http://prosody.im/doc/modules/mod_storage_sql#table_management for help.",
+                       err or "unknown error");
        end
 end
 
@@ -134,6 +165,9 @@ do -- process options to get a db connection
        end
        
        assert(params.driver and params.database, "Both the SQL driver and the database need to be specified");
+
+       dburi = db2uri(params);
+       connection = connections[dburi];
        
        assert(connect());
        
@@ -163,7 +197,7 @@ local function deserialize(t, value)
        end
 end
 
-local function getsql(sql, ...)
+local function dosql(sql, ...)
        if params.driver == "PostgreSQL" then
                sql = sql:gsub("`", "\"");
        end
@@ -172,12 +206,15 @@ local function getsql(sql, ...)
        if not stmt and not test_connection() then error("connection failed"); end
        if not stmt then module:log("error", "QUERY FAILED: %s %s", err, debug.traceback()); return nil, err; end
        -- run query
-       local ok, err = stmt:execute(host or "", user or "", store or "", ...);
+       local ok, err = stmt:execute(...);
        if not ok and not test_connection() then error("connection failed"); end
        if not ok then return nil, err; end
        
        return stmt;
 end
+local function getsql(sql, ...)
+       return dosql(sql, host or "", user or "", store or "", ...);
+end
 local function setsql(sql, ...)
        local stmt, err = getsql(sql, ...);
        if not stmt then return stmt, err; end
@@ -191,7 +228,8 @@ local function rollback(...)
        return ...;
 end
 local function commit(...)
-       if not connection:commit() then return nil, "SQL commit failed"; end
+       local success,err = connection:commit();
+       if not success then return nil, "SQL commit failed: "..tostring(err); end
        return ...;
 end
 
@@ -261,6 +299,17 @@ function keyval_store:set(username, data)
        end
        if success then return ret, err; else return rollback(nil, ret); end
 end
+function keyval_store:users()
+       local stmt, err = dosql("SELECT DISTINCT `user` FROM `prosody` WHERE `host`=? AND `store`=?", host, self.store);
+       if not stmt then
+               return rollback(nil, err);
+       end
+       local next = stmt:rows();
+       return commit(function()
+               local row = next();
+               return row and row[1];
+       end);
+end
 
 local function map_store_get(key)
        local stmt, err = getsql("SELECT * FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", key or "");
@@ -328,7 +377,7 @@ function list_store:scan(username, from, to, jid, typ)
        return nil, "not-implemented"
 end
 
-local driver = { name = "sql" };
+local driver = {};
 
 function driver:open(store, typ)
        if not typ then -- default key-value store
@@ -337,4 +386,29 @@ function driver:open(store, typ)
        return nil, "unsupported-store";
 end
 
-module:add_item("data-driver", driver);
+function driver:stores(username)
+       local sql = "SELECT DISTINCT `store` FROM `prosody` WHERE `host`=? AND `user`" ..
+               (username == true and "!=?" or "=?");
+       if username == true or not username then
+               username = "";
+       end
+       local stmt, err = dosql(sql, host, username);
+       if not stmt then
+               return rollback(nil, err);
+       end
+       local next = stmt:rows();
+       return commit(function()
+               local row = next();
+               return row and row[1];
+       end);
+end
+
+function driver:purge(username)
+       local stmt, err = dosql("DELETE FROM `prosody` WHERE `host`=? AND `user`=?", host, username);
+       if not stmt then return rollback(stmt, err); end
+       local changed, err = stmt:affected();
+       if not changed then return rollback(changed, err); end
+       return commit(true, changed);
+end
+
+module:provides("storage", driver);