cosmopolitan/tool/net/lsqlite3.c
Justine Tunney a4b455185b
Bring back gc() function
Renaming gc() to _gc() was a mistake since the better thing to do is put
it behind the _COSMO_SOURCE macro. We need this change because I haven't
wanted to use my amazing garbage collector ever since we renamed it. You
now need to define _COSMO_SOURCE yourself when using amalgamation header
and cosmocc users need to pass the -mcosmo flag to get the gc() function

Some other issues relating to cancelation have been fixed along the way.
We're also now putting cosmocc in a folder named `.cosmocc` so it can be
more safely excluded by grep --exclude-dir=.cosmocc --exclude-dir=o etc.
2024-01-08 10:26:28 -08:00

2856 lines
85 KiB
C

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:4;tab-width:8;coding:utf-8 -*-│
│ vi: set et ft=c ts=4 sts=4 sw=4 fenc=utf-8 :vi │
╞══════════════════════════════════════════════════════════════════════════════╡
│ lsqlite3 │
│ Copyright (C) 2002-2016 Tiago Dionizio, Doug Currie │
│ All rights reserved. │
│ Author : Tiago Dionizio <tiago.dionizio@ist.utl.pt> │
│ Author : Doug Currie <doug.currie@alum.mit.edu> │
│ Library : lsqlite3 - an SQLite 3 database binding for Lua 5 │
│ │
│ Permission is hereby granted, free of charge, to any person obtaining │
│ a copy of this software and associated documentation files (the │
│ "Software"), to deal in the Software without restriction, including │
│ without limitation the rights to use, copy, modify, merge, publish, │
│ distribute, sublicense, and/or sell copies of the Software, and to │
│ permit persons to whom the Software is furnished to do so, subject to │
│ the following conditions: │
│ │
│ The above copyright notice and this permission notice shall be │
│ included in all copies or substantial portions of the Software. │
│ │
│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │
│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │
│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. │
│ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY │
│ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, │
│ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE │
│ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/weirdtypes.h"
#include "libc/mem/gc.h"
#include "libc/mem/mem.h"
#include "libc/str/str.h"
#include "third_party/lua/lauxlib.h"
#include "third_party/lua/lua.h"
#include "third_party/lua/luaconf.h"
#include "third_party/sqlite3/extensions.h"
#include "third_party/sqlite3/sqlite3.h"
// clang-format off
asm(".ident\t\"\\n\\n\
lsqlite3 (MIT License)\\n\
Copyright 2002-2016 Tiago Dionizio, Doug Currie\"");
asm(".include \"libc/disclaimer.inc\"");
// LOCAL CHANGES
//
// - Remove online backup code
// - Remove trace callback code
// - Remove progress callback code
// - Removed extension loading code
// - Relocate static .data to .rodata
// - Changed lua_strlen() to lua_rawlen()
//
#define LSQLITE_VERSION "0.9.5"
/* luaL_typerror always used with arg at ndx == NULL */
#define luaL_typerror(L,ndx,str) luaL_error(L,"bad argument %d (%s expected, got nil)",ndx,str)
/* luaL_register used once, so below expansion is OK for this case */
#define luaL_register(L,name,reg) lua_newtable(L);luaL_setfuncs(L,reg,0)
/* luaL_openlib always used with name == NULL */
#define luaL_openlib(L,name,reg,nup) luaL_setfuncs(L,reg,nup)
#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n)))
#define PUSH_INT64(L,i64in,fallback) \
do { \
sqlite_int64 i64 = i64in; \
lua_Integer i = (lua_Integer )i64; \
if (i == i64) lua_pushinteger(L, i);\
else { \
lua_Number n = (lua_Number)i64; \
if (n == i64) lua_pushnumber(L, n); \
else fallback; \
} \
} while (0)
typedef struct sdb sdb;
typedef struct sdb_vm sdb_vm;
typedef struct sdb_bu sdb_bu;
typedef struct sdb_func sdb_func;
/* to use as C user data so i know what function sqlite is calling */
struct sdb_func {
/* references to associated lua values */
int fn_step;
int fn_finalize;
int udata;
sdb *db;
char aggregate;
sdb_func *next;
};
/* information about database */
struct sdb {
/* associated lua state */
lua_State *L;
/* sqlite database handle */
sqlite3 *db;
/* sql functions stack usage */
sdb_func *func; /* top SQL function being called */
/* references */
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;
int commit_hook_cb; /* commit_hook callback */
int commit_hook_udata;
int rollback_hook_cb; /* rollback_hook callback */
int rollback_hook_udata;
};
static const char *const sqlite_meta = ":sqlite3";
static const char *const sqlite_vm_meta = ":sqlite3:vm";
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
/* global config configuration */
static int log_cb = LUA_NOREF; /* log callback */
static int log_udata;
/*
** =======================================================
** Database Virtual Machine Operations
** =======================================================
*/
static void vm_push_column(lua_State *L, sqlite3_stmt *vm, int idx) {
switch (sqlite3_column_type(vm, idx)) {
case SQLITE_INTEGER:
PUSH_INT64(L, sqlite3_column_int64(vm, idx)
, lua_pushlstring(L, (const char*)sqlite3_column_text(vm, idx)
, sqlite3_column_bytes(vm, idx)));
break;
case SQLITE_FLOAT:
lua_pushnumber(L, sqlite3_column_double(vm, idx));
break;
case SQLITE_TEXT:
lua_pushlstring(L, (const char*)sqlite3_column_text(vm, idx), sqlite3_column_bytes(vm, idx));
break;
case SQLITE_BLOB:
lua_pushlstring(L, sqlite3_column_blob(vm, idx), sqlite3_column_bytes(vm, idx));
break;
case SQLITE_NULL:
lua_pushnil(L);
break;
default:
lua_pushnil(L);
break;
}
}
/* virtual machine information */
struct sdb_vm {
sdb *db; /* associated database handle */
sqlite3_stmt *vm; /* virtual machine */
/* sqlite3_step info */
int columns; /* number of columns in result */
char has_values; /* true when step succeeds */
char temp; /* temporary vm used in db:rows */
};
/* called with db,sql text on the lua stack */
static sdb_vm *newvm(lua_State *L, sdb *db) {
sdb_vm *svm = (sdb_vm*)lua_newuserdata(L, sizeof(sdb_vm)); /* db sql svm_ud -- */
luaL_getmetatable(L, sqlite_vm_meta);
lua_setmetatable(L, -2); /* set metatable */
svm->db = db;
svm->columns = 0;
svm->has_values = 0;
svm->vm = NULL;
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_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) {
svm->columns = 0;
svm->has_values = 0;
if (!svm->vm) return 0;
lua_pushinteger(L, sqlite3_finalize(svm->vm));
svm->vm = NULL;
return 1;
}
static int stepvm(lua_State *L, sdb_vm *svm) {
return sqlite3_step(svm->vm);
}
static sdb_vm *lsqlite_getvm(lua_State *L, int index) {
sdb_vm *svm = (sdb_vm*)luaL_checkudata(L, index, sqlite_vm_meta);
if (svm == NULL) luaL_argerror(L, index, "bad sqlite virtual machine");
return svm;
}
static sdb_vm *lsqlite_checkvm(lua_State *L, int index) {
sdb_vm *svm = lsqlite_getvm(L, index);
if (svm->vm == NULL) luaL_argerror(L, index, "attempt to use closed sqlite virtual machine");
return svm;
}
static int dbvm_isopen(lua_State *L) {
sdb_vm *svm = lsqlite_getvm(L, 1);
lua_pushboolean(L, svm->vm != NULL ? 1 : 0);
return 1;
}
static int dbvm_readonly(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
lua_pushboolean(L, sqlite3_stmt_readonly(svm->vm));
return 1;
}
static int dbvm_tostring(lua_State *L) {
char buff[40];
sdb_vm *svm = lsqlite_getvm(L, 1);
if (svm->vm == NULL)
strcpy(buff, "closed");
else
sprintf(buff, "%p", svm);
lua_pushfstring(L, "sqlite virtual machine (%s)", buff);
return 1;
}
static int dbvm_gc(lua_State *L) {
cleanupvm(L, lsqlite_getvm(L, 1));
return 0;
}
static int dbvm_step(lua_State *L) {
int result;
sdb_vm *svm = lsqlite_checkvm(L, 1);
result = stepvm(L, svm);
svm->has_values = result == SQLITE_ROW ? 1 : 0;
svm->columns = sqlite3_data_count(svm->vm);
lua_pushinteger(L, result);
return 1;
}
static int dbvm_finalize(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
return cleanupvm(L, svm);
}
static int dbvm_reset(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_reset(svm->vm);
lua_pushinteger(L, sqlite3_errcode(svm->db->db));
return 1;
}
static void dbvm_check_contents(lua_State *L, sdb_vm *svm) {
if (!svm->has_values) {
luaL_error(L, "misuse of function");
}
}
static void dbvm_check_index(lua_State *L, sdb_vm *svm, int index) {
if (index < 0 || index >= svm->columns) {
luaL_error(L, "index out of range [0..%d]", svm->columns - 1);
}
}
static void dbvm_check_bind_index(lua_State *L, sdb_vm *svm, int index) {
if (index < 1 || index > sqlite3_bind_parameter_count(svm->vm)) {
luaL_error(L, "bind index out of range [1..%d]", sqlite3_bind_parameter_count(svm->vm));
}
}
static int dbvm_last_insert_rowid(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
/* conversion warning: int64 -> luaNumber */
sqlite_int64 rowid = sqlite3_last_insert_rowid(svm->db->db);
PUSH_INT64(L, rowid, lua_pushfstring(L, "%ll", rowid));
return 1;
}
/*
** =======================================================
** Virtual Machine - generic info
** =======================================================
*/
static int dbvm_columns(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
lua_pushinteger(L, sqlite3_column_count(svm->vm));
return 1;
}
/*
** =======================================================
** Virtual Machine - getters
** =======================================================
*/
static int dbvm_get_value(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
int index = luaL_checkint(L, 2);
dbvm_check_contents(L, svm);
dbvm_check_index(L, svm, index);
vm_push_column(L, svm->vm, index);
return 1;
}
static int dbvm_get_name(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
int index = luaL_checknumber(L, 2);
dbvm_check_index(L, svm, index);
lua_pushstring(L, sqlite3_column_name(svm->vm, index));
return 1;
}
static int dbvm_get_type(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
int index = luaL_checknumber(L, 2);
dbvm_check_index(L, svm, index);
lua_pushstring(L, sqlite3_column_decltype(svm->vm, index));
return 1;
}
static int dbvm_get_values(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int columns = svm->columns;
int n;
dbvm_check_contents(L, svm);
lua_createtable(L, columns, 0);
for (n = 0; n < columns;) {
vm_push_column(L, vm, n++);
lua_rawseti(L, -2, n);
}
return 1;
}
static int dbvm_get_names(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int columns = sqlite3_column_count(vm); /* valid as soon as statement prepared */
int n;
lua_createtable(L, columns, 0);
for (n = 0; n < columns;) {
lua_pushstring(L, sqlite3_column_name(vm, n++));
lua_rawseti(L, -2, n);
}
return 1;
}
static int dbvm_get_types(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int columns = sqlite3_column_count(vm); /* valid as soon as statement prepared */
int n;
lua_createtable(L, columns, 0);
for (n = 0; n < columns;) {
lua_pushstring(L, sqlite3_column_decltype(vm, n++));
lua_rawseti(L, -2, n);
}
return 1;
}
static int dbvm_get_uvalues(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int columns = svm->columns;
int n;
dbvm_check_contents(L, svm);
lua_checkstack(L, columns);
for (n = 0; n < columns; ++n)
vm_push_column(L, vm, n);
return columns;
}
static int dbvm_get_unames(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int columns = sqlite3_column_count(vm); /* valid as soon as statement prepared */
int n;
lua_checkstack(L, columns);
for (n = 0; n < columns; ++n)
lua_pushstring(L, sqlite3_column_name(vm, n));
return columns;
}
static int dbvm_get_utypes(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int columns = sqlite3_column_count(vm); /* valid as soon as statement prepared */
int n;
lua_checkstack(L, columns);
for (n = 0; n < columns; ++n)
lua_pushstring(L, sqlite3_column_decltype(vm, n));
return columns;
}
static int dbvm_get_named_values(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int columns = svm->columns;
int n;
dbvm_check_contents(L, svm);
lua_createtable(L, 0, columns);
for (n = 0; n < columns; ++n) {
lua_pushstring(L, sqlite3_column_name(vm, n));
vm_push_column(L, vm, n);
lua_rawset(L, -3);
}
return 1;
}
static int dbvm_get_named_types(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int columns = sqlite3_column_count(vm);
int n;
lua_createtable(L, 0, columns);
for (n = 0; n < columns; ++n) {
lua_pushstring(L, sqlite3_column_name(vm, n));
lua_pushstring(L, sqlite3_column_decltype(vm, n));
lua_rawset(L, -3);
}
return 1;
}
/*
** =======================================================
** Virtual Machine - Bind
** =======================================================
*/
static int dbvm_bind_index(lua_State *L, sqlite3_stmt *vm, int index, int lindex) {
switch (lua_type(L, lindex)) {
case LUA_TSTRING:
return sqlite3_bind_text(vm, index, lua_tostring(L, lindex), lua_rawlen(L, lindex), SQLITE_TRANSIENT);
case LUA_TNUMBER:
#if LUA_VERSION_NUM > 502
if (lua_isinteger(L, lindex))
return sqlite3_bind_int64(vm, index, lua_tointeger(L, lindex));
#endif
return sqlite3_bind_double(vm, index, lua_tonumber(L, lindex));
case LUA_TBOOLEAN:
return sqlite3_bind_int(vm, index, lua_toboolean(L, lindex) ? 1 : 0);
case LUA_TNONE:
case LUA_TNIL:
return sqlite3_bind_null(vm, index);
default:
luaL_error(L, "index (%d) - invalid data type for bind (%s)", index, lua_typename(L, lua_type(L, lindex)));
return SQLITE_MISUSE; /*!*/
}
}
static int dbvm_bind_parameter_count(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
lua_pushinteger(L, sqlite3_bind_parameter_count(svm->vm));
return 1;
}
static int dbvm_bind_parameter_name(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
int index = luaL_checknumber(L, 2);
dbvm_check_bind_index(L, svm, index);
lua_pushstring(L, sqlite3_bind_parameter_name(svm->vm, index));
return 1;
}
static int dbvm_bind(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int index = luaL_checkint(L, 2);
int result;
dbvm_check_bind_index(L, svm, index);
result = dbvm_bind_index(L, vm, index, 3);
lua_pushinteger(L, result);
return 1;
}
static int dbvm_bind_blob(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
int index = luaL_checkint(L, 2);
const char *value = luaL_checkstring(L, 3);
int len = lua_rawlen(L, 3);
lua_pushinteger(L, sqlite3_bind_blob(svm->vm, index, value, len, SQLITE_TRANSIENT));
return 1;
}
static int dbvm_bind_values(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int top = lua_gettop(L);
int result, n;
if (top - 1 != sqlite3_bind_parameter_count(vm))
luaL_error(L,
"incorrect number of parameters to bind (%d given, %d to bind)",
top - 1,
sqlite3_bind_parameter_count(vm)
);
for (n = 2; n <= top; ++n) {
if ((result = dbvm_bind_index(L, vm, n - 1, n)) != SQLITE_OK) {
lua_pushinteger(L, result);
return 1;
}
}
lua_pushinteger(L, SQLITE_OK);
return 1;
}
static int dbvm_bind_names(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int count = sqlite3_bind_parameter_count(vm);
const char *name;
int result, n;
luaL_checktype(L, 2, LUA_TTABLE);
for (n = 1; n <= count; ++n) {
name = sqlite3_bind_parameter_name(vm, n);
if (name && (name[0] == ':' || name[0] == '$')) {
lua_pushstring(L, ++name);
lua_gettable(L, 2);
result = dbvm_bind_index(L, vm, n, -1);
lua_pop(L, 1);
}
else {
lua_pushinteger(L, n);
lua_gettable(L, 2);
result = dbvm_bind_index(L, vm, n, -1);
lua_pop(L, 1);
}
if (result != SQLITE_OK) {
lua_pushinteger(L, result);
return 1;
}
}
lua_pushinteger(L, SQLITE_OK);
return 1;
}
/*
** =======================================================
** Database (internal management)
** =======================================================
*/
/*
** When creating database handles, always creates a `closed' database handle
** before opening the actual database; so, if there is a memory error, the
** database is not left opened.
**
** Creates a new 'table' and leaves it in the stack
*/
static sdb *newdb (lua_State *L) {
sdb *db = (sdb*)lua_newuserdata(L, sizeof(sdb));
db->L = L;
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->update_hook_cb =
db->update_hook_udata =
db->commit_hook_cb =
db->commit_hook_udata =
db->rollback_hook_cb =
db->rollback_hook_udata =
LUA_NOREF;
luaL_getmetatable(L, sqlite_meta);
lua_setmetatable(L, -2); /* set metatable */
/* to keep track of 'open' virtual machines; make keys week */
lua_pushlightuserdata(L, db);
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;
}
/* 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 */
lua_pushnil(L);
while (lua_next(L, -2)) {
sdb_vm *svm = lua_touserdata(L, -2); /* key: vm; val: sql text */
if ((!temp || svm->temp)) lua_pop(L, cleanupvm(L, svm));
lua_pop(L, 1); /* pop value; leave key in the stack */
}
}
static int cleanupdb(lua_State *L, sdb *db) {
sdb_func *func;
sdb_func *func_next;
int top = lua_gettop(L);
int result;
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->update_hook_cb);
luaL_unref(L, LUA_REGISTRYINDEX, db->update_hook_udata);
luaL_unref(L, LUA_REGISTRYINDEX, db->commit_hook_cb);
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);
/* 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) {
func_next = func->next;
luaL_unref(L, LUA_REGISTRYINDEX, func->fn_step);
luaL_unref(L, LUA_REGISTRYINDEX, func->fn_finalize);
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);
if (db == NULL) luaL_typerror(L, index, "sqlite database");
return db;
}
static sdb *lsqlite_checkdb(lua_State *L, int index) {
sdb *db = lsqlite_getdb(L, index);
if (db->db == NULL) luaL_argerror(L, index, "attempt to use closed sqlite database");
return db;
}
/*
** =======================================================
** User Defined Functions - Context Methods
** =======================================================
*/
typedef struct {
sqlite3_context *ctx;
int ud;
} lcontext;
static lcontext *lsqlite_make_context(lua_State *L) {
lcontext *ctx = (lcontext*)lua_newuserdata(L, sizeof(lcontext));
lua_rawgeti(L, LUA_REGISTRYINDEX, sqlite_ctx_meta_ref);
lua_setmetatable(L, -2);
ctx->ctx = NULL;
ctx->ud = LUA_NOREF;
return ctx;
}
static lcontext *lsqlite_getcontext(lua_State *L, int index) {
lcontext *ctx = (lcontext*)luaL_checkudata(L, index, sqlite_ctx_meta);
if (ctx == NULL) luaL_typerror(L, index, "sqlite context");
return ctx;
}
static lcontext *lsqlite_checkcontext(lua_State *L, int index) {
lcontext *ctx = lsqlite_getcontext(L, index);
if (ctx->ctx == NULL) luaL_argerror(L, index, "invalid sqlite context");
return ctx;
}
static int lcontext_tostring(lua_State *L) {
char buff[41];
lcontext *ctx = lsqlite_getcontext(L, 1);
if (ctx->ctx == NULL)
strcpy(buff, "closed");
else
sprintf(buff, "%p", ctx->ctx);
lua_pushfstring(L, "sqlite function context (%s)", buff);
return 1;
}
static void lcontext_check_aggregate(lua_State *L, lcontext *ctx) {
sdb_func *func = (sdb_func*)sqlite3_user_data(ctx->ctx);
if (!func->aggregate) {
luaL_error(L, "attempt to call aggregate method from scalar function");
}
}
static int lcontext_user_data(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
sdb_func *func = (sdb_func*)sqlite3_user_data(ctx->ctx);
lua_rawgeti(L, LUA_REGISTRYINDEX, func->udata);
return 1;
}
static int lcontext_get_aggregate_context(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
lcontext_check_aggregate(L, ctx);
lua_rawgeti(L, LUA_REGISTRYINDEX, ctx->ud);
return 1;
}
static int lcontext_set_aggregate_context(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
lcontext_check_aggregate(L, ctx);
lua_settop(L, 2);
luaL_unref(L, LUA_REGISTRYINDEX, ctx->ud);
ctx->ud = luaL_ref(L, LUA_REGISTRYINDEX);
return 0;
}
#if 0
void *sqlite3_get_auxdata(sqlite3_context*, int);
void sqlite3_set_auxdata(sqlite3_context*, int, void*, void (*)(void*));
#endif
static int lcontext_result(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
switch (lua_type(L, 2)) {
case LUA_TNUMBER:
#if LUA_VERSION_NUM > 502
if (lua_isinteger(L, 2))
sqlite3_result_int64(ctx->ctx, luaL_checkinteger(L, 2));
else
#endif
sqlite3_result_double(ctx->ctx, luaL_checknumber(L, 2));
break;
case LUA_TSTRING:
sqlite3_result_text(ctx->ctx, luaL_checkstring(L, 2), lua_rawlen(L, 2), SQLITE_TRANSIENT);
break;
case LUA_TNIL:
case LUA_TNONE:
sqlite3_result_null(ctx->ctx);
break;
default:
luaL_error(L, "invalid result type %s", lua_typename(L, 2));
break;
}
return 0;
}
static int lcontext_result_blob(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
const char *blob = luaL_checkstring(L, 2);
int size = lua_rawlen(L, 2);
sqlite3_result_blob(ctx->ctx, (const void*)blob, size, SQLITE_TRANSIENT);
return 0;
}
static int lcontext_result_double(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
double d = luaL_checknumber(L, 2);
sqlite3_result_double(ctx->ctx, d);
return 0;
}
static int lcontext_result_error(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
const char *err = luaL_checkstring(L, 2);
int size = lua_rawlen(L, 2);
sqlite3_result_error(ctx->ctx, err, size);
return 0;
}
static int lcontext_result_int(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
int i = luaL_checkint(L, 2);
sqlite3_result_int(ctx->ctx, i);
return 0;
}
static int lcontext_result_null(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
sqlite3_result_null(ctx->ctx);
return 0;
}
static int lcontext_result_text(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
const char *text = luaL_checkstring(L, 2);
int size = lua_rawlen(L, 2);
sqlite3_result_text(ctx->ctx, text, size, SQLITE_TRANSIENT);
return 0;
}
/*
** =======================================================
** Database Methods
** =======================================================
*/
static int db_isopen(lua_State *L) {
sdb *db = lsqlite_getdb(L, 1);
lua_pushboolean(L, db->db != NULL ? 1 : 0);
return 1;
}
static int db_last_insert_rowid(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
/* conversion warning: int64 -> luaNumber */
sqlite_int64 rowid = sqlite3_last_insert_rowid(db->db);
PUSH_INT64(L, rowid, lua_pushfstring(L, "%ll", rowid));
return 1;
}
static int db_changes(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
lua_pushinteger(L, sqlite3_changes(db->db));
return 1;
}
static int db_total_changes(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
lua_pushinteger(L, sqlite3_total_changes(db->db));
return 1;
}
static int db_errcode(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
lua_pushinteger(L, sqlite3_errcode(db->db));
return 1;
}
static int db_errmsg(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
lua_pushstring(L, sqlite3_errmsg(db->db));
return 1;
}
static int db_interrupt(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
sqlite3_interrupt(db->db);
return 0;
}
static int db_db_filename(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
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_readonly(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
const char *zDb = luaL_optstring(L, 2, "main");
int res = sqlite3_db_readonly(db->db, zDb);
if (res == -1) return pusherrstr(L, "unknown (not attached) database name");
lua_pushboolean(L, res);
return 1;
}
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:
*/
static void db_push_value(lua_State *L, sqlite3_value *value) {
switch (sqlite3_value_type(value)) {
case SQLITE_TEXT:
lua_pushlstring(L, (const char*)sqlite3_value_text(value), sqlite3_value_bytes(value));
break;
case SQLITE_INTEGER:
PUSH_INT64(L, sqlite3_value_int64(value)
, lua_pushlstring(L, (const char*)sqlite3_value_text(value)
, sqlite3_value_bytes(value)));
break;
case SQLITE_FLOAT:
lua_pushnumber(L, sqlite3_value_double(value));
break;
case SQLITE_BLOB:
lua_pushlstring(L, sqlite3_value_blob(value), sqlite3_value_bytes(value));
break;
case SQLITE_NULL:
lua_pushnil(L);
break;
default:
/* things done properly (SQLite + Lua SQLite)
** this should never happen */
lua_pushnil(L);
break;
}
}
/*
** callback functions used when calling registered sql functions
*/
/* scalar function to be called
** callback params: context, values... */
static void db_sql_normal_function(sqlite3_context *context, int argc, sqlite3_value **argv) {
sdb_func *func = (sdb_func*)sqlite3_user_data(context);
lua_State *L = func->db->L;
int n;
lcontext *ctx;
int top = lua_gettop(L);
/* ensure there is enough space in the stack */
lua_checkstack(L, argc + 3);
lua_rawgeti(L, LUA_REGISTRYINDEX, func->fn_step); /* function to call */
if (!func->aggregate) {
ctx = lsqlite_make_context(L); /* push context - used to set results */
}
else {
/* reuse context userdata value */
void *p = sqlite3_aggregate_context(context, 1);
/* i think it is OK to use assume that using a light user data
** as an entry on LUA REGISTRY table will be unique */
lua_pushlightuserdata(L, p);
lua_rawget(L, LUA_REGISTRYINDEX); /* context table */
if (lua_isnil(L, -1)) { /* not yet created? */
lua_pop(L, 1);
ctx = lsqlite_make_context(L);
lua_pushlightuserdata(L, p);
lua_pushvalue(L, -2);
lua_rawset(L, LUA_REGISTRYINDEX);
}
else
ctx = lsqlite_getcontext(L, -1);
}
/* push params */
for (n = 0; n < argc; ++n) {
db_push_value(L, argv[n]);
}
/* set context */
ctx->ctx = context;
if (lua_pcall(L, argc + 1, 0, 0)) {
const char *errmsg = lua_tostring(L, -1);
int size = lua_rawlen(L, -1);
sqlite3_result_error(context, errmsg, size);
}
/* invalidate context */
ctx->ctx = NULL;
if (!func->aggregate) {
luaL_unref(L, LUA_REGISTRYINDEX, ctx->ud);
}
lua_settop(L, top);
}
static void db_sql_finalize_function(sqlite3_context *context) {
sdb_func *func = (sdb_func*)sqlite3_user_data(context);
lua_State *L = func->db->L;
void *p = sqlite3_aggregate_context(context, 1); /* minimal mem usage */
lcontext *ctx;
int top = lua_gettop(L);
lua_rawgeti(L, LUA_REGISTRYINDEX, func->fn_finalize); /* function to call */
/* i think it is OK to use assume that using a light user data
** as an entry on LUA REGISTRY table will be unique */
lua_pushlightuserdata(L, p);
lua_rawget(L, LUA_REGISTRYINDEX); /* context table */
if (lua_isnil(L, -1)) { /* not yet created? - shouldn't happen in finalize function */
lua_pop(L, 1);
ctx = lsqlite_make_context(L);
lua_pushlightuserdata(L, p);
lua_pushvalue(L, -2);
lua_rawset(L, LUA_REGISTRYINDEX);
}
else
ctx = lsqlite_getcontext(L, -1);
/* set context */
ctx->ctx = context;
if (lua_pcall(L, 1, 0, 0)) {
sqlite3_result_error(context, lua_tostring(L, -1), -1);
}
/* invalidate context */
ctx->ctx = NULL;
/* cleanup context */
luaL_unref(L, LUA_REGISTRYINDEX, ctx->ud);
/* remove it from registry */
lua_pushlightuserdata(L, p);
lua_pushnil(L);
lua_rawset(L, LUA_REGISTRYINDEX);
lua_settop(L, top);
}
/*
** Register a normal function
** Params: db, function name, number arguments, [ callback | step, finalize], user data
** Returns: true on success
**
** Normal function:
** Params: context, params
**
** Aggregate function:
** Params of step: context, params
** Params of finalize: context
*/
static int db_register_function(lua_State *L, int aggregate) {
sdb *db = lsqlite_checkdb(L, 1);
const char *name;
int args;
int result;
sdb_func *func;
/* safety measure */
if (aggregate) aggregate = 1;
name = luaL_checkstring(L, 2);
args = luaL_checkint(L, 3);
luaL_checktype(L, 4, LUA_TFUNCTION);
if (aggregate) luaL_checktype(L, 5, LUA_TFUNCTION);
/* maybe an alternative way to allocate memory should be used/avoided */
func = (sdb_func*)malloc(sizeof(sdb_func));
if (func == NULL) {
luaL_error(L, "out of memory");
}
result = sqlite3_create_function(
db->db, name, args, SQLITE_UTF8, func,
aggregate ? NULL : db_sql_normal_function,
aggregate ? db_sql_normal_function : NULL,
aggregate ? db_sql_finalize_function : NULL
);
if (result == SQLITE_OK) {
/* safety measures for userdata field to be present in the stack */
lua_settop(L, 5 + aggregate);
/* save registered function in db function list */
func->db = db;
func->aggregate = aggregate;
func->next = db->func;
db->func = func;
/* save the setp/normal function callback */
lua_pushvalue(L, 4);
func->fn_step = luaL_ref(L, LUA_REGISTRYINDEX);
/* save user data */
lua_pushvalue(L, 5+aggregate);
func->udata = luaL_ref(L, LUA_REGISTRYINDEX);
if (aggregate) {
lua_pushvalue(L, 5);
func->fn_finalize = luaL_ref(L, LUA_REGISTRYINDEX);
}
else
func->fn_finalize = LUA_NOREF;
}
else {
/* free allocated memory */
free(func);
}
lua_pushboolean(L, result == SQLITE_OK ? 1 : 0);
return 1;
}
static int db_create_function(lua_State *L) {
return db_register_function(L, 0);
}
static int db_create_aggregate(lua_State *L) {
return db_register_function(L, 1);
}
/* create_collation; contributed by Thomas Lauer
*/
typedef struct {
lua_State *L;
int ref;
} scc;
static int collwrapper(scc *co,int l1,const void *p1,
int l2,const void *p2) {
int res=0;
lua_State *L=co->L;
lua_rawgeti(L,LUA_REGISTRYINDEX,co->ref);
lua_pushlstring(L,p1,l1);
lua_pushlstring(L,p2,l2);
if (lua_pcall(L,2,1,0)==0) res=(int)lua_tonumber(L,-1);
lua_pop(L,1);
return res;
}
static void collfree(scc *co) {
if (co) {
luaL_unref(co->L,LUA_REGISTRYINDEX,co->ref);
free(co);
}
}
static int db_create_collation(lua_State *L) {
sdb *db=lsqlite_checkdb(L,1);
const char *collname=luaL_checkstring(L,2);
scc *co=NULL;
int (*collfunc)(scc *,int,const void *,int,const void *)=NULL;
lua_settop(L,3); /* default args to nil, and exclude extras */
if (lua_isfunction(L,3)) collfunc=collwrapper;
else if (!lua_isnil(L,3))
luaL_error(L,"create_collation: function or nil expected");
if (collfunc != NULL) {
co=(scc *)malloc(sizeof(scc)); /* userdata is a no-no as it
will be garbage-collected */
if (co) {
co->L=L;
/* lua_settop(L,3) above means we don't need: lua_pushvalue(L,3); */
co->ref=luaL_ref(L,LUA_REGISTRYINDEX);
}
else luaL_error(L,"create_collation: could not allocate callback");
}
sqlite3_create_collation_v2(db->db, collname, SQLITE_UTF8,
(void *)co,
(int(*)(void*,int,const void*,int,const void*))collfunc,
(void(*)(void*))collfree);
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) != 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;
}
/*
** update_hook callback:
** Params: database, callback function, userdata
**
** callback function:
** Params: userdata, {one of SQLITE_INSERT, SQLITE_DELETE, or SQLITE_UPDATE},
** database name, table name (containing the affected row), rowid of the row
*/
static void db_update_hook_callback(void *user, int op, char const *dbname, char const *tblname, sqlite3_int64 rowid) {
sdb *db = (sdb*)user;
lua_State *L = db->L;
int top = lua_gettop(L);
/* setup lua callback call */
lua_rawgeti(L, LUA_REGISTRYINDEX, db->update_hook_cb); /* get callback */
lua_rawgeti(L, LUA_REGISTRYINDEX, db->update_hook_udata); /* get callback user data */
lua_pushinteger(L, op);
lua_pushstring(L, dbname); /* update_hook database name */
lua_pushstring(L, tblname); /* update_hook database name */
PUSH_INT64(L, rowid, lua_pushfstring(L, "%ll", rowid));
/* call lua function */
lua_pcall(L, 5, 0, 0);
/* ignore any error generated by this function */
lua_settop(L, top);
}
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)) {
db->update_hook_cb =
db->update_hook_udata = LUA_NOREF;
/* clear update_hook handler */
sqlite3_update_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->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);
}
return 0;
}
/*
** commit_hook callback:
** Params: database, callback function, userdata
**
** callback function:
** Params: userdata
** Returned value: Return false or nil to continue the COMMIT operation normally.
** return true (non false, non nil), then the COMMIT is converted into a ROLLBACK.
*/
static int db_commit_hook_callback(void *user) {
sdb *db = (sdb*)user;
lua_State *L = db->L;
int top = lua_gettop(L);
int rollback = 0;
/* setup lua callback call */
lua_rawgeti(L, LUA_REGISTRYINDEX, db->commit_hook_cb); /* get callback */
lua_rawgeti(L, LUA_REGISTRYINDEX, db->commit_hook_udata); /* get callback user data */
/* call lua function */
if (!lua_pcall(L, 1, 1, 0))
rollback = lua_toboolean(L, -1); /* use result if there was no error */
lua_settop(L, top);
return rollback;
}
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)) {
db->commit_hook_cb =
db->commit_hook_udata = LUA_NOREF;
/* clear commit_hook handler */
sqlite3_commit_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->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);
}
return 0;
}
/*
** rollback hook callback:
** Params: database, callback function, userdata
**
** callback function:
** Params: userdata
*/
static void db_rollback_hook_callback(void *user) {
sdb *db = (sdb*)user;
lua_State *L = db->L;
int top = lua_gettop(L);
/* setup lua callback call */
lua_rawgeti(L, LUA_REGISTRYINDEX, db->rollback_hook_cb); /* get callback */
lua_rawgeti(L, LUA_REGISTRYINDEX, db->rollback_hook_udata); /* get callback user data */
/* call lua function */
lua_pcall(L, 1, 0, 0);
/* ignore any error generated by this function */
lua_settop(L, top);
}
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)) {
db->rollback_hook_cb =
db->rollback_hook_udata = LUA_NOREF;
/* clear rollback_hook handler */
sqlite3_rollback_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->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);
}
return 0;
}
/*
** busy handler:
** Params: database, callback function, userdata
**
** callback function:
** Params: userdata, number of tries
** returns: 0 to return immediately and return SQLITE_BUSY, non-zero to try again
*/
static int db_busy_callback(void *user, int tries) {
int retry = 0; /* abort by default */
sdb *db = (sdb*)user;
lua_State *L = db->L;
int top = lua_gettop(L);
lua_rawgeti(L, LUA_REGISTRYINDEX, db->busy_cb);
lua_rawgeti(L, LUA_REGISTRYINDEX, db->busy_udata);
lua_pushinteger(L, tries);
/* call lua function */
if (!lua_pcall(L, 2, 1, 0))
retry = lua_toboolean(L, -1);
lua_settop(L, top);
return retry;
}
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)) {
db->busy_cb =
db->busy_udata = LUA_NOREF;
/* clear busy handler */
sqlite3_busy_handler(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->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);
}
return 0;
}
static int db_busy_timeout(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
int timeout = luaL_checkint(L, 2);
sqlite3_busy_timeout(db->db, timeout);
/* if there was a timeout callback registered, it is now
** invalid/useless. free any references we may have */
luaL_unref(L, LUA_REGISTRYINDEX, db->busy_cb);
luaL_unref(L, LUA_REGISTRYINDEX, db->busy_udata);
db->busy_cb =
db->busy_udata = LUA_NOREF;
return 0;
}
/*
** Params: db, sql, callback, user
** returns: code [, errmsg]
**
** Callback:
** Params: user, number of columns, values, names
** Returns: 0 to continue, other value will cause abort
*/
static int db_exec_callback(void* user, int columns, char **data, char **names) {
int result = SQLITE_ABORT; /* abort by default */
lua_State *L = (lua_State*)user;
int n;
int top = lua_gettop(L);
lua_pushvalue(L, 3); /* function to call */
lua_pushvalue(L, 4); /* user data */
lua_pushinteger(L, columns); /* total number of rows in result */
/* column values */
lua_pushvalue(L, 6);
for (n = 0; n < columns;) {
lua_pushstring(L, data[n++]);
lua_rawseti(L, -2, n);
}
/* columns names */
lua_pushvalue(L, 5);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
lua_createtable(L, columns, 0);
lua_pushvalue(L, -1);
lua_replace(L, 5);
for (n = 0; n < columns;) {
lua_pushstring(L, names[n++]);
lua_rawseti(L, -2, n);
}
}
/* call lua function */
if (!lua_pcall(L, 4, 1, 0)) {
#if LUA_VERSION_NUM > 502
if (lua_isinteger(L, -1))
result = lua_tointeger(L, -1);
else
#endif
if (lua_isnumber(L, -1))
result = lua_tonumber(L, -1);
}
lua_settop(L, top);
return result;
}
static int db_exec(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
const char *sql = luaL_checkstring(L, 2);
int result;
if (!lua_isnoneornil(L, 3)) {
/* stack:
** 3: callback function
** 4: userdata
** 5: column names
** 6: reusable column values
*/
luaL_checktype(L, 3, LUA_TFUNCTION);
lua_settop(L, 4); /* 'trap' userdata - nil extra parameters */
lua_pushnil(L); /* column names not known at this point */
lua_newtable(L); /* column values table */
result = sqlite3_exec(db->db, sql, db_exec_callback, L, NULL);
}
else {
/* no callbacks */
result = sqlite3_exec(db->db, sql, NULL, NULL, NULL);
}
lua_pushinteger(L, result);
return 1;
}
/*
** Params: db, sql
** returns: code, compiled length or error message
*/
static int db_prepare(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
const char *sql = luaL_checkstring(L, 2);
int sql_len = lua_rawlen(L, 2);
const char *sqltail;
sdb_vm *svm;
lua_settop(L,2); /* db,sql is on top of stack for call to newvm */
svm = newvm(L, db);
if (sqlite3_prepare_v2(db->db, sql, sql_len, &svm->vm, &sqltail) != SQLITE_OK) {
lua_pushnil(L);
lua_pushinteger(L, sqlite3_errcode(db->db));
if (cleanupvm(L, svm) == 1)
lua_pop(L, 1); /* this should not happen since sqlite3_prepare_v2 will not set ->vm on error */
return 2;
}
/* vm already in the stack */
lua_pushstring(L, sqltail);
return 2;
}
static int db_do_next_row(lua_State *L, int packed) {
int result;
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm;
int columns;
int i;
result = stepvm(L, svm);
vm = svm->vm; /* stepvm may change svm->vm if re-prepare is needed */
svm->has_values = result == SQLITE_ROW ? 1 : 0;
svm->columns = columns = sqlite3_data_count(vm);
if (result == SQLITE_ROW) {
if (packed) {
if (packed == 1) {
lua_createtable(L, columns, 0);
for (i = 0; i < columns;) {
vm_push_column(L, vm, i);
lua_rawseti(L, -2, ++i);
}
}
else {
lua_createtable(L, 0, columns);
for (i = 0; i < columns; ++i) {
lua_pushstring(L, sqlite3_column_name(vm, i));
vm_push_column(L, vm, i);
lua_rawset(L, -3);
}
}
return 1;
}
else {
lua_checkstack(L, columns);
for (i = 0; i < columns; ++i)
vm_push_column(L, vm, i);
return svm->columns;
}
}
if (svm->temp) {
/* finalize and check for errors */
result = sqlite3_finalize(vm);
svm->vm = NULL;
cleanupvm(L, svm);
}
else if (result == SQLITE_DONE) {
result = sqlite3_reset(vm);
}
if (result != SQLITE_OK) {
lua_pushstring(L, sqlite3_errmsg(svm->db->db));
lua_error(L);
}
return 0;
}
static int db_next_row(lua_State *L) {
return db_do_next_row(L, 0);
}
static int db_next_packed_row(lua_State *L) {
return db_do_next_row(L, 1);
}
static int db_next_named_row(lua_State *L) {
return db_do_next_row(L, 2);
}
static int dbvm_do_rows(lua_State *L, int(*f)(lua_State *)) {
/* sdb_vm *svm = */
lsqlite_checkvm(L, 1);
lua_pushvalue(L,1);
lua_pushcfunction(L, f);
lua_insert(L, -2);
return 2;
}
static int dbvm_rows(lua_State *L) {
return dbvm_do_rows(L, db_next_packed_row);
}
static int dbvm_nrows(lua_State *L) {
return dbvm_do_rows(L, db_next_named_row);
}
static int dbvm_urows(lua_State *L) {
return dbvm_do_rows(L, db_next_row);
}
static int db_do_rows(lua_State *L, int(*f)(lua_State *)) {
sdb *db = lsqlite_checkdb(L, 1);
const char *sql = luaL_checkstring(L, 2);
sdb_vm *svm;
lua_settop(L,2); /* db,sql is on top of stack for call to newvm */
svm = newvm(L, db);
svm->temp = 1;
if (sqlite3_prepare_v2(db->db, sql, -1, &svm->vm, NULL) != SQLITE_OK) {
lua_pushstring(L, sqlite3_errmsg(svm->db->db));
if (cleanupvm(L, svm) == 1)
lua_pop(L, 1); /* this should not happen since sqlite3_prepare_v2 will not set ->vm on error */
lua_error(L);
}
lua_pushcfunction(L, f);
lua_insert(L, -2);
return 2;
}
static int db_rows(lua_State *L) {
return db_do_rows(L, db_next_packed_row);
}
static int db_nrows(lua_State *L) {
return db_do_rows(L, db_next_named_row);
}
/* unpacked version of db:rows */
static int db_urows(lua_State *L) {
return db_do_rows(L, db_next_row);
}
static int db_tostring(lua_State *L) {
char buff[33];
sdb *db = lsqlite_getdb(L, 1);
if (db->db == NULL)
strcpy(buff, "closed");
else
sprintf(buff, "%p", lua_touserdata(L, 1));
lua_pushfstring(L, "sqlite database (%s)", buff);
return 1;
}
static int db_close(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
lua_pushinteger(L, cleanupdb(L, db));
return 1;
}
static int db_close_vm(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
closevms(L, db, lua_toboolean(L, 2));
return 0;
}
static int db_gc(lua_State *L) {
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");
const char *sqlbuf = memcpy(sqlite3_malloc(size), buffer, size);
sqlite3_deserialize(db->db, "main", (void *)sqlbuf, size, size,
SQLITE_DESERIALIZE_FREEONCLOSE + SQLITE_DESERIALIZE_RESIZEABLE);
return 0;
}
#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) {
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_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, 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;
lsqlite_checkdb(L, 1);
const char *cset = luaL_checkstring(L, 2);
int nset = lua_rawlen(L, 2);
int flags = luaL_optinteger(L, 3, 0);
int rc;
if ((rc = sqlite3changeset_start_v2(&p, nset, (void *)cset, flags)) != SQLITE_OK) {
return pusherr(L, rc);
}
(void)lsqlite_makeiter(L, p, 1);
return 1;
}
static int db_invert_changeset(lua_State *L) {
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) {
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_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);
void *cset = (void *)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
** =======================================================
*/
static int lsqlite_version(lua_State *L) {
lua_pushstring(L, sqlite3_libversion());
return 1;
}
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_v2(filename, &db->db, flags, 0) == SQLITE_OK) {
/* database handle already in the stack - return it */
sqlite3_zipfile_init(db->db, 0, 0);
return 1;
}
/* failed to open database */
lua_pushnil(L); /* push nil */
lua_pushinteger(L, sqlite3_errcode(db->db));
lua_pushstring(L, sqlite3_errmsg(db->db)); /* push error message */
/* clean things up */
cleanupdb(L, db);
/* return */
return 3;
}
static int lsqlite_open(lua_State *L) {
const char *filename = luaL_checkstring(L, 1);
int flags = luaL_optinteger(L, 2, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
return lsqlite_do_open(L, filename, flags);
}
static int lsqlite_open_memory(lua_State *L) {
return lsqlite_do_open(L, ":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
}
/*
** 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;
}
/* Version number of this library
*/
static int lsqlite_lversion(lua_State *L) {
lua_pushstring(L, LSQLITE_VERSION);
return 1;
}
/*
** =======================================================
** Register functions
** =======================================================
*/
#define SC(s) { #s, SQLITE_ ## s },
#define LSC(s) { #s, LSQLITE_ ## s },
static const struct {
const char* name;
int value;
} sqlite_constants[] = {
/* error codes */
SC(OK) SC(ERROR) SC(INTERNAL) SC(PERM)
SC(ABORT) SC(BUSY) SC(LOCKED) SC(NOMEM)
SC(READONLY) SC(INTERRUPT) SC(IOERR) SC(CORRUPT)
SC(NOTFOUND) SC(FULL) SC(CANTOPEN) SC(PROTOCOL)
SC(EMPTY) SC(SCHEMA) SC(TOOBIG) SC(CONSTRAINT)
SC(MISMATCH) SC(MISUSE) SC(NOLFS)
SC(FORMAT) SC(NOTADB)
/* sqlite_step specific return values */
SC(RANGE) SC(ROW) SC(DONE)
/* column types */
SC(INTEGER) SC(FLOAT) SC(TEXT) SC(BLOB)
SC(NULL)
/* Authorizer Action Codes */
SC(CREATE_INDEX )
SC(CREATE_TABLE )
SC(CREATE_TEMP_INDEX )
SC(CREATE_TEMP_TABLE )
SC(CREATE_TEMP_TRIGGER)
SC(CREATE_TEMP_VIEW )
SC(CREATE_TRIGGER )
SC(CREATE_VIEW )
SC(DELETE )
SC(DROP_INDEX )
SC(DROP_TABLE )
SC(DROP_TEMP_INDEX )
SC(DROP_TEMP_TABLE )
SC(DROP_TEMP_TRIGGER )
SC(DROP_TEMP_VIEW )
SC(DROP_TRIGGER )
SC(DROP_VIEW )
SC(INSERT )
SC(PRAGMA )
SC(READ )
SC(SELECT )
SC(TRANSACTION )
SC(UPDATE )
SC(ATTACH )
SC(DETACH )
SC(ALTER_TABLE )
SC(REINDEX )
SC(ANALYZE )
SC(CREATE_VTABLE )
SC(DROP_VTABLE )
SC(FUNCTION )
SC(SAVEPOINT )
/* file open flags */
SC(OPEN_READONLY)
SC(OPEN_READWRITE)
SC(OPEN_CREATE)
SC(OPEN_URI)
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(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 }
};
/* ======================================================= */
static const luaL_Reg dblib[] = {
{"isopen", db_isopen },
{"readonly", db_readonly },
{"last_insert_rowid", db_last_insert_rowid },
{"changes", db_changes },
{"total_changes", db_total_changes },
{"errcode", db_errcode },
{"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 },
{"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 },
{"prepare", db_prepare },
{"rows", db_rows },
{"urows", db_urows },
{"nrows", db_nrows },
{"exec", db_exec },
{"execute", db_exec },
{"close", db_close },
{"close_vm", db_close_vm },
#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}
};
static const luaL_Reg vmlib[] = {
{"isopen", dbvm_isopen },
{"readonly", dbvm_readonly },
{"step", dbvm_step },
{"reset", dbvm_reset },
{"finalize", dbvm_finalize },
{"columns", dbvm_columns },
{"bind", dbvm_bind },
{"bind_values", dbvm_bind_values },
{"bind_names", dbvm_bind_names },
{"bind_blob", dbvm_bind_blob },
{"bind_parameter_count",dbvm_bind_parameter_count},
{"bind_parameter_name", dbvm_bind_parameter_name},
{"get_value", dbvm_get_value },
{"get_values", dbvm_get_values },
{"get_name", dbvm_get_name },
{"get_names", dbvm_get_names },
{"get_type", dbvm_get_type },
{"get_types", dbvm_get_types },
{"get_uvalues", dbvm_get_uvalues },
{"get_unames", dbvm_get_unames },
{"get_utypes", dbvm_get_utypes },
{"get_named_values", dbvm_get_named_values },
{"get_named_types", dbvm_get_named_types },
{"rows", dbvm_rows },
{"urows", dbvm_urows },
{"nrows", dbvm_nrows },
{"last_insert_rowid", dbvm_last_insert_rowid },
/* compatibility names (added by request) */
{"idata", dbvm_get_values },
{"inames", dbvm_get_names },
{"itypes", dbvm_get_types },
{"data", dbvm_get_named_values },
{"type", dbvm_get_named_types },
{"__tostring", dbvm_tostring },
{"__gc", dbvm_gc },
{ NULL, NULL }
};
static const luaL_Reg ctxlib[] = {
{"user_data", lcontext_user_data },
{"get_aggregate_data", lcontext_get_aggregate_context },
{"set_aggregate_data", lcontext_set_aggregate_context },
{"result", lcontext_result },
{"result_null", lcontext_result_null },
{"result_number", lcontext_result_double },
{"result_double", lcontext_result_double },
{"result_int", lcontext_result_int },
{"result_text", lcontext_result_text },
{"result_blob", lcontext_result_blob },
{"result_error", lcontext_result_error },
{"__tostring", lcontext_tostring },
{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 },
{"open", lsqlite_open },
{"open_memory", lsqlite_open_memory },
{"config", lsqlite_config },
{"__newindex", lsqlite_newindex },
{NULL, NULL}
};
static void create_meta(lua_State *L, const char *name, const luaL_Reg *lib) {
luaL_newmetatable(L, name);
lua_pushliteral(L, "__index");
lua_pushvalue(L, -2); /* push metatable */
lua_rawset(L, -3); /* metatable.__index = metatable */
/* register metatable functions */
luaL_openlib(L, NULL, lib, 0);
/* 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_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);
{
int i = 0;
/* add constants to global table */
while (sqlite_constants[i].name) {
lua_pushstring(L, sqlite_constants[i].name);
lua_pushinteger(L, sqlite_constants[i].value);
lua_rawset(L, -3);
++i;
}
}
/* set sqlite's metatable to itself - set as readonly (__newindex) */
lua_pushvalue(L, -1);
lua_setmetatable(L, -2);
return 1;
}