summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2014-01-03 00:09:35 +0200
committerKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2014-01-03 03:45:20 +0200
commit15ea19da7079cb4c2019b53dae62d5cf6562ce63 (patch)
tree84fa3f23603ba3e969bc75576d18e6c870403823
parent9ff4c5411397ecc6c2b18547a53d4a94df930b88 (diff)
downloadaconf-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.lua6
-rw-r--r--acf2/transaction/backend.lua70
-rw-r--r--acf2/transaction/base.lua221
-rw-r--r--acf2/transaction/init.lua180
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