Current section

Files

Jump to
exqlite c_src sqlite3_nif.c
Raw

c_src/sqlite3_nif.c

#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <erl_nif.h>
#include <sqlite3.h>
#include "utf8.h"
#define MAX_ATOM_LENGTH 255
#define MAX_PATHNAME 512
static ErlNifResourceType* connection_type = NULL;
static ErlNifResourceType* statement_type = NULL;
typedef struct connection
{
sqlite3* db;
} connection_t;
typedef struct statement
{
sqlite3_stmt* statement;
} statement_t;
static const char*
get_sqlite3_return_code_msg(int rc)
{
switch (rc) {
case SQLITE_OK:
return "ok";
case SQLITE_ERROR:
return "sqlite_error";
case SQLITE_INTERNAL:
return "internal";
case SQLITE_PERM:
return "perm";
case SQLITE_ABORT:
return "abort";
case SQLITE_BUSY:
return "busy";
case SQLITE_LOCKED:
return "locked";
case SQLITE_NOMEM:
return "nomem";
case SQLITE_READONLY:
return "readonly";
case SQLITE_INTERRUPT:
return "interrupt";
case SQLITE_IOERR:
return "ioerr";
case SQLITE_CORRUPT:
return "corrupt";
case SQLITE_NOTFOUND:
return "notfound";
case SQLITE_FULL:
return "full";
case SQLITE_CANTOPEN:
return "cantopen";
case SQLITE_PROTOCOL:
return "protocol";
case SQLITE_EMPTY:
return "empty";
case SQLITE_SCHEMA:
return "schema";
case SQLITE_TOOBIG:
return "toobig";
case SQLITE_CONSTRAINT:
return "constraint";
case SQLITE_MISMATCH:
return "mismatch";
case SQLITE_MISUSE:
return "misuse";
case SQLITE_NOLFS:
return "nolfs";
case SQLITE_AUTH:
return "auth";
case SQLITE_FORMAT:
return "format";
case SQLITE_RANGE:
return "range";
case SQLITE_NOTADB:
return "notadb";
case SQLITE_NOTICE:
return "notice";
case SQLITE_WARNING:
return "warning";
case SQLITE_ROW:
return "row";
case SQLITE_DONE:
return "done";
case SQLITE_ERROR_MISSING_COLLSEQ:
return "error_missing_collseq";
case SQLITE_ERROR_RETRY:
return "error_retry";
case SQLITE_ERROR_SNAPSHOT:
return "error_snapshot";
case SQLITE_IOERR_READ:
return "ioerr_read";
case SQLITE_IOERR_SHORT_READ:
return "ioerr_short_read";
case SQLITE_IOERR_WRITE:
return "ioerr_write";
case SQLITE_IOERR_FSYNC:
return "ioerr_fsync";
case SQLITE_IOERR_DIR_FSYNC:
return "ioerr_dir_fsync";
case SQLITE_IOERR_TRUNCATE:
return "ioerr_truncate";
case SQLITE_IOERR_FSTAT:
return "ioerr_fstat";
case SQLITE_IOERR_UNLOCK:
return "ioerr_unlock";
case SQLITE_IOERR_RDLOCK:
return "ioerr_rdlock";
case SQLITE_IOERR_DELETE:
return "ioerr_delete";
case SQLITE_IOERR_BLOCKED:
return "ioerr_blocked";
case SQLITE_IOERR_NOMEM:
return "ioerr_nomem";
case SQLITE_IOERR_ACCESS:
return "ioerr_access";
case SQLITE_IOERR_CHECKRESERVEDLOCK:
return "ioerr_checkreservedlock";
case SQLITE_IOERR_LOCK:
return "ioerr_lock";
case SQLITE_IOERR_CLOSE:
return "ioerr_close";
case SQLITE_IOERR_DIR_CLOSE:
return "ioerr_dir_close";
case SQLITE_IOERR_SHMOPEN:
return "ioerr_shmopen";
case SQLITE_IOERR_SHMSIZE:
return "ioerr_shmsize";
case SQLITE_IOERR_SHMLOCK:
return "ioerr_shmlock";
case SQLITE_IOERR_SHMMAP:
return "ioerr_shmmap";
case SQLITE_IOERR_SEEK:
return "ioerr_seek";
case SQLITE_IOERR_DELETE_NOENT:
return "ioerr_delete_noent";
case SQLITE_IOERR_MMAP:
return "ioerr_mmap";
case SQLITE_IOERR_GETTEMPPATH:
return "ioerr_gettemppath";
case SQLITE_IOERR_CONVPATH:
return "ioerr_convpath";
case SQLITE_IOERR_VNODE:
return "ioerr_vnode";
case SQLITE_IOERR_AUTH:
return "ioerr_auth";
case SQLITE_IOERR_BEGIN_ATOMIC:
return "ioerr_begin_atomic";
case SQLITE_IOERR_COMMIT_ATOMIC:
return "ioerr_commit_atomic";
case SQLITE_IOERR_ROLLBACK_ATOMIC:
return "ioerr_rollback_atomic";
case SQLITE_IOERR_DATA:
return "ioerr_data";
case SQLITE_IOERR_CORRUPTFS:
return "ioerr_corruptfs";
case SQLITE_LOCKED_SHAREDCACHE:
return "locked_sharedcache";
case SQLITE_LOCKED_VTAB:
return "locked_vtab";
case SQLITE_BUSY_RECOVERY:
return "busy_recovery";
case SQLITE_BUSY_SNAPSHOT:
return "busy_snapshot";
case SQLITE_BUSY_TIMEOUT:
return "busy_timeout";
case SQLITE_CANTOPEN_NOTEMPDIR:
return "cantopen_notempdir";
case SQLITE_CANTOPEN_ISDIR:
return "cantopen_isdir";
case SQLITE_CANTOPEN_FULLPATH:
return "cantopen_fullpath";
case SQLITE_CANTOPEN_CONVPATH:
return "cantopen_convpath";
case SQLITE_CANTOPEN_DIRTYWAL:
return "cantopen_dirtywal";
case SQLITE_CANTOPEN_SYMLINK:
return "cantopen_symlink";
case SQLITE_CORRUPT_VTAB:
return "corrupt_vtab";
case SQLITE_CORRUPT_SEQUENCE:
return "corrupt_sequence";
case SQLITE_CORRUPT_INDEX:
return "corrupt_index";
case SQLITE_READONLY_RECOVERY:
return "readonly_recovery";
case SQLITE_READONLY_CANTLOCK:
return "readonly_cantlock";
case SQLITE_READONLY_ROLLBACK:
return "readonly_rollback";
case SQLITE_READONLY_DBMOVED:
return "readonly_dbmoved";
case SQLITE_READONLY_CANTINIT:
return "readonly_cantinit";
case SQLITE_READONLY_DIRECTORY:
return "readonly_directory";
case SQLITE_ABORT_ROLLBACK:
return "abort_rollback";
case SQLITE_CONSTRAINT_CHECK:
return "constraint_check";
case SQLITE_CONSTRAINT_COMMITHOOK:
return "constraint_commithook";
case SQLITE_CONSTRAINT_FOREIGNKEY:
return "constraint_foreignkey";
case SQLITE_CONSTRAINT_FUNCTION:
return "constraint_function";
case SQLITE_CONSTRAINT_NOTNULL:
return "constraint_notnull";
case SQLITE_CONSTRAINT_PRIMARYKEY:
return "constraint_primarykey";
case SQLITE_CONSTRAINT_TRIGGER:
return "constraint_trigger";
case SQLITE_CONSTRAINT_UNIQUE:
return "constraint_unique";
case SQLITE_CONSTRAINT_VTAB:
return "constraint_vtab";
case SQLITE_CONSTRAINT_ROWID:
return "constraint_rowid";
case SQLITE_CONSTRAINT_PINNED:
return "constraint_pinned";
case SQLITE_NOTICE_RECOVER_WAL:
return "notice_recover_wal";
case SQLITE_NOTICE_RECOVER_ROLLBACK:
return "notice_recover_rollback";
case SQLITE_WARNING_AUTOINDEX:
return "warning_autoindex";
default:
return "unknown";
}
}
static const char*
get_sqlite3_error_msg(int rc, sqlite3* db)
{
if (rc == SQLITE_MISUSE) {
return "Sqlite3 was invoked incorrectly.";
}
const char* message = sqlite3_errmsg(db);
if (!message) {
return "No error message available.";
}
return message;
}
static ERL_NIF_TERM
make_atom(ErlNifEnv* env, const char* atom_name)
{
assert(env);
assert(atom_name);
ERL_NIF_TERM atom;
if (enif_make_existing_atom(env, atom_name, &atom, ERL_NIF_LATIN1)) {
return atom;
}
return enif_make_atom(env, atom_name);
}
static ERL_NIF_TERM
make_ok_tuple(ErlNifEnv* env, ERL_NIF_TERM value)
{
assert(env);
assert(value);
return enif_make_tuple2(env, make_atom(env, "ok"), value);
}
static ERL_NIF_TERM
make_error_tuple(ErlNifEnv* env, const char* reason)
{
assert(env);
assert(reason);
return enif_make_tuple2(env, make_atom(env, "error"), make_atom(env, reason));
}
static ERL_NIF_TERM
make_sqlite3_error_tuple(ErlNifEnv* env, int rc, sqlite3* db)
{
const char* error_code_msg = get_sqlite3_return_code_msg(rc);
const char* msg = get_sqlite3_error_msg(rc, db);
return enif_make_tuple2(
env,
make_atom(env, "error"),
enif_make_tuple2(
env,
make_atom(env, error_code_msg),
enif_make_string(env, msg, ERL_NIF_LATIN1)));
}
static ERL_NIF_TERM
exqlite_open(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(env);
int rc = 0;
int size = 0;
connection_t* conn = NULL;
char filename[MAX_PATHNAME];
ERL_NIF_TERM result;
if (argc != 1) {
return enif_make_badarg(env);
}
size = enif_get_string(env, argv[0], filename, MAX_PATHNAME, ERL_NIF_LATIN1);
if (size <= 0) {
return make_error_tuple(env, "invalid_filename");
}
conn = enif_alloc_resource(connection_type, sizeof(connection_t));
if (!conn) {
return make_error_tuple(env, "out_of_memory");
}
rc = sqlite3_open(filename, &conn->db);
if (rc != SQLITE_OK) {
enif_release_resource(conn);
return make_error_tuple(env, "database_open_failed");
}
sqlite3_busy_timeout(conn->db, 2000);
result = enif_make_resource(env, conn);
enif_release_resource(conn);
return make_ok_tuple(env, result);
}
static ERL_NIF_TERM
exqlite_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(env);
connection_t* conn = NULL;
if (argc != 1) {
return enif_make_badarg(env);
}
if (!enif_get_resource(env, argv[0], connection_type, (void**)&conn)) {
return make_error_tuple(env, "invalid_connection");
}
sqlite3_close_v2(conn->db);
conn->db = NULL;
return make_atom(env, "ok");
}
///
/// @brief Executes an SQL string.
///
static ERL_NIF_TERM
exqlite_execute(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(env);
ErlNifBinary bin;
connection_t* conn = NULL;
ERL_NIF_TERM eos = enif_make_int(env, 0);
int rc = SQLITE_OK;
if (argc != 2) {
return enif_make_badarg(env);
}
if (!enif_get_resource(env, argv[0], connection_type, (void**)&conn)) {
return make_error_tuple(env, "invalid_connection");
}
if (!enif_inspect_iolist_as_binary(env, enif_make_list2(env, argv[1], eos), &bin)) {
return make_error_tuple(env, "sql_not_iolist");
}
enif_inspect_iolist_as_binary(env,
enif_make_list2(env, argv[1], eos),
&bin);
rc = sqlite3_exec(conn->db, (char*)bin.data, NULL, NULL, NULL);
if (rc != SQLITE_OK) {
return make_sqlite3_error_tuple(env, rc, conn->db);
}
return make_atom(env, "ok");
}
///
/// @brief Get the number of changes recently done to the database.
///
static ERL_NIF_TERM
exqlite_changes(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(env);
connection_t* conn = NULL;
if (argc != 1) {
return enif_make_badarg(env);
}
if (!enif_get_resource(env, argv[0], connection_type, (void**)&conn)) {
return make_error_tuple(env, "invalid_connection");
}
int changes = sqlite3_changes(conn->db);
return make_ok_tuple(env, enif_make_int(env, changes));
}
///
/// @brief Prepares an Sqlite3 statement for execution
///
static ERL_NIF_TERM
exqlite_prepare(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(env);
ErlNifBinary bin;
connection_t* conn = NULL;
statement_t* statement = NULL;
ERL_NIF_TERM result;
int rc;
ERL_NIF_TERM eos = enif_make_int(env, 0);
if (argc != 2) {
return enif_make_badarg(env);
}
if (!enif_get_resource(env, argv[0], connection_type, (void**)&conn)) {
return make_error_tuple(env, "invalid_connection");
}
if (!enif_inspect_iolist_as_binary(env, enif_make_list2(env, argv[1], eos), &bin)) {
return make_error_tuple(env, "sql_not_iolist");
}
statement = enif_alloc_resource(statement_type, sizeof(statement_t));
if (!statement) {
return make_error_tuple(env, "out_of_memory");
}
rc = sqlite3_prepare_v3(conn->db, (char*)bin.data, bin.size, 0, &statement->statement, NULL);
if (rc != SQLITE_OK) {
enif_release_resource(statement);
return make_sqlite3_error_tuple(env, rc, conn->db);
}
result = enif_make_resource(env, statement);
enif_release_resource(statement);
return make_ok_tuple(env, result);
}
static int
bind(ErlNifEnv* env, const ERL_NIF_TERM arg, sqlite3_stmt* statement, int index)
{
int the_int;
ErlNifSInt64 the_long_int;
double the_double;
char the_atom[MAX_ATOM_LENGTH + 1];
ErlNifBinary the_blob;
int arity;
const ERL_NIF_TERM* tuple;
if (enif_get_int(env, arg, &the_int)) {
return sqlite3_bind_int(statement, index, the_int);
}
if (enif_get_int64(env, arg, &the_long_int)) {
return sqlite3_bind_int64(statement, index, the_long_int);
}
if (enif_get_double(env, arg, &the_double)) {
return sqlite3_bind_double(statement, index, the_double);
}
if (enif_get_atom(env, arg, the_atom, sizeof(the_atom), ERL_NIF_LATIN1)) {
if (0 == utf8ncmp("undefined", the_atom, 9)) {
return sqlite3_bind_null(statement, index);
}
return sqlite3_bind_text(statement, index, the_atom, utf8len(the_atom), SQLITE_TRANSIENT);
}
if (enif_inspect_iolist_as_binary(env, arg, &the_blob)) {
return sqlite3_bind_text(statement, index, (char*)the_blob.data, the_blob.size, SQLITE_TRANSIENT);
}
if (enif_get_tuple(env, arg, &arity, &tuple)) {
if (arity != 2) {
return -1;
}
if (enif_get_atom(env, tuple[0], the_atom, sizeof(the_atom), ERL_NIF_LATIN1)) {
if (0 == utf8ncmp("blob", the_atom, 4)) {
if (enif_inspect_iolist_as_binary(env, tuple[1], &the_blob)) {
return sqlite3_bind_blob(statement, index, the_blob.data, the_blob.size, SQLITE_TRANSIENT);
}
}
}
}
return -1;
}
///
/// @brief Binds arguments to the sql statement
///
static ERL_NIF_TERM
exqlite_bind(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(env);
unsigned int parameter_count = 0;
unsigned int argument_list_length = 0;
connection_t* conn = NULL;
statement_t* statement = NULL;
ERL_NIF_TERM list;
ERL_NIF_TERM head;
ERL_NIF_TERM tail;
if (argc != 3) {
return enif_make_badarg(env);
}
if (!enif_get_resource(env, argv[0], connection_type, (void**)&conn)) {
return make_error_tuple(env, "invalid_connection");
}
if (!enif_get_resource(env, argv[1], statement_type, (void**)&statement)) {
return make_error_tuple(env, "invalid_statement");
}
if (!enif_get_list_length(env, argv[2], &argument_list_length)) {
return make_error_tuple(env, "bad_argument_list");
}
parameter_count = (unsigned int)sqlite3_bind_parameter_count(statement->statement);
if (parameter_count != argument_list_length) {
return make_error_tuple(env, "arguments_wrong_length");
}
sqlite3_reset(statement->statement);
list = argv[2];
for (unsigned int i = 0; i < argument_list_length; i++) {
enif_get_list_cell(env, list, &head, &tail);
int rc = bind(env, head, statement->statement, i + 1);
if (rc == -1) {
return enif_make_tuple2(
env,
make_atom(env, "error"),
enif_make_tuple2(
env,
make_atom(env, "wrong_type"),
head));
}
if (rc != SQLITE_OK) {
return make_sqlite3_error_tuple(env, rc, conn->db);
}
list = tail;
}
return make_atom(env, "ok");
}
static ERL_NIF_TERM
make_binary(ErlNifEnv* env, const void* bytes, unsigned int size)
{
ErlNifBinary blob;
ERL_NIF_TERM term;
if (!enif_alloc_binary(size, &blob)) {
return make_atom(env, "out_of_memory");
}
memcpy(blob.data, bytes, size);
term = enif_make_binary(env, &blob);
enif_release_binary(&blob);
return term;
}
static ERL_NIF_TERM
make_cell(ErlNifEnv* env, sqlite3_stmt* statement, unsigned int i)
{
size_t len = 0;
switch (sqlite3_column_type(statement, i)) {
case SQLITE_INTEGER:
return enif_make_int64(env, sqlite3_column_int64(statement, i));
case SQLITE_FLOAT:
return enif_make_double(env, sqlite3_column_double(statement, i));
case SQLITE_NULL:
return make_atom(env, "undefined");
case SQLITE_BLOB:
len = sqlite3_column_bytes(statement, i);
return enif_make_tuple2(
env,
make_atom(env, "blob"),
make_binary(env, sqlite3_column_blob(statement, i), len));
case SQLITE_TEXT:
len = sqlite3_column_bytes(statement, i);
return enif_make_tuple2(
env,
make_atom(env, "text"),
make_binary(env, sqlite3_column_text(statement, i), len));
default:
return make_atom(env, "unsupported");
}
}
static ERL_NIF_TERM
make_row(ErlNifEnv* env, sqlite3_stmt* statement)
{
assert(env);
assert(statement);
ERL_NIF_TERM* columns = NULL;
ERL_NIF_TERM row;
unsigned int count = sqlite3_column_count(statement);
columns = enif_alloc(sizeof(ERL_NIF_TERM) * count);
if (!columns) {
return make_error_tuple(env, "out_of_memory");
}
for (unsigned int i = 0; i < count; i++) {
columns[i] = make_cell(env, statement, i);
}
row = enif_make_list_from_array(env, columns, count);
enif_free(columns);
return enif_make_tuple2(env, make_atom(env, "row"), row);
}
static ERL_NIF_TERM
exqlite_step(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(env);
statement_t* statement = NULL;
connection_t* conn = NULL;
if (argc != 2) {
return enif_make_badarg(env);
}
if (!enif_get_resource(env, argv[0], connection_type, (void**)&conn)) {
return make_error_tuple(env, "invalid_connection");
}
if (!enif_get_resource(env, argv[1], statement_type, (void**)&statement)) {
return make_error_tuple(env, "invalid_statement");
}
int rc = sqlite3_step(statement->statement);
switch (rc) {
case SQLITE_ROW:
return make_row(env, statement->statement);
case SQLITE_BUSY:
return make_atom(env, "busy");
case SQLITE_DONE:
return make_atom(env, "done");
default:
return make_sqlite3_error_tuple(env, rc, conn->db);
}
}
static ERL_NIF_TERM
exqlite_columns(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(env);
int size = 0;
statement_t* statement = NULL;
connection_t* conn = NULL;
ERL_NIF_TERM* columns;
ERL_NIF_TERM result;
if (argc != 2) {
return enif_make_badarg(env);
}
if (!enif_get_resource(env, argv[0], connection_type, (void**)&conn)) {
return make_error_tuple(env, "invalid_connection");
}
if (!enif_get_resource(env, argv[1], statement_type, (void**)&statement)) {
return make_error_tuple(env, "invalid_statement");
}
size = sqlite3_column_count(statement->statement);
if (size == 0) {
return make_ok_tuple(env, enif_make_list(env, 0));
} else if (size < 0) {
return make_error_tuple(env, "invalid_column_count");
}
columns = enif_alloc(sizeof(ERL_NIF_TERM) * size);
if (!columns) {
return make_error_tuple(env, "out_of_memory");
}
for (int i = 0; i < size; i++) {
const char* name = sqlite3_column_name(statement->statement, i);
const char* type = sqlite3_column_decltype(statement->statement, i);
if (!name) {
enif_free(columns);
return make_error_tuple(env, "out_of_memory");
}
if (type == NULL) {
type = "nil";
}
columns[i] = enif_make_tuple2(env, make_atom(env, name), make_atom(env, type));
}
result = enif_make_list_from_array(env, columns, size);
enif_free(columns);
return make_ok_tuple(env, result);
}
static ERL_NIF_TERM
exqlite_last_insert_rowid(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
assert(env);
connection_t* conn = NULL;
if (argc != 1) {
return enif_make_badarg(env);
}
if (!enif_get_resource(env, argv[0], connection_type, (void**)&conn)) {
return make_error_tuple(env, "invalid_connection");
}
sqlite3_int64 last_rowid = sqlite3_last_insert_rowid(conn->db);
return make_ok_tuple(env, enif_make_int64(env, last_rowid));
}
static void
connection_type_destructor(ErlNifEnv* env, void* arg)
{
assert(env);
assert(arg);
connection_t* conn = (connection_t*)arg;
if (conn->db) {
sqlite3_close_v2(conn->db);
conn->db = NULL;
}
}
static void
statement_type_destructor(ErlNifEnv* env, void* arg)
{
assert(env);
statement_t* statement = (statement_t*)arg;
sqlite3_finalize(statement->statement);
statement->statement = NULL;
}
static int
on_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
{
assert(env);
connection_type = enif_open_resource_type(
env,
"exqlite",
"connection_type",
connection_type_destructor,
ERL_NIF_RT_CREATE,
NULL);
if (!connection_type) {
return -1;
}
statement_type = enif_open_resource_type(
env,
"exqlite",
"statement_type",
statement_type_destructor,
ERL_NIF_RT_CREATE,
NULL);
if (!statement_type) {
return -1;
}
return 0;
}
//
// Most of our nif functions are going to be IO bounded
//
static ErlNifFunc nif_funcs[] = {
{"open", 1, exqlite_open, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"close", 1, exqlite_close, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"execute", 2, exqlite_execute, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"changes", 1, exqlite_changes, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"prepare", 2, exqlite_prepare, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"bind", 3, exqlite_bind, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"step", 2, exqlite_step, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"columns", 2, exqlite_columns, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"last_insert_rowid", 1, exqlite_last_insert_rowid, ERL_NIF_DIRTY_JOB_IO_BOUND},
};
ERL_NIF_INIT(Elixir.Exqlite.Sqlite3NIF, nif_funcs, on_load, NULL, NULL, NULL)