mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-02-25 07:19:02 +00:00
Add redbean sqlite session support (#680)
This commit is contained in:
parent
b407327972
commit
251dcb07eb
3 changed files with 714 additions and 5 deletions
7
third_party/sqlite3/sqlite3.mk
vendored
7
third_party/sqlite3/sqlite3.mk
vendored
|
@ -126,7 +126,9 @@ THIRD_PARTY_SQLITE3_FLAGS = \
|
|||
-DSQLITE_HAVE_C99_MATH_FUNCS \
|
||||
-DSQLITE_ENABLE_MATH_FUNCTIONS \
|
||||
-DSQLITE_ENABLE_JSON1 \
|
||||
-DSQLITE_ENABLE_DESERIALIZE
|
||||
-DSQLITE_ENABLE_DESERIALIZE \
|
||||
-DSQLITE_ENABLE_PREUPDATE_HOOK \
|
||||
-DSQLITE_ENABLE_SESSION
|
||||
|
||||
ifeq ($(MODE),dbg)
|
||||
THIRD_PARTY_SQLITE3_CPPFLAGS_DEBUG = -DSQLITE_DEBUG
|
||||
|
@ -136,7 +138,6 @@ $(THIRD_PARTY_SQLITE3_A_OBJS): private \
|
|||
OVERRIDE_CFLAGS += \
|
||||
$(THIRD_PARTY_SQLITE3_FLAGS) \
|
||||
$(THIRD_PARTY_SQLITE3_CPPFLAGS_DEBUG) \
|
||||
-DSQLITE_OMIT_UPDATE_HOOK
|
||||
|
||||
$(THIRD_PARTY_SQLITE3_SHELL_OBJS): private \
|
||||
OVERRIDE_CFLAGS += \
|
||||
|
@ -146,11 +147,9 @@ $(THIRD_PARTY_SQLITE3_SHELL_OBJS): private \
|
|||
-DHAVE_EDITLINE=0 \
|
||||
-DSQLITE_HAVE_ZLIB \
|
||||
-DSQLITE_ENABLE_IOTRACE \
|
||||
-DSQLITE_ENABLE_PREUPDATE_HOOK \
|
||||
-DSQLITE_ENABLE_COLUMN_METADATA \
|
||||
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
|
||||
-DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION \
|
||||
-DSQLITE_ENABLE_SESSION \
|
||||
-DSQLITE_ENABLE_STMTVTAB \
|
||||
-DSQLITE_ENABLE_DBPAGE_VTAB \
|
||||
-DSQLITE_ENABLE_DBSTAT_VTAB \
|
||||
|
|
|
@ -105,6 +105,9 @@ struct sdb {
|
|||
int busy_cb; /* busy callback */
|
||||
int busy_udata;
|
||||
|
||||
int wal_hook_cb; /* wal_hook callback */
|
||||
int wal_hook_udata;
|
||||
|
||||
int update_hook_cb; /* update_hook callback */
|
||||
int update_hook_udata;
|
||||
|
||||
|
@ -120,6 +123,14 @@ static const char *const sqlite_vm_meta = ":sqlite3:vm";
|
|||
static const char *const sqlite_bu_meta = ":sqlite3:bu";
|
||||
static const char *const 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
|
||||
|
||||
/*
|
||||
** =======================================================
|
||||
|
@ -594,6 +605,8 @@ static sdb *newdb (lua_State *L) {
|
|||
|
||||
db->busy_cb =
|
||||
db->busy_udata =
|
||||
db->wal_hook_cb =
|
||||
db->wal_hook_udata =
|
||||
db->update_hook_cb =
|
||||
db->update_hook_udata =
|
||||
db->commit_hook_cb =
|
||||
|
@ -644,6 +657,8 @@ static int cleanupdb(lua_State *L, sdb *db) {
|
|||
/* '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->update_hook_cb);
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, db->update_hook_udata);
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, db->commit_hook_cb);
|
||||
|
@ -887,6 +902,25 @@ static int db_db_filename(lua_State *L) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int pusherr(lua_State *L, int rc) {
|
||||
lua_pushnil(L);
|
||||
lua_pushinteger(L, rc);
|
||||
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:
|
||||
*/
|
||||
|
@ -1166,6 +1200,62 @@ static int db_create_collation(lua_State *L) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** 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) != SQLITE_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);
|
||||
|
||||
if (lua_gettop(L) < 2 || lua_isnil(L, 2)) {
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, db->wal_hook_cb);
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, db->wal_hook_udata);
|
||||
|
||||
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);
|
||||
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, db->wal_hook_cb);
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, db->wal_hook_udata);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
** update_hook callback:
|
||||
** Params: database, callback function, userdata
|
||||
|
@ -1718,7 +1808,6 @@ static int db_deserialize(lua_State *L) {
|
|||
if (db->db == NULL) /* ignore closed databases */
|
||||
return 0;
|
||||
|
||||
|
||||
const char *buffer = luaL_checklstring(L, 2, &size);
|
||||
if (buffer == NULL || size == 0) /* ignore empty database content */
|
||||
return 0;
|
||||
|
@ -1728,6 +1817,539 @@ static int db_deserialize(lua_State *L) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_ENABLE_SESSION
|
||||
|
||||
/*
|
||||
** =======================================================
|
||||
** Iterator functions (for session support)
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
sqlite3_changeset_iter *itr;
|
||||
} liter;
|
||||
|
||||
static liter *lsqlite_makeiter(lua_State *L, sqlite3_changeset_iter *piter) {
|
||||
liter *litr = (liter*)lua_newuserdata(L, sizeof(liter));
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, sqlite_itr_meta_ref);
|
||||
lua_setmetatable(L, -2);
|
||||
litr->itr = piter;
|
||||
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_tostring(lua_State *L) {
|
||||
char buff[32];
|
||||
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_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_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[30];
|
||||
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_rawlen(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);
|
||||
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?
|
||||
if (session_cb_udata != LUA_NOREF) {
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, session_filter_cb);
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, session_cb_udata);
|
||||
session_filter_cb =
|
||||
session_cb_udata = LUA_NOREF;
|
||||
}
|
||||
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_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_gc(lua_State *L) {
|
||||
return lsession_delete(L);
|
||||
}
|
||||
|
||||
static int lsession_tostring(lua_State *L) {
|
||||
char buff[30];
|
||||
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_invert_changeset(lua_State *L) {
|
||||
sdb *db = lsqlite_checkdb(L, 1);
|
||||
const char *cset = luaL_checkstring(L, 2);
|
||||
int nset = lua_rawlen(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 = lua_tostring(L, -1);
|
||||
nset = lua_rawlen(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_rawlen(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, 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
|
||||
|
@ -1859,6 +2481,27 @@ static const struct {
|
|||
SC(OPEN_SHAREDCACHE)
|
||||
SC(OPEN_PRIVATECACHE)
|
||||
|
||||
/* checkpoint flags */
|
||||
SC(CHECKPOINT_PASSIVE)
|
||||
SC(CHECKPOINT_FULL)
|
||||
SC(CHECKPOINT_RESTART)
|
||||
SC(CHECKPOINT_TRUNCATE)
|
||||
|
||||
#ifdef SQLITE_ENABLE_SESSION
|
||||
/* session constants */
|
||||
SC(CHANGESETSTART_INVERT)
|
||||
SC(CHANGESETAPPLY_NOSAVEPOINT)
|
||||
SC(CHANGESETAPPLY_INVERT)
|
||||
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 }
|
||||
};
|
||||
|
@ -1876,6 +2519,7 @@ static const luaL_Reg dblib[] = {
|
|||
{"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 },
|
||||
|
@ -1883,6 +2527,7 @@ static const luaL_Reg dblib[] = {
|
|||
|
||||
{"busy_timeout", db_busy_timeout },
|
||||
{"busy_handler", db_busy_handler },
|
||||
{"wal_hook", db_wal_hook },
|
||||
{"update_hook", db_update_hook },
|
||||
{"commit_hook", db_commit_hook },
|
||||
{"rollback_hook", db_rollback_hook },
|
||||
|
@ -1900,6 +2545,14 @@ static const luaL_Reg dblib[] = {
|
|||
{"serialize", db_serialize },
|
||||
{"deserialize", db_deserialize },
|
||||
|
||||
#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 },
|
||||
#endif
|
||||
|
||||
{"__tostring", db_tostring },
|
||||
{"__gc", db_gc },
|
||||
|
||||
|
@ -1973,6 +2626,44 @@ static const luaL_Reg ctxlib[] = {
|
|||
{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 },
|
||||
{"delete", lsession_delete },
|
||||
|
||||
{"__tostring", lsession_tostring },
|
||||
{"__gc", lsession_gc },
|
||||
{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[] = {
|
||||
{"pk", liter_pk },
|
||||
{"new", liter_new },
|
||||
{"old", liter_old },
|
||||
{"conflict", liter_conflict },
|
||||
{"fk_conflicts", liter_fk_conflicts },
|
||||
|
||||
{"__tostring", liter_tostring },
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
static const luaL_Reg sqlitelib[] = {
|
||||
{"lversion", lsqlite_lversion },
|
||||
{"version", lsqlite_version },
|
||||
|
@ -2006,6 +2697,21 @@ LUALIB_API int luaopen_lsqlite3(lua_State *L) {
|
|||
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);
|
||||
|
||||
|
|
|
@ -128,6 +128,10 @@ o/$(MODE)/tool/net/redbean.com: \
|
|||
@$(MAKE_SYMTAB_ZIP)
|
||||
@$(TOOL_NET_REDBEAN_STANDARD_ASSETS_ZIP)
|
||||
|
||||
o/$(MODE)/tool/net/lsqlite3.o: private \
|
||||
OVERRIDE_CFLAGS += \
|
||||
-DSQLITE_ENABLE_SESSION
|
||||
|
||||
# REDBEAN-DEMO.COM
|
||||
#
|
||||
# This redbean-demo.com program is the same as redbean.com except it
|
||||
|
|
Loading…
Add table
Reference in a new issue