ADDED examples/session.lua Index: examples/session.lua ================================================================== --- /dev/null +++ examples/session.lua @@ -0,0 +1,34 @@ + +local function showdata(db) + for r in db:nrows("select * from foo") do print("select: ", r.k, + r.a, r.b, r.c) + end +end + + +local sqlite3 = require("lsqlite3") +local db = sqlite3.open_memory() +local ses = db:create_session() + +print("create", db:exec("create table foo(k primary key, a, b, c)")) +print("insert", db:exec("insert into foo(k, a, b, c) values (1, 'v1', 'v1', 'v1')")) + +ses:attach() +print("update/1", db:exec("update foo set a = 'v2', b = 'v2' where k = 1")) +showdata(db) +local cs2 = ses:changeset() + +print("update/2", db:exec("update foo set a = 'v3', b = 'v1' where k = 1")) +showdata(db) +local cs3 = ses:changeset() + +print("apply1/abort", db:apply_changeset(cs2, + function(...) print("filter", ...) return 1 end, + function(...) print("conflict", ...) return sqlite3.CHANGESET_ABORT end)) +showdata(db) + +local cs4 = db:concat_changeset { db:invert_changeset(cs3), cs2, cs3 } +print("apply2/no-conflict", db:apply_changeset(cs4, + function(...) print("filter", ...) return 1 end, + function(...) print("conflict", ...) return sqlite3.CHANGESET_REPLACE end)) +showdata(db) Index: lsqlite3-0.9.5-1.rockspec ================================================================== --- lsqlite3-0.9.5-1.rockspec +++ lsqlite3-0.9.5-1.rockspec @@ -27,11 +27,15 @@ build = { type = "builtin", modules = { lsqlite3 = { sources = { "lsqlite3.c" }, - defines = {'LSQLITE_VERSION="0.9.5"'}, + defines = { + 'LSQLITE_VERSION="0.9.5"', + 'SQLITE_ENABLE_SESSION', + 'SQLITE_ENABLE_PREUPDATE_HOOK' + }, libraries = { "sqlite3" }, incdirs = { "$(SQLITE_INCDIR)" }, libdirs = { "$(SQLITE_LIBDIR)" } }, }, Index: lsqlite3.c ================================================================== --- lsqlite3.c +++ lsqlite3.c @@ -27,14 +27,33 @@ ************************************************************************/ #include #include #include +#include #define LUA_LIB #include "lua.h" #include "lauxlib.h" + +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + +#if LUA_VERSION_NUM < 502 +/* +** Lua 5.1 (or LuaJIT) +*/ + +#ifndef LUA_OK +#define LUA_OK 0 +#endif + +#ifndef luaL_len +#define luaL_len lua_objlen +#endif + +#endif #if LUA_VERSION_NUM > 501 /* ** Lua 5.2 */ @@ -108,10 +127,13 @@ int trace_cb; /* trace callback */ int trace_udata; #if !defined(LSQLITE_OMIT_UPDATE_HOOK) || !LSQLITE_OMIT_UPDATE_HOOK + + int wal_hook_cb; /* wal_hook callback */ + int wal_hook_udata; int update_hook_cb; /* update_hook callback */ int update_hook_udata; int commit_hook_cb; /* commit_hook callback */ @@ -126,10 +148,21 @@ static const char *sqlite_meta = ":sqlite3"; static const char *sqlite_vm_meta = ":sqlite3:vm"; static const char *sqlite_bu_meta = ":sqlite3:bu"; static const char *sqlite_ctx_meta = ":sqlite3:ctx"; static int sqlite_ctx_meta_ref; +#ifdef SQLITE_ENABLE_SESSION +static const char *const sqlite_ses_meta = ":sqlite3:ses"; +static const char *const sqlite_reb_meta = ":sqlite3:reb"; +static const char *const sqlite_itr_meta = ":sqlite3:itr"; +static int sqlite_ses_meta_ref; +static int sqlite_reb_meta_ref; +static int sqlite_itr_meta_ref; +#endif +/* global config configuration */ +static int log_cb = LUA_NOREF; /* log callback */ +static int log_udata; /* Lua 5.3 introduced an integer type, but depending on the implementation, it could be 32 ** or 64 bits (or something else?). This helper macro tries to do "the right thing." */ @@ -212,33 +245,23 @@ svm->temp = 0; /* add an entry on the database table: svm -> db to keep db live while svm is live */ lua_pushlightuserdata(L, db); /* db sql svm_ud db_lud -- */ lua_rawget(L, LUA_REGISTRYINDEX); /* db sql svm_ud reg[db_lud] -- */ - lua_pushlightuserdata(L, svm); /* db sql svm_ud reg[db_lud] svm_lud -- */ - lua_pushvalue(L, -5); /* db sql svm_ud reg[db_lud] svm_lud db -- */ - lua_rawset(L, -3); /* (reg[db_lud])[svm_lud] = db ; set the db for this vm */ + lua_pushvalue(L, -2); /* db sql svm_ud reg[db_lud] svm_ud -- */ + lua_pushvalue(L, -5); /* db sql svm_ud reg[db_lud] svm_ud db -- */ + lua_rawset(L, -3); /* (reg[db_lud])[svm_ud] = db ; set the db for this vm */ lua_pop(L, 1); /* db sql svm_ud -- */ return svm; } static int cleanupvm(lua_State *L, sdb_vm *svm) { - - /* remove entry in database table - no harm if not present in the table */ - lua_pushlightuserdata(L, svm->db); - lua_rawget(L, LUA_REGISTRYINDEX); - lua_pushlightuserdata(L, svm); - lua_pushnil(L); - lua_rawset(L, -3); - lua_pop(L, 1); - svm->columns = 0; svm->has_values = 0; if (!svm->vm) return 0; - lua_pushinteger(L, sqlite3_finalize(svm->vm)); svm->vm = NULL; return 1; } @@ -264,11 +287,11 @@ lua_pushboolean(L, svm->vm != NULL ? 1 : 0); return 1; } static int dbvm_tostring(lua_State *L) { - char buff[39]; + char buff[40]; sdb_vm *svm = lsqlite_getvm(L, 1); if (svm->vm == NULL) strcpy(buff, "closed"); else sprintf(buff, "%p", svm); @@ -275,13 +298,11 @@ lua_pushfstring(L, "sqlite virtual machine (%s)", buff); return 1; } static int dbvm_gc(lua_State *L) { - sdb_vm *svm = lsqlite_getvm(L, 1); - if (svm->vm != NULL) /* ignore closed vms */ - cleanupvm(L, svm); + cleanupvm(L, lsqlite_getvm(L, 1)); return 0; } static int dbvm_step(lua_State *L) { int result; @@ -632,10 +653,12 @@ db->db = NULL; /* database handle is currently `closed' */ db->func = NULL; db->busy_cb = db->busy_udata = + db->wal_hook_cb = + db->wal_hook_udata = db->progress_cb = db->progress_udata = db->trace_cb = db->trace_udata = #if !defined(LSQLITE_OMIT_UPDATE_HOOK) || !LSQLITE_OMIT_UPDATE_HOOK @@ -644,54 +667,62 @@ db->commit_hook_cb = db->commit_hook_udata = db->rollback_hook_cb = db->rollback_hook_udata = #endif - LUA_NOREF; + LUA_NOREF; luaL_getmetatable(L, sqlite_meta); lua_setmetatable(L, -2); /* set metatable */ - /* to keep track of 'open' virtual machines */ + /* to keep track of 'open' virtual machines; make keys week */ lua_pushlightuserdata(L, db); - lua_newtable(L); + lua_newtable(L); // t + lua_newtable(L); // t mt + lua_pushstring(L, "k"); // t mt v + lua_setfield(L, -2, "__mode"); // t mt + lua_setmetatable(L, -2); // t lua_rawset(L, LUA_REGISTRYINDEX); return db; } -static int cleanupdb(lua_State *L, sdb *db) { - sdb_func *func; - sdb_func *func_next; - int top; - int result; - +/* cleanup all vms or just temporary ones */ +static void closevms(lua_State *L, sdb *db, int temp) { /* free associated virtual machines */ lua_pushlightuserdata(L, db); lua_rawget(L, LUA_REGISTRYINDEX); /* close all used handles */ - top = lua_gettop(L); lua_pushnil(L); while (lua_next(L, -2)) { sdb_vm *svm = lua_touserdata(L, -2); /* key: vm; val: sql text */ - cleanupvm(L, svm); + if ((!temp || svm->temp)) lua_pop(L, cleanupvm(L, svm)); + lua_pop(L, 1); /* pop value; leave key in the stack */ + } +} - lua_settop(L, top); - lua_pushnil(L); - } +static int cleanupdb(lua_State *L, sdb *db) { + sdb_func *func; + sdb_func *func_next; + int top = lua_gettop(L); + int result; - lua_pop(L, 1); /* pop vm table */ + if (!db->db) return SQLITE_MISUSE; + + closevms(L, db, 0); /* remove entry in lua registry table */ lua_pushlightuserdata(L, db); lua_pushnil(L); lua_rawset(L, LUA_REGISTRYINDEX); /* 'free' all references */ luaL_unref(L, LUA_REGISTRYINDEX, db->busy_cb); luaL_unref(L, LUA_REGISTRYINDEX, db->busy_udata); + luaL_unref(L, LUA_REGISTRYINDEX, db->wal_hook_cb); + luaL_unref(L, LUA_REGISTRYINDEX, db->wal_hook_udata); luaL_unref(L, LUA_REGISTRYINDEX, db->progress_cb); luaL_unref(L, LUA_REGISTRYINDEX, db->progress_udata); luaL_unref(L, LUA_REGISTRYINDEX, db->trace_cb); luaL_unref(L, LUA_REGISTRYINDEX, db->trace_udata); #if !defined(LSQLITE_OMIT_UPDATE_HOOK) || !LSQLITE_OMIT_UPDATE_HOOK @@ -701,12 +732,13 @@ luaL_unref(L, LUA_REGISTRYINDEX, db->commit_hook_udata); luaL_unref(L, LUA_REGISTRYINDEX, db->rollback_hook_cb); luaL_unref(L, LUA_REGISTRYINDEX, db->rollback_hook_udata); #endif - /* close database */ - result = sqlite3_close(db->db); + /* close database; _v2 is intended for use with garbage collected languages + and where the order in which destructors are called is arbitrary. */ + result = sqlite3_close_v2(db->db); db->db = NULL; /* free associated memory with created functions */ func = db->func; while (func) { @@ -716,10 +748,12 @@ luaL_unref(L, LUA_REGISTRYINDEX, func->udata); free(func); func = func_next; } db->func = NULL; + + lua_settop(L, top); return result; } static sdb *lsqlite_getdb(lua_State *L, int index) { sdb *db = (sdb*)luaL_checkudata(L, index, sqlite_meta); @@ -764,11 +798,11 @@ if (ctx->ctx == NULL) luaL_argerror(L, index, "invalid sqlite context"); return ctx; } static int lcontext_tostring(lua_State *L) { - char buff[39]; + char buff[41]; lcontext *ctx = lsqlite_getcontext(L, 1); if (ctx->ctx == NULL) strcpy(buff, "closed"); else sprintf(buff, "%p", ctx->ctx); @@ -943,10 +977,35 @@ const char *db_name = luaL_checkstring(L, 2); /* sqlite3_db_filename may return NULL, in that case Lua pushes nil... */ lua_pushstring(L, sqlite3_db_filename(db->db, db_name)); return 1; } + +static int pusherr(lua_State *L, int rc) { + lua_pushnil(L); + lua_pushinteger(L, rc); + return 2; +} + +static int pusherrstr(lua_State *L, char *str) { + lua_pushnil(L); + lua_pushstring(L, str); + return 2; +} + +static int db_wal_checkpoint(lua_State *L) { + sdb *db = lsqlite_checkdb(L, 1); + int eMode = luaL_optinteger(L, 2, SQLITE_CHECKPOINT_PASSIVE); + const char *db_name = luaL_optstring(L, 3, NULL); + int nLog, nCkpt; + if (sqlite3_wal_checkpoint_v2(db->db, db_name, eMode, &nLog, &nCkpt) != SQLITE_OK) { + return pusherr(L, sqlite3_errcode(db->db)); + } + lua_pushinteger(L, nLog); + lua_pushinteger(L, nCkpt); + return 2; +} /* ** Registering SQL functions: */ @@ -1250,10 +1309,62 @@ lua_pushboolean(L,0); /* so, assert(load_extension(...)) works */ lua_pushstring(L,errmsg); sqlite3_free(errmsg); return 2; } + +/* +** wal_hook callback: +** Params: database, callback function, userdata +** +** callback function: +** Params: userdata, db handle, database name, number of wal file pages +*/ +static int db_wal_hook_callback(void *user, sqlite3 *dbh, char const *dbname, int pnum) { + sdb *db = (sdb*)user; + lua_State *L = db->L; + int top = lua_gettop(L); + + /* setup lua callback call */ + lua_rawgeti(L, LUA_REGISTRYINDEX, db->wal_hook_cb); /* get callback */ + lua_rawgeti(L, LUA_REGISTRYINDEX, db->wal_hook_udata); /* get callback user data */ + lua_pushstring(L, dbname); /* hook database name */ + lua_pushinteger(L, pnum); + + if (lua_pcall(L, 3, 0, 0) != LUA_OK) return lua_error(L); + + lua_settop(L, top); + return SQLITE_OK; +} + +static int db_wal_hook(lua_State *L) { + sdb *db = lsqlite_checkdb(L, 1); + + luaL_unref(L, LUA_REGISTRYINDEX, db->wal_hook_cb); + luaL_unref(L, LUA_REGISTRYINDEX, db->wal_hook_udata); + if (lua_gettop(L) < 2 || lua_isnil(L, 2)) { + db->wal_hook_cb = + db->wal_hook_udata = LUA_NOREF; + + /* clear hook handler */ + sqlite3_wal_hook(db->db, NULL, NULL); + } + else { + luaL_checktype(L, 2, LUA_TFUNCTION); + + /* make sure we have an userdata field (even if nil) */ + lua_settop(L, 3); + + db->wal_hook_udata = luaL_ref(L, LUA_REGISTRYINDEX); + db->wal_hook_cb = luaL_ref(L, LUA_REGISTRYINDEX); + + /* set hook handler */ + sqlite3_wal_hook(db->db, db_wal_hook_callback, db); + } + + return 0; +} /* ** trace callback: ** Params: database, callback function, userdata ** @@ -1341,13 +1452,13 @@ } static int db_update_hook(lua_State *L) { sdb *db = lsqlite_checkdb(L, 1); + luaL_unref(L, LUA_REGISTRYINDEX, db->update_hook_cb); + luaL_unref(L, LUA_REGISTRYINDEX, db->update_hook_udata); if (lua_gettop(L) < 2 || lua_isnil(L, 2)) { - luaL_unref(L, LUA_REGISTRYINDEX, db->update_hook_cb); - luaL_unref(L, LUA_REGISTRYINDEX, db->update_hook_udata); db->update_hook_cb = db->update_hook_udata = LUA_NOREF; /* clear update_hook handler */ @@ -1357,13 +1468,10 @@ luaL_checktype(L, 2, LUA_TFUNCTION); /* make sure we have an userdata field (even if nil) */ lua_settop(L, 3); - luaL_unref(L, LUA_REGISTRYINDEX, db->update_hook_cb); - luaL_unref(L, LUA_REGISTRYINDEX, db->update_hook_udata); - db->update_hook_udata = luaL_ref(L, LUA_REGISTRYINDEX); db->update_hook_cb = luaL_ref(L, LUA_REGISTRYINDEX); /* set update_hook handler */ sqlite3_update_hook(db->db, db_update_hook_callback, db); @@ -1400,13 +1508,13 @@ } static int db_commit_hook(lua_State *L) { sdb *db = lsqlite_checkdb(L, 1); + luaL_unref(L, LUA_REGISTRYINDEX, db->commit_hook_cb); + luaL_unref(L, LUA_REGISTRYINDEX, db->commit_hook_udata); if (lua_gettop(L) < 2 || lua_isnil(L, 2)) { - luaL_unref(L, LUA_REGISTRYINDEX, db->commit_hook_cb); - luaL_unref(L, LUA_REGISTRYINDEX, db->commit_hook_udata); db->commit_hook_cb = db->commit_hook_udata = LUA_NOREF; /* clear commit_hook handler */ @@ -1416,13 +1524,10 @@ luaL_checktype(L, 2, LUA_TFUNCTION); /* make sure we have an userdata field (even if nil) */ lua_settop(L, 3); - luaL_unref(L, LUA_REGISTRYINDEX, db->commit_hook_cb); - luaL_unref(L, LUA_REGISTRYINDEX, db->commit_hook_udata); - db->commit_hook_udata = luaL_ref(L, LUA_REGISTRYINDEX); db->commit_hook_cb = luaL_ref(L, LUA_REGISTRYINDEX); /* set commit_hook handler */ sqlite3_commit_hook(db->db, db_commit_hook_callback, db); @@ -1455,13 +1560,13 @@ } static int db_rollback_hook(lua_State *L) { sdb *db = lsqlite_checkdb(L, 1); + luaL_unref(L, LUA_REGISTRYINDEX, db->rollback_hook_cb); + luaL_unref(L, LUA_REGISTRYINDEX, db->rollback_hook_udata); if (lua_gettop(L) < 2 || lua_isnil(L, 2)) { - luaL_unref(L, LUA_REGISTRYINDEX, db->rollback_hook_cb); - luaL_unref(L, LUA_REGISTRYINDEX, db->rollback_hook_udata); db->rollback_hook_cb = db->rollback_hook_udata = LUA_NOREF; /* clear rollback_hook handler */ @@ -1471,13 +1576,10 @@ luaL_checktype(L, 2, LUA_TFUNCTION); /* make sure we have an userdata field (even if nil) */ lua_settop(L, 3); - luaL_unref(L, LUA_REGISTRYINDEX, db->rollback_hook_cb); - luaL_unref(L, LUA_REGISTRYINDEX, db->rollback_hook_udata); - db->rollback_hook_udata = luaL_ref(L, LUA_REGISTRYINDEX); db->rollback_hook_cb = luaL_ref(L, LUA_REGISTRYINDEX); /* set rollback_hook handler */ sqlite3_rollback_hook(db->db, db_rollback_hook_callback, db); @@ -1701,13 +1803,13 @@ } static int db_busy_handler(lua_State *L) { sdb *db = lsqlite_checkdb(L, 1); + luaL_unref(L, LUA_REGISTRYINDEX, db->busy_cb); + luaL_unref(L, LUA_REGISTRYINDEX, db->busy_udata); if (lua_gettop(L) < 2 || lua_isnil(L, 2)) { - luaL_unref(L, LUA_REGISTRYINDEX, db->busy_cb); - luaL_unref(L, LUA_REGISTRYINDEX, db->busy_udata); db->busy_cb = db->busy_udata = LUA_NOREF; /* clear busy handler */ @@ -1716,13 +1818,10 @@ else { luaL_checktype(L, 2, LUA_TFUNCTION); /* make sure we have an userdata field (even if nil) */ lua_settop(L, 3); - luaL_unref(L, LUA_REGISTRYINDEX, db->busy_cb); - luaL_unref(L, LUA_REGISTRYINDEX, db->busy_udata); - db->busy_udata = luaL_ref(L, LUA_REGISTRYINDEX); db->busy_cb = luaL_ref(L, LUA_REGISTRYINDEX); /* set busy handler */ sqlite3_busy_handler(db->db, db_busy_callback, db); @@ -2010,11 +2109,11 @@ static int db_urows(lua_State *L) { return db_do_rows(L, db_next_row); } static int db_tostring(lua_State *L) { - char buff[32]; + char buff[33]; sdb *db = lsqlite_getdb(L, 1); if (db->db == NULL) strcpy(buff, "closed"); else sprintf(buff, "%p", lua_touserdata(L, 1)); @@ -2028,31 +2127,11 @@ return 1; } static int db_close_vm(lua_State *L) { sdb *db = lsqlite_checkdb(L, 1); - /* cleanup temporary only tables? */ - int temp = lua_toboolean(L, 2); - - /* free associated virtual machines */ - lua_pushlightuserdata(L, db); - lua_rawget(L, LUA_REGISTRYINDEX); - - /* close all used handles */ - lua_pushnil(L); - while (lua_next(L, -2)) { - sdb_vm *svm = lua_touserdata(L, -2); /* key: vm; val: sql text */ - - if ((!temp || svm->temp) && svm->vm) - { - sqlite3_finalize(svm->vm); - svm->vm = NULL; - } - - /* leave key in the stack */ - lua_pop(L, 1); - } + closevms(L, db, lua_toboolean(L, 2)); return 0; } /* From: Wolfgang Oertl When using lsqlite3 in a multithreaded environment, each thread has a separate Lua @@ -2069,10 +2148,637 @@ sdb *db = lsqlite_getdb(L, 1); if (db->db != NULL) /* ignore closed databases */ cleanupdb(L, db); return 0; } + +#ifdef SQLITE_ENABLE_DESERIALIZE + +static int db_serialize(lua_State *L) { + sdb *db = lsqlite_checkdb(L, 1); + sqlite_int64 size = 0; + + char *buffer = (char *)sqlite3_serialize(db->db, "main", &size, 0); + if (buffer == NULL) + return pusherrstr(L, "failed to serialize"); + + lua_pushlstring(L, buffer, size); + free(buffer); + return 1; +} + +static int db_deserialize(lua_State *L) { + sdb *db = lsqlite_checkdb(L, 1); + size_t size = 0; + + const char *buffer = luaL_checklstring(L, 2, &size); + if (buffer == NULL || size == 0) + return pusherrstr(L, "failed to deserialize"); + + unsigned char *sqlbuf = memcpy(sqlite3_malloc(size), buffer, size); + sqlite3_deserialize(db->db, "main", sqlbuf, size, size, + SQLITE_DESERIALIZE_FREEONCLOSE + SQLITE_DESERIALIZE_RESIZEABLE); + lua_pushinteger(L, SQLITE_OK); + return 1; +} + +#endif + +#ifdef SQLITE_ENABLE_SESSION + +/* +** ======================================================= +** Iterator functions (for session support) +** ======================================================= +*/ + +typedef struct { + sqlite3_changeset_iter *itr; + bool collectable; +} liter; + +static liter *lsqlite_makeiter(lua_State *L, sqlite3_changeset_iter *piter, bool collectable) { + liter *litr = (liter*)lua_newuserdata(L, sizeof(liter)); + lua_rawgeti(L, LUA_REGISTRYINDEX, sqlite_itr_meta_ref); + lua_setmetatable(L, -2); + litr->itr = piter; + litr->collectable = collectable; + return litr; +} + +static liter *lsqlite_getiter(lua_State *L, int index) { + return (liter *)luaL_checkudata(L, index, sqlite_itr_meta); +} + +static liter *lsqlite_checkiter(lua_State *L, int index) { + liter *litr = lsqlite_getiter(L, index); + if (litr->itr == NULL) luaL_argerror(L, index, "invalid sqlite iterator"); + return litr; +} + +static int liter_finalize(lua_State *L) { + liter *litr = lsqlite_getiter(L, 1); + int rc; + if (litr->itr) { + if (!litr->collectable) { + return pusherr(L, SQLITE_CORRUPT); + } + if ((rc = sqlite3changeset_finalize(litr->itr)) != SQLITE_OK) { + return pusherr(L, rc); + } + litr->itr = NULL; + } + lua_pushboolean(L, 1); + return 1; +} + +static int liter_gc(lua_State *L) { + return liter_finalize(L); +} + +static int liter_tostring(lua_State *L) { + char buff[33]; + liter *litr = lsqlite_getiter(L, 1); + if (litr->itr == NULL) + strcpy(buff, "closed"); + else + sprintf(buff, "%p", litr->itr); + lua_pushfstring(L, "sqlite iterator (%s)", buff); + return 1; +} + +static int liter_table( + lua_State *L, + int (*iter_func)(sqlite3_changeset_iter *pIter, int val, sqlite3_value **ppValue) +) { + const char *zTab; + int n, rc, nCol, Op, bIndirect; + sqlite3_value *pVal; + liter *litr = lsqlite_checkiter(L, 1); + sqlite3changeset_op(litr->itr, &zTab, &nCol, &Op, &bIndirect); + lua_createtable(L, nCol, 0); + for (n = 0; n < nCol; n++) { + if ((rc = (*iter_func)(litr->itr, n, &pVal)) != LUA_OK) { + return pusherr(L, rc); + } + if (pVal) { + db_push_value(L, pVal); + } else { + // push `false` to indicate that the value wasn't changed + // and not included in the record and to keep table valid + lua_pushboolean(L, 0); + } + lua_rawseti(L, -2, n+1); + } + return 1; +} + +static int liter_new(lua_State *L) { + return liter_table(L, sqlite3changeset_new); +} + +static int liter_old(lua_State *L) { + return liter_table(L, sqlite3changeset_old); +} + +static int liter_conflict(lua_State *L) { + return liter_table(L, sqlite3changeset_conflict); +} + +static int liter_op(lua_State *L) { + const char *zTab; + int rc, nCol, Op, bIndirect; + liter *litr = lsqlite_checkiter(L, 1); + + if ((rc = sqlite3changeset_op(litr->itr, &zTab, &nCol, &Op, &bIndirect)) != LUA_OK) { + return pusherr(L, rc); + } + lua_pushstring(L, zTab); + lua_pushinteger(L, Op); + lua_pushboolean(L, bIndirect); + return 3; +} + +static int liter_fk_conflicts(lua_State *L) { + int rc, nOut; + liter *litr = lsqlite_checkiter(L, 1); + if ((rc = sqlite3changeset_fk_conflicts(litr->itr, &nOut)) != LUA_OK) { + return pusherr(L, rc); + } + lua_pushinteger(L, nOut); + return 1; +} + +static int liter_next(lua_State *L) { + liter *litr = lsqlite_checkiter(L, 1); + if (!litr->collectable) { + return pusherr(L, SQLITE_CORRUPT); + } + lua_pushinteger(L, sqlite3changeset_next(litr->itr)); + return 1; +} + +static int liter_pk(lua_State *L) { + const char *zTab; + int n, rc, nCol; + unsigned char *abPK; + liter *litr = lsqlite_checkiter(L, 1); + if ((rc = sqlite3changeset_pk(litr->itr, &abPK, &nCol)) != LUA_OK) { + return pusherr(L, rc); + } + lua_createtable(L, nCol, 0); + for (n = 0; n < nCol; n++) { + lua_pushboolean(L, abPK[n]); + lua_rawseti(L, -2, n+1); + } + return 1; +} + +/* +** ======================================================= +** Rebaser functions (for session support) +** ======================================================= +*/ + +typedef struct { + sqlite3_rebaser *reb; +} lrebaser; + +static lrebaser *lsqlite_makerebaser(lua_State *L, sqlite3_rebaser *reb) { + lrebaser *lreb = (lrebaser*)lua_newuserdata(L, sizeof(lrebaser)); + lua_rawgeti(L, LUA_REGISTRYINDEX, sqlite_reb_meta_ref); + lua_setmetatable(L, -2); + lreb->reb = reb; + return lreb; +} + +static lrebaser *lsqlite_getrebaser(lua_State *L, int index) { + return (lrebaser *)luaL_checkudata(L, index, sqlite_reb_meta); +} + +static lrebaser *lsqlite_checkrebaser(lua_State *L, int index) { + lrebaser *lreb = lsqlite_getrebaser(L, index); + if (lreb->reb == NULL) luaL_argerror(L, index, "invalid sqlite rebaser"); + return lreb; +} + +static int lrebaser_delete(lua_State *L) { + lrebaser *lreb = lsqlite_getrebaser(L, 1); + if (lreb->reb != NULL) { + sqlite3rebaser_delete(lreb->reb); + lreb->reb = NULL; + } + return 0; +} + +static int lrebaser_gc(lua_State *L) { + return lrebaser_delete(L); +} + +static int lrebaser_tostring(lua_State *L) { + char buff[32]; + lrebaser *lreb = lsqlite_getrebaser(L, 1); + if (lreb->reb == NULL) + strcpy(buff, "closed"); + else + sprintf(buff, "%p", lreb->reb); + lua_pushfstring(L, "sqlite rebaser (%s)", buff); + return 1; +} + +static int lrebaser_rebase(lua_State *L) { + lrebaser *lreb = lsqlite_checkrebaser(L, 1); + const char *cset = luaL_checkstring(L, 2); + int nset = lua_strlen(L, 2); + int rc; + int size; + void *buf; + + if ((rc = sqlite3rebaser_rebase(lreb->reb, nset, cset, &size, &buf)) != SQLITE_OK) { + return pusherr(L, rc); + } + lua_pushlstring(L, buf, size); + sqlite3_free(buf); + return 1; +} + +static int db_create_rebaser(lua_State *L) { + sqlite3_rebaser *reb; + int rc; + + if ((rc = sqlite3rebaser_create(&reb)) != SQLITE_OK) { + return pusherr(L, rc); + } + (void)lsqlite_makerebaser(L, reb); + return 1; +} + +/* session/changeset callbacks */ + +static int changeset_conflict_cb = LUA_NOREF; +static int changeset_filter_cb = LUA_NOREF; +static int changeset_cb_udata = LUA_NOREF; +static int session_filter_cb = LUA_NOREF; +static int session_cb_udata = LUA_NOREF; + +static int db_changeset_conflict_callback( + void *user, /* Copy of sixth arg to _apply_v2() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ +) { + // return default code if no callback is provided + if (changeset_conflict_cb == LUA_NOREF) return SQLITE_CHANGESET_OMIT; + sdb *db = (sdb*)user; + lua_State *L = db->L; + int top = lua_gettop(L); + int result, isint; + const char *zTab; + int nCol, Op, bIndirect; + + if (sqlite3changeset_op(p, &zTab, &nCol, &Op, &bIndirect) != LUA_OK) { + lua_pushliteral(L, "invalid return from changeset iterator"); + return lua_error(L); + } + + lua_rawgeti(L, LUA_REGISTRYINDEX, changeset_conflict_cb); /* get callback */ + lua_rawgeti(L, LUA_REGISTRYINDEX, changeset_cb_udata); /* get callback user data */ + lua_pushinteger(L, eConflict); + (void)lsqlite_makeiter(L, p, 0); + lua_pushstring(L, zTab); + lua_pushinteger(L, Op); + lua_pushboolean(L, bIndirect); + + if (lua_pcall(L, 6, 1, 0) != LUA_OK) return lua_error(L); + + result = lua_tointegerx(L, -1, &isint); /* use result if there was no error */ + if (!isint) { + lua_pushliteral(L, "non-integer returned from conflict callback"); + return lua_error(L); + } + + lua_settop(L, top); + return result; +} + +static int db_filter_callback( + void *user, /* Context */ + const char *zTab, /* Table name */ + int filter_cb, + int filter_udata +) { + // allow the table if no filter callback is provided + if (filter_cb == LUA_NOREF || filter_cb == LUA_REFNIL) return 1; + sdb *db = (sdb*)user; + lua_State *L = db->L; + int top = lua_gettop(L); + int result, isint; + + lua_rawgeti(L, LUA_REGISTRYINDEX, filter_cb); /* get callback */ + lua_pushstring(L, zTab); + lua_rawgeti(L, LUA_REGISTRYINDEX, filter_udata); /* get callback user data */ + + if (lua_pcall(L, 2, 1, 0) != LUA_OK) return lua_error(L); + + // allow 0/1 and false/true to be returned + // returning 0/false skips the table + result = lua_tointegerx(L, -1, &isint); /* use result if there was no error */ + if (!isint && !lua_isboolean(L, -1)) { + lua_pushliteral(L, "non-integer and non-boolean returned from filter callback"); + return lua_error(L); + } + if (!isint) result = lua_toboolean(L, -1); + + lua_settop(L, top); + return result; +} + +static int db_changeset_filter_callback( + void *user, /* Copy of sixth arg to _apply_v2() */ + const char *zTab /* Table name */ +) { + return db_filter_callback(user, zTab, changeset_filter_cb, changeset_cb_udata); +} + +static int db_session_filter_callback( + void *user, /* Copy of third arg to session_attach() */ + const char *zTab /* Table name */ +) { + return db_filter_callback(user, zTab, session_filter_cb, session_cb_udata); +} + +/* +** ======================================================= +** Session functions +** ======================================================= +*/ + +typedef struct { + sqlite3_session *ses; + sdb *db; // keep track of the DB this session is for +} lsession; + +static lsession *lsqlite_makesession(lua_State *L, sqlite3_session *ses, sdb *db) { + lsession *lses = (lsession*)lua_newuserdata(L, sizeof(lsession)); + lua_rawgeti(L, LUA_REGISTRYINDEX, sqlite_ses_meta_ref); + lua_setmetatable(L, -2); + lses->ses = ses; + lses->db = db; + return lses; +} + +static lsession *lsqlite_getsession(lua_State *L, int index) { + return (lsession *)luaL_checkudata(L, index, sqlite_ses_meta); +} + +static lsession *lsqlite_checksession(lua_State *L, int index) { + lsession *lses = lsqlite_getsession(L, index); + if (lses->ses == NULL) luaL_argerror(L, index, "invalid sqlite session"); + return lses; +} + +static int lsession_attach(lua_State *L) { + lsession *lses = lsqlite_checksession(L, 1); + int rc; + // allow either a table or a callback function to filter tables + const char *zTab = lua_type(L, 2) == LUA_TFUNCTION + ? NULL + : luaL_optstring(L, 2, NULL); + if ((rc = sqlite3session_attach(lses->ses, zTab)) != SQLITE_OK) { + return pusherr(L, rc); + } + // allow to pass a filter callback, + // but only one shared for all sessions where this callback is used + if (lua_type(L, 2) == LUA_TFUNCTION) { + // TBD: does this *also* need to be done in cleanupvm? + luaL_unref(L, LUA_REGISTRYINDEX, session_filter_cb); + luaL_unref(L, LUA_REGISTRYINDEX, session_cb_udata); + lua_settop(L, 3); // add udata even if it's not provided + session_cb_udata = luaL_ref(L, LUA_REGISTRYINDEX); + session_filter_cb = luaL_ref(L, LUA_REGISTRYINDEX); + sqlite3session_table_filter(lses->ses, + db_session_filter_callback, lses->db); + } + lua_pushboolean(L, 1); + return 1; +} + +static int lsession_isempty(lua_State *L) { + lsession *lses = lsqlite_checksession(L, 1); + lua_pushboolean(L, sqlite3session_isempty(lses->ses)); + return 1; +} + +static int lsession_diff(lua_State *L) { + lsession *lses = lsqlite_checksession(L, 1); + const char *zFromDb = luaL_checkstring(L, 2); + const char *zTbl = luaL_checkstring(L, 3); + int rc = sqlite3session_diff(lses->ses, zFromDb, zTbl, NULL); + if (rc != SQLITE_OK) return pusherr(L, rc); + lua_pushboolean(L, 1); + return 1; +} + +static int lsession_bool( + lua_State *L, + int (*session_func)(sqlite3_session *ses, int val) +) { + lsession *lses = lsqlite_checksession(L, 1); + int val = lua_isboolean(L, 2) + ? lua_toboolean(L, 2) + : luaL_optinteger(L, 2, -1); + lua_pushboolean(L, (*session_func)(lses->ses, val)); + return 1; +} + +static int lsession_indirect(lua_State *L) { + return lsession_bool(L, sqlite3session_indirect); +} + +static int lsession_enable(lua_State *L) { + return lsession_bool(L, sqlite3session_enable); +} + +static int lsession_getset( + lua_State *L, + int (*session_setfunc)(sqlite3_session *ses, int *size, void **buf) +) { + lsession *lses = lsqlite_checksession(L, 1); + int rc; + int size; + void *buf; + + if ((rc = (*session_setfunc)(lses->ses, &size, &buf)) != SQLITE_OK) { + return pusherr(L, rc); + } + lua_pushlstring(L, buf, size); + sqlite3_free(buf); + return 1; +} + +static int lsession_changeset(lua_State *L) { + return lsession_getset(L, sqlite3session_changeset); +} + +static int lsession_patchset(lua_State *L) { + return lsession_getset(L, sqlite3session_patchset); +} + +static int lsession_delete(lua_State *L) { + lsession *lses = lsqlite_getsession(L, 1); + if (lses->ses != NULL) { + sqlite3session_delete(lses->ses); + lses->ses = NULL; + } + return 0; +} + +static int lsession_tostring(lua_State *L) { + char buff[32]; + lsession *lses = lsqlite_getsession(L, 1); + if (lses->ses == NULL) + strcpy(buff, "closed"); + else + sprintf(buff, "%p", lses->ses); + lua_pushfstring(L, "sqlite session (%s)", buff); + return 1; +} + +static int db_create_session(lua_State *L) { + sdb *db = lsqlite_checkdb(L, 1); + const char *zDb = luaL_optstring(L, 2, "main"); + sqlite3_session *ses; + + if (sqlite3session_create(db->db, zDb, &ses) != SQLITE_OK) { + return pusherr(L, sqlite3_errcode(db->db)); + } + (void)lsqlite_makesession(L, ses, db); + return 1; +} + +static int db_iterate_changeset(lua_State *L) { + sqlite3_changeset_iter *p; + sdb *db = lsqlite_checkdb(L, 1); + const char *cset = luaL_checkstring(L, 2); + int nset = lua_strlen(L, 2); + int flags = luaL_optinteger(L, 3, 0); + int rc; + + if ((rc = sqlite3changeset_start_v2(&p, nset, cset, flags)) != SQLITE_OK) { + return pusherr(L, rc); + } + (void)lsqlite_makeiter(L, p, 1); + return 1; +} + +static int db_invert_changeset(lua_State *L) { + sdb *db = lsqlite_checkdb(L, 1); + const char *cset = luaL_checkstring(L, 2); + int nset = lua_strlen(L, 2); + int rc; + int size; + void *buf; + + if ((rc = sqlite3changeset_invert(nset, cset, &size, &buf)) != SQLITE_OK) { + return pusherr(L, rc); + } + lua_pushlstring(L, buf, size); + sqlite3_free(buf); + return 1; +} + +static int db_concat_changeset(lua_State *L) { + sdb *db = lsqlite_checkdb(L, 1); + int size, nset; + void *buf, *cset; + sqlite3_changegroup *pGrp; + + luaL_checktype(L, 2, LUA_TTABLE); + int n = luaL_len(L, 2); + int rc = sqlite3changegroup_new(&pGrp); + for (int i = 1; rc == SQLITE_OK && i <= n; i++) { + lua_rawgeti(L, 2, i); + cset = (void*)lua_tostring(L, -1); + nset = lua_strlen(L, -1); + rc = sqlite3changegroup_add(pGrp, nset, cset); + lua_pop(L, 1); // pop the string + } + if (rc == SQLITE_OK) rc = sqlite3changegroup_output(pGrp, &size, &buf); + sqlite3changegroup_delete(pGrp); + + if (rc != SQLITE_OK) return pusherr(L, rc); + lua_pushlstring(L, buf, size); + sqlite3_free(buf); + return 1; +} + +static int db_apply_changeset(lua_State *L) { + sdb *db = lsqlite_checkdb(L, 1); + const char *cset = luaL_checkstring(L, 2); + int nset = lua_strlen(L, 2); + int top = lua_gettop(L); + int rc; + int flags = 0; + void *pRebase; + int nRebase; + lrebaser *lreb = NULL; + + // parameters: db, changeset[[, filter cb], conflict cb[, udata[, rebaser[, flags]]]] + + // TBD: does this *also* need to be done in cleanupvm? + if (changeset_cb_udata != LUA_NOREF) { + luaL_unref(L, LUA_REGISTRYINDEX, changeset_conflict_cb); + luaL_unref(L, LUA_REGISTRYINDEX, changeset_filter_cb); + luaL_unref(L, LUA_REGISTRYINDEX, changeset_cb_udata); + + changeset_conflict_cb = + changeset_filter_cb = + changeset_cb_udata = LUA_NOREF; + } + + // check for conflict/filter callback type if provided + if (top >= 3) { + luaL_checktype(L, 3, LUA_TFUNCTION); + // if no filter callback, insert a dummy one to simplify stack handling + if (lua_type(L, 4) != LUA_TFUNCTION) { + lua_pushnil(L); + lua_insert(L, 3); + top = lua_gettop(L); + } + } + if (top >= 6) lreb = lsqlite_checkrebaser(L, 6); + if (top >= 7) flags = luaL_checkinteger(L, 7); + if (top >= 4) { // two callback are guaranteed to be on the stack in this case + // shorten stack or extend to set udata to `nil` if not provided + lua_settop(L, 5); + changeset_cb_udata = luaL_ref(L, LUA_REGISTRYINDEX); + changeset_conflict_cb = luaL_ref(L, LUA_REGISTRYINDEX); + changeset_filter_cb = luaL_ref(L, LUA_REGISTRYINDEX); + } + + rc = sqlite3changeset_apply_v2(db->db, nset, (void*)cset, + db_changeset_filter_callback, + db_changeset_conflict_callback, + db, // context + lreb ? &pRebase : 0, + lreb ? &nRebase : 0, + flags); + + if (rc != SQLITE_OK) return pusherr(L, sqlite3_errcode(db->db)); + + if (lreb) { // if rebaser is present + rc = sqlite3rebaser_configure(lreb->reb, nRebase, pRebase); + if (rc == SQLITE_OK) lua_pushstring(L, pRebase); + sqlite3_free(pRebase); + if (rc == SQLITE_OK) return 1; + return pusherr(L, rc); + } + + lua_pushboolean(L, 1); + return 1; +} + +#endif /* ** ======================================================= ** General library functions ** ======================================================= @@ -2109,10 +2815,11 @@ return 1; } #endif static int lsqlite_do_open(lua_State *L, const char *filename, int flags) { + sqlite3_initialize(); /* initialize the engine if hasn't been done yet */ sdb *db = newdb(L); /* create and leave in stack */ if (SQLITE3_OPEN(filename, &db->db, flags) == SQLITE_OK) { /* database handle already in the stack - return it */ return 1; @@ -2161,10 +2868,72 @@ db = newdb(L); /* create and leave in stack */ db->db = db_ptr; return 1; } + +/* +** Log callback: +** Params: user, result code, log message +** Returns: none +*/ +static void log_callback(void* user, int rc, const char *msg) { + if (log_cb != LUA_NOREF) { + lua_State *L = (lua_State*)user; + + /* setup lua callback call */ + int top = lua_gettop(L); + lua_rawgeti(L, LUA_REGISTRYINDEX, log_cb); /* get callback */ + lua_rawgeti(L, LUA_REGISTRYINDEX, log_udata); /* get callback user data */ + lua_pushinteger(L, rc); + lua_pushstring(L, msg); + + if (lua_pcall(L, 3, 0, 0) != LUA_OK) lua_error(L); + + lua_settop(L, top); + } +} + +static int lsqlite_config(lua_State *L) { + int rc = SQLITE_MISUSE; + int option = luaL_checkint(L, 1); + + switch (option) { + case SQLITE_CONFIG_SINGLETHREAD: + case SQLITE_CONFIG_MULTITHREAD: + case SQLITE_CONFIG_SERIALIZED: + if ((rc = sqlite3_config(option)) == SQLITE_OK) { + lua_pushinteger(L, rc); + return 1; + } + break; + case SQLITE_CONFIG_LOG: + /* make sure we have an userdata field (even if nil) */ + lua_settop(L, 3); + + // prepate to return current (possibly nil) values + lua_pushinteger(L, SQLITE_OK); + lua_rawgeti(L, LUA_REGISTRYINDEX, log_cb); /* get callback */ + lua_rawgeti(L, LUA_REGISTRYINDEX, log_udata); /* get callback user data */ + + luaL_unref(L, LUA_REGISTRYINDEX, log_cb); + luaL_unref(L, LUA_REGISTRYINDEX, log_udata); + if (lua_isnil(L, 2)) { + log_cb = + log_udata = LUA_NOREF; + } + else { + luaL_checktype(L, 2, LUA_TFUNCTION); + lua_pushvalue(L, 2); + lua_pushvalue(L, 3); + log_udata = luaL_ref(L, LUA_REGISTRYINDEX); + log_cb = luaL_ref(L, LUA_REGISTRYINDEX); + } + return 3; // return OK and previous callback and userdata + } + return pusherr(L, rc); +} static int lsqlite_newindex(lua_State *L) { lua_pushliteral(L, "attempt to change readonly table"); lua_error(L); return 0; @@ -2253,11 +3022,36 @@ SC(OPEN_MEMORY) SC(OPEN_NOMUTEX) SC(OPEN_FULLMUTEX) SC(OPEN_SHAREDCACHE) SC(OPEN_PRIVATECACHE) - + + /* config flags */ + SC(CONFIG_SINGLETHREAD) + SC(CONFIG_MULTITHREAD) + SC(CONFIG_SERIALIZED) + SC(CONFIG_LOG) + + /* checkpoint flags */ + SC(CHECKPOINT_PASSIVE) + SC(CHECKPOINT_FULL) + SC(CHECKPOINT_RESTART) + SC(CHECKPOINT_TRUNCATE) + +#ifdef SQLITE_ENABLE_SESSION + /* session constants */ + SC(CHANGESETAPPLY_NOSAVEPOINT) + SC(CHANGESET_DATA) + SC(CHANGESET_NOTFOUND) + SC(CHANGESET_CONFLICT) + SC(CHANGESET_CONSTRAINT) + SC(CHANGESET_FOREIGN_KEY) + SC(CHANGESET_OMIT) + SC(CHANGESET_REPLACE) + SC(CHANGESET_ABORT) +#endif + /* terminator */ { NULL, 0 } }; /* ======================================================= */ @@ -2271,10 +3065,11 @@ {"error_code", db_errcode }, {"errmsg", db_errmsg }, {"error_message", db_errmsg }, {"interrupt", db_interrupt }, {"db_filename", db_db_filename }, + {"wal_checkpoint", db_wal_checkpoint }, {"create_function", db_create_function }, {"create_aggregate", db_create_aggregate }, {"create_collation", db_create_collation }, {"load_extension", db_load_extension }, @@ -2281,10 +3076,11 @@ {"trace", db_trace }, {"progress_handler", db_progress_handler }, {"busy_timeout", db_busy_timeout }, {"busy_handler", db_busy_handler }, + {"wal_hook", db_wal_hook }, #if !defined(LSQLITE_OMIT_UPDATE_HOOK) || !LSQLITE_OMIT_UPDATE_HOOK {"update_hook", db_update_hook }, {"commit_hook", db_commit_hook }, {"rollback_hook", db_rollback_hook }, #endif @@ -2297,10 +3093,24 @@ {"exec", db_exec }, {"execute", db_exec }, {"close", db_close }, {"close_vm", db_close_vm }, {"get_ptr", db_get_ptr }, + +#ifdef SQLITE_ENABLE_SESSION + {"create_session", db_create_session }, + {"create_rebaser", db_create_rebaser }, + {"apply_changeset", db_apply_changeset }, + {"invert_changeset", db_invert_changeset }, + {"concat_changeset", db_concat_changeset }, + {"iterate_changeset", db_iterate_changeset }, +#endif + +#ifdef SQLITE_ENABLE_DESERIALIZE + {"serialize", db_serialize }, + {"deserialize", db_deserialize }, +#endif {"__tostring", db_tostring }, {"__gc", db_gc }, {NULL, NULL} @@ -2383,15 +3193,58 @@ /* {"__tostring", dbbu_tostring }, */ {"__gc", dbbu_gc }, {NULL, NULL} }; + +#ifdef SQLITE_ENABLE_SESSION + +static const luaL_Reg seslib[] = { + {"attach", lsession_attach }, + {"changeset", lsession_changeset }, + {"patchset", lsession_patchset }, + {"isempty", lsession_isempty }, + {"indirect", lsession_indirect }, + {"enable", lsession_enable }, + {"diff", lsession_diff }, + {"delete", lsession_delete }, + + {"__tostring", lsession_tostring }, + {NULL, NULL} +}; + +static const luaL_Reg reblib[] = { + {"rebase", lrebaser_rebase }, + {"delete", lrebaser_delete }, + + {"__tostring", lrebaser_tostring }, + {"__gc", lrebaser_gc }, + {NULL, NULL} +}; + +static const luaL_Reg itrlib[] = { + {"op", liter_op }, + {"pk", liter_pk }, + {"new", liter_new }, + {"old", liter_old }, + {"next", liter_next }, + {"conflict", liter_conflict }, + {"finalize", liter_finalize }, + {"fk_conflicts", liter_fk_conflicts }, + + {"__tostring", liter_tostring }, + {"__gc", liter_gc }, + {NULL, NULL} +}; + +#endif static const luaL_Reg sqlitelib[] = { {"lversion", lsqlite_lversion }, {"version", lsqlite_version }, {"complete", lsqlite_complete }, + {"config", lsqlite_config }, #ifndef _WIN32 {"temp_directory", lsqlite_temp_directory }, #endif {"open", lsqlite_open }, {"open_memory", lsqlite_open_memory }, @@ -2415,17 +3268,35 @@ /* remove metatable from stack */ lua_pop(L, 1); } LUALIB_API int luaopen_lsqlite3(lua_State *L) { + /* call config before calling initialize */ + sqlite3_config(SQLITE_CONFIG_LOG, log_callback, L); + create_meta(L, sqlite_meta, dblib); create_meta(L, sqlite_vm_meta, vmlib); create_meta(L, sqlite_bu_meta, dbbulib); create_meta(L, sqlite_ctx_meta, ctxlib); luaL_getmetatable(L, sqlite_ctx_meta); sqlite_ctx_meta_ref = luaL_ref(L, LUA_REGISTRYINDEX); + +#ifdef SQLITE_ENABLE_SESSION + create_meta(L, sqlite_ses_meta, seslib); + create_meta(L, sqlite_reb_meta, reblib); + create_meta(L, sqlite_itr_meta, itrlib); + + luaL_getmetatable(L, sqlite_ses_meta); + sqlite_ses_meta_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + luaL_getmetatable(L, sqlite_reb_meta); + sqlite_reb_meta_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + luaL_getmetatable(L, sqlite_itr_meta); + sqlite_itr_meta_ref = luaL_ref(L, LUA_REGISTRYINDEX); +#endif /* register (local) sqlite metatable */ luaL_register(L, "sqlite3", sqlitelib); { Index: lsqlite3complete-0.9.5-1.rockspec ================================================================== --- lsqlite3complete-0.9.5-1.rockspec +++ lsqlite3complete-0.9.5-1.rockspec @@ -22,11 +22,16 @@ build = { type = "builtin", modules = { lsqlite3complete = { sources = { "lsqlite3.c", "sqlite3.c" }, - defines = {'LSQLITE_VERSION="0.9.5"', 'luaopen_lsqlite3=luaopen_lsqlite3complete'} + defines = { + 'LSQLITE_VERSION="0.9.5"', + 'luaopen_lsqlite3=luaopen_lsqlite3complete', + 'SQLITE_ENABLE_SESSION', + 'SQLITE_ENABLE_PREUPDATE_HOOK' + } }, }, platforms = { unix = { modules = {