diff options
author | the lemons <citrons@mondecitronne.com> | 2022-12-04 04:14:22 -0600 |
---|---|---|
committer | the lemons <citrons@mondecitronne.com> | 2022-12-04 04:14:22 -0600 |
commit | 8e4af6b2c8602bfb7fd086375d741345947494aa (patch) | |
tree | 99cfa055853df745c7bbb486b3dcb6bacf7b9d14 |
initial
-rw-r--r-- | Makefile | 16 | ||||
-rw-r--r-- | README.md | 49 | ||||
-rw-r--r-- | lmdb.c | 343 |
3 files changed, 408 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4b4860a --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +LUA_VER=5.3 +CC = cc +LD = ld +CFLAGS = -std=c99 -Wall -O2 -fPIC -I/usr/include/lua$(LUA_VER) +LFLAGS = -shared -llmdb + +lmdb.so: lmdb.o + $(LD) $(LFLAGS) -o $@ $< + +lmdb.o: lmdb.c + $(CC) $(CFLAGS) -c $< -o $@ + +.PHONY: clean +clean: + rm -rf lmdb.o + rm -rf lmdb.so diff --git a/README.md b/README.md new file mode 100644 index 0000000..71b0e62 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# [LMDB](http://www.lmdb.tech/doc/index.html) bindings for lua +license: MIT + +## errors +operations that can fail will return `nil, error, errno` on failure. + +## functions +### `lmdb.open(path, opt)` +create a new environment handle and open the database at the `path`. returns the environment handle (`env`) on success. + +`opt` is an optional table of options: + +* `maxdbs`: number: the maximum number of databases in the environment. must be set if multiple databases will be used. +* `rdonly`: boolean: open the environment in read-only mode. +* `nosubdir`: boolean: the `path` is used for the database file itself and not its containing directory. +* `nosync`: boolean: don't flush buffers to disk when committing. +* `nolock`: boolean: turn off locking. the application must manage all concurrency. + +### `lmdb.version()` +returns the LMDB version as `major, minor, patch`. + +### `env:txn_begin(rdonly)` +create a new transaction in the environment. if `rdonly` is true, then the transaction is not to be used to perform write operation. returns the transaction handle (`txn`) on success. + +### `env:copy(path)` +copy the environment to the specified `path`. return `true` on success. + +### `env:sync(force)` +flush buffers to disk. if `force` is `true`, a synchronous flush is performed even if `nosync` is set. return `true` on success. + +### `env:close()` +close the environment. + +### `txn:open(name, create)` +open a database in the environment. if multiple databases are to be used in the environment, `name` is the name of the database to open. otherwise, it should not be supplied. if `create` is true, the database is created if it does not exist. returns a database handle on success. + +the database handle behaves as a table that may be indexed with string keys and contains string values, read from the database. any changes made will be saved to the database when the transaction is committed. + +## `txn:drop(name)` +delete the database `name` from the environment. (or clear the database, if `name` is not supplied) + +### `txn:abort()` +abandon all operations performed in the transaction. + +### `txn:commit()` +commit all operations of the transaction into the database. + +### `txn:txn_begin(rdonly)` +create a nested transaction in the parent transaction. @@ -0,0 +1,343 @@ +#include <stdlib.h> +#include <stdbool.h> +#include <lmdb.h> +#include <lua.h> +#include <lauxlib.h> +#include <assert.h> + +struct handle { + bool closed; + bool has_child; + void *obj; +}; + +#define reterr(e) \ + do { \ + int __res = (e); if (__res != 0) { \ + lua_pushnil(L); \ + lua_pushstring(L, mdb_strerror(__res)); \ + lua_pushinteger(L, __res); \ + return 3; \ + } \ + } while (0) + +static void check_valid(lua_State *L, struct handle *h) { + if (h->closed) luaL_error(L, "attempt to use expired handle"); +} + +static struct {const char *name; int flag;} env_flags[] = { + {"rdonly", MDB_RDONLY}, + {"nosubdir", MDB_NOSUBDIR}, + {"nosync", MDB_NOSYNC}, + {"nometasync", MDB_NOMETASYNC}, + {"nordahead", MDB_NORDAHEAD}, + {"nolock", MDB_NOLOCK}, + {0}, +}; + +static int version(lua_State *L) { + int maj, min, patch; + mdb_version(&maj, &min, &patch); + lua_pushinteger(L, maj); + lua_pushinteger(L, min); + lua_pushinteger(L, patch); + return 3; +} + +static int env_open(lua_State *L) { + lua_settop(L, 2); + luaL_argcheck(L, lua_isstring(L, 1), 1, "expected string"); + luaL_argcheck(L, + lua_isnil(L, 2) || lua_istable(L, 2), 2, "expected table or nil"); + + int flags = 0; + int maxdbs = 0; + int maxdbs_supplied = 0; + if (!lua_isnil(L, 2)) { + for (int i = 0; env_flags[i].name != NULL; i++) { + lua_getfield(L, 2, env_flags[i].name); + if (lua_toboolean(L, -1)) flags |= env_flags[i].flag; + lua_pop(L, 1); + } + lua_getfield(L, 2, "maxdbs"); + maxdbs = lua_tointegerx(L, -1, &maxdbs_supplied); + } + + MDB_env *env; + reterr(mdb_env_create(&env)); + if (maxdbs_supplied) mdb_env_set_maxdbs(env, maxdbs); + int result = mdb_env_open(env, lua_tostring(L, 1), flags, 0664); + if (result != 0) { + mdb_env_close(env); + reterr(result); + } + + struct handle *ud = lua_newuserdata(L, sizeof(struct handle)); + ud->closed = false; + ud->has_child = false; + ud->obj = env; + luaL_getmetatable(L, "lmdb.env"); + lua_setmetatable(L, -2); + return 1; +}; + +static int env_close(lua_State *L) { + lua_settop(L, 1); + struct handle *ud = luaL_checkudata(L, 1, "lmdb.env"); + lua_pushboolean(L, true); + if (ud->closed) return 1; + mdb_env_close(ud->obj); + ud->closed = true; + return 1; +} + +static int env_sync(lua_State *L) { + lua_settop(L, 2); + struct handle *ud = luaL_checkudata(L, 1, "lmdb.env"); + check_valid(L, ud); + reterr(mdb_env_sync(ud->obj, lua_toboolean(L, 2))); + lua_pushboolean(L, true); + return 1; +} + +static int env_copy(lua_State *L) { + lua_settop(L, 2); + struct handle *ud = luaL_checkudata(L, 1, "lmdb.env"); + check_valid(L, ud); + luaL_argcheck(L, lua_isstring(L, 2), 2, "expected string"); + reterr(mdb_env_copy(ud->obj, lua_tostring(L, 2))); + lua_pushboolean(L, true); + return 1; +} + +static MDB_env *get_env(lua_State *L, int idx) { + lua_pushvalue(L, idx); + struct handle *ud = NULL; + while (lua_getuservalue(L, -1) == LUA_TUSERDATA) { + ud = lua_touserdata(L, -1); + if (ud->closed) return NULL; + check_valid(L, ud); + lua_remove(L, -2); + } + lua_pop(L, 2); + return ud->obj; +} + +static MDB_env *check_env(lua_State *L, int idx) { + MDB_env *env = get_env(L, idx); + if (env == NULL) luaL_error(L, "attempt to use expired handle"); + return env; +} + +static int txn_begin(lua_State *L, bool nested) { + lua_settop(L, 2); + struct handle *ud = luaL_checkudata(L, 1, !nested ? "lmdb.env" : "lmdb.txn"); + check_valid(L, ud); + if (ud->has_child) + luaL_error(L, "cannot create new transaction while another exists"); + + MDB_env *env; + MDB_txn *parent = NULL; + if (!nested) env = ud->obj; + else { + parent = ud->obj; + env = check_env(L, 1); + } + + MDB_txn *txn; + reterr(mdb_txn_begin( + env, parent, lua_toboolean(L, 2) ? MDB_RDONLY : 0, &txn)); + + struct handle *tud = lua_newuserdata(L, sizeof(struct handle)); + tud->closed = false; + tud->has_child = false; + tud->obj = txn; + luaL_getmetatable(L, "lmdb.txn"); + lua_setmetatable(L, -2); + // store the supplied transaction/env as the parent + lua_pushvalue(L, 1); + lua_setuservalue(L, -2); + + ud->has_child = true; + return 1; +} + +static int env_txn_begin(lua_State *L) { + return txn_begin(L, false); +} + +static int txn_txn_begin(lua_State *L) { + return txn_begin(L, true); +} + +static int txn_abort(lua_State *L) { + lua_settop(L, 1); + struct handle *ud = luaL_checkudata(L, 1, "lmdb.txn"); + if (ud->closed || get_env(L, 1) == NULL) { + lua_pushboolean(L, true); + return 1; + } + mdb_txn_abort(ud->obj); + ud->closed = true; + + lua_getuservalue(L, 1); + struct handle *pud = lua_touserdata(L, -1); + pud->has_child = false; + + lua_pushboolean(L, true); + return 1; +} + +static int txn_commit(lua_State *L) { + lua_settop(L, 1); + struct handle *ud = luaL_checkudata(L, 1, "lmdb.txn"); + check_valid(L, ud); + check_env(L, 1); + ud->closed = true; + + lua_getuservalue(L, 1); + struct handle *pud = lua_touserdata(L, -1); + pud->has_child = false; + + reterr(mdb_txn_commit(ud->obj)); + lua_pushboolean(L, true); + return 1; +} + +static int db_open(lua_State *L) { + lua_settop(L, 3); + struct handle *ud = luaL_checkudata(L, 1, "lmdb.txn"); + check_valid(L, ud); + check_env(L, 1); + luaL_argcheck(L, + lua_isnil(L, 2) || lua_isstring(L, 2), 2, "expected string or nil"); + + MDB_dbi dbi; + reterr(mdb_dbi_open(ud->obj, + lua_tostring(L, 2), lua_toboolean(L, 3) ? MDB_CREATE : 0, &dbi)); + *(MDB_dbi *) lua_newuserdata(L, sizeof(MDB_dbi)) = dbi; + luaL_getmetatable(L, "lmdb.db"); + lua_setmetatable(L, -2); + // transaction is parent of DB handle + lua_pushvalue(L, 1); + lua_setuservalue(L, -2); + return 1; +} + +static MDB_val toval(lua_State *L, int idx) { + MDB_val val; + val.mv_data = lua_tolstring(L, idx, &val.mv_size); + return val; +} + +static void fromval(lua_State *L, MDB_val val) { + lua_pushlstring(L, val.mv_data, val.mv_size); +} + +static int db_get(lua_State *L) { + lua_settop(L, 2); + MDB_dbi dbi = *(MDB_dbi *) luaL_checkudata(L, 1, "lmdb.db"); + check_env(L, 1); + luaL_argcheck(L, lua_isstring(L, 2), 2, "expected string"); + + lua_getuservalue(L, 1); + struct handle *tud = lua_touserdata(L, -1); + MDB_val key = toval(L, 2); + MDB_val data; + int result = mdb_get(tud->obj, dbi, &key, &data); + if (result == MDB_NOTFOUND) { + lua_pushnil(L); + return 1; + } else if (result != 0) luaL_error(L, "%s", mdb_strerror(result)); + fromval(L, data); + return 1; +} + +static int db_put(lua_State *L) { + lua_settop(L, 3); + MDB_dbi dbi = *(MDB_dbi *) luaL_checkudata(L, 1, "lmdb.db"); + check_env(L, 1); + luaL_argcheck(L, lua_isstring(L, 2), 2, "expected string"); + luaL_argcheck( + L, lua_isnil(L, 3) || lua_isstring(L, 3), 3, "expected string or nil"); + + lua_getuservalue(L, 1); + struct handle *tud = lua_touserdata(L, -1); + MDB_val key = toval(L, 2); + int result; + if (!lua_isnil(L, 3)) { + MDB_val data = toval(L, 3); + result = mdb_put(tud->obj, dbi, &key, &data, 0); + } else result = mdb_del(tud->obj, dbi, &key, NULL); + if (result !=0) luaL_error(L, "%s", mdb_strerror(result)); + return 0; +} + +static int db_drop(lua_State *L) { + lua_settop(L, 2); + struct handle *ud = luaL_checkudata(L, 1, "lmdb.txn"); + check_valid(L, ud); + check_env(L, 1); + luaL_argcheck(L, + lua_isnil(L, 2) || lua_isstring(L, 2), 2, "expected string or nil"); + + MDB_dbi dbi; + reterr(mdb_dbi_open(ud->obj, lua_tostring(L, 2), 0, &dbi)); + reterr(mdb_drop(ud->obj, dbi, !lua_isnil(L, 2))); + lua_pushboolean(L, true); + return 1; +} + +static const struct luaL_Reg lmdb[] = { + {"open", env_open}, + {"version", version}, + {NULL, NULL}, +}; + +static const struct luaL_Reg lmdb_env[] = { + {"txn_begin", env_txn_begin}, + {"copy", env_copy}, + {"sync", env_sync}, + {"close", env_close}, + {"abort", env_close}, + {"__close", env_close}, + {"__gc", env_close}, + {NULL, NULL}, +}; + +static const struct luaL_Reg lmdb_txn[] = { + {"open", db_open}, + {"abort", txn_abort}, + {"commit", txn_commit}, + {"txn_begin", txn_txn_begin}, + {"drop", db_drop}, + {"__close", txn_commit}, + {"__gc", txn_abort}, + {NULL, NULL}, +}; + +static const struct luaL_Reg lmdb_db[] = { + {"__index", db_get}, + {"__newindex", db_put}, + {NULL, NULL}, +}; + +static void mt(lua_State *L, const char *name, const struct luaL_Reg l[]) { + luaL_newmetatable(L, name); + luaL_setfuncs(L, l, 0); + lua_getfield(L, -1, "__index"); + if (lua_isnil(L, -1)){ + lua_pop(L, 1); + lua_setfield(L, -1, "__index"); + } + lua_pop(L, 2); +} + +int luaopen_lmdb(lua_State *L) { + mt(L, "lmdb.env", lmdb_env); + mt(L, "lmdb.txn", lmdb_txn); + mt(L, "lmdb.db", lmdb_db); + + luaL_newlib(L, lmdb); + return 1; +} |