comparison plugins/mod_storage_sql.lua @ 10411:db2a06b9ff98

Merge 0.11->trunk
author Kim Alvefur <zash@zash.se>
date Sat, 16 Nov 2019 16:52:31 +0100
parents 51f145094648
children 3098eac31139
comparison
equal deleted inserted replaced
10410:659b577f280c 10411:db2a06b9ff98
1 1
2 -- luacheck: ignore 212/self 2 -- luacheck: ignore 212/self
3 3
4 local cache = require "util.cache";
4 local json = require "util.json"; 5 local json = require "util.json";
5 local sql = require "util.sql"; 6 local sql = require "util.sql";
6 local xml_parse = require "util.xml".parse; 7 local xml_parse = require "util.xml".parse;
7 local uuid = require "util.uuid"; 8 local uuid = require "util.uuid";
8 local resolve_relative_path = require "util.paths".resolve_relative_path; 9 local resolve_relative_path = require "util.paths".resolve_relative_path;
10 local jid_join = require "util.jid".join;
9 11
10 local is_stanza = require"util.stanza".is_stanza; 12 local is_stanza = require"util.stanza".is_stanza;
11 local t_concat = table.concat; 13 local t_concat = table.concat;
12 14
13 local noop = function() end 15 local noop = function() end
14 local unpack = table.unpack or unpack; 16 local unpack = table.unpack or unpack; -- luacheck: ignore 113
15 local function iterator(result) 17 local function iterator(result)
16 return function(result_) 18 return function(result_)
17 local row = result_(); 19 local row = result_();
18 if row ~= nil then 20 if row ~= nil then
19 return unpack(row); 21 return unpack(row);
146 return iterator(result); 148 return iterator(result);
147 end 149 end
148 150
149 --- Archive store API 151 --- Archive store API
150 152
151 -- luacheck: ignore 512 431/user 431/store 153 local archive_item_limit = module:get_option_number("storage_archive_item_limit");
154 local archive_item_count_cache = cache.new(module:get_option("storage_archive_item_limit_cache_size", 1000));
155
156 -- luacheck: ignore 512 431/user 431/store 431/err
152 local map_store = {}; 157 local map_store = {};
153 map_store.__index = map_store; 158 map_store.__index = map_store;
154 map_store.remove = {}; 159 map_store.remove = {};
155 function map_store:get(username, key) 160 function map_store:get(username, key)
156 local ok, result = engine:transaction(function() 161 local ok, result = engine:transaction(function()
226 end 231 end
227 232
228 local archive_store = {} 233 local archive_store = {}
229 archive_store.caps = { 234 archive_store.caps = {
230 total = true; 235 total = true;
236 quota = archive_item_limit;
237 truncate = true;
231 }; 238 };
232 archive_store.__index = archive_store 239 archive_store.__index = archive_store
233 function archive_store:append(username, key, value, when, with) 240 function archive_store:append(username, key, value, when, with)
234 local user,store = username,self.store; 241 local user,store = username,self.store;
242 local cache_key = jid_join(username, host, store);
243 local item_count = archive_item_count_cache:get(cache_key);
244 if not item_count then
245 local ok, ret = engine:transaction(function()
246 local count_sql = [[
247 SELECT COUNT(*) FROM "prosodyarchive"
248 WHERE "host"=? AND "user"=? AND "store"=?;
249 ]];
250 local result = engine:select(count_sql, host, user, store);
251 if result then
252 for row in result do
253 item_count = row[1];
254 end
255 end
256 end);
257 if not ok or not item_count then
258 module:log("error", "Failed while checking quota for %s: %s", username, ret);
259 return nil, "Failure while checking quota";
260 end
261 archive_item_count_cache:set(cache_key, item_count);
262 end
263
264 if archive_item_limit then
265 module:log("debug", "%s has %d items out of %d limit", username, item_count, archive_item_limit);
266 if item_count >= archive_item_limit then
267 return nil, "quota-limit";
268 end
269 end
270
235 when = when or os.time(); 271 when = when or os.time();
236 with = with or ""; 272 with = with or "";
237 local ok, ret = engine:transaction(function() 273 local ok, ret = engine:transaction(function()
238 local delete_sql = [[ 274 local delete_sql = [[
239 DELETE FROM "prosodyarchive" 275 DELETE FROM "prosodyarchive"
243 INSERT INTO "prosodyarchive" 279 INSERT INTO "prosodyarchive"
244 ("host", "user", "store", "when", "with", "key", "type", "value") 280 ("host", "user", "store", "when", "with", "key", "type", "value")
245 VALUES (?,?,?,?,?,?,?,?); 281 VALUES (?,?,?,?,?,?,?,?);
246 ]]; 282 ]];
247 if key then 283 if key then
248 engine:delete(delete_sql, host, user or "", store, key); 284 local result, err = engine:delete(delete_sql, host, user or "", store, key);
285 if result then
286 item_count = item_count - result:affected();
287 end
249 else 288 else
250 key = uuid.generate(); 289 key = uuid.generate();
251 end 290 end
252 local t, encoded_value = assert(serialize(value)); 291 local t, encoded_value = assert(serialize(value));
253 engine:insert(insert_sql, host, user or "", store, when, with, key, t, encoded_value); 292 engine:insert(insert_sql, host, user or "", store, when, with, key, t, encoded_value);
293 archive_item_count_cache:set(cache_key, item_count+1);
254 return key; 294 return key;
255 end); 295 end);
256 if not ok then return ok, ret; end 296 if not ok then return ok, ret; end
257 return ret; -- the key 297 return ret; -- the key
258 end 298 end
285 where[#where+1] = "\"key\" = ?"; 325 where[#where+1] = "\"key\" = ?";
286 args[#args+1] = query.key 326 args[#args+1] = query.key
287 end 327 end
288 end 328 end
289 local function archive_where_id_range(query, args, where) 329 local function archive_where_id_range(query, args, where)
290 local args_len = #args
291 -- Before or after specific item, exclusive 330 -- Before or after specific item, exclusive
331 local id_lookup_sql = [[
332 SELECT "sort_id"
333 FROM "prosodyarchive"
334 WHERE "key" = ? AND "host" = ? AND "user" = ? AND "store" = ?
335 LIMIT 1;
336 ]];
292 if query.after then -- keys better be unique! 337 if query.after then -- keys better be unique!
293 where[#where+1] = [[ 338 local after_id = nil;
294 "sort_id" > COALESCE( 339 for row in engine:select(id_lookup_sql, query.after, args[1], args[2], args[3]) do
295 ( 340 after_id = row[1];
296 SELECT "sort_id" 341 end
297 FROM "prosodyarchive" 342 if not after_id then
298 WHERE "key" = ? AND "host" = ? AND "user" = ? AND "store" = ? 343 return nil, "item-not-found";
299 LIMIT 1 344 end
300 ), 0) 345 where[#where+1] = '"sort_id" > ?';
301 ]]; 346 args[#args+1] = after_id;
302 args[args_len+1], args[args_len+2], args[args_len+3], args[args_len+4] = query.after, args[1], args[2], args[3];
303 args_len = args_len + 4
304 end 347 end
305 if query.before then 348 if query.before then
306 where[#where+1] = [[ 349 local before_id = nil;
307 "sort_id" < COALESCE( 350 for row in engine:select(id_lookup_sql, query.after, args[1], args[2], args[3]) do
308 ( 351 before_id = row[1];
309 SELECT "sort_id" 352 end
310 FROM "prosodyarchive" 353 if not before_id then
311 WHERE "key" = ? AND "host" = ? AND "user" = ? AND "store" = ? 354 return nil, "item-not-found";
312 LIMIT 1 355 end
313 ), 356 where[#where+1] = '"sort_id" < ?';
314 ( 357 args[#args+1] = before_id;
315 SELECT MAX("sort_id")+1 358 end
316 FROM "prosodyarchive" 359 return true;
317 )
318 )
319 ]]
320 args[args_len+1], args[args_len+2], args[args_len+3], args[args_len+4] = query.before, args[1], args[2], args[3];
321 end
322 end 360 end
323 361
324 function archive_store:find(username, query) 362 function archive_store:find(username, query)
325 query = query or {}; 363 query = query or {};
326 local user,store = username,self.store; 364 local user,store = username,self.store;
327 local total; 365 local cache_key = jid_join(username, host, self.store);
328 local ok, result = engine:transaction(function() 366 local total = archive_item_count_cache:get(cache_key);
367 if total ~= nil and query.limit == 0 and query.start == nil and query.with == nil and query["end"] == nil and query.key == nil then
368 return noop, total;
369 end
370 local ok, result, err = engine:transaction(function()
329 local sql_query = [[ 371 local sql_query = [[
330 SELECT "key", "type", "value", "when", "with" 372 SELECT "key", "type", "value", "when", "with"
331 FROM "prosodyarchive" 373 FROM "prosodyarchive"
332 WHERE %s 374 WHERE %s
333 ORDER BY "sort_id" %s%s; 375 ORDER BY "sort_id" %s%s;
344 if stats then 386 if stats then
345 for row in stats do 387 for row in stats do
346 total = row[1]; 388 total = row[1];
347 end 389 end
348 end 390 end
391 if query.start == nil and query.with == nil and query["end"] == nil and query.key == nil then
392 archive_item_count_cache:set(cache_key, total);
393 end
349 if query.limit == 0 then -- Skip the real query 394 if query.limit == 0 then -- Skip the real query
350 return noop, total; 395 return noop, total;
351 end 396 end
352 end 397 end
353 398
354 archive_where_id_range(query, args, where); 399 local ok, err = archive_where_id_range(query, args, where);
400 if not ok then return ok, err; end
355 401
356 if query.limit then 402 if query.limit then
357 args[#args+1] = query.limit; 403 args[#args+1] = query.limit;
358 end 404 end
359 405
360 sql_query = sql_query:format(t_concat(where, " AND "), query.reverse 406 sql_query = sql_query:format(t_concat(where, " AND "), query.reverse
361 and "DESC" or "ASC", query.limit and " LIMIT ?" or ""); 407 and "DESC" or "ASC", query.limit and " LIMIT ?" or "");
362 return engine:select(sql_query, unpack(args)); 408 return engine:select(sql_query, unpack(args));
363 end); 409 end);
364 if not ok then return ok, result end 410 if not ok then return ok, result; end
411 if not result then return nil, err; end
365 return function() 412 return function()
366 local row = result(); 413 local row = result();
367 if row ~= nil then 414 if row ~= nil then
368 local value, err = deserialize(row[2], row[3]); 415 local value, err = deserialize(row[2], row[3]);
369 assert(value ~= nil, err); 416 assert(value ~= nil, err);
370 return row[1], value, row[4], row[5]; 417 return row[1], value, row[4], row[5];
371 end 418 end
372 end, total; 419 end, total;
420 end
421
422 function archive_store:summary(username, query)
423 query = query or {};
424 local user,store = username,self.store;
425 local ok, result = engine:transaction(function()
426 local sql_query = [[
427 SELECT DISTINCT "with", COUNT(*), MIN("when"), MAX("when")
428 FROM "prosodyarchive"
429 WHERE %s
430 GROUP BY "with"
431 ORDER BY "sort_id" %s%s;
432 ]];
433 local args = { host, user or "", store, };
434 local where = { "\"host\" = ?", "\"user\" = ?", "\"store\" = ?", };
435
436 archive_where(query, args, where);
437
438 archive_where_id_range(query, args, where);
439
440 if query.limit then
441 args[#args+1] = query.limit;
442 end
443
444 sql_query = sql_query:format(t_concat(where, " AND "), query.reverse
445 and "DESC" or "ASC", query.limit and " LIMIT ?" or "");
446 return engine:select(sql_query, unpack(args));
447 end);
448 if not ok then return ok, result end
449 local counts = {};
450 local earliest, latest = {}, {};
451 for row in result do
452 local with, count = row[1], row[2];
453 counts[with] = count;
454 earliest[with] = row[3];
455 latest[with] = row[4];
456 end
457 return {
458 counts = counts;
459 earliest = earliest;
460 latest = latest;
461 };
373 end 462 end
374 463
375 function archive_store:delete(username, query) 464 function archive_store:delete(username, query)
376 query = query or {}; 465 query = query or {};
377 local user,store = username,self.store; 466 local user,store = username,self.store;
382 if user == true then 471 if user == true then
383 table.remove(args, 2); 472 table.remove(args, 2);
384 table.remove(where, 2); 473 table.remove(where, 2);
385 end 474 end
386 archive_where(query, args, where); 475 archive_where(query, args, where);
387 archive_where_id_range(query, args, where); 476 local ok, err = archive_where_id_range(query, args, where);
477 if not ok then return ok, err; end
388 if query.truncate == nil then 478 if query.truncate == nil then
389 sql_query = sql_query:format(t_concat(where, " AND ")); 479 sql_query = sql_query:format(t_concat(where, " AND "));
390 else 480 else
391 args[#args+1] = query.truncate; 481 args[#args+1] = query.truncate;
392 local unlimited = "ALL"; 482 local unlimited = "ALL";
421 sql_query = string.format(sql_query, t_concat(where, " AND "), 511 sql_query = string.format(sql_query, t_concat(where, " AND "),
422 query.reverse and "ASC" or "DESC", unlimited); 512 query.reverse and "ASC" or "DESC", unlimited);
423 end 513 end
424 return engine:delete(sql_query, unpack(args)); 514 return engine:delete(sql_query, unpack(args));
425 end); 515 end);
516 local cache_key = jid_join(username, host, self.store);
517 archive_item_count_cache:set(cache_key, nil);
426 return ok and stmt:affected(), stmt; 518 return ok and stmt:affected(), stmt;
519 end
520
521 function archive_store:users()
522 local ok, result = engine:transaction(function()
523 local select_sql = [[
524 SELECT DISTINCT "user"
525 FROM "prosodyarchive"
526 WHERE "host"=? AND "store"=?;
527 ]];
528 return engine:select(select_sql, host, self.store);
529 end);
530 if not ok then error(result); end
531 return iterator(result);
427 end 532 end
428 533
429 local stores = { 534 local stores = {
430 keyval = keyval_store; 535 keyval = keyval_store;
431 map = map_store; 536 map = map_store;