mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 03:27:39 +00:00
e16a7d8f3b
`et` means `expandtab`. ```sh rg 'vi: .* :vi' -l -0 | \ xargs -0 sed -i '' 's/vi: \(.*\) et\(.*\) :vi/vi: \1 xoet\2:vi/' rg 'vi: .* :vi' -l -0 | \ xargs -0 sed -i '' 's/vi: \(.*\)noet\(.*\):vi/vi: \1et\2 :vi/' rg 'vi: .* :vi' -l -0 | \ xargs -0 sed -i '' 's/vi: \(.*\)xoet\(.*\):vi/vi: \1noet\2:vi/' ```
2856 lines
85 KiB
C
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.internal.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;
|
|
}
|