Software /
code /
prosody
Comparison
plugins/mod_storage_sql2.lua @ 5494:9916f0a2d178
mod_storage_sql2 (temporary name), sql.lib, util.sql: New SQL API supporting cross-module connection sharing, transactions and Things - a work in progress
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Thu, 18 Apr 2013 11:13:40 +0100 |
child | 5711:254a9420e53d |
comparison
equal
deleted
inserted
replaced
5492:5d0cc5de5c51 | 5494:9916f0a2d178 |
---|---|
1 | |
2 local json = require "util.json"; | |
3 local resolve_relative_path = require "core.configmanager".resolve_relative_path; | |
4 | |
5 local mod_sql = module:require("sql"); | |
6 local params = module:get_option("sql"); | |
7 | |
8 local engine; -- TODO create engine | |
9 | |
10 local function create_table() | |
11 --[[local Table,Column,Index = mod_sql.Table,mod_sql.Column,mod_sql.Index; | |
12 local ProsodyTable = Table { | |
13 name="prosody"; | |
14 Column { name="host", type="TEXT", nullable=false }; | |
15 Column { name="user", type="TEXT", nullable=false }; | |
16 Column { name="store", type="TEXT", nullable=false }; | |
17 Column { name="key", type="TEXT", nullable=false }; | |
18 Column { name="type", type="TEXT", nullable=false }; | |
19 Column { name="value", type="TEXT", nullable=false }; | |
20 Index { name="prosody_index", "host", "user", "store", "key" }; | |
21 }; | |
22 engine:transaction(function() | |
23 ProsodyTable:create(engine); | |
24 end);]] | |
25 if not module:get_option("sql_manage_tables", true) then | |
26 return; | |
27 end | |
28 | |
29 local create_sql = "CREATE TABLE `prosody` (`host` TEXT, `user` TEXT, `store` TEXT, `key` TEXT, `type` TEXT, `value` TEXT);"; | |
30 if params.driver == "PostgreSQL" then | |
31 create_sql = create_sql:gsub("`", "\""); | |
32 elseif params.driver == "MySQL" then | |
33 create_sql = create_sql:gsub("`value` TEXT", "`value` MEDIUMTEXT") | |
34 :gsub(";$", " CHARACTER SET 'utf8' COLLATE 'utf8_bin';"); | |
35 end | |
36 | |
37 local index_sql = "CREATE INDEX `prosody_index` ON `prosody` (`host`, `user`, `store`, `key`)"; | |
38 if params.driver == "PostgreSQL" then | |
39 index_sql = index_sql:gsub("`", "\""); | |
40 elseif params.driver == "MySQL" then | |
41 index_sql = index_sql:gsub("`([,)])", "`(20)%1"); | |
42 end | |
43 | |
44 local success,err = engine:transaction(function() | |
45 engine:execute(create_sql); | |
46 engine:execute(index_sql); | |
47 end); | |
48 if not success then -- so we failed to create | |
49 if params.driver == "MySQL" then | |
50 success,err = engine:transaction(function() | |
51 local result = engine:execute("SHOW COLUMNS FROM prosody WHERE Field='value' and Type='text'"); | |
52 if result:rowcount() > 0 then | |
53 module:log("info", "Upgrading database schema..."); | |
54 engine:execute("ALTER TABLE prosody MODIFY COLUMN `value` MEDIUMTEXT"); | |
55 module:log("info", "Database table automatically upgraded"); | |
56 end | |
57 return true; | |
58 end); | |
59 if not success then | |
60 module:log("error", "Failed to check/upgrade database schema (%s), please see " | |
61 .."http://prosody.im/doc/mysql for help", | |
62 err or "unknown error"); | |
63 end | |
64 end | |
65 end | |
66 end | |
67 local function set_encoding() | |
68 if params.driver ~= "SQLite3" then | |
69 local set_names_query = "SET NAMES 'utf8';"; | |
70 if params.driver == "MySQL" then | |
71 set_names_query = set_names_query:gsub(";$", " COLLATE 'utf8_bin';"); | |
72 end | |
73 local success,err = engine:transaction(function() return engine:execute(set_names_query); end); | |
74 if not success then | |
75 module:log("error", "Failed to set database connection encoding to UTF8: %s", err); | |
76 return; | |
77 end | |
78 if params.driver == "MySQL" then | |
79 -- COMPAT w/pre-0.9: Upgrade tables to UTF-8 if not already | |
80 local check_encoding_query = "SELECT `COLUMN_NAME`,`COLUMN_TYPE` FROM `information_schema`.`columns` WHERE `TABLE_NAME`='prosody' AND ( `CHARACTER_SET_NAME`!='utf8' OR `COLLATION_NAME`!='utf8_bin' );"; | |
81 local success,err = engine:transaction(function() | |
82 local result = engine:execute(check_encoding_query); | |
83 local n_bad_columns = result:rowcount(); | |
84 if n_bad_columns > 0 then | |
85 module:log("warn", "Found %d columns in prosody table requiring encoding change, updating now...", n_bad_columns); | |
86 local fix_column_query1 = "ALTER TABLE `prosody` CHANGE `%s` `%s` BLOB;"; | |
87 local fix_column_query2 = "ALTER TABLE `prosody` CHANGE `%s` `%s` %s CHARACTER SET 'utf8' COLLATE 'utf8_bin';"; | |
88 for row in success:rows() do | |
89 local column_name, column_type = unpack(row); | |
90 engine:execute(fix_column_query1:format(column_name, column_name)); | |
91 engine:execute(fix_column_query2:format(column_name, column_name, column_type)); | |
92 end | |
93 module:log("info", "Database encoding upgrade complete!"); | |
94 end | |
95 end); | |
96 local success,err = engine:transaction(function() return engine:execute(check_encoding_query); end); | |
97 if not success then | |
98 module:log("error", "Failed to check/upgrade database encoding: %s", err or "unknown error"); | |
99 end | |
100 end | |
101 end | |
102 end | |
103 | |
104 do -- process options to get a db connection | |
105 params = params or { driver = "SQLite3" }; | |
106 | |
107 if params.driver == "SQLite3" then | |
108 params.database = resolve_relative_path(prosody.paths.data or ".", params.database or "prosody.sqlite"); | |
109 end | |
110 | |
111 assert(params.driver and params.database, "Both the SQL driver and the database need to be specified"); | |
112 | |
113 --local dburi = db2uri(params); | |
114 engine = mod_sql:create_engine(params); | |
115 | |
116 -- Encoding mess | |
117 set_encoding(); | |
118 | |
119 -- Automatically create table, ignore failure (table probably already exists) | |
120 create_table(); | |
121 end | |
122 | |
123 local function serialize(value) | |
124 local t = type(value); | |
125 if t == "string" or t == "boolean" or t == "number" then | |
126 return t, tostring(value); | |
127 elseif t == "table" then | |
128 local value,err = json.encode(value); | |
129 if value then return "json", value; end | |
130 return nil, err; | |
131 end | |
132 return nil, "Unhandled value type: "..t; | |
133 end | |
134 local function deserialize(t, value) | |
135 if t == "string" then return value; | |
136 elseif t == "boolean" then | |
137 if value == "true" then return true; | |
138 elseif value == "false" then return false; end | |
139 elseif t == "number" then return tonumber(value); | |
140 elseif t == "json" then | |
141 return json.decode(value); | |
142 end | |
143 end | |
144 | |
145 local host = module.host; | |
146 local user, store; | |
147 | |
148 local function keyval_store_get() | |
149 local haveany; | |
150 local result = {}; | |
151 for row in engine:select("SELECT `key`,`type`,`value` FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=?", host, user, store) do | |
152 haveany = true; | |
153 local k = row[1]; | |
154 local v = deserialize(row[2], row[3]); | |
155 if k and v then | |
156 if k ~= "" then result[k] = v; elseif type(v) == "table" then | |
157 for a,b in pairs(v) do | |
158 result[a] = b; | |
159 end | |
160 end | |
161 end | |
162 end | |
163 if haveany then | |
164 return result; | |
165 end | |
166 end | |
167 local function keyval_store_set(data) | |
168 engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=?", host, user, store); | |
169 | |
170 if data and next(data) ~= nil then | |
171 local extradata = {}; | |
172 for key, value in pairs(data) do | |
173 if type(key) == "string" and key ~= "" then | |
174 local t, value = serialize(value); | |
175 assert(t, value); | |
176 engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, user, store, key, t, value); | |
177 else | |
178 extradata[key] = value; | |
179 end | |
180 end | |
181 if next(extradata) ~= nil then | |
182 local t, extradata = serialize(extradata); | |
183 assert(t, extradata); | |
184 engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, user, store, "", t, extradata); | |
185 end | |
186 end | |
187 return true; | |
188 end | |
189 | |
190 local keyval_store = {}; | |
191 keyval_store.__index = keyval_store; | |
192 function keyval_store:get(username) | |
193 user,store = username,self.store; | |
194 return select(2, engine:transaction(keyval_store_get)); | |
195 end | |
196 function keyval_store:set(username, data) | |
197 user,store = username,self.store; | |
198 return engine:transaction(function() | |
199 return keyval_store_set(data); | |
200 end); | |
201 end | |
202 function keyval_store:users() | |
203 return engine:transaction(function() | |
204 return engine:select("SELECT DISTINCT `user` FROM `prosody` WHERE `host`=? AND `store`=?", host, self.store); | |
205 end); | |
206 end | |
207 | |
208 local driver = {}; | |
209 | |
210 function driver:open(store, typ) | |
211 if not typ then -- default key-value store | |
212 return setmetatable({ store = store }, keyval_store); | |
213 end | |
214 return nil, "unsupported-store"; | |
215 end | |
216 | |
217 function driver:stores(username) | |
218 local sql = "SELECT DISTINCT `store` FROM `prosody` WHERE `host`=? AND `user`" .. | |
219 (username == true and "!=?" or "=?"); | |
220 if username == true or not username then | |
221 username = ""; | |
222 end | |
223 return engine:transaction(function() | |
224 return engine:select(sql, host, username); | |
225 end); | |
226 end | |
227 | |
228 function driver:purge(username) | |
229 return engine:transaction(function() | |
230 local stmt,err = engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=?", host, username); | |
231 return true,err; | |
232 end); | |
233 end | |
234 | |
235 module:provides("storage", driver); | |
236 | |
237 |