summaryrefslogtreecommitdiffstats
path: root/aconf/transaction/init.lua
blob: d4c79fe00aa85ca0d1c767a57c58451955626b71 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
--[[
Copyright (c) 2012-2014 Kaarle Ritvanen
See LICENSE file for license details
--]]

local ErrorDict = require('aconf.error').ErrorDict
local root = require('aconf.model.root')

local object = require('aconf.object')
local super = object.super

local pth = require('aconf.path')

local util = require('aconf.util')
local copy = util.copy


local ModelTransaction = object.class(
   require('aconf.transaction.base').Transaction
)

function ModelTransaction:init(backend, validate, user)
   super(self, ModelTransaction):init(backend)

   self.validate = validate
   self.validable = {}

   self.user = user

   self.root = root.RootModel{txn=self}
end

function ModelTransaction:committing()
   return self.commit_val and true or false
end

function ModelTransaction:check()
   if not self.backend then error('Transaction already committed') end
end

function ModelTransaction:get(path)
   self:check()
   return super(self, ModelTransaction):get(path)
end

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
	 for _, val in ipairs{self.validable, self.commit_val} do
	    local done
	    repeat
	       done = true
	       for path, a in pairs(copy(val)) do
		  if a == addr then
		     for p, _ in pairs(copy(val)) do
			if pth.is_subordinate(p, path) then val[p] = nil end
		     end
		     done = false
		     break
		  end
	       end
	    until done
	 end
      end
   end
end

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
      errors:raise()
   end
end

function ModelTransaction:fetch(path, escalate)
   local root = self.root
   return (escalate and getmetatable(root).escalate or root):fetch(path)
end

function ModelTransaction:meta(path) return self.root:meta(path) end

function ModelTransaction:commit()
   self:check()

   if self.validate then
      self.commit_val = copy(self.validable)
      local errors = ErrorDict()

      local function validate(path)
	 if path > '/' then validate(pth.parent(path)) end
	 if not self.commit_val[path] then return end
	 errors:collect(getmetatable(self:fetch(path, true)).validate)
	 self.commit_val[path] = nil
      end

      while next(self.commit_val) do validate(next(self.commit_val)) end
      self.commit_val = nil
      errors:raise()
   end

   super(self, ModelTransaction):commit()

   if not self.validate then
      util.update(self.backend.validable, self.validable)
   end
   
   self.backend = nil
end


local store = require('aconf.persistence')
local def_store = require('aconf.persistence.defer')

return function(options)
   options = options or {}
   return ModelTransaction(
      options.parent or (options.allow_commit_defer and def_store or store),
      not (options.parent and options.defer_validation),
      options.parent and options.parent.user or options.user
   )
end