diff options
author | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2014-01-03 00:09:35 +0200 |
---|---|---|
committer | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2014-01-03 03:45:20 +0200 |
commit | 15ea19da7079cb4c2019b53dae62d5cf6562ce63 (patch) | |
tree | 84fa3f23603ba3e969bc75576d18e6c870403823 | |
parent | 9ff4c5411397ecc6c2b18547a53d4a94df930b88 (diff) | |
download | aconf-15ea19da7079cb4c2019b53dae62d5cf6562ce63.tar.bz2 aconf-15ea19da7079cb4c2019b53dae62d5cf6562ce63.tar.xz |
transaction: split Transaction class into two parts
model-dependent functions moved to ModelTransaction
-rw-r--r-- | acf2/persistence/init.lua | 6 | ||||
-rw-r--r-- | acf2/transaction/backend.lua | 70 | ||||
-rw-r--r-- | acf2/transaction/base.lua | 221 | ||||
-rw-r--r-- | acf2/transaction/init.lua | 180 |
4 files changed, 251 insertions, 226 deletions
diff --git a/acf2/persistence/init.lua b/acf2/persistence/init.lua index 74ca447..fee408a 100644 --- a/acf2/persistence/init.lua +++ b/acf2/persistence/init.lua @@ -1,10 +1,8 @@ --[[ -Copyright (c) 2012-2013 Kaarle Ritvanen +Copyright (c) 2012-2014 Kaarle Ritvanen See LICENSE file for license details --]] -local M = {} - local loadmods = require('acf2.loader') local topology = require('acf2.model.root').topology local object = require('acf2.object') @@ -18,7 +16,7 @@ local stringy = require('stringy') local DataStore = object.class( - require('acf2.transaction.backend').TransactionBackend + require('acf2.transaction.base').TransactionBackend ) function DataStore:init() diff --git a/acf2/transaction/backend.lua b/acf2/transaction/backend.lua deleted file mode 100644 index 79ea83a..0000000 --- a/acf2/transaction/backend.lua +++ /dev/null @@ -1,70 +0,0 @@ ---[[ -Copyright (c) 2012-2013 Kaarle Ritvanen -See LICENSE file for license details ---]] - -local M = {} - -local err = require('acf2.error') - --- TODO each transaction backend (i.e. persistence manager or --- transaction proper) should be implemented as a thread or have its --- internal state stored in shared storage (with appropriate locking) - - -local generation = 0 -function M.gen_number() - generation = generation + 1 - return generation -end - - -M.TransactionBackend = require('acf2.object').class() - -function M.TransactionBackend:init() self.mod_time = {} end - -function M.TransactionBackend:get_if_older(path, timestamp) - local value, ts = self:get(path) - if ts > timestamp then err.raise('conflict', path) end - return value, ts -end - -function M.TransactionBackend:set(path, value) - self:set_multiple{{path, value}} -end - -function M.TransactionBackend:set_multiple(mods) - -- TODO delegate to PM backends? - local timestamp = M.gen_number() - local effective = {} - - local function tostr(s) return s ~= nil and tostring(s) or nil end - - for _, mod in ipairs(mods) do - local path, value = unpack(mod) - - if type(value) == 'table' or type( - self:get(path) - ) == 'table' or self:get(path) ~= value then - - table.insert(effective, mod) - self.mod_time[path] = timestamp - end - end - - self:_set_multiple(effective) -end - --- TODO should be atomic, mutex with set_multiple -function M.TransactionBackend:comp_and_setm(accessed, mods) - local errors = err.ErrorDict() - for path, timestamp in pairs(accessed) do - errors:collect(self.get_if_older, self, path, timestamp) - end - errors:raise() - - self:set_multiple(mods) -end - - -return M diff --git a/acf2/transaction/base.lua b/acf2/transaction/base.lua new file mode 100644 index 0000000..142b0a5 --- /dev/null +++ b/acf2/transaction/base.lua @@ -0,0 +1,221 @@ +--[[ +Copyright (c) 2012-2014 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local M = {} + +local err = require('acf2.error') + +local object = require('acf2.object') +local class = object.class + +local pth = require('acf2.path') +local copy = require('acf2.util').copy + +-- TODO each transaction backend (i.e. persistence manager or +-- transaction proper) should be implemented as a thread or have its +-- internal state stored in shared storage (with appropriate locking) + + +local generation = 0 +local function gen_number() + generation = generation + 1 + return generation +end + + +M.TransactionBackend = class() + +function M.TransactionBackend:init() self.mod_time = {} end + +function M.TransactionBackend:get_if_older(path, timestamp) + local value, ts = self:get(path) + if ts > timestamp then err.raise('conflict', path) end + return value, ts +end + +function M.TransactionBackend:set(path, value) + self:set_multiple{{path, value}} +end + +function M.TransactionBackend:set_multiple(mods) + -- TODO delegate to PM backends? + local timestamp = gen_number() + local effective = {} + + local function tostr(s) return s ~= nil and tostring(s) or nil end + + for _, mod in ipairs(mods) do + local path, value = unpack(mod) + + if type(value) == 'table' or type( + self:get(path) + ) == 'table' or self:get(path) ~= value then + + table.insert(effective, mod) + self.mod_time[path] = timestamp + end + end + + self:_set_multiple(effective) +end + +-- TODO should be atomic, mutex with set_multiple +function M.TransactionBackend:comp_and_setm(accessed, mods) + local errors = err.ErrorDict() + for path, timestamp in pairs(accessed) do + errors:collect(self.get_if_older, self, path, timestamp) + end + errors:raise() + + self:set_multiple(mods) +end + + + +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 +end + + +M.Transaction = class(M.TransactionBackend) + +function M.Transaction:init(backend) + object.super(self, M.Transaction):init() + + self.backend = backend + + self.started = gen_number() + self.access_time = {} + + self.added = {} + self.modified = {} + self.deleted = {} +end + +function M.Transaction:get(path) + 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]), 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 M.Transaction:_set_multiple(mods) + + local function set(path, value, new) + local delete = value == 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, value = unpack(mod) + + local ppath = pth.parent(path) + local parent = self:get(ppath) + if parent == nil then + parent = {} + self:set(ppath, parent) + end + + local name = pth.name(path) + local old = self:get(path) + + local is_table = type(value) == 'table' + local delete = value == nil + + if delete then self:check_deleted(path) end + + 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, value, old == nil) + + local function set_parent() + set(ppath, parent) + self.mod_time[ppath] = self.mod_time[path] + end + + if old == nil and not delete then + table.insert(parent, name) + set_parent() + elseif old ~= nil and delete then + remove_list_value(parent, name) + set_parent() + end + end +end + +function M.Transaction:check_deleted(path) end + +function M.Transaction:commit() + local mods = {} + local handled = {} + + local function insert(path, value) + assert(not handled[path]) + table.insert(mods, {path, 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 + insert(path, self.added[path]) + end + end + + local function insert_del(path) + if not handled[path] then + 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]) + insert_del(cp) + end + end + insert(path) + end + 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 + if type(value) ~= 'table' then insert(path, value) end + end + + for path, _ in pairs(self.added) do insert_add(path) end + + self.backend:comp_and_setm(self.access_time, mods) +end + + +return M diff --git a/acf2/transaction/init.lua b/acf2/transaction/init.lua index ec8da59..482c00e 100644 --- a/acf2/transaction/init.lua +++ b/acf2/transaction/init.lua @@ -1,5 +1,5 @@ --[[ -Copyright (c) 2012-2013 Kaarle Ritvanen +Copyright (c) 2012-2014 Kaarle Ritvanen See LICENSE file for license details --]] @@ -10,37 +10,17 @@ local object = require('acf2.object') local super = object.super local pth = require('acf2.path') -local be_mod = require('acf2.transaction.backend') local util = require('acf2.util') local copy = util.copy -local function remove_list_value(list, value) - value = tostring(value) +local ModelTransaction = object.class( + require('acf2.transaction.base').Transaction +) - for i, v in ipairs(list) do - if tostring(v) == value then - table.remove(list, i) - return - end - end -end - - -local Transaction = object.class(be_mod.TransactionBackend) - -function Transaction:init(backend, validate) - super(self, Transaction):init() - - self.backend = backend - - self.started = be_mod.gen_number() - self.access_time = {} - - self.added = {} - self.modified = {} - self.deleted = {} +function ModelTransaction:init(backend, validate) + super(self, ModelTransaction):init(backend) self.validate = validate self.validable = {} @@ -48,29 +28,21 @@ function Transaction:init(backend, validate) self.root = root.RootModel(self) end -function Transaction:committing() return self.commit_val and true or false end +function ModelTransaction:committing() + return self.commit_val and true or false +end -function Transaction:check() +function ModelTransaction:check() if not self.backend then error('Transaction already committed') end end -function Transaction:get(path) +function ModelTransaction: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]), 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 + return super(self, ModelTransaction):get(path) end -function Transaction:set_multiple(mods) - super(self, Transaction):set_multiple(mods) +function ModelTransaction:set_multiple(mods) + super(self, ModelTransaction):set_multiple(mods) for _, mod in ipairs(mods) do local addr, value = unpack(mod) if value == nil then @@ -93,79 +65,25 @@ function Transaction:set_multiple(mods) end end -function Transaction:_set_multiple(mods) - - local function set(path, value, new) - local delete = value == 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, value = unpack(mod) - - local ppath = pth.parent(path) - local parent = self:get(ppath) - if parent == nil then - parent = {} - self:set(ppath, parent) - end - - local name = pth.name(path) - local old = self:get(path) - - local is_table = type(value) == 'table' - local delete = value == nil - - if delete then - -- assume one-level refs for now - local top = root.topology(ppath) - if top then - local errors = ErrorDict() - for _, refs in ipairs(top.referrers) do - for _, ref in ipairs(self.root:search_refs(refs)) do - errors:collect(ref.deleted, ref, path) - end - end - errors:raise() - end - end - - 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) +function ModelTransaction:check_deleted(path) + -- assume one-level refs for now + local top = root.topology(pth.parent(path)) + if top then + local errors = ErrorDict() + for _, refs in ipairs(top.referrers) do + for _, ref in ipairs(self.root:search_refs(refs)) do + errors:collect(ref.deleted, ref, path) end end - - if is_table then value = {} end - set(path, value, old == nil) - - local function set_parent() - set(ppath, parent) - self.mod_time[ppath] = self.mod_time[path] - end - - if old == nil and not delete then - table.insert(parent, name) - set_parent() - elseif old ~= nil and delete then - remove_list_value(parent, name) - set_parent() - end + errors:raise() end end -function Transaction:fetch(path) return self.root:fetch(path) end +function ModelTransaction:fetch(path) return self.root:fetch(path) end -function Transaction:meta(path) return self.root:meta(path) end +function ModelTransaction:meta(path) return self.root:meta(path) end -function Transaction:commit() +function ModelTransaction:commit() self:check() if self.validate then @@ -184,49 +102,7 @@ function Transaction:commit() errors:raise() end - local mods = {} - local handled = {} - - local function insert(path, value) - assert(not handled[path]) - table.insert(mods, {path, 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 - insert(path, self.added[path]) - end - end - - local function insert_del(path) - if not handled[path] then - 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]) - insert_del(cp) - end - end - insert(path) - end - 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 - if type(value) ~= 'table' then insert(path, value) end - end - - for path, _ in pairs(self.added) do insert_add(path) end - - self.backend:comp_and_setm(self.access_time, mods) - + super(self, ModelTransaction):commit() if not self.validate then util.update(self.backend.validable, self.validable) @@ -239,5 +115,5 @@ end local store = require('acf2.persistence') return function(txn, defer_validation) - return Transaction(txn or store, not (txn and defer_validation)) + return ModelTransaction(txn or store, not (txn and defer_validation)) end |