Below is the file 'mlsqlite/ocaml-sqlite3.c' from this revision. You can also download the file.

#include <string.h>
#include <assert.h>

#define CAML_NAME_SPACE

#include <caml/alloc.h>
#include <caml/memory.h>
#include <caml/fail.h>
#include <caml/callback.h>
#include <caml/custom.h>

#include <sqlite3.h>

#include "ocaml-sqlite3.h"

/* Not wrapped :
   - user-defined aggregate functions
   - collation functions
   - sqlite3_db_handle -> should not be wrapped !
   - sqlite3_commit_hook
   - sqlite3_global_recover
   - sqlite3_set_authorizer
*/



/* Error handling */

void ml_sqlite3_raise_exn (int status, const char *errmsg, int static_errmsg)
{
  static value *sqlite3_exn;

  /* check this is really an error */
  assert (status > SQLITE_OK && status < SQLITE_ROW);

  if (sqlite3_exn == NULL)
    {
      sqlite3_exn = caml_named_value ("mlsqlite3_exn");
      if (sqlite3_exn == NULL)
	caml_failwith ("Sqlite3 exception not registered");
    }

  {
    CAMLlocal1(bucket);
    bucket = caml_alloc (3, 0);
    Store_field(bucket, 0, *sqlite3_exn);
    Store_field(bucket, 1, Val_long (status - 1));
    Store_field(bucket, 2, caml_copy_string (errmsg ? (char *) errmsg : ""));
    if (! static_errmsg)
      sqlite3_free ((char *) errmsg);
    caml_raise (bucket);
  }
}




/* 0 -> busy
 * 1 -> trace
 * 2 -> progress
 */
#define NUM_CALLBACKS 3

static void
ml_finalize_sqlite3 (value v)
{
  struct user_function *list, *next;
  struct ml_sqlite3_data *data = Sqlite3_data_val(v);
  caml_remove_global_root (&data->callbacks);
  caml_remove_global_root (&data->stmt_store);
  list = data->user_functions;
  while (list != NULL)
    {
      caml_remove_global_root (&list->fun);
      next = list->next;
      caml_stat_free (list);
      list = next;
    }
  caml_stat_free (data);
}

static value
ml_wrap_sqlite3 (sqlite3 *db)
{
  static struct custom_operations ops = {
    "mlsqlite3/001",
    ml_finalize_sqlite3,
    custom_compare_default,
    custom_hash_default,
    custom_serialize_default,
    custom_deserialize_default
  };
  CAMLparam0();
  CAMLlocal1(v);
  struct ml_sqlite3_data **store, *data;
  data = caml_stat_alloc (sizeof *data);
  v = caml_alloc_custom (&ops, sizeof data, 1, 100);
  store = Data_custom_val (v);
  *store = data;
  data->db = db;
  data->callbacks = caml_alloc (NUM_CALLBACKS, 0);
  data->stmt_store = Val_unit;
  data->user_functions = NULL;
  caml_register_global_root (&data->callbacks);
  caml_register_global_root (&data->stmt_store);
  CAMLreturn(v);
}

CAMLprim value
ml_sqlite3_open (value filename)
{
  sqlite3 *db;
  int status;

  status = sqlite3_open (String_val(filename), &db);
  if (status != SQLITE_OK)
    {
      char *errmsg;
      errmsg = (char *) sqlite3_errmsg (db);
      sqlite3_close (db); /* ignore status here */
      ml_sqlite3_raise_exn (status, errmsg, TRUE);
    }

  return ml_wrap_sqlite3 (db);
}

CAMLprim value
ml_sqlite3_close (value db)
{
  struct ml_sqlite3_data *data = Sqlite3_data_val(db);
  if (data->db != NULL)
    {
      int status;
      status = sqlite3_close (data->db);
      if (status != SQLITE_OK)
	raise_sqlite3_exn (db);
      data->db = NULL;
    }
  return Val_unit;
}

CAMLprim value
ml_sqlite3_set_stmt_store (value db, value s)
{
  struct ml_sqlite3_data *data = Sqlite3_data_val (db);
  if (Is_block (s))
    data->stmt_store = Field (s, 0);
  else
    data->stmt_store = Val_unit;
  return Val_unit;
}

CAMLprim value
ml_sqlite3_get_stmt_store (value db, value s)
{
  struct ml_sqlite3_data *data = Sqlite3_data_val (db);
  if (data->stmt_store == Val_unit)
    caml_raise_not_found();
  return data->stmt_store;
}



/* Misc general functions */

CAMLprim value
ml_sqlite3_interrupt (value db)
{
  sqlite3_interrupt (Sqlite3_val (db));
  return Val_unit;
}

CAMLprim value
ml_sqlite3_complete (value sql)
{
#ifdef HAVE_SQLITE3_COMPLETE
  return Val_bool (sqlite3_complete (String_val (sql)));
#else
  caml_failwith ("sqlite3_complete unavailable");
#endif
}

CAMLprim value
ml_sqlite3_version (value unit)
{
  return caml_copy_string (sqlite3_version);
}

CAMLprim value
ml_sqlite3_last_insert_rowid (value db)
{
  return caml_copy_int64 (sqlite3_last_insert_rowid (Sqlite3_val (db)));
}

CAMLprim value
ml_sqlite3_changes (value db)
{
  return Val_long (sqlite3_changes (Sqlite3_val (db)));
}

CAMLprim value
ml_sqlite3_total_changes (value db)
{
  return Val_long (sqlite3_total_changes (Sqlite3_val (db)));
}

CAMLprim value
ml_sqlite3_get_autocommit (value db)
{
#ifdef HAVE_SQLITE3_GET_AUTOCOMMIT
  return Val_bool (sqlite3_get_autocommit (Sqlite3_val (db)));
#else
  caml_failwith ("sqlite3_get_autocommit unavailable");
#endif
}

CAMLprim value
ml_sqlite3_sleep (value ms)
{
#if HAVE_SQLITE3_SLEEP
  return Val_int (sqlite3_sleep (Int_val (ms)));
#else
  caml_failwith ("sqlite3_sleep unavailable");
#endif
}


/* callbacks */

#define MLTAG_RETRY -915497327L
#define MLTAG_FAIL  1559036861L

static int
ml_sqlite3_busy_handler_cb (void *data, int num)
{
  struct ml_sqlite3_data *db = data;
  value res;
  res = caml_callback_exn (Field (db->callbacks, 0), Val_int (num));
  if (Is_exception_result (res))
    return 0;
  return (res == MLTAG_RETRY);
}

CAMLprim value
ml_sqlite3_busy_handler (value db, value cb)
{
  struct ml_sqlite3_data *db_data = Sqlite3_data_val(db);
  sqlite3_busy_handler (Sqlite3_val(db),
			ml_sqlite3_busy_handler_cb, db_data);
  Store_field (db_data->callbacks, 0, cb);
  return Val_unit;
}

CAMLprim value
ml_sqlite3_busy_handler_unset (value db)
{
  struct ml_sqlite3_data *db_data = Sqlite3_data_val(db);
  sqlite3_busy_handler (Sqlite3_val(db), NULL, NULL);
  Store_field (db_data->callbacks, 0, Val_unit);
  return Val_unit;
}

CAMLprim value
ml_sqlite3_busy_timeout (value db, value t)
{
  sqlite3_busy_timeout (Sqlite3_val (db), Int_val (t));
  return Val_unit;
}

static void
ml_sqlite3_trace_handler (void *data, const char *req)
{
 struct ml_sqlite3_data *db = data;
 value s = caml_copy_string (req);
 caml_callback_exn (Field (db->callbacks, 1), s);
}

CAMLprim value
ml_sqlite3_trace (value db, value cb)
{
  struct ml_sqlite3_data *db_data = Sqlite3_data_val(db);
  sqlite3_trace (Sqlite3_val (db), ml_sqlite3_trace_handler, db_data);
  Store_field (db_data->callbacks, 1, cb);
  return Val_unit;
}

CAMLprim value
ml_sqlite3_trace_unset (value db)
{
  struct ml_sqlite3_data *db_data = Sqlite3_data_val(db);
  sqlite3_trace (Sqlite3_val (db), NULL, NULL);
  Store_field (db_data->callbacks, 1, Val_unit);
  return Val_unit;
}

#ifdef HAVE_SQLITE3_PROGRESS_HANDLER
static int
ml_sqlite3_progress_handler_cb (void *data)
{
 struct ml_sqlite3_data *db = data;
 value res;
 res = caml_callback_exn (Field (db->callbacks, 2), Val_unit);
 return Is_exception_result(res);
}
#endif

CAMLprim value
ml_sqlite3_progress_handler (value db, value delay, value cb)
{
#ifdef HAVE_SQLITE3_PROGRESS_HANDLER
  struct ml_sqlite3_data *db_data = Sqlite3_data_val(db);
  sqlite3_progress_handler (Sqlite3_val (db), Int_val (delay),
			    ml_sqlite3_progress_handler_cb, db_data);
  Store_field (db_data->callbacks, 2, cb);
#endif
  return Val_unit;
}

CAMLprim value
ml_sqlite3_progress_handler_unset (value db)
{
#ifdef HAVE_SQLITE3_PROGRESS_HANDLER
  struct ml_sqlite3_data *db_data = Sqlite3_data_val(db);
  sqlite3_progress_handler (Sqlite3_val(db), 0, NULL, NULL);
  Store_field (db_data->callbacks, 2, Val_unit);
#endif
  return Val_unit;
}



#define MLTAG_INTEGER  769598269L
#define MLTAG_FLOAT     17431289L
#define MLTAG_TEXT    1869949275L
#define MLTAG_BLOB    1471417019L
#define MLTAG_NULL    1738460431L

#define MLTAG_INT        7295391L
#define MLTAG_INT64   2015220635L
#define MLTAG_VALUE   1598910115L

static value
convert_sqlite3_type (int t)
{
  switch (t)
    {
    case SQLITE_INTEGER:
      return MLTAG_INTEGER;
    case SQLITE_FLOAT:
      return MLTAG_FLOAT;
    case SQLITE_TEXT:
      return MLTAG_TEXT;
    case SQLITE_BLOB:
      return MLTAG_BLOB;
    default:
      return MLTAG_NULL;
    }
}




/* Prepared statements */

CAMLprim value
ml_sqlite3_finalize_noerr (value s)
{
  sqlite3_stmt **p_stmt = (sqlite3_stmt **) Field (s, 0);

  if (*p_stmt != NULL)
    {
      sqlite3_finalize (*p_stmt);
      *p_stmt = NULL;
    }
  return Val_unit;
}

static sqlite3_stmt *
ml_sqlite3_prepare_stmt (value db, value sql, value sql_off, unsigned int *tail_pos)
{
  CAMLparam2(db, sql);
  sqlite3_stmt *stmt = NULL;
  const char *tail;
  int status;
  unsigned int off = Unsigned_int_val (sql_off);
  status = sqlite3_prepare (Sqlite3_val (db),
			    String_val (sql) + off,
			    caml_string_length (sql) - off,
			    &stmt, &tail);
  if (status != SQLITE_OK)
    {
      if (stmt != NULL)
	sqlite3_finalize (stmt);
      raise_sqlite3_exn (db);
    }
  if (tail_pos != NULL)
    *tail_pos = tail - String_val (sql);
  CAMLreturn (stmt);
}

CAMLprim value
ml_sqlite3_prepare (value db, value sql, value sql_off)
{
  CAMLparam2(db, sql);
  CAMLlocal4(t, o, r, s);
  sqlite3_stmt *stmt;
  unsigned int tail_pos;

  stmt = ml_sqlite3_prepare_stmt (db, sql, sql_off, &tail_pos);
  if (stmt == NULL)
    o = Val_unit;
  else
    {
      s = caml_alloc_small (1, Abstract_tag);
      Field (s, 0) = Val_bp (stmt);
      r = caml_alloc_small (4, 0);
      Field (r, 0) = s;
      Field (r, 1) = db;
      Field (r, 2) = sql;
      Field (r, 3) = sql_off;
      o = caml_alloc_small (1, 0);
      Field (o, 0) = r;
    }
  t = caml_alloc_small (2, 0);
  Field (t, 0) = o;
  Field (t, 1) = Val_int (tail_pos);
  CAMLreturn (t);
}

static sqlite3_stmt *
ml_sqlite3_recompile (value v, sqlite3_stmt *old_stmt)
{
  CAMLparam1(v);
  CAMLlocal1(s);
  sqlite3_stmt *stmt;

  stmt = ml_sqlite3_prepare_stmt (Field (v, 1), Field (v, 2), Field (v, 3), NULL);
  if (stmt == NULL)
    caml_failwith ("Sqlite3.recompile");
  if (old_stmt != NULL)
    {
      sqlite3_transfer_bindings (old_stmt, stmt);
      sqlite3_finalize (old_stmt);
    }

  s = caml_alloc_small (1, Abstract_tag);
  Field (s, 0) = Val_bp (stmt);
  Store_field (v, 0, s);
  CAMLreturn (stmt);
}

CAMLprim value
ml_sqlite3_reset (value stmt)
{
  sqlite3_stmt *s = Sqlite3_stmt_val (stmt);
  sqlite3_reset (s);
  return Val_unit;
}

CAMLprim value
ml_sqlite3_expired (value stmt)
{
  sqlite3_stmt *s = * ((sqlite3_stmt **) Field (stmt, 0));
  return Val_bool (s == NULL);
}

#define MLTAG_ROW	   8190965L
#define MLTAG_DONE	1516073221L

CAMLprim value
ml_sqlite3_step (value stmt)
{
  CAMLparam1(stmt);
  CAMLlocal1(r);
  int status;
  sqlite3_stmt *s = Sqlite3_stmt_val (stmt);

 again:
  status = sqlite3_step (s);
  switch (status)
    {
    case SQLITE_ROW:
      r = MLTAG_ROW; break;
    case SQLITE_DONE:
      r = MLTAG_DONE; break;
    default: /* either BUSY, ERROR or MISUSE */
      {
	sqlite3 *db;
	if (status == SQLITE_ERROR)
	  status = sqlite3_reset (s);
	if (status == SQLITE_SCHEMA)
	  {
	    s = ml_sqlite3_recompile (stmt, s);
	    goto again;
	  }
	db = sqlite3_db_handle (s);
	ml_sqlite3_raise_exn (status, sqlite3_errmsg (db), TRUE);
      }
    }
  CAMLreturn (r);
}



/* sqlite3_bind_* */

CAMLprim value
ml_sqlite3_bind (value s, value idx, value v)
{
  sqlite3_stmt *stmt = Sqlite3_stmt_val (s);
  int i = Int_val (idx);
  int status;

  if (Is_long (v))
    status = sqlite3_bind_null (stmt, i);
  else
    {
      value val = Field (v, 1);
      switch (Field (v, 0))
	{
	case MLTAG_INT:
	  status = sqlite3_bind_int (stmt, i, Int_val (val)); break;
	case MLTAG_INT64:
	  status = sqlite3_bind_int64 (stmt, i, Int64_val (val)); break;
	case MLTAG_FLOAT:
	  status = sqlite3_bind_double (stmt, i, Double_val (val)); break;
	case MLTAG_TEXT:
	  status = sqlite3_bind_text (stmt, i,
				      String_val (val),
				      caml_string_length (val),
				      SQLITE_TRANSIENT);
	  break;
	case MLTAG_BLOB:
	  status = sqlite3_bind_blob (stmt, i,
				      String_val (val),
				      caml_string_length (val),
				      SQLITE_TRANSIENT);
	  break;
	case MLTAG_VALUE:
#if HAVE_SQLITE3_BIND_VALUE
	  status = sqlite3_bind_value (stmt, i, Sqlite3_value_val (val)); break;
#else
	  caml_failwith ("sqlite3_bind_value unavailable");
#endif
	default:
	  status = SQLITE_MISUSE;
	}
    }
  if (status != SQLITE_OK)
    ml_sqlite3_raise_exn (status, "sqlite3_bind failed", TRUE);
  return Val_unit;
}

CAMLprim value
ml_sqlite3_bind_parameter_count (value s)
{
  return Val_int (sqlite3_bind_parameter_count (Sqlite3_stmt_val (s)));
}

CAMLprim value
ml_sqlite3_bind_parameter_index (value s, value n)
{
  return Val_int (sqlite3_bind_parameter_index (Sqlite3_stmt_val (s),
						String_val(n)));
}

CAMLprim value
ml_sqlite3_bind_parameter_name (value s, value idx)
{
  return caml_copy_string (sqlite3_bind_parameter_name (Sqlite3_stmt_val (s),
							Int_val (idx)));
}

CAMLprim value
ml_sqlite3_clear_bindings (value s)
{
#if HAVE_SQLITE3_CLEAR_BINDINGS
  int status;
  status = sqlite3_clear_bindings (Sqlite3_stmt_val (s));
  if (status != SQLITE_OK)
    ml_sqlite3_raise_exn (status, "clear_bindings failed", TRUE);
  return Val_unit;
#else
  sqlite3_stmt *stmt = Sqlite3_stmt_val (s);
  int i, n, status;
  n = sqlite3_bind_parameter_count(stmt);
  for (i = 1; i <= n; i++)
    {
      status = sqlite3_bind_null(stmt, i);
      if (status != SQLITE_OK)
	ml_sqlite3_raise_exn (status, "clear_bindings failed", TRUE);
    }
  return Val_unit;
#endif
}

CAMLprim value
ml_sqlite3_transfer_bindings (value s1, value s2)
{
  int status;
  status = sqlite3_transfer_bindings (Sqlite3_stmt_val (s1),
				      Sqlite3_stmt_val (s2));
  if (status != SQLITE_OK)
    ml_sqlite3_raise_exn (status, "transfer_bindings failed", TRUE);
  return Val_unit;
}


/* sqlite3_column_* */

CAMLprim value
ml_sqlite3_column_blob (value s, value i)
{
  CAMLparam1(s);
  CAMLlocal1(r);
  int len;
  const void * data;
  len = sqlite3_column_bytes (Sqlite3_stmt_val (s), Int_val (i));
  r = caml_alloc_string (len);
  data = sqlite3_column_blob (Sqlite3_stmt_val (s), Int_val(i));
  memcpy (Bp_val (r), data, len);
  CAMLreturn(r);
}

CAMLprim value
ml_sqlite3_column_double (value s, value i)
{
  return caml_copy_double (sqlite3_column_double (Sqlite3_stmt_val (s), Int_val(i)));
}

CAMLprim value
ml_sqlite3_column_int (value s, value i)
{
  return Val_int (sqlite3_column_int (Sqlite3_stmt_val (s), Int_val(i)));
}

CAMLprim value
ml_sqlite3_column_int64 (value s, value i)
{
  return caml_copy_int64 (sqlite3_column_int64 (Sqlite3_stmt_val (s), Int_val(i)));
}

CAMLprim value
ml_sqlite3_column_text (value s, value i)
{
  CAMLparam1(s);
  CAMLlocal1(r);
  int len;
  const void * data;
  len = sqlite3_column_bytes (Sqlite3_stmt_val (s), Int_val (i));
  r = caml_alloc_string (len);
  data = sqlite3_column_text (Sqlite3_stmt_val (s), Int_val(i));
  memcpy (Bp_val (r), data, len);
  CAMLreturn(r);
}

CAMLprim value
ml_sqlite3_column_type (value s, value i)
{
  return convert_sqlite3_type (sqlite3_column_type (Sqlite3_stmt_val (s), Int_val(i)));
}

CAMLprim value
ml_sqlite3_data_count (value s)
{
  return Val_int (sqlite3_data_count (Sqlite3_stmt_val (s)));
}

CAMLprim value
ml_sqlite3_column_count (value s)
{
  return Val_int (sqlite3_column_count (Sqlite3_stmt_val (s)));
}

CAMLprim value
ml_sqlite3_column_name (value s, value i)
{
  return caml_copy_string (sqlite3_column_name (Sqlite3_stmt_val (s),
						Int_val(i)));
}

CAMLprim value
ml_sqlite3_column_decltype (value s, value i)
{
  return caml_copy_string (sqlite3_column_decltype (Sqlite3_stmt_val (s),
						    Int_val(i)));
}


/* User-defined functions */

static struct user_function *
register_user_function (struct ml_sqlite3_data *db_data, value name, value cb)
{
  CAMLparam2(name, cb);
  CAMLlocal1(cell);
  struct user_function *link;
  cell = caml_alloc (2, 0);
  Store_field (cell, 0, name);
  Store_field (cell, 1, cb);
  link = caml_stat_alloc (sizeof *link);
  link->fun = cell;
  link->next = db_data->user_functions;
  caml_register_global_root (&link->fun);
  db_data->user_functions = link;
  CAMLreturn(link);
}

static void
unregister_user_function (struct ml_sqlite3_data *db_data, value name)
{
  struct user_function *prev, *link;
  prev = NULL;
  link = db_data->user_functions;
  while (link != NULL)
    {
      if (strcmp (String_val (Field (link->fun, 0)), String_val (name)) == 0)
	{
	  if (prev == NULL)
	    db_data->user_functions = link->next;
	  else
	    prev->next = link->next;
	  caml_remove_global_root (&link->fun);
	  caml_stat_free (link);
	  break;
	}
      prev = link;
      link = link->next;
    }
}

static value
ml_sqlite3_wrap_values (int argc, sqlite3_value **args)
{
  int i;
  CAMLparam0();
  CAMLlocal2(a, v);
  if (argc <= 0 || args == NULL)
    CAMLreturn (Atom (0));
  a = caml_alloc (argc, 0);
  for (i=0; i<argc; i++)
    {
      v = caml_alloc_small (1, Abstract_tag);
      Field (v, 0) = Val_bp (args[i]);
      Store_field (a, i, v);
    }
  CAMLreturn (a);
}

static void
ml_sqlite3_wipe_values (value a)
{
  mlsize_t i, len = Wosize_val (a);
  for (i=0; i<len; i++)
    Store_field (Field (a, i), 0, 0);
}

CAMLprim value
ml_sqlite3_value_blob (value v)
{
  CAMLparam1(v);
  CAMLlocal1(r);
  int len;
  const void *data;
  len = sqlite3_value_bytes (Sqlite3_value_val (v));
  r = caml_alloc_string (len);
  data = sqlite3_value_blob (Sqlite3_value_val (v));
  memcpy (Bp_val (r), data, len);
  CAMLreturn(r);
}

CAMLprim value
ml_sqlite3_value_double (value v)
{
  return caml_copy_double (sqlite3_value_double (Sqlite3_value_val (v)));
}

CAMLprim value
ml_sqlite3_value_int (value v)
{
  return Val_long (sqlite3_value_int (Sqlite3_value_val (v)));
}

CAMLprim value
ml_sqlite3_value_int64 (value v)
{
  return caml_copy_int64 (sqlite3_value_int64 (Sqlite3_value_val (v)));
}

CAMLprim value
ml_sqlite3_value_text (value v)
{
  CAMLparam1(v);
  CAMLlocal1(r);
  int len;
  const void *data;
  len = sqlite3_value_bytes (Sqlite3_value_val (v));
  r = caml_alloc_string (len);
  data = sqlite3_value_text (Sqlite3_value_val (v));
  memcpy (Bp_val (r), data, len);
  CAMLreturn(r);
}

CAMLprim value
ml_sqlite3_value_type (value v)
{
  return convert_sqlite3_type (sqlite3_value_type (Sqlite3_value_val (v)));
}

static void
ml_sqlite3_set_result (sqlite3_context *ctx, value res)
{
  if (Is_exception_result (res))
    sqlite3_result_error (ctx, "ocaml callback raised an exception", -1);
  else if (Is_long (res))
    sqlite3_result_null (ctx);
  else
    {
      value v = Field (res, 1);
      switch (Field (res, 0))
	{
	case MLTAG_INT:
	  sqlite3_result_int (ctx, Long_val (v));
	  break;
	case MLTAG_INT64:
	  sqlite3_result_int64 (ctx, Int64_val (v));
	  break;
	case MLTAG_FLOAT:
	  sqlite3_result_double (ctx, Double_val (v));
	  break;
	case MLTAG_TEXT:
	  sqlite3_result_text (ctx, String_val (v), caml_string_length (v), SQLITE_TRANSIENT);
	  break;
	case MLTAG_BLOB:
	  sqlite3_result_blob (ctx, String_val (v), caml_string_length (v), SQLITE_TRANSIENT);
	  break;
	case MLTAG_VALUE:
	  {
	    sqlite3_result_value (ctx, Sqlite3_value_val (v));
	    break;
	  }
	default:
	  sqlite3_result_error (ctx, "unknown value returned by callback", -1);
	}
    }
}

static void
ml_sqlite3_user_function (sqlite3_context *ctx, int argc, sqlite3_value **argv)
{
  struct user_function *data = sqlite3_user_data (ctx);
  CAMLparam0();
  CAMLlocal2(res, args);

  args = ml_sqlite3_wrap_values (argc, argv);
  res = caml_callback_exn (Field (data->fun, 1), args);
  ml_sqlite3_set_result (ctx, res);
  ml_sqlite3_wipe_values (args);
  CAMLreturn0;
}

CAMLprim value
ml_sqlite3_create_function (value db, value name, value nargs, value fun)
{
  CAMLparam3(db, name, fun);
  int status;
  sqlite3 *s_db = Sqlite3_val(db);
  struct user_function *param;

  param = register_user_function (Sqlite3_data_val(db), name, fun);
  status = sqlite3_create_function (s_db, String_val (name),
				    Int_val (nargs), SQLITE_UTF8, param,
				    ml_sqlite3_user_function, NULL, NULL);
  if (status != SQLITE_OK)
    {
      unregister_user_function (Sqlite3_data_val(db), name);
      raise_sqlite3_exn (db);
    }
  CAMLreturn(Val_unit);
}

CAMLprim value
ml_sqlite3_delete_function (value db, value name)
{
  int status;
  status = sqlite3_create_function (Sqlite3_val (db),
				    String_val (name),
				    0, SQLITE_UTF8, NULL,
				    NULL, NULL, NULL);
  if (status != SQLITE_OK)
    raise_sqlite3_exn (db);
  unregister_user_function (Sqlite3_data_val(db), name);
  return Val_unit;
}