local ffi = require "ffi"
ffi.cdef[[
typedef struct sqlite3 sqlite3;
typedef struct sqlite3_stmt sqlite3_stmt;
int sqlite3_open(
const char *filename, /* Database filename (UTF-8) */
sqlite3 **ppDb /* OUT: SQLite db handle */
);
int sqlite3_close(sqlite3*);
int sqlite3_prepare_v2(sqlite3 *conn, const char *zSql, int nByte,
sqlite3_stmt **ppStmt, const char **pzTail);
int sqlite3_bind_null(sqlite3_stmt*, int);
int sqlite3_bind_double(sqlite3_stmt*, int, double);
int sqlite3_bind_text(sqlite3_stmt*,int,const char*,int,void(*)(void*));
int sqlite3_step(sqlite3_stmt*);
int sqlite3_column_count(sqlite3_stmt *pStmt);
const char *sqlite3_column_name(sqlite3_stmt*, int N);
int sqlite3_column_type(sqlite3_stmt*, int iCol);
int sqlite3_column_int(sqlite3_stmt*, int iCol);
double sqlite3_column_double(sqlite3_stmt*, int iCol);
const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
int sqlite3_reset(sqlite3_stmt *pStmt);
int sqlite3_finalize(sqlite3_stmt *pStmt);
]]
local C = ffi.load("sqlite3")
local CODE = {
[0] = "OK", "ERROR", "INTERNAL", "PERM", "ABORT", "BUSY", "LOCKED", "NOMEM",
"READONLY", "INTERRUPT", "IOERR", "CORRUPT", "NOTFOUND", "FULL", "CANTOPEN",
"PROTOCOL", "EMPTY", "SCHEMA", "TOOBIG", "CONSTRAINT", "MISMATCH", "MISUSE",
"NOLFS", "AUTH", "FORMAT", "RANGE", "NOTADB", "NOTICE", "WARNING",
[100] = "ROW", [101] = "DONE"
}
local TYPE = {"INTEGER", "FLOAT", "TEXT", "BLOB", "NULL"}
local DB = {}
DB.__index = DB
function DB:execute(sql, ...)
local pstmt = ffi.new("sqlite3_stmt *[1]")
local res = CODE[C.sqlite3_prepare_v2(self.db, sql, #sql, pstmt, nil)]
if res ~= "OK" then error(sql) end
local stmt = pstmt[0]
local arg = {...}
for i, v in ipairs(arg) do
if type(v) == "nil" then
C.sqlite3_bind_null(stmt, i)
elseif type(v) == "number" then
C.sqlite3_bind_double(stmt, i, v)
elseif type(v) == "string" then
C.sqlite3_bind_text(stmt, i, v, #v, ffi.cast("void(*)(void*)", 0))
else
error(("invalid type for query parameter: %s"):format(type(v)))
end
end
local rows = {}
repeat
local done = true
local res = CODE[C.sqlite3_step(stmt)]
-- TODO: handle res == "BUSY"
if res == "ROW" then
local row = {}
local ncols = C.sqlite3_column_count(stmt)
for i = 0, ncols-1 do
local col_name = ffi.string(C.sqlite3_column_name(stmt, i))
local col_type = TYPE[C.sqlite3_column_type(stmt, i)]
local value
if col_type == "INTEGER" then
value = C.sqlite3_column_int(stmt, i)
elseif col_type == "FLOAT" then
value = C.sqlite3_column_double(stmt, i)
elseif col_type == "TEXT" then
value = ffi.string(C.sqlite3_column_text(stmt, i))
elseif col_type == "BLOB" then
value = C.sqlite3_column_blob(stmt, i)
end
row[col_name] = value
end
table.insert(rows, row)
done = false
end
until done
C.sqlite3_finalize(stmt)
return rows
end
function DB:close()
C.sqlite3_close(self.db)
end
local function open(fname)
local self = setmetatable({}, DB)
local pdb = ffi.new("sqlite3 *[1]")
C.sqlite3_open(fname, pdb)
self.db = pdb[0]
self.err = ffi.new("char *[1]")
return self
end
return {open=open}