summaryrefslogtreecommitdiffstats
path: root/acf/transaction/init.lua
diff options
context:
space:
mode:
authorKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2012-12-16 19:10:38 +0200
committerKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2012-12-16 19:10:38 +0200
commite4361842fcdec369fbd4466f2528b2815f504ff9 (patch)
tree4694ca56f9e9a4869763d8fe1f31a81f6f62ac0b /acf/transaction/init.lua
downloadacf2-e4361842fcdec369fbd4466f2528b2815f504ff9.tar.bz2
acf2-e4361842fcdec369fbd4466f2528b2815f504ff9.tar.xz
initial version
Diffstat (limited to 'acf/transaction/init.lua')
-rw-r--r--acf/transaction/init.lua174
1 files changed, 174 insertions, 0 deletions
diff --git a/acf/transaction/init.lua b/acf/transaction/init.lua
new file mode 100644
index 0000000..7f2f6b9
--- /dev/null
+++ b/acf/transaction/init.lua
@@ -0,0 +1,174 @@
+--[[
+Copyright (c) 2012 Kaarle Ritvanen
+See LICENSE file for license details
+--]]
+
+module(..., package.seeall)
+
+local RootModel = require('acf.model').RootModel
+local object = require('acf.object')
+local super = object.super
+local pth = require('acf.path')
+local be_mod = require('acf.transaction.backend')
+local copy = require('acf.util').copy
+
+
+local function remove_list_value(list, value)
+ value = tostring(value)
+
+ for i, v in ipairs(list) do
+ if tostring(v) == value then
+ table.remove(list, i)
+ return
+ end
+ end
+
+ assert(false)
+end
+
+
+local Transaction = object.class(be_mod.TransactionBackend)
+
+function Transaction:init(backend)
+ super(self, Transaction):init()
+
+ self.backend = backend
+
+ self.started = be_mod.gen_number()
+ self.access_time = {}
+
+ self.added = {}
+ self.modified = {}
+ self.deleted = {}
+
+ self.validate = {}
+
+ self.root = RootModel(self)
+end
+
+function Transaction:check()
+ if not self.backend then error('Transaction already committed') end
+end
+
+function Transaction:get(path)
+ self:check()
+
+ if self.deleted[path] then return nil, self.mod_time[path] end
+ for _, tbl in ipairs{self.added, self.modified} do
+ if tbl[path] ~= nil then
+ return copy(tbl[path][2]), self.mod_time[path]
+ end
+ end
+
+ local value, timestamp = self.backend:get_if_older(path, self.started)
+ self.access_time[path] = timestamp
+ return value, timestamp
+end
+
+function Transaction:set_multiple(mods)
+ super(self, Transaction):set_multiple(mods)
+
+ local function set(path, t, value, new)
+ local delete = value == nil
+ value = not delete and {t, value} or nil
+
+ if self.added[path] == nil and (not new or self.deleted[path]) then
+ self.modified[path] = value
+ self.deleted[path] = delete
+ else self.added[path] = value end
+ end
+
+ for _, mod in ipairs(mods) do
+ local path, t, value = unpack(mod)
+
+ local ppath = pth.parent(path)
+ local parent = self:get(ppath)
+ if parent == nil then
+ self:set(ppath, 'table', true)
+ parent = {}
+ end
+
+ local name = pth.name(path)
+ local old = self:get(path)
+
+ local is_table = t == 'table'
+ local delete = not is_table and value == nil
+
+ if type(old) == 'table' then
+ if delete then
+ for _, child in ipairs(old) do self:set(pth.join(path, child)) end
+ elseif is_table then return
+ elseif #old > 0 then
+ error('Cannot assign a primitive value to non-leaf node '..path)
+ end
+ end
+
+ if is_table then value = {} end
+ set(path, not delete and t or nil, value, old == nil)
+
+ if old == nil and not delete then
+ table.insert(parent, name)
+ set(ppath, 'table', parent)
+ elseif old ~= nil and delete then
+ remove_list_value(parent, name)
+ set(ppath, 'table', parent)
+ end
+ end
+end
+
+function Transaction:search(path) return self.root:search(pth.split(path)) end
+
+function Transaction:commit()
+ self:check()
+
+ for _, func in pairs(self.validate) do func() end
+
+ local mods = {}
+ local handled = {}
+
+ local function insert(path, t, value)
+ assert(not handled[path])
+ table.insert(mods, {path, t, value})
+ handled[path] = true
+ end
+
+ local function insert_add(path)
+ if not handled[path] then
+ local pp = pth.parent(path)
+ if self.added[pp] then insert_add(pp) end
+
+ local t, value = unpack(self.added[path])
+ if t == 'table' then value = nil end
+ insert(path, t, value)
+ end
+ end
+
+ local function insert_del(path)
+ local value = self.backend:get(path)
+ if type(value) == 'table' then
+ for _, child in ipairs(value) do
+ local cp = pth.join(path, child)
+ assert(self.deleted[cp])
+ if not handled[cp] then insert_del(cp) end
+ end
+ end
+ insert(path)
+ end
+
+ for path, deleted in pairs(self.deleted) do
+ if deleted then insert_del(path) end
+ end
+
+ for path, value in pairs(self.modified) do
+ local t, v = unpack(value)
+ if t ~= 'table' then insert(path, t, v) end
+ end
+
+ for path, _ in pairs(self.added) do insert_add(path) end
+
+ self.backend:comp_and_setm(self.access_time, mods)
+ self.backend = nil
+end
+
+local store = require('acf.persistence').DataStore()
+function start(txn) return Transaction(txn or store) end