-
Notifications
You must be signed in to change notification settings - Fork 0
/
lmdb_env.lua
248 lines (214 loc) · 5.29 KB
/
lmdb_env.lua
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
local tracker = require("table-tracker")
local lmdb_env
local databases = {}
--- LMDB Wrapper
local lightningmdb_lib = require("lightningmdb")
--- Filesystem
local lfs = require("lfs")
local serpent = require("serpent")
--Set up lmdb and a table of constants
local lightningmdb = _VERSION >= "Lua 5.2" and lightningmdb_lib or lightningmdb
local MDB = setmetatable({}, {
__index = function(_, k)
return lightningmdb["MDB_" .. k]
end
})
--- cursor_pairs. Use a coroutine to iterate through the
-- open lmdb data set
local function cursor_pairs(cursor_, key_, op_)
return coroutine.wrap(function()
local k = key_
repeat
local k, v = cursor_:get(k, op_ or MDB.NEXT)
if k then
coroutine.yield(k, v)
end
until not k
end)
end
local function open_tx(name,readonly)
local t,dh
local opts
if readonly then opts = MDB.RDONLY else opts = 0 end
t = assert(lmdb_env:txn_begin(nil, opts))
dh = assert(t:dbi_open(name, MDB.CREATE))
return t, dh
end
local function clean_items(key,value,throw_on_key)
if type(key) == 'table' then
if throw_on_key then error('Key cannot be of type table.') end
key = serpent.block(key)
end
if type(value) == 'table' then
value = serpent.block(value)
elseif type(value) == 'boolean' then
if value then value = 1 else value = 0 end
end
return key,value
end
local function db_print_entries(self)
local t,d = open_tx(self.name, true)
local cursor, error, errorno = t:cursor_open(d)
local k
for k, v in cursor_pairs(cursor) do
print(k,v)
end
cursor:close()
t:abort()
end
---Need to create a function for fetch size and offset?
local function db_get_entries(self)
local retval = {}
local t,d = open_tx(self.name, true)
local cursor, error, errorno = t:cursor_open(d)
local k = 0
for k, v in cursor_pairs(cursor) do
local ok, ret = serpent.load(v)
if ok then retval[tonumber(k)] = ret end
end
cursor:close()
t:abort()
return retval
end
local function db_search_entries(self,func,...)
local t,d = open_tx(self.name, true)
local cursor, error, errorno = t:cursor_open(d)
local k
local retval= {}
for k, v in cursor_pairs(cursor) do
local ok,val = func(k,v,...)
if ok then
retval[ok] = val
end
end
cursor:close()
t:abort()
return retval
end
local function db_get_item(self,key)
local t,d = open_tx(self.name, true)
local ok,res,errno = t:get(d,key, _, 0)
t:commit()
return ok,res,errno
end
local function db_add_table_item(self,table, check_dups)
local t,d = open_tx(self.name)
local ok, err, errno
local cursor
cursor, err, errno= t:cursor_open(d)
if not cursor then
t:abort()
return nil, err, errno
end
local tmp = 0
for k,v in pairs(table) do
if check_dups then
clean_items(k,nil,true)
ok, err, errno = cursor:get(k, MDB.FIRST)
if not ok and err == MDB.NOTFOUND then return end
end
k,v = clean_items(k,v,true)
ok, err, errno = cursor:put(k,v,0)
if not ok then
cursor:close()
t:abort()
return err, errno
end
end
cursor:close()
t:commit()
end
local function db_item_exists(self,key)
local t,d = open_tx(self.name)
clean_items(key, nil, true)
local ok, err, errno = t:get(d, key)
if not ok then
t:abort()
return nil, err, errno
end
t:commit()
return key
end
--If the item exists, throws an exception
local function db_add_item(self,key,value)
local t,d = open_tx(self.name)
--Need to wrap in pcall to catch errors.
--should return key if success or
--nil, error, errorno
key, value = clean_items(key,value, true)
local ok, err, errno = t:put(d, key, value, MDB.NOOVERWRITE)
if not ok then
t:abort()
return nil, err, errno
end
t:commit()
return key
end
local function db_upsert_item(key,value)
local t,d = open_tx(self.name)
key, value = clean_items(key,value, true)
local ok, err, errno = t:put(d, key, value, 0)
if not ok then
t:abort()
return nil, err, errno
end
t:commit()
return key
end
--need to check if database exists yet!
local function open_database(name,create)
if lmdb_env then
if databases[name] then
return databases[name]
end
local t = lmdb_env:txn_begin(nil, 0)
if not name then name = "" end
local opts
if create then
opts = MDB.CREATE
else
opts = 0
end
local dh = assert(t:dbi_open(name, opts))
local cursor = t:cursor_open(dh)
cursor:close()
t:abort()
local db = {
name = name,
update = db_upsert_item,
add = db_add_item,
add_table = db_add_table_item,
print_all = db_print_entries,
search = db_search_entries,
get_item = db_get_item,
get_all = db_get_entries
}
databases[name] = db
return db
else
return nil, "NO_ENV_AVAIL", 100
end
end
local function stats()
return lmdb_env:stat()
end
local function close_env()
lmdb_env:close()
end
local function new(datadir)
lmdb_env = lightningmdb.env_create()
lmdb_env:set_mapsize(10485760)
lmdb_env:set_maxdbs(4)
lmdb_env:open(datadir, 0, 420)
return {
datadir = datadir,
databases = databases,
open_database = open_database,
open_tx = open_tx,
close_env = close_env,
stats = stats
}
end
return {
new = new
}