Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

1442 lines (1279 sloc) 47.198 kb
#include "sqlite3_drv.h"
#include <stdarg.h>
#include <limits.h>
// MSVC needs "__inline" instead of "inline" in C-source files.
#if defined(_MSC_VER)
#define inline __inline
#endif
#ifdef DEBUG
static int DEBUG = 1;
#else
static int DEBUG = 0;
#endif
#define LOG_DEBUG(M, ...) do { \
if (DEBUG && drv->log) \
fprintf(drv->log, "[DEBUG] (%s:%d) " M "\n", __FILE__, __LINE__, __VA_ARGS__); \
} while (0)
#define LOG_ERROR(M, ...) do { \
if (drv->log) \
fprintf(drv->log, "[ERROR] (%s:%d) " M "\n", __FILE__, __LINE__, __VA_ARGS__); \
if (drv->log != stderr) \
fprintf(stderr, "[ERROR] (%s:%d) " M "\n", __FILE__, __LINE__, __VA_ARGS__); \
} while(0)
#define EXTEND_DATASET(n, term_count, term_allocated, dataset) \
term_count += n; \
if (term_count > term_allocated) { \
term_allocated = max(term_count, term_allocated*2); \
dataset = driver_realloc(dataset, sizeof(ErlDrvTermData) * term_allocated); \
}
#define EXTEND_DATASET_DIRECT(n) EXTEND_DATASET(n, term_count, term_allocated, dataset)
#define EXTEND_DATASET_PTR(n) EXTEND_DATASET(n, *term_count_p, *term_allocated_p, *dataset_p)
static void append_to_dataset(int n, ErlDrvTermData* dataset, int term_count, ...) {
int i;
va_list new_terms;
va_start(new_terms, term_count);
for (i = -n; i < 0; i++) {
dataset[term_count + i] = va_arg(new_terms, ErlDrvTermData);
}
va_end(new_terms);
}
static inline ptr_list *add_to_ptr_list(ptr_list *list, void *value_ptr) {
ptr_list* new_node = driver_alloc(sizeof(ptr_list));
new_node->head = value_ptr;
new_node->tail = list;
return new_node;
}
static inline void free_ptr_list(ptr_list *list, void(* free_head)(void *)) {
ptr_list* tail;
while (list) {
tail = list->tail;
(*free_head)(list->head);
driver_free(list);
list = tail;
}
}
#ifndef max // macro in Windows
static inline int max(int a, int b) {
return a >= b ? a : b;
}
#endif
static inline int sql_is_insert(const char *sql) {
// neither strcasestr nor strnicmp are portable, so have to do this
int i;
char *insert = "insert";
for (i = 0; i < 6; i++) {
if ((tolower(sql[i]) != insert[i]) && (sql[i] != ' ')) { return 0; }
}
return 1;
}
#ifdef DEBUG
static void fprint_dataset(FILE* log, ErlDrvTermData* dataset, int term_count);
#endif
// required because driver_free(_binary) are macros in Windows
static void driver_free_fun(void *ptr) {
driver_free(ptr);
}
static void driver_free_binary_fun(void *ptr) {
driver_free_binary((ErlDrvBinary *) ptr);
}
// sdbm from http://www.cse.yorku.ca/~oz/hash.html
unsigned int hash(const char *str) {
unsigned int hash = 0;
unsigned int c = 0;
do {
hash = c + (hash << 6) + (hash << 16) - hash;
c = *str++;
} while (c);
return hash;
}
// Returns a key determined by the file name for an on-disk database,
// determined by the port for a private database.
// This way all access to a single DB will go through one async thread.
static inline unsigned int sql_async_key(char *db_name, ErlDrvPort port) {
const char *memory_db_name = ":memory:";
if (strcmp(db_name, memory_db_name)) {
return hash(db_name);
} else {
#if ERL_DRV_EXTENDED_MAJOR_VERSION > 2 || \
(ERL_DRV_EXTENDED_MAJOR_VERSION == 2 && ERL_DRV_EXTENDED_MINOR_VERSION >= 2)
return driver_async_port_key(port);
#else
return (unsigned int) (uintptr_t) port;
#endif
}
}
static inline int return_error(
sqlite3_drv_t *drv, int error_code, const char *error,
ErlDrvTermData **dataset_p, int *term_count_p, int *term_allocated_p,
int* error_code_p) {
if (error_code_p) {
*error_code_p = error_code;
}
EXTEND_DATASET_PTR(9);
append_to_dataset(9, *dataset_p, *term_count_p,
ERL_DRV_ATOM, drv->atom_error,
ERL_DRV_INT, (ErlDrvTermData) error_code,
ERL_DRV_STRING, (ErlDrvTermData) error, (ErlDrvTermData) strlen(error),
ERL_DRV_TUPLE, (ErlDrvTermData) 3);
// int i;
// for (i = 0; i < *term_count_p; i++) {
// printf("%d\n", (*dataset_p)[i]);
// }
return 0;
}
static inline int output_error(
sqlite3_drv_t *drv, int error_code, const char *error) {
int term_count = 2, term_allocated = 13;
// for some reason breaks if allocated as an array on stack
// even though it shouldn't be extended
ErlDrvTermData *dataset = driver_alloc(sizeof(ErlDrvTermData) * term_allocated);
dataset[0] = ERL_DRV_PORT;
dataset[1] = driver_mk_port(drv->port);
return_error(drv, error_code, error, &dataset, &term_count, &term_allocated, NULL);
term_count += 2;
dataset[11] = ERL_DRV_TUPLE;
dataset[12] = 2;
#ifdef PRE_R16B
driver_output_term(drv->port,
#else
erl_drv_output_term(dataset[1],
#endif
dataset, term_count);
driver_free(dataset);
return 0;
}
static inline int output_db_error(sqlite3_drv_t *drv) {
return output_error(drv, sqlite3_errcode(drv->db), sqlite3_errmsg(drv->db));
}
static inline int output_ok(sqlite3_drv_t *drv) {
// Return {Port, ok}
ErlDrvTermData spec[] = {
ERL_DRV_PORT, driver_mk_port(drv->port),
ERL_DRV_ATOM, drv->atom_ok,
ERL_DRV_TUPLE, 2
};
return
#ifdef PRE_R16B
driver_output_term(drv->port,
#else
erl_drv_output_term(spec[1],
#endif
spec, sizeof(spec) / sizeof(spec[0]));
}
static ErlDrvEntry sqlite3_driver_entry = {
NULL, /* init */
start, /* startup (defined below) */
stop, /* shutdown (defined below) */
NULL, /* output */
NULL, /* ready_input */
NULL, /* ready_output */
"sqlite3_drv", /* the name of the driver */
NULL, /* finish */
NULL, /* handle */
control, /* control */
NULL, /* timeout */
NULL, /* outputv */
ready_async, /* ready_async (defined below) */
NULL, /* flush */
NULL, /* call */
NULL, /* event */
ERL_DRV_EXTENDED_MARKER, /* ERL_DRV_EXTENDED_MARKER */
ERL_DRV_EXTENDED_MAJOR_VERSION, /* ERL_DRV_EXTENDED_MAJOR_VERSION */
ERL_DRV_EXTENDED_MINOR_VERSION, /* ERL_DRV_EXTENDED_MINOR_VERSION */
ERL_DRV_FLAG_USE_PORT_LOCKING, /* ERL_DRV_FLAGs */
NULL /* handle2 */,
NULL /* process_exit */,
NULL /* stop_select */
};
DRIVER_INIT(sqlite3_driver) {
return &sqlite3_driver_entry;
}
// Driver Start
static ErlDrvData start(ErlDrvPort port, char* cmd) {
sqlite3_drv_t* drv = (sqlite3_drv_t*) driver_alloc(sizeof(sqlite3_drv_t));
struct sqlite3 *db = NULL;
int status = 0;
char *db_name = strstr(cmd, " ");
size_t db_name_len;
char *db_name_copy;
#ifdef DEBUG
errno_t file_open_errno;
#ifdef _MSC_VER
const char *log_file = _tempnam(NULL, "erlang-sqlite3-log-");
file_open_errno = fopen_s(drv->log, log_file, "a+");
#else
const char *log_file = tempnam(NULL, "erlang-sqlite3-log-");
drv->log =
fopen(log_file, "ax");
file_open_errno = errno;
#endif
if (file_open_errno) {
fprintf(stderr, "Error creating log file %s; reason %s\n", log_file, strerror(file_open_errno));
// if we can't open the log file we shouldn't hide the data or the problem
drv->log = stderr; // noisy
}
free(log_file);
#else
drv->log = NULL;
#endif
#if defined(_MSC_VER)
#pragma warning(disable: 4306)
#endif
if (!db_name) {
driver_free(drv);
return ERL_DRV_ERROR_BADARG;
} else {
++db_name; // move to first character after ' '
}
// Create and open the database
status = sqlite3_open(db_name, &db);
#if defined(_MSC_VER)
#pragma warning(default: 4306)
#endif
db_name_len = strlen(db_name) + 1; // include terminator
db_name_copy = driver_alloc(sizeof(char) * db_name_len);
strcpy(db_name_copy, db_name);
// Set the state for the driver
drv->port = port;
drv->db = db;
drv->db_name = db_name_copy;
drv->key = sql_async_key(db_name_copy, port);
drv->prepared_stmts = NULL;
drv->prepared_count = 0;
drv->prepared_alloc = 0;
drv->atom_blob = driver_mk_atom("blob");
drv->atom_error = driver_mk_atom("error");
drv->atom_columns = driver_mk_atom("columns");
drv->atom_rows = driver_mk_atom("rows");
drv->atom_null = driver_mk_atom("null");
drv->atom_rowid = driver_mk_atom("rowid");
drv->atom_ok = driver_mk_atom("ok");
drv->atom_done = driver_mk_atom("done");
drv->atom_unknown_cmd = driver_mk_atom("unknown_command");
if (status != SQLITE_OK) {
LOG_ERROR("Unable to open file %s: \"%s\"\n\n", db_name, sqlite3_errmsg(db));
output_db_error(drv);
} else {
LOG_DEBUG("Opened file %s\n", db_name);
output_ok(drv);
}
return (ErlDrvData) drv;
}
// Driver Stop
static void stop(ErlDrvData handle) {
sqlite3_drv_t* drv = (sqlite3_drv_t*) handle;
unsigned int i;
int close_result;
if (drv->prepared_stmts) {
for (i = 0; i < drv->prepared_count; i++) {
sqlite3_finalize(drv->prepared_stmts[i]);
}
driver_free(drv->prepared_stmts);
}
close_result = sqlite3_close(drv->db);
if (close_result != SQLITE_OK) {
LOG_ERROR("Failed to close DB %s, some resources aren't finalized!", drv->db_name);
}
if (drv->log && (drv->log != stderr)) {
fclose(drv->log);
}
driver_free(drv->db_name);
driver_free(drv);
}
// Handle input from Erlang VM
static ErlDrvSSizeT control(
ErlDrvData drv_data, unsigned int command, char *buf,
ErlDrvSizeT len, char **rbuf, ErlDrvSizeT rlen) {
sqlite3_drv_t* drv = (sqlite3_drv_t*) drv_data;
if (len > INT_MAX) {
output_error(drv, SQLITE_MISUSE, "Command size doesn't fit into int type");
} else {
switch (command) {
case CMD_SQL_EXEC:
sql_exec(drv, buf, (int) len);
break;
case CMD_SQL_BIND_AND_EXEC:
sql_bind_and_exec(drv, buf, (int) len);
break;
case CMD_PREPARE:
prepare(drv, buf, (int) len);
break;
case CMD_PREPARED_BIND:
prepared_bind(drv, buf, (int) len);
break;
case CMD_PREPARED_STEP:
prepared_step(drv, buf, (int) len);
break;
case CMD_PREPARED_RESET:
prepared_reset(drv, buf, (int) len);
break;
case CMD_PREPARED_CLEAR_BINDINGS:
prepared_clear_bindings(drv, buf, (int) len);
break;
case CMD_PREPARED_FINALIZE:
prepared_finalize(drv, buf, (int) len);
break;
case CMD_PREPARED_COLUMNS:
prepared_columns(drv, buf, (int) len);
break;
case CMD_SQL_EXEC_SCRIPT:
sql_exec_script(drv, buf, (int) len);
break;
case CMD_ENABLE_LOAD_EXTENSION:
enable_load_extension(drv, buf, (int) len);
break;
case CMD_CHANGES:
changes(drv, buf, (int) len);
break;
default:
unknown(drv, buf, (int) len);
}
}
return 0;
}
static int changes(sqlite3_drv_t *drv, char *buf, int len) {
int changes = sqlite3_changes(drv->db);
ErlDrvTermData spec[6];
spec[0] = ERL_DRV_PORT;
spec[1] = driver_mk_port(drv->port);
spec[2] = ERL_DRV_UINT;
spec[3] = changes;
spec[4] = ERL_DRV_TUPLE;
spec[5] = 2;
return
#ifdef PRE_R16B
driver_output_term(drv->port,
#else
erl_drv_output_term(spec[1],
#endif
spec, sizeof(spec) / sizeof(spec[0]));
}
static int enable_load_extension(sqlite3_drv_t* drv, char *buf, int len) {
#ifdef ERLANG_SQLITE3_LOAD_EXTENSION
char enable = buf[0];
int result = sqlite3_enable_load_extension(drv->db, (int) enable);
if (result) {
output_db_error(drv);
return result;
} else {
output_ok(drv);
return 0;
}
#else
output_error(drv, SQLITE_MISUSE, "extension loading not enabled, recompile erlang-sqlite3 with ERLANG_SQLITE3_LOAD_EXTENSION defined");
return -1;
#endif
}
static inline async_sqlite3_command *make_async_command_statement(
sqlite3_drv_t *drv, sqlite3_stmt *statement, int finalize) {
async_sqlite3_command *result =
(async_sqlite3_command *) driver_alloc(sizeof(async_sqlite3_command));
memset(result, 0, sizeof(async_sqlite3_command));
result->driver_data = drv;
result->type = t_stmt;
result->statement = statement;
result->finalize_statement_on_free = finalize;
return result;
}
static inline async_sqlite3_command *make_async_command_script(
sqlite3_drv_t *drv, char *script, int script_length) {
async_sqlite3_command *result =
(async_sqlite3_command *) driver_alloc(sizeof(async_sqlite3_command));
char *script_copy = driver_alloc(sizeof(char) * script_length);
memset(result, 0, sizeof(async_sqlite3_command));
memcpy(script_copy, script, sizeof(char) * script_length);
result->driver_data = drv;
result->type = t_script;
result->script = script_copy;
result->end = script_copy + script_length;
return result;
}
static inline void exec_async_command(
sqlite3_drv_t *drv, void (*async_invoke)(void*),
async_sqlite3_command *async_command) {
// Check is required because we are sometimes accessing
// sqlite3 from the emulator thread. Could also be fixed
// by making _all_ access except start/stop go through driver_async
if (sqlite3_threadsafe()) {
long status = driver_async(drv->port, &drv->key, async_invoke,
async_command, sql_free_async);
// see https://groups.google.com/d/msg/erlang-programming/XiFR6xxhGos/B6ARBIlvpMUJ
if (status < 0) {
LOG_ERROR("driver_async call failed", 0);
output_error(drv, SQLITE_ERROR, "driver_async call failed");
}
} else {
async_invoke(async_command);
ready_async((ErlDrvData) drv, (ErlDrvThreadData) async_command);
}
}
static inline int sql_exec_statement(
sqlite3_drv_t *drv, sqlite3_stmt *statement) {
async_sqlite3_command *async_command = make_async_command_statement(drv, statement, 1);
LOG_DEBUG("Driver async: %d %p\n", SQLITE_VERSION_NUMBER, async_command->statement);
exec_async_command(drv, sql_exec_async, async_command);
return 0;
}
static int sql_exec(sqlite3_drv_t *drv, char *command, int command_size) {
int result;
const char *rest;
sqlite3_stmt *statement;
LOG_DEBUG("Preexec: %.*s\n", command_size, command);
result = sqlite3_prepare_v2(drv->db, command, command_size, &statement, &rest);
if (result != SQLITE_OK) {
return output_db_error(drv);
} else if (statement == NULL) {
return output_error(drv, SQLITE_MISUSE, "empty statement");
}
return sql_exec_statement(drv, statement);
}
static int sql_exec_script(sqlite3_drv_t *drv, char *command, int command_size) {
async_sqlite3_command *async_command = make_async_command_script(drv, command, command_size);
LOG_DEBUG("Driver async: %d %p\n", SQLITE_VERSION_NUMBER, async_command->statement);
exec_async_command(drv, sql_exec_async, async_command);
return 0;
}
static inline int decode_and_bind_param(
sqlite3_drv_t *drv, char *buffer, int *p_index,
sqlite3_stmt *statement, int param_index, int *p_type, int *p_size) {
int result;
sqlite3_int64 int64_val;
double double_val;
char* char_buf_val;
long bin_size;
ei_get_type(buffer, p_index, p_type, p_size);
switch (*p_type) {
case ERL_SMALL_INTEGER_EXT:
case ERL_INTEGER_EXT:
case ERL_SMALL_BIG_EXT:
case ERL_LARGE_BIG_EXT:
ei_decode_longlong(buffer, p_index, &int64_val);
result = sqlite3_bind_int64(statement, param_index, int64_val);
break;
case ERL_FLOAT_EXT:
#ifdef NEW_FLOAT_EXT
case NEW_FLOAT_EXT: // what's the difference?
#endif
ei_decode_double(buffer, p_index, &double_val);
result = sqlite3_bind_double(statement, param_index, double_val);
break;
case ERL_ATOM_EXT:
// include space for null separator
char_buf_val = driver_alloc((*p_size + 1) * sizeof(char));
ei_decode_atom(buffer, p_index, char_buf_val);
if (strncmp(char_buf_val, "null", 5) == 0) {
result = sqlite3_bind_null(statement, param_index);
} else {
output_error(drv, SQLITE_MISUSE, "Non-null atom as parameter");
return 1;
}
break;
case ERL_STRING_EXT:
// include space for null separator
char_buf_val = driver_alloc((*p_size + 1) * sizeof(char));
ei_decode_string(buffer, p_index, char_buf_val);
result = sqlite3_bind_text(statement, param_index, char_buf_val, *p_size, &driver_free_fun);
break;
case ERL_BINARY_EXT:
char_buf_val = driver_alloc(*p_size * sizeof(char));
ei_decode_binary(buffer, p_index, char_buf_val, &bin_size);
result = sqlite3_bind_text(statement, param_index, char_buf_val, *p_size, &driver_free_fun);
break;
case ERL_SMALL_TUPLE_EXT:
// assume this is {blob, Blob}
ei_get_type(buffer, p_index, p_type, p_size);
ei_decode_tuple_header(buffer, p_index, p_size);
if (*p_size != 2) {
output_error(drv, SQLITE_MISUSE, "bad parameter type");
return 1;
}
ei_skip_term(buffer, p_index); // skipped the atom 'blob'
ei_get_type(buffer, p_index, p_type, p_size);
if (*p_type != ERL_BINARY_EXT) {
output_error(drv, SQLITE_MISUSE, "bad parameter type");
return 1;
}
char_buf_val = driver_alloc(*p_size * sizeof(char));
ei_decode_binary(buffer, p_index, char_buf_val, &bin_size);
result = sqlite3_bind_blob(statement, param_index, char_buf_val, *p_size, &driver_free_fun);
break;
default:
output_error(drv, SQLITE_MISUSE, "bad parameter type");
return 1;
}
if (result != SQLITE_OK) {
output_db_error(drv);
return result;
}
return SQLITE_OK;
}
static int bind_parameters(
sqlite3_drv_t *drv, char *buffer, int buffer_size, int *p_index,
sqlite3_stmt *statement, int *p_type, int *p_size) {
// decoding parameters
int i, cur_list_size = -1, param_index = 1, param_indices_are_explicit = 0, result = 0;
long param_index_long;
char param_name[MAXATOMLEN + 1]; // parameter names shouldn't be longer than 256!
char *acc_string;
result = ei_decode_list_header(buffer, p_index, &cur_list_size);
if (result) {
// probably all parameters are integers between 0 and 255
// and the list was encoded as string (see ei documentation)
ei_get_type(buffer, p_index, p_type, p_size);
if (*p_type != ERL_STRING_EXT) {
return output_error(drv, SQLITE_ERROR,
"error while binding parameters");
}
acc_string = driver_alloc(sizeof(char) * (*p_size + 1));
ei_decode_string(buffer, p_index, acc_string);
for (param_index = 1; param_index <= *p_size; param_index++) {
sqlite3_bind_int(statement, param_index, (int) (unsigned char) acc_string[param_index - 1]);
}
driver_free(acc_string);
return 0;
}
for (i = 0; i < cur_list_size; i++) {
if (*p_index >= buffer_size) {
return output_error(drv, SQLITE_ERROR,
"error while binding parameters");
}
ei_get_type(buffer, p_index, p_type, p_size);
if (*p_type == ERL_SMALL_TUPLE_EXT) {
int old_index = *p_index;
// param with name or explicit index
param_indices_are_explicit = 1;
if (*p_size != 2) {
return output_error(drv, SQLITE_MISUSE,
"tuple should contain index or name, and value");
}
ei_decode_tuple_header(buffer, p_index, p_size);
ei_get_type(buffer, p_index, p_type, p_size);
// first element of tuple is int (index), atom, or string (name)
switch (*p_type) {
case ERL_SMALL_INTEGER_EXT:
case ERL_INTEGER_EXT:
ei_decode_long(buffer, p_index, &param_index_long);
param_index = param_index_long;
break;
case ERL_ATOM_EXT:
ei_decode_atom(buffer, p_index, param_name);
// insert zero terminator
param_name[*p_size] = '\0';
if (strncmp(param_name, "blob", 5) == 0) {
// this isn't really a parameter name!
*p_index = old_index;
param_indices_are_explicit = 0;
goto IMPLICIT_INDEX; // yuck
} else {
param_index = sqlite3_bind_parameter_index(statement, param_name);
}
break;
case ERL_STRING_EXT:
if (*p_size >= MAXATOMLEN) {
return output_error(drv, SQLITE_TOOBIG, "parameter name too long");
}
ei_decode_string(buffer, p_index, param_name);
// insert zero terminator
param_name[*p_size] = '\0';
param_index = sqlite3_bind_parameter_index(statement, param_name);
break;
default:
return output_error(drv, SQLITE_MISMATCH,
"parameter index must be given as integer, atom, or string");
}
result = decode_and_bind_param(
drv, buffer, p_index, statement, param_index, p_type, p_size);
if (result != SQLITE_OK) {
return result; // error has already been output
}
}
else {
IMPLICIT_INDEX:
if (param_indices_are_explicit) {
return output_error(drv, SQLITE_MISUSE,
"parameters without indices shouldn't follow indexed or named parameters");
}
result = decode_and_bind_param(
drv, buffer, p_index, statement, param_index, p_type, p_size);
if (result != SQLITE_OK) {
return result; // error has already been output
}
++param_index;
}
}
return result;
}
static void get_columns(
sqlite3_drv_t *drv, sqlite3_stmt *statement, int column_count, int base,
int *term_count_p, int *term_allocated_p, ptr_list** p_ptrs, ErlDrvTermData **dataset_p) {
int i;
EXTEND_DATASET_PTR(column_count * 3 + 3);
for (i = 0; i < column_count; i++) {
const char *column_name = sqlite3_column_name(statement, i);
size_t column_name_length = strlen(column_name);
char *column_name_copy = driver_alloc(sizeof(char) * (column_name_length + 1));
strcpy(column_name_copy, column_name);
*p_ptrs = add_to_ptr_list(*p_ptrs, column_name_copy);
LOG_DEBUG("Column: %s\n", column_name_copy);
(*dataset_p)[base + (i * 3)] = ERL_DRV_STRING;
(*dataset_p)[base + (i * 3) + 1] = (ErlDrvTermData) column_name_copy;
(*dataset_p)[base + (i * 3) + 2] = column_name_length;
}
(*dataset_p)[base + column_count * 3 + 0] = ERL_DRV_NIL;
(*dataset_p)[base + column_count * 3 + 1] = ERL_DRV_LIST;
(*dataset_p)[base + column_count * 3 + 2] = column_count + 1;
}
static int sql_bind_and_exec(sqlite3_drv_t *drv, char *buffer, int buffer_size) {
int result;
int index = 0;
int type, size;
const char *rest;
sqlite3_stmt *statement;
long bin_size;
char *command;
LOG_DEBUG("Preexec: %.*s\n", buffer_size, buffer);
ei_decode_version(buffer, &index, NULL);
result = ei_decode_tuple_header(buffer, &index, &size);
if (result || (size != 2)) {
return output_error(drv, SQLITE_MISUSE,
"Expected a tuple of SQL command and params");
}
// decode SQL statement
ei_get_type(buffer, &index, &type, &size);
// TODO support any iolists
if (type != ERL_BINARY_EXT) {
return output_error(drv, SQLITE_MISUSE,
"SQL should be sent as an Erlang binary");
}
command = driver_alloc(size * sizeof(char));
ei_decode_binary(buffer, &index, command, &bin_size);
// assert(bin_size == size)
result = sqlite3_prepare_v2(drv->db, command, size, &statement, &rest);
driver_free(command);
if (result != SQLITE_OK) {
return output_db_error(drv);
} else if (statement == NULL) {
return output_error(drv, SQLITE_MISUSE, "empty statement");
}
result = bind_parameters(drv, buffer, buffer_size, &index, statement, &type, &size);
if (result == SQLITE_OK) {
return sql_exec_statement(drv, statement);
} else {
sqlite3_finalize(statement);
return result; // error has already been output
}
}
static void sql_free_async(void *_async_command) {
async_sqlite3_command *async_command =
(async_sqlite3_command *) _async_command;
driver_free(async_command->dataset);
free_ptr_list(async_command->ptrs, &driver_free_fun);
free_ptr_list(async_command->binaries, &driver_free_binary_fun);
if ((async_command->type == t_stmt) &&
async_command->finalize_statement_on_free &&
async_command->statement) {
sqlite3_finalize(async_command->statement);
async_command->statement = NULL;
} else if (async_command->type == t_script) {
driver_free(async_command->script);
}
driver_free(async_command);
}
static int sql_exec_one_statement(
sqlite3_stmt *statement, async_sqlite3_command *async_command,
int *term_count_p, int *term_allocated_p, ErlDrvTermData **dataset_p) {
int column_count = sqlite3_column_count(statement);
int row_count = 0, next_row;
int base_term_count;
int has_error = 0; // bool
sqlite3_drv_t *drv = async_command->driver_data;
ptr_list **ptrs_p = &(async_command->ptrs);
ptr_list **binaries_p = &(async_command->binaries);
// printf("\nsql_exec_one_statement. SQL:\n%s\n Term count: %d, terms alloc: %d\n", sqlite3_sql(statement), *term_count_p, *term_allocated_p);
int i;
if (column_count > 0) {
EXTEND_DATASET_PTR(2);
append_to_dataset(2, *dataset_p, *term_count_p, ERL_DRV_ATOM, drv->atom_columns);
base_term_count = *term_count_p;
get_columns(
drv, statement, column_count, base_term_count, term_count_p, term_allocated_p, ptrs_p, dataset_p);
EXTEND_DATASET_PTR(4);
append_to_dataset(4, *dataset_p, base_term_count + column_count * 3 + 7,
ERL_DRV_TUPLE, (ErlDrvTermData) 2, ERL_DRV_ATOM, drv->atom_rows);
}
LOG_DEBUG("Exec: %s\n", sqlite3_sql(statement));
while ((next_row = sqlite3_step(statement)) == SQLITE_ROW) {
for (i = 0; i < column_count; i++) {
LOG_DEBUG("Column %d type: %d\n", i, sqlite3_column_type(statement, i));
switch (sqlite3_column_type(statement, i)) {
case SQLITE_INTEGER: {
ErlDrvSInt64 *int64_ptr = driver_alloc(sizeof(ErlDrvSInt64));
*int64_ptr = (ErlDrvSInt64) sqlite3_column_int64(statement, i);
*ptrs_p = add_to_ptr_list(*ptrs_p, int64_ptr);
EXTEND_DATASET_PTR(2);
append_to_dataset(2, *dataset_p, *term_count_p, ERL_DRV_INT64, (ErlDrvTermData) int64_ptr);
break;
}
case SQLITE_FLOAT: {
double *float_ptr = driver_alloc(sizeof(double));
*float_ptr = sqlite3_column_double(statement, i);
*ptrs_p = add_to_ptr_list(*ptrs_p, float_ptr);
EXTEND_DATASET_PTR(2);
append_to_dataset(2, *dataset_p, *term_count_p, ERL_DRV_FLOAT, (ErlDrvTermData) float_ptr);
break;
}
case SQLITE_BLOB: {
int bytes = sqlite3_column_bytes(statement, i);
ErlDrvBinary* binary = driver_alloc_binary(bytes);
binary->orig_size = bytes;
memcpy(binary->orig_bytes,
sqlite3_column_blob(statement, i), bytes);
*binaries_p = add_to_ptr_list(*binaries_p, binary);
EXTEND_DATASET_PTR(8);
append_to_dataset(8, *dataset_p, *term_count_p,
ERL_DRV_ATOM, drv->atom_blob,
ERL_DRV_BINARY, (ErlDrvTermData) binary, (ErlDrvTermData) bytes, (ErlDrvTermData) 0,
ERL_DRV_TUPLE, (ErlDrvTermData) 2);
break;
}
case SQLITE_TEXT: {
int bytes = sqlite3_column_bytes(statement, i);
ErlDrvBinary* binary = driver_alloc_binary(bytes);
binary->orig_size = bytes;
memcpy(binary->orig_bytes,
sqlite3_column_blob(statement, i), bytes);
*binaries_p = add_to_ptr_list(*binaries_p, binary);
EXTEND_DATASET_PTR(4);
append_to_dataset(4, *dataset_p, *term_count_p,
ERL_DRV_BINARY, (ErlDrvTermData) binary, (ErlDrvTermData) bytes, (ErlDrvTermData) 0);
break;
}
case SQLITE_NULL: {
EXTEND_DATASET_PTR(2);
append_to_dataset(2, *dataset_p, *term_count_p, ERL_DRV_ATOM, drv->atom_null);
break;
}
}
}
EXTEND_DATASET_PTR(2);
append_to_dataset(2, *dataset_p, *term_count_p, ERL_DRV_TUPLE, (ErlDrvTermData) column_count);
row_count++;
}
if (next_row != SQLITE_DONE) {
if (column_count == 0) {
return_error(drv, next_row, sqlite3_errmsg(drv->db),
dataset_p, term_count_p,
term_allocated_p, &async_command->error_code);
async_command->finalize_statement_on_free = 1;
return 1;
} else {
has_error = 1;
}
}
if (column_count > 0) {
EXTEND_DATASET_PTR(5);
append_to_dataset(5, *dataset_p, *term_count_p,
ERL_DRV_NIL, ERL_DRV_LIST, (ErlDrvTermData) (row_count + 1),
ERL_DRV_TUPLE, (ErlDrvTermData) 2);
if (has_error) {
return_error(drv, next_row, sqlite3_errmsg(drv->db),
dataset_p, term_count_p,
term_allocated_p, &async_command->error_code);
}
EXTEND_DATASET_PTR(3);
append_to_dataset(3, *dataset_p, *term_count_p,
ERL_DRV_NIL, ERL_DRV_LIST, (ErlDrvTermData) (3 + has_error));
} else if (sql_is_insert(sqlite3_sql(statement))) {
ErlDrvSInt64 *rowid_ptr = driver_alloc(sizeof(ErlDrvSInt64));
*rowid_ptr = (ErlDrvSInt64) sqlite3_last_insert_rowid(drv->db);
*ptrs_p = add_to_ptr_list(*ptrs_p, rowid_ptr);
EXTEND_DATASET_PTR(6);
append_to_dataset(6, *dataset_p, *term_count_p,
ERL_DRV_ATOM, drv->atom_rowid,
ERL_DRV_INT64, (ErlDrvTermData) rowid_ptr,
ERL_DRV_TUPLE, (ErlDrvTermData) 2);
} else {
EXTEND_DATASET_PTR(2);
append_to_dataset(2, *dataset_p, *term_count_p, ERL_DRV_ATOM, drv->atom_ok);
}
LOG_DEBUG("Total term count: %p %d, rows count: %dx%d\n",
statement, *term_count_p, column_count, row_count);
async_command->finalize_statement_on_free = 1;
return has_error;
}
static void sql_exec_async(void *_async_command) {
async_sqlite3_command *async_command = (async_sqlite3_command *) _async_command;
sqlite3_stmt *statement = NULL;
int result;
const char *rest;
const char *end;
int num_statements = 0;
int term_count = 0, term_allocated = 0;
ErlDrvTermData *dataset = NULL;
sqlite3_drv_t *drv = async_command->driver_data;
EXTEND_DATASET_DIRECT(2);
append_to_dataset(2, dataset, term_count, ERL_DRV_PORT, driver_mk_port(drv->port));
switch (async_command->type) {
case t_stmt:
statement = async_command->statement;
sql_exec_one_statement(statement, async_command, &term_count,
&term_allocated, &dataset);
break;
case t_script:
rest = async_command->script;
end = async_command->end;
while ((rest < end) && !(async_command->error_code)) {
result = sqlite3_prepare_v2(drv->db, rest, (int) (end - rest), &statement, &rest);
if (result != SQLITE_OK) {
// sqlite doc says statement will be NULL here, so no need to finalize it
num_statements++;
return_error(drv, result, sqlite3_errmsg(drv->db), &dataset,
&term_count, &term_allocated, &async_command->error_code);
break;
} else if (statement == NULL) {
// the script has completed
break;
} else {
num_statements++;
result = sql_exec_one_statement(statement, async_command, &term_count,
&term_allocated, &dataset);
sqlite3_finalize(statement);
if (result) {
break;
}
}
}
EXTEND_DATASET_DIRECT(3);
append_to_dataset(3, dataset, term_count,
ERL_DRV_NIL, ERL_DRV_LIST, (ErlDrvTermData) (num_statements + 1));
}
EXTEND_DATASET_DIRECT(2);
append_to_dataset(2, dataset, term_count, ERL_DRV_TUPLE, (ErlDrvTermData) 2);
// print_dataset(dataset, term_count);
async_command->term_count = term_count;
async_command->term_allocated = term_allocated;
async_command->dataset = dataset;
}
static void sql_step_async(void *_async_command) {
async_sqlite3_command *async_command = (async_sqlite3_command *) _async_command;
int term_count = 0;
int term_allocated = 0;
ErlDrvTermData *dataset = NULL;
sqlite3_drv_t *drv = async_command->driver_data;
int column_count = 0;
sqlite3_stmt *statement = async_command->statement;
ptr_list *ptrs = NULL;
ptr_list *binaries = NULL;
int i;
int result;
switch(result = sqlite3_step(statement)) {
case SQLITE_ROW:
column_count = sqlite3_column_count(statement);
EXTEND_DATASET_DIRECT(2);
append_to_dataset(2, dataset, term_count, ERL_DRV_PORT, driver_mk_port(drv->port));
for (i = 0; i < column_count; i++) {
LOG_DEBUG("Column %d type: %d\n", i, sqlite3_column_type(statement, i));
switch (sqlite3_column_type(statement, i)) {
case SQLITE_INTEGER: {
ErlDrvSInt64 *int64_ptr = driver_alloc(sizeof(ErlDrvSInt64));
*int64_ptr = (ErlDrvSInt64) sqlite3_column_int64(statement, i);
ptrs = add_to_ptr_list(ptrs, int64_ptr);
EXTEND_DATASET_DIRECT(2);
append_to_dataset(2, dataset, term_count, ERL_DRV_INT64, (ErlDrvTermData) int64_ptr);
break;
}
case SQLITE_FLOAT: {
double *float_ptr = driver_alloc(sizeof(double));
*float_ptr = sqlite3_column_double(statement, i);
ptrs = add_to_ptr_list(ptrs, float_ptr);
EXTEND_DATASET_DIRECT(2);
append_to_dataset(2, dataset, term_count, ERL_DRV_FLOAT, (ErlDrvTermData) float_ptr);
break;
}
case SQLITE_BLOB: {
int bytes = sqlite3_column_bytes(statement, i);
ErlDrvBinary* binary = driver_alloc_binary(bytes);
binary->orig_size = bytes;
memcpy(binary->orig_bytes,
sqlite3_column_blob(statement, i), bytes);
binaries = add_to_ptr_list(binaries, binary);
EXTEND_DATASET_DIRECT(8);
append_to_dataset(8, dataset, term_count,
ERL_DRV_ATOM, drv->atom_blob,
ERL_DRV_BINARY, (ErlDrvTermData) binary, (ErlDrvTermData) bytes, (ErlDrvTermData) 0,
ERL_DRV_TUPLE, (ErlDrvTermData) 2);
break;
}
case SQLITE_TEXT: {
int bytes = sqlite3_column_bytes(statement, i);
ErlDrvBinary* binary = driver_alloc_binary(bytes);
binary->orig_size = bytes;
memcpy(binary->orig_bytes,
sqlite3_column_blob(statement, i), bytes);
binaries = add_to_ptr_list(binaries, binary);
EXTEND_DATASET_DIRECT(4);
append_to_dataset(4, dataset, term_count,
ERL_DRV_BINARY, (ErlDrvTermData) binary, (ErlDrvTermData) bytes, (ErlDrvTermData) 0);
break;
}
case SQLITE_NULL: {
EXTEND_DATASET_DIRECT(2);
append_to_dataset(2, dataset, term_count, ERL_DRV_ATOM, drv->atom_null);
break;
}
}
}
EXTEND_DATASET_DIRECT(2);
append_to_dataset(2, dataset, term_count, ERL_DRV_TUPLE, (ErlDrvTermData) column_count);
async_command->ptrs = ptrs;
async_command->binaries = binaries;
break;
case SQLITE_DONE:
EXTEND_DATASET_DIRECT(4);
append_to_dataset(4, dataset, term_count,
ERL_DRV_PORT, driver_mk_port(drv->port),
ERL_DRV_ATOM, drv->atom_done);
sqlite3_reset(statement);
break;
case SQLITE_BUSY:
return_error(drv, SQLITE_BUSY, "SQLite3 database is busy",
&dataset, &term_count, &term_allocated,
&async_command->error_code);
sqlite3_reset(statement);
goto POPULATE_COMMAND;
break;
default:
return_error(drv, result, sqlite3_errmsg(drv->db),
&dataset, &term_count, &term_allocated,
&async_command->error_code);
sqlite3_reset(statement);
goto POPULATE_COMMAND;
}
EXTEND_DATASET_DIRECT(2);
append_to_dataset(2, dataset, term_count, ERL_DRV_TUPLE, (ErlDrvTermData) 2);
POPULATE_COMMAND:
async_command->dataset = dataset;
async_command->term_count = term_count;
async_command->ptrs = ptrs;
async_command->binaries = binaries;
async_command->row_count = 1;
LOG_DEBUG("Total term count: %p %d, columns count: %d\n", statement, term_count, column_count);
}
static void ready_async(ErlDrvData drv_data, ErlDrvThreadData thread_data) {
async_sqlite3_command *async_command = (async_sqlite3_command *) thread_data;
sqlite3_drv_t *drv = async_command->driver_data;
int res =
#ifdef PRE_R16B
driver_output_term(drv->port,
#else
erl_drv_output_term(driver_mk_port(drv->port),
#endif
async_command->dataset,
async_command->term_count);
if (res != 1) {
LOG_DEBUG("driver_output_term returned %d\n", res);
#ifdef DEBUG
fprint_dataset(drv->log, async_command->dataset, async_command->term_count);
#endif
}
LOG_DEBUG("Total term count: %p %d, rows count: %d (%d)\n", async_command->statement, async_command->term_count, async_command->row_count, res);
sql_free_async(async_command);
}
static int prepare(sqlite3_drv_t *drv, char *command, int command_size) {
int result;
const char *rest;
sqlite3_stmt *statement;
ErlDrvTermData spec[6];
LOG_DEBUG("Preparing statement: %.*s\n", command_size, command);
result = sqlite3_prepare_v2(drv->db, command, command_size, &statement, &rest);
if (result != SQLITE_OK) {
return output_db_error(drv);
} else if (statement == NULL) {
return output_error(drv, SQLITE_MISUSE, "empty statement");
}
if (drv->prepared_count >= drv->prepared_alloc) {
drv->prepared_alloc =
(drv->prepared_alloc != 0) ? 2*drv->prepared_alloc : 4;
drv->prepared_stmts =
driver_realloc(drv->prepared_stmts,
drv->prepared_alloc * sizeof(sqlite3_stmt *));
}
drv->prepared_stmts[drv->prepared_count] = statement;
drv->prepared_count++;
spec[0] = ERL_DRV_PORT;
spec[1] = driver_mk_port(drv->port);
spec[2] = ERL_DRV_UINT;
spec[3] = drv->prepared_count - 1;
spec[4] = ERL_DRV_TUPLE;
spec[5] = 2;
return
#ifdef PRE_R16B
driver_output_term(drv->port,
#else
erl_drv_output_term(spec[1],
#endif
spec, sizeof(spec) / sizeof(spec[0]));
}
static int prepared_bind(sqlite3_drv_t *drv, char *buffer, int buffer_size) {
int result;
unsigned int prepared_index;
long long_prepared_index;
int index = 0, type, size;
sqlite3_stmt *statement;
LOG_DEBUG("Finalizing prepared statement: %.*s\n", buffer_size, buffer);
ei_decode_version(buffer, &index, NULL);
ei_decode_tuple_header(buffer, &index, &size);
// assert(size == 2);
ei_decode_long(buffer, &index, &long_prepared_index);
prepared_index = (unsigned int) long_prepared_index;
if (prepared_index >= drv->prepared_count) {
return output_error(drv, SQLITE_MISUSE,
"Trying to bind non-existent prepared statement");
}
statement = drv->prepared_stmts[prepared_index];
result =
bind_parameters(drv, buffer, buffer_size, &index, statement, &type, &size);
if (result == SQLITE_OK) {
return output_ok(drv);
} else {
return result; // error has already been output
}
}
static int prepared_columns(sqlite3_drv_t *drv, char *buffer, int buffer_size) {
unsigned int prepared_index;
long long_prepared_index;
int index = 0, term_count = 0, term_allocated = 0, column_count;
sqlite3_stmt *statement;
ErlDrvTermData *dataset = NULL, port;
ptr_list* ptrs = NULL;
ei_decode_version(buffer, &index, NULL);
ei_decode_long(buffer, &index, &long_prepared_index);
prepared_index = (unsigned int) long_prepared_index;
if (prepared_index >= drv->prepared_count) {
LOG_DEBUG("Tried to get columns for prepared statement #%d, but maximum possible is #%d\n", prepared_index, drv->prepared_count - 1);
return output_error(drv, SQLITE_MISUSE,
"Trying to reset non-existent prepared statement");
}
LOG_DEBUG("Getting the columns for prepared statement #%d\n", prepared_index);
statement = drv->prepared_stmts[prepared_index];
port = driver_mk_port(drv->port);
EXTEND_DATASET_DIRECT(2);
append_to_dataset(2, dataset, term_count, ERL_DRV_PORT, port);
column_count = sqlite3_column_count(statement);
get_columns(
drv, statement, column_count, 2, &term_count, &term_allocated, &ptrs, &dataset);
EXTEND_DATASET_DIRECT(2);
append_to_dataset(2, dataset, term_count, ERL_DRV_TUPLE, (ErlDrvTermData) 2);
#ifdef PRE_R16B
driver_output_term(drv->port,
#else
erl_drv_output_term(port,
#endif
dataset, term_count);
free_ptr_list(ptrs, driver_free_fun);
driver_free(dataset);
return 0;
}
static int prepared_step(sqlite3_drv_t *drv, char *buffer, int buffer_size) {
unsigned int prepared_index;
long long_prepared_index;
int index = 0;
sqlite3_stmt *statement;
async_sqlite3_command *async_command;
ei_decode_version(buffer, &index, NULL);
ei_decode_long(buffer, &index, &long_prepared_index);
prepared_index = (unsigned int) long_prepared_index;
if (prepared_index >= drv->prepared_count) {
LOG_DEBUG("Tried to make a step in prepared statement #%d, but maximum possible is #%d\n", prepared_index, drv->prepared_count - 1);
return output_error(drv, SQLITE_MISUSE,
"Trying to evaluate non-existent prepared statement");
}
LOG_DEBUG("Making a step in prepared statement #%d\n", prepared_index);
statement = drv->prepared_stmts[prepared_index];
async_command = make_async_command_statement(drv, statement, 0);
exec_async_command(drv, sql_step_async, async_command);
return 0;
}
static int prepared_reset(sqlite3_drv_t *drv, char *buffer, int buffer_size) {
unsigned int prepared_index;
long long_prepared_index;
int index = 0;
sqlite3_stmt *statement;
ei_decode_version(buffer, &index, NULL);
ei_decode_long(buffer, &index, &long_prepared_index);
prepared_index = (unsigned int) long_prepared_index;
if (prepared_index >= drv->prepared_count) {
LOG_DEBUG("Tried to reset prepared statement #%d, but maximum possible is #%d\n",
prepared_index, drv->prepared_count - 1);
return output_error(drv, SQLITE_MISUSE,
"Trying to reset non-existent prepared statement");
}
LOG_DEBUG("Resetting prepared statement #%d\n", prepared_index);
// don't bother about error code, any errors should already be shown by step
statement = drv->prepared_stmts[prepared_index];
sqlite3_reset(statement);
return output_ok(drv);
}
static int prepared_clear_bindings(sqlite3_drv_t *drv, char *buffer, int buffer_size) {
unsigned int prepared_index;
long long_prepared_index;
int index = 0;
sqlite3_stmt *statement;
ei_decode_version(buffer, &index, NULL);
ei_decode_long(buffer, &index, &long_prepared_index);
prepared_index = (unsigned int) long_prepared_index;
if (prepared_index >= drv->prepared_count) {
LOG_DEBUG("Tried to clear bindings of prepared statement #%d, but maximum possible is #%d\n", prepared_index, drv->prepared_count - 1);
return output_error(drv, SQLITE_MISUSE,
"Trying to clear bindings of non-existent prepared statement");
}
LOG_DEBUG("Clearing bindings of prepared statement #%d\n", prepared_index);
statement = drv->prepared_stmts[prepared_index];
sqlite3_clear_bindings(statement);
return output_ok(drv);
}
static int prepared_finalize(sqlite3_drv_t *drv, char *buffer, int buffer_size) {
unsigned int prepared_index;
long long_prepared_index;
int index = 0;
ei_decode_version(buffer, &index, NULL);
ei_decode_long(buffer, &index, &long_prepared_index);
prepared_index = (unsigned int) long_prepared_index;
if (prepared_index >= drv->prepared_count) {
LOG_DEBUG("Tried to finalize prepared statement #%d, but maximum possible is #%d\n",
prepared_index, drv->prepared_count - 1);
return output_error(drv, SQLITE_MISUSE,
"Trying to finalize non-existent prepared statement");
}
LOG_DEBUG("Finalizing prepared statement #%d\n", prepared_index);
// finalize the statement and make sure it isn't accidentally executed again
sqlite3_finalize(drv->prepared_stmts[prepared_index]);
drv->prepared_stmts[prepared_index] = NULL;
// if the statement is at the end of the array, space can be reused;
// otherwise don't bother
if (prepared_index == drv->prepared_count - 1) {
drv->prepared_count--;
}
return output_ok(drv);
}
// Unknown Command
static int unknown(sqlite3_drv_t *drv, char *command, int command_size) {
// Return {Port, error, -1, unknown_command}
ErlDrvTermData spec[] = {
ERL_DRV_PORT, driver_mk_port(drv->port),
ERL_DRV_ATOM, drv->atom_error,
ERL_DRV_INT, (ErlDrvTermData) ((ErlDrvSInt) -1),
ERL_DRV_ATOM, drv->atom_unknown_cmd,
ERL_DRV_TUPLE, 4
};
return
#ifdef PRE_R16B
driver_output_term(drv->port,
#else
erl_drv_output_term(spec[1],
#endif
spec, sizeof(spec) / sizeof(spec[0]));
}
#ifdef DEBUG
static void fprint_dataset(FILE* log, ErlDrvTermData *dataset, int term_count) {
int i = 0, stack_size = 0;
ErlDrvUInt length;
fprintf(log, "\nPrinting dataset\n");
while(i < term_count) {
switch (dataset[i]) {
case ERL_DRV_NIL:
fprintf(log, "%d: []", i);
i++;
stack_size++;
break;
case ERL_DRV_ATOM:
fprintf(log, "%d-%d: an atom", i, i+1);
i += 2;
stack_size++;
break;
case ERL_DRV_INT:
fprintf(log, "%d-%d: int %ld", i, i+1, (ErlDrvSInt) dataset[i+1]);
i += 2;
stack_size++;
break;
case ERL_DRV_PORT:
fprintf(log, "%d-%d: a port", i, i+1);
i += 2;
stack_size++;
break;
case ERL_DRV_BINARY:
fprintf(log, "%d-%d: a binary (length %lu, offset %lu)",
i, i+3, (ErlDrvUInt) dataset[i+2], (ErlDrvUInt) dataset[i+3]);
i += 4;
stack_size++;
break;
case ERL_DRV_BUF2BINARY:
fprintf(log, "%d-%d: a string used as binary (length %lu)", i, i+2, (ErlDrvUInt) dataset[i+2]);
i += 3;
stack_size++;
break;
case ERL_DRV_STRING:
fprintf(log, "%d-%d: a string (length %lu)", i, i+2, (ErlDrvUInt) dataset[i+2]);
i += 3;
stack_size++;
break;
case ERL_DRV_TUPLE:
length = (ErlDrvUInt) dataset[i+1];
fprintf(log, "%d-%d: a tuple (size %lu)", i, i+1, length);
i += 2;
stack_size -= length - 1;
break;
case ERL_DRV_LIST:
length = (ErlDrvUInt) dataset[i+1];
fprintf(log, "%d-%d: a list (length %lu)", i, i+1, length);
i += 2;
stack_size -= length - 1;
break;
case ERL_DRV_PID:
fprintf(log, "%d-%d: a pid", i, i+1);
i += 2;
stack_size++;
break;
case ERL_DRV_STRING_CONS:
length = (ErlDrvUInt) dataset[i+2];
fprintf(log, "%d-%d: a string inside surrounding list (length %lu)", i, i+2, length);
i += 3;
break;
case ERL_DRV_FLOAT:
fprintf(log, "%d-%d: float %f", i, i+1, (double) dataset[i+1]);
i += 2;
stack_size++;
break;
case ERL_DRV_EXT2TERM:
fprintf(log, "%d-%d: a term in external format of length %lu", i, i+1, (ErlDrvUInt) dataset[i+1]);
i += 2;
stack_size++;
break;
case ERL_DRV_INT64:
#if defined(_MSC_VER)
fprintf(log, "%d-%d: int %I64d", i, i+1, (ErlDrvSInt64) dataset[i+1]);
#else
fprintf(log, "%d-%d: int %lld", i, i+1, (ErlDrvSInt64) dataset[i+1]);
#endif
i += 2;
stack_size++;
break;
case ERL_DRV_UINT64:
#if defined(_MSC_VER)
fprintf(log, "%d-%d: int %I64lu", i, i+1, (ErlDrvUInt64) dataset[i+1]);
#else
fprintf(log, "%d-%d: int %llu", i, i+1, (ErlDrvUInt64) dataset[i+1]);
#endif
i += 2;
stack_size++;
break;
default:
fprintf(log, "%d: unexpected type", i);
i++;
break;
}
fprintf(log, ".\tStack size: %d\n", stack_size);
fflush(log);
}
}
#endif
Jump to Line
Something went wrong with that request. Please try again.