changeset 2061:b84284144e21

mod_storage_appendmap: Experimental storage module optimized for map stores
author Kim Alvefur <zash@zash.se>
date Sun, 06 Mar 2016 17:03:19 +0100
parents bd0c5d546bf8
children 8f7083b980cf
files mod_storage_appendmap/README.markdown mod_storage_appendmap/mod_storage_appendmap.lua
diffstat 2 files changed, 110 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_storage_appendmap/README.markdown	Sun Mar 06 17:03:19 2016 +0100
@@ -0,0 +1,16 @@
+---
+labels:
+- 'Stage-Alpha'
+- 'Type-Storage'
+summary: Experimental map store optimized for small incremental changes
+...
+
+This is an experimental storage driver where changed data is appended.
+Data is simply written as `key = value` pairs to the end of the file.
+This allows changes to individual keys to be written without needing to
+write out the entire object again, but reads would grow gradually larger
+as it still needs to read old overwritten keys. This may be suitable for
+eg rosters where individual contacts are changed at a time. In theory,
+this could also allow rolling back changes.
+
+Requires 0.10
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_storage_appendmap/mod_storage_appendmap.lua	Sun Mar 06 17:03:19 2016 +0100
@@ -0,0 +1,94 @@
+local dump = require "util.serialization".serialize;
+local load = require "util.envload".envloadfile;
+local dm = require "core.storagemanager".olddm;
+
+local driver = {};
+
+local map = {};
+local map_mt = { __index = map };
+map.remove = {};
+
+function map:get(user, key)
+	module:log("debug", "map:get(%s, %s)", tostring(user), tostring(key))
+	local filename = dm.getpath(user, module.host, self.store, "map");
+	module:log("debug", "File is %s", filename);
+	local env = {};
+	if _VERSION == "Lua 5.1" then -- HACK
+		env._ENV = env; -- HACK
+	end -- SO MANY HACKS
+	local chunk, err = load(filename, env);
+	if not chunk then return chunk, err; end
+	local ok, err = pcall(chunk);
+	if not ok then return ok, err; end
+	if _VERSION == "Lua 5.1" then -- HACK
+		env._ENV = nil; -- HACK
+	end -- HACKS EVERYWHERE
+	if key == nil then
+		return env;
+	end
+	return env[key];
+end
+
+function map:set_keys(user, keyvalues)
+	local keys, values = {}, {};
+	if _VERSION == "Lua 5.1" then
+		assert(not keyvalues._ENV, "'_ENV' is a restricted key");
+	end
+	for key, value in pairs(keyvalues) do
+		module:log("debug", "user %s sets %q to %s", user, key, tostring(value))
+		if type(key) ~= "string" or not key:find("^[%w_][%w%d_]*$") or key == "_ENV" then
+			key = "_ENV[" .. dump(key) .. "]";
+		end
+		table.insert(keys, key);
+		if value == self.remove then
+			table.insert(values, "nil")
+		else
+			table.insert(values, dump(value))
+		end
+	end
+	local data = table.concat(keys, ", ") .. " = " .. table.concat(values, ", ") .. ";\n";
+	return dm.append_raw(user, module.host, self.store, "map", data);
+end
+
+function map:set(user, key, value)
+	if _VERSION == "Lua 5.1" then
+		assert(key ~= "_ENV", "'_ENV' is a restricted key");
+	end
+	if key == nil then
+		local filename = dm.getpath(user, module.host, self.store, "map");
+		os.remove(filename);
+		return true;
+	end
+	if type(key) ~= "string" or not key:find("^[%w_][%w%d_]*$") or key == "_ENV" then
+		key = "_ENV[" .. dump(key) .. "]";
+	end
+	local data = key .. " = " .. dump(value) .. ";\n";
+	return dm.append_raw(user, module.host, self.store, "map", data);
+end
+
+local keyval = {};
+local keyval_mt = { __index = keyval };
+
+function keyval:get(user)
+	return map.get(self, user);
+end
+
+keyval.set = map.set_keys;
+
+-- TODO some kind of periodic compaction thing?
+function map:_compact(user)
+	local data = self:get(user);
+	return keyval.set(self, user, data);
+end
+
+function driver:open(store, typ)
+	if typ == "map" then
+		return setmetatable({ store = store, }, map_mt);
+	elseif typ == nil or typ == "keyval" then
+		return setmetatable({ store = store, }, keyval_mt);
+	end
+	return nil, "unsupported-store";
+end
+
+module:provides("storage", driver);
+