diff options
author | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2012-12-16 19:10:38 +0200 |
---|---|---|
committer | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2012-12-16 19:10:38 +0200 |
commit | e4361842fcdec369fbd4466f2528b2815f504ff9 (patch) | |
tree | 4694ca56f9e9a4869763d8fe1f31a81f6f62ac0b /acf/transaction/init.lua | |
download | acf2-e4361842fcdec369fbd4466f2528b2815f504ff9.tar.bz2 acf2-e4361842fcdec369fbd4466f2528b2815f504ff9.tar.xz |
initial version
Diffstat (limited to 'acf/transaction/init.lua')
-rw-r--r-- | acf/transaction/init.lua | 174 |
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 |