Module persist

This module allows to save and retrieve Lua values in table-like objects, in a non-volatile way.

Once stored in a persisted table, these Lua values can be retrieved even after the Lua process and/or the CPU running it have been rebooted.

Compared to raw files, the persist module offers a higher level of abstraction, allowing to save and retrieve Lua objects directly, without explicitly dealing with serialization, deserialization or other filesystem issues. It can also be ported to environmnets which don't have a filesystem.

This version of the module naively writes data in a file, and keeps whole tables' content in RAM. Its purpose is to avoid using the more efficient QDBM version, whose LGPL license might be problematic to some use cases.

Persistence services are offered through two public APIs:

  • general purpose persisted tables: they behave mostly as regular tables, except that their content survives across reboots. These tables are created with persist.table.new;

  • To easily save and retrieve isolated objects, persist.save and persist.load allow single-line operations with no extra bookkeeping.

Persisted Tables.

Persisted tables behave mostly as regular Lua tables, except that their content survives across reboots. They can hold strings, numbers, booleans, and possibly nested tables thereof, both as their keys and as their values.

Functions can be persisted if and only if they don't capture any upvalue (see examples below).

Beware that tables are stored structurally; what's retrieved are copies of the objects store in them, not the objects themselves:

local persist = require 'persist'
t = persist.table.new('test')
t[1] = { }
assert(t[1] ~= t[1])

However, loops and shared table parts are preserved within a single item (key or value) stored and retrieved from a persisted table:

local y  = { }
local x1 = { y1=y; y2=y } -- y1 and y2 point to the same object
x1.x = x                  -- x1 points to itself through its field x
assert(x1.x == x1)
assert(x1.y1 == x1.y2)
t[2] = x1                 -- save it in a table
local x2 = t[2]           -- retrieve a copy from the table
assert(x2 ~= x1)          -- it's a copy, not the original
assert(x2.x == x2)        -- but it points to itself
assert(x2.y1 == x2.y2)    -- and its shared parts are still shared

Since tables retrieved from persisted tables are actually copies, alterations of these returned tables won't affect the store's content:

x = { foo = 'bar' }
t.x = x
x.foo = 42 -- modifying `x`, not the copy in `t`
assert (t.x.foo == 'bar') -- `t` remains unchanged
t.x = x -- overriding `t.x` with the whole `x` value
assert (t.x.foo == 42) -- now `t` reflects the change

"Simple" function, which don't capture any local variable, can be saved:

function plus_one(x) return x+1 end
t.plus_one = plus_one
assert(t.plus_one(1) == 2)

As for tables, persisted functions don't retain their identity:

assert(t.plus_one ~= t.plus_one)

Finally, functions which capture local variables cannot be saved. Below, the alternative implementation of plus_one captures a local variable i, and won't be properly serialized:

function make_incrementer (i)
    return function(x) return x+i end
end
plus_one = make_incrementer(1)
t.plus_one = plus_one

Persisted Objects.

The usual way to persist data with the persist module is to create a table object, then fill it with values to persist. However, this is neither practical nor efficient when only one or a couple of small values need to be saved.

To avoid a needless proliferation of tiny persisted tables, a pair of persist.save and persist.load functions are provided, to easily store individual objects with a single line of code.

Objects stored through this API have the same limitations as those stored in full-featured persisted tables: no userdata, no threads, no upvalues in functions, no preservation of table and function identities.

POSIX implementation details.

All objects are saved in a regular binary file called persist/name.l2b

Type persist

persist.load(name)

Retrieve from flash an object saved with persist.save.

persist.save(name, obj)

Saves an object for later retrieval.

Type persist.table

persist.table.close(t)

Close a persisted table.

persist.table.empty(t)

Empties a table and releases associated resources.

persist.table.emptyall()

Resets all persisted tables.

persist.table.new(name)

Creates or loads a new persisted table.

Type persist

Field(s)

persist.load(name)

Retrieve from flash an object saved with persist.save.

Parameter

  • name : the name of the persisted object to load.

Return value

the object stored under that name, or nil if no such object exists.

persist.save(name, obj)

Saves an object for later retrieval.

If the saving operation cannot be performed successfully, an error is thrown. Objects saved with this function can be retrieved with persist.load, by giving back the same name, even after a reboot.

Parameters

  • name : the name of the persisted object to save.

  • obj : the object to persist.

Usage:


persist.save ('xxx', 1357) -- Save it in the store as 'xxx'
[...] -- reboot
x = persist.load 'xxx' -- Retrieve it from the store
assert(x == 1357)

Type persist.table

Field(s)

persist.table.close(t)

Close a persisted table.

This close and release all attachesd ressources to a persisted table, included the store file.

Parameter

persist.table.empty(t)

Empties a table and releases associated resources.

Parameter

persist.table.emptyall()

Resets all persisted tables.

this function is not to be mistaken with persist.table.empty API this function resets all persisted files: all tables explicitly created by user, and the PersistStore used to provide load/save API in persist module. (This data rest only applies to the current Lua framework running persist module).

persist.table.new(name)

Creates or loads a new persisted table.

If a table already exists with the provided name, it is loaded; otherwise, a new one is created.

Note: A persisted table is not an exact Lua table even though it looks like one. One caveat of persisted tables is that you cannot use standard Lua global next() function to check if the table is empty or not. if you want to check if the table is empty. However you can do this:

 local t = persist.table.new("a_table")
 local next = pairs(t)
 if next(t) then
      -- the persisted table is not empty
 end

Note: persist.table.new always return a table, possibly an empty one if the stored file is too much corrupted. When an un recoverable error occurs and exception is raised instead of returning a warning. Ex. of unrecoverable error: permission on the file system prevent the stored file to be opened.

Parameter

  • name : persisted table name (string only).

Return value

the persisted table, optionally followed by a warning if the stored file is corrupted.