/* sqlite-module.c
 * S-Lang bindings for the sqlite3 library
 * This was tested with sqlite3 version 3.3.8
 * 
 * $Id: sqlite-module.c,v 1.5 2007/10/13 12:39:36 paul Exp paul $
 * 
 * Copyright (c) 2006, 2007 Paul Boekholt.
 * Released under the terms of the GNU GPL (version 2 or later).
 */
#include <sqlite3.h>
#include <slang.h>
#include <string.h>

SLANG_MODULE(sqlite);

#define MODULE_MAJOR_VERSION	0
#define MODULE_MINOR_VERSION	4
#define MODULE_PATCH_LEVEL	0
static char *Module_Version_String = "0.4.0";
#define MODULE_VERSION_NUMBER	\
   (MODULE_MAJOR_VERSION*10000+MODULE_MINOR_VERSION*100+MODULE_PATCH_LEVEL)

/*{{{ sqlite type */

static int DB_Type_Id = 0;

typedef struct
{
   sqlite3 * db;
} db_type;

#define DUMMY_SQLITE_TYPE 255
/*}}}*/
/*{{{ exceptions */
static int  Sqlite_Error = 0;
static int  Sqlite_Internal_Error = 0;
static int  Sqlite_Perm_Error = 0;
static int  Sqlite_Abort_Error = 0;
static int  Sqlite_Busy_Error = 0;
static int  Sqlite_Locked_Error = 0;
static int  Sqlite_Nomem_Error = 0;
static int  Sqlite_Readonly_Error = 0;
static int  Sqlite_Interrupt_Error = 0;
static int  Sqlite_Ioerr_Error = 0;
static int  Sqlite_Corrupt_Error = 0;
static int  Sqlite_Notfound_Error = 0;
static int  Sqlite_Full_Error = 0;
static int  Sqlite_Cantopen_Error = 0;
static int  Sqlite_Protocol_Error = 0;
static int  Sqlite_Empty_Error = 0;
static int  Sqlite_Schema_Error = 0;
static int  Sqlite_Toobig_Error = 0;
static int  Sqlite_Constraint_Error = 0;
static int  Sqlite_Mismatch_Error = 0;
static int  Sqlite_Misuse_Error = 0;
static int  Sqlite_Nolfs_Error = 0;
static int  Sqlite_Auth_Error = 0;
static int  Sqlite_Format_Error = 0;
static int  Sqlite_Range_Error = 0;
static int  Sqlite_Notadb_Error = 0;

typedef struct
{
   int error_code;
   int *errcode_ptr;
   char *name;
   char *description;
}
Sqlite_Exception_Table_Type;

static const Sqlite_Exception_Table_Type Sqlite_Exception_Table [] = 
{
     { SQLITE_ERROR, &Sqlite_Error, "Sqlite_Error", "Sqlite Error"},
     { SQLITE_INTERNAL, &Sqlite_Internal_Error, "Sqlite_Internal_Error", "NOT USED. Internal logic error in SQLite"},
     { SQLITE_PERM, &Sqlite_Perm_Error, "Sqlite_Perm_Error", "Access permission denied"},
     { SQLITE_ABORT, &Sqlite_Abort_Error, "Sqlite_Abort_Error", "Callback routine requested an abort"},
     { SQLITE_BUSY, &Sqlite_Busy_Error, "Sqlite_Busy_Error", "The database file is locked"},
     { SQLITE_LOCKED, &Sqlite_Locked_Error, "Sqlite_Locked_Error", "A table in the database is locked"},
     { SQLITE_NOMEM, &Sqlite_Nomem_Error, "Sqlite_Nomem_Error", "A malloc() failed"},
     { SQLITE_READONLY, &Sqlite_Readonly_Error, "Sqlite_Readonly_Error", "Attempt to write a readonly database"},
     { SQLITE_INTERRUPT, &Sqlite_Interrupt_Error, "Sqlite_Interrupt_Error", "Operation terminated by sqlite3_interrupt()*/"},
     { SQLITE_IOERR, &Sqlite_Ioerr_Error, "Sqlite_Ioerr_Error", "Some kind of disk I/O error occurred"},
     { SQLITE_CORRUPT, &Sqlite_Corrupt_Error, "Sqlite_Corrupt_Error", "The database disk image is malformed"},
     { SQLITE_NOTFOUND, &Sqlite_Notfound_Error, "Sqlite_Notfound_Error", "NOT USED. Table or record not found"},
     { SQLITE_FULL, &Sqlite_Full_Error, "Sqlite_Full_Error", "Insertion failed because database is full"},
     { SQLITE_CANTOPEN, &Sqlite_Cantopen_Error, "Sqlite_Cantopen_Error", "Unable to open the database file"},
     { SQLITE_PROTOCOL, &Sqlite_Protocol_Error, "Sqlite_Protocol_Error", "Database lock protocol error"},
     { SQLITE_EMPTY, &Sqlite_Empty_Error, "Sqlite_Empty_Error", "Database is empty"},
     { SQLITE_SCHEMA, &Sqlite_Schema_Error, "Sqlite_Schema_Error", "The database schema changed"},
     { SQLITE_TOOBIG, &Sqlite_Toobig_Error, "Sqlite_Toobig_Error", "NOT USED. Too much data for one row"},
     { SQLITE_CONSTRAINT, &Sqlite_Constraint_Error, "Sqlite_Constraint_Error", "Abort due to contraint violation"},
     { SQLITE_MISMATCH, &Sqlite_Mismatch_Error, "Sqlite_Mismatch_Error", "Data type mismatch"},
     { SQLITE_MISUSE, &Sqlite_Misuse_Error, "Sqlite_Misuse_Error", "Library used incorrectly"},
     { SQLITE_NOLFS, &Sqlite_Nolfs_Error, "Sqlite_Nolfs_Error", "Uses OS features not supported on host"},
     { SQLITE_AUTH, &Sqlite_Auth_Error, "Sqlite_Auth_Error", "Authorization denied"},
     { SQLITE_FORMAT, &Sqlite_Format_Error, "Sqlite_Format_Error", "Auxiliary database format error"},
     { SQLITE_RANGE, &Sqlite_Range_Error, "Sqlite_Range_Error", "2nd parameter to sqlite3_bind out of range"},
     { SQLITE_NOTADB, &Sqlite_Notadb_Error, "Sqlite_Notadb_Error", "File opened that is not a database file"},
     { SQLITE_OK, 0, 0, 0 }
};

static int check_error (sqlite3 *db, int error_code)
{
   const Sqlite_Exception_Table_Type *b;
   int error;
   if (error_code == SQLITE_OK || error_code == SQLITE_DONE) return 0;
   b = Sqlite_Exception_Table;
   
   while (b->errcode_ptr != NULL)
     {
	if (b->error_code == error_code)
	  break;
	b++;
     }
   if (b->errcode_ptr == NULL) error = Sqlite_Error;
   else error = *(b->errcode_ptr);
   SLang_verror (error, (char *) sqlite3_errmsg(db));
   return 1;
}

/*}}}*/
/*{{{ helper functions */

static void free_db_type (db_type *pt)
{
   if (pt->db != NULL)
     sqlite3_close(pt->db);
   SLfree ((char *) pt);
}

static void destroy_sqlite (SLtype type, VOID_STAR f)
{
   (void) type;
   free_db_type ((db_type *) f);
}

static SLang_MMT_Type *allocate_db_type (sqlite3 *db)
{
   db_type *pt;
   SLang_MMT_Type *mmt;
   
   pt = (db_type *) SLmalloc (sizeof (db_type));
   if (pt == NULL)
     return NULL;
   memset ((char *) pt, 0, sizeof (db_type));
   
   pt->db = db;
   
   if (NULL == (mmt = SLang_create_mmt (DB_Type_Id, (VOID_STAR) pt)))
     {
	free_db_type (pt);
	return NULL;
     }   
   return mmt;
}

static int sqlite_step(sqlite3 *db, sqlite3_stmt *ppStmt)
{
   int i, res;
   SLang_BString_Type *bstr;
   res = sqlite3_step(ppStmt);
   if (res == SQLITE_ROW)
     {
	for (i = 0; i < sqlite3_data_count (ppStmt); i++)
	  {
	     switch (sqlite3_column_type(ppStmt, i))
	       {
		case SQLITE_INTEGER:
		  SLang_push_integer(sqlite3_column_int(ppStmt, i));
		  break;
		case SQLITE_FLOAT:
		  SLang_push_double(sqlite3_column_double(ppStmt, i));
		  break;
		case SQLITE_TEXT:
		  SLang_push_string((char *)sqlite3_column_text(ppStmt, i));
		  break;
		case SQLITE_BLOB:
		  bstr = SLbstring_create((unsigned char *)sqlite3_column_blob(ppStmt, i),
					  sqlite3_column_bytes(ppStmt, i));
		  if (bstr != NULL)
		    (void) SLang_push_bstring(bstr);
		  SLbstring_free(bstr);
		  break;
		case SQLITE_NULL:
		  SLang_push_null();
	       }
	  }
	return 1;
     }
   if (res != SQLITE_DONE)
     check_error(db, sqlite3_reset(ppStmt));
   return -1;
}

static int sqlite_bind(sqlite3 *db, sqlite3_stmt *ppStmt, int num)
{
   int i;
   int ivalue;
   float fvalue;
   double dvalue;
   char *svalue;
   SLang_BString_Type *bvalue;
   unsigned int bstrlen;
   unsigned char *bptr;
   
   for (i = 1; i <= num; i++)
     {
	switch (SLang_peek_at_stack())
	  {
	   case SLANG_INT_TYPE:
	     SLang_pop_int(&ivalue);
	     if (check_error(db, sqlite3_bind_int(ppStmt, i, ivalue)))
	       return -1;
	     break;
	   case SLANG_FLOAT_TYPE:
	     SLang_pop_float(&fvalue);
	     if (check_error(db, sqlite3_bind_double(ppStmt, i, (double) fvalue)))
	       return -1;
	     break;
	   case SLANG_DOUBLE_TYPE:
	     SLang_pop_double(&dvalue);
	     if (check_error(db, sqlite3_bind_double(ppStmt, i, dvalue)))
	       return -1;
	     break;
	   case SLANG_STRING_TYPE:
	     SLang_pop_slstring(&svalue);
	     if (check_error(db, sqlite3_bind_text(ppStmt, i, svalue,
						   strlen(svalue), (void *)&SLang_free_slstring)))
	       return -1;
	     break;
	   case SLANG_BSTRING_TYPE:
	     SLang_pop_bstring(&bvalue);
	     bptr = SLbstring_get_pointer(bvalue, &bstrlen);
	     if (check_error(db, sqlite3_bind_blob(ppStmt, i, bptr,
						   bstrlen, SQLITE_TRANSIENT)))
	       {
		  SLbstring_free(bvalue);
		  return -1;
	       }
	     SLbstring_free(bvalue);
	     break;
	   default:
	     SLdo_pop_n(num + 1 - i);
	     SLang_verror(SL_Usage_Error, "attempt to bind unsupported type");
	     return -1;
	  }
     }
   return 0;
}

/*}}}*/
/*{{{ exported functions */

static void slsqlite_open(char *name)
{
   SLang_MMT_Type *mmt;
   sqlite3 *db;

   if (check_error(db, sqlite3_open(name, &db))
       || (NULL == (mmt = allocate_db_type (db))))
     {
	(void) SLang_push_null();
	sqlite3_close(db);
	return;
     }

   if (-1 == SLang_push_mmt (mmt))
     {
	SLang_free_mmt (mmt);
	(void) SLang_push_null();
	return;
     }
}

static void slsqlite_get_table (const char *sql)
{
   db_type *p;
   SLang_MMT_Type *mmt;
   SLindex_Type dims[2];
   int nrow, ncolumn;
   char **resultp;
   SLang_Array_Type *at;
   if (NULL == (mmt = SLang_pop_mmt (DB_Type_Id)))
     {
	SLang_free_mmt (mmt);
	(void) SLang_push_null();
	return;
     }
   p = SLang_object_from_mmt (mmt);
   
   if (check_error(p->db, sqlite3_get_table(p->db, sql, &resultp, &nrow, &ncolumn, NULL)))
     {
	SLang_free_mmt(mmt);
	return;
     }
   
   dims[0] = nrow + 1;
   dims[1] = ncolumn;
   
   at = SLang_create_array(SLANG_STRING_TYPE, 0, NULL, dims, 2);
   
   if (at != NULL)
     {
	char **p = resultp;
	SLindex_Type j, k, d[2];
	for (j=0; j<dims[0]; j++)
	  {
	     d[0]=j;
	     for (k=0; k<dims[1]; k++)
	       {
		  d[1]=k;
		  SLang_set_array_element (at, d, p++);
	       }
	  }
	
	(void) SLang_push_array (at, 1);
     }
   else
     {
	SLang_free_array (at);
	SLang_push_null();
     }
   sqlite3_free_table(resultp);
   SLang_free_mmt (mmt);
}

static void slsqlite_get_row(void)
{
   db_type *p;
   SLang_MMT_Type *mmt;
   sqlite3_stmt *ppStmt;
   int nargs;
   char *sql;
   
   nargs = SLang_Num_Function_Args;
   
   if (nargs < 2)
     {
	SLang_verror(SL_Usage_Error, "usage: sqlite_get_row(Sqlite db, String sql, ...)");
	return;
     }
   
   SLreverse_stack(nargs);
   if (NULL == (mmt = SLang_pop_mmt (DB_Type_Id)))
     {
	SLang_free_mmt (mmt);
	return;
     }  
   p = SLang_object_from_mmt (mmt);
   
   if (-1 == SLang_pop_slstring(&sql))
     {
	SLang_verror(SL_Usage_Error, "usage: sqlite_get_row(Sqlite db, String sql, ...)");
	SLang_free_mmt(mmt);
	return;
     }
   
   if (check_error(p->db, sqlite3_prepare(p->db, sql, strlen(sql), &ppStmt, NULL)))
     {
	goto free_return;
     }

   if (sqlite_bind(p->db, ppStmt, nargs - 2))
     {
	sqlite3_finalize(ppStmt);
	goto free_return;
     }

   if (-1 == sqlite_step(p->db, ppStmt))
     SLang_verror (Sqlite_Error, "Query returned no result");
   
   check_error(p->db, sqlite3_finalize (ppStmt));
   
free_return:
   SLang_free_slstring(sql);
   SLang_free_mmt(mmt);
   
}

/*{{{ sqlite_get_array */

static void sqlite_get_integer_array(sqlite3_stmt *ppStmt)
{
   unsigned int num_items = 0, max_num_items = 1024;
   SLindex_Type dims[2];
   int *list;
   SLang_Array_Type *at;
   list = (int *) SLmalloc (sizeof (int) * max_num_items);
   if (list == NULL)
     {
	SLang_verror(SL_Malloc_Error, "Out of memory");
	return;
     }
   dims[0] = 0;

   while (SQLITE_ROW == sqlite3_step(ppStmt))
     {
	int i;
	dims[0]++;
	dims[1] = sqlite3_data_count(ppStmt);

	for (i = 0; i < sqlite3_data_count (ppStmt); i++)
	  {
	     int value = sqlite3_column_int(ppStmt, i);
	     if (max_num_items == num_items)
	       {
		  int *new_list;
		  max_num_items += 4096;
		  
		  new_list = (int *) SLrealloc ((char *)list, sizeof (int) * max_num_items);
		  if (new_list == NULL)
		    {
		       SLang_verror(SL_Malloc_Error, "Out of memory");
		       goto return_error;
		    }
		  list = new_list;
	       }
	     
	     list[num_items] = value;
	     num_items++;
	  }
     }
   if (num_items != max_num_items)
     {
	int *new_list = (int *)SLrealloc ((char *)list, sizeof (int) * (num_items + 1));
	if (new_list == NULL)
	  {
	     SLang_verror(SL_Malloc_Error, "Out of memory");
	     goto return_error;
	  }
	list = new_list;
     }
   if ((NULL == (at = SLang_create_array (SLANG_INT_TYPE, 0, (VOID_STAR) list, dims, 2)))
       || (-1 == SLang_push_array (at, 1)))
     SLang_push_null ();
   return;
   
return_error:
   SLfree ((char *)list);
}

static void sqlite_get_double_array(sqlite3_stmt *ppStmt)
{
   unsigned int num_items = 0, max_num_items = 1024;
   SLindex_Type dims[2];
   double *list;
   SLang_Array_Type *at;
   list = (double *) SLmalloc (sizeof (double) * max_num_items);
   if (list == NULL)
     {
	SLang_verror(SL_Malloc_Error, "Out of memory");
	return;
     }

   dims[0] = 0;
   
   while (SQLITE_ROW == sqlite3_step(ppStmt))
     {
	int i;
	dims[0]++;
	dims[1] = sqlite3_data_count(ppStmt);
	
	for (i = 0; i < sqlite3_data_count (ppStmt); i++)
	  {
	     double value = sqlite3_column_double(ppStmt, i);
	     if (max_num_items == num_items)
	       {
		  double *new_list;
		  max_num_items += 4096;
		  
		  new_list = (double *) SLrealloc ((char *)list, sizeof (double) * max_num_items);
		  if (new_list == NULL)
		    {
		       SLang_verror(SL_Malloc_Error, "Out of memory");
		       goto return_error;
		    }
		  list = new_list;
	       }
	     
	     list[num_items] = value;
	     num_items++;
	  }
	
     }
   
   if (num_items != max_num_items)
     {
	double *new_list = (double *)SLrealloc ((char *)list, sizeof (double) * (num_items + 1));
	if (new_list == NULL)
	  {
	     SLang_verror(SL_Malloc_Error, "Out of memory");
	     goto return_error;
	  }
	list = new_list;
     }
   if ((NULL == (at = SLang_create_array (SLANG_DOUBLE_TYPE, 0, (VOID_STAR) list, dims, 2)))
       || (-1 == SLang_push_array (at, 1)))
     SLang_push_null ();
   return;
   
return_error:
   SLfree ((char *)list);
}

static void sqlite_get_string_array(sqlite3_stmt *ppStmt)
{
   unsigned int num_items = 0, max_num_items = 1024;
   SLindex_Type dims[2];
   char **list;
   SLang_Array_Type *at;
   list = (char **) SLmalloc (sizeof (char *) * max_num_items);
   if (list == NULL)
     {
	SLang_verror(SL_Malloc_Error, "Out of memory");
	return;
     }
   
   dims[0] = 0;

   while (SQLITE_ROW == sqlite3_step(ppStmt))
     {
	int i;
	dims[0]++;
	dims[1] = sqlite3_data_count(ppStmt);
	
	for (i = 0; i < sqlite3_data_count (ppStmt); i++)
	  {
	     char * strp = (char *)sqlite3_column_text(ppStmt, i);
	     if (max_num_items == num_items)
	       {
		  char **new_list;
		  max_num_items += 4096;
		  
		  new_list = (char **) SLrealloc ((char *)list, sizeof (char *) * max_num_items);
		  if (new_list == NULL)
		    {
		       SLang_verror(SL_Malloc_Error, "Out of memory");
		       goto return_error;
		    }
		  list = new_list;
	       }
	     strp = SLang_create_slstring(strp);
	     
	     list[num_items] = strp;
	     num_items++;
	  }
	
     }
   
   if (num_items != max_num_items)
     {
	char **new_list = (char **)SLrealloc ((char *)list, sizeof (char *) * (num_items + 1));
	if (new_list == NULL)
	  {
	     SLang_verror(SL_Malloc_Error, "Out of memory");
	     goto return_error;
	  }
	list = new_list;
     }
   if ((NULL == (at = SLang_create_array (SLANG_STRING_TYPE, 0, (VOID_STAR) list, dims, 2)))
       || (-1 == SLang_push_array (at, 1)))
     SLang_push_null ();
   return;
   
return_error:
   while (num_items > 0)
     {
	num_items--;
	SLang_free_slstring (list[num_items]);
     }
   SLfree ((char *)list);
}
static void sqlite_get_bstring_array(sqlite3_stmt *ppStmt)
{
   unsigned int num_items = 0, max_num_items = 1024;
   SLindex_Type dims[2];
   SLang_BString_Type **list;
   SLang_Array_Type *at;
   SLang_BString_Type *bstr;
   
   list = (SLang_BString_Type **) SLmalloc (sizeof (SLang_BString_Type *) * max_num_items);
   if (list == NULL)
     {
	SLang_verror(SL_Malloc_Error, "Out of memory");
	return;
     }
   
   dims[0] = 0;

   while (SQLITE_ROW == sqlite3_step(ppStmt))
     {
	int i;
	dims[0]++;
	dims[1] = sqlite3_data_count(ppStmt);
	
	for (i = 0; i < sqlite3_data_count (ppStmt); i++)
	  {
	     if (max_num_items == num_items)
	       {
		  SLang_BString_Type **new_list;
		  max_num_items += 4096;
		  
		  new_list = (SLang_BString_Type **) SLrealloc ((char *)list, sizeof (SLang_BString_Type *) * max_num_items);
		  if (new_list == NULL)
		    {
		       SLang_verror(SL_Malloc_Error, "Out of memory");
		       goto return_error;
		    }
		  list = new_list;
	       }
	     bstr = SLbstring_create((unsigned char *)sqlite3_column_blob(ppStmt, i),
				     sqlite3_column_bytes(ppStmt, i));
	     
	     list[num_items] = bstr;
	     num_items++;
	  }
	
     }
   
   if (num_items != max_num_items)
     {
	SLang_BString_Type **new_list = (SLang_BString_Type **)SLrealloc ((char *)list, sizeof (SLang_BString_Type *) * (num_items + 1));
	if (new_list == NULL)
	  {
	     SLang_verror(SL_Malloc_Error, "Out of memory");
	     goto return_error;
	  }
	list = new_list;
     }
   if ((NULL == (at = SLang_create_array (SLANG_BSTRING_TYPE, 0, (VOID_STAR) list, dims, 2)))
       || (-1 == SLang_push_array (at, 1)))
     SLang_push_null ();
   return;
   
return_error:
   while (num_items > 0)
     {
	num_items--;
	SLbstring_free (list[num_items]);
     }
   SLfree ((char *)list);
}

static void slsqlite_get_array(void)
{
   db_type *p;
   SLang_MMT_Type *mmt;
   sqlite3_stmt *ppStmt;
   int nargs;
   char *sql;
   SLtype type;
   
   nargs = SLang_Num_Function_Args;
   
   if (nargs < 3)
     {
	SLang_verror(SL_Usage_Error, "usage: sqlite_get_array(Sqlite db, DataType type, String sql, ...)");
	SLdo_pop_n (SLang_Num_Function_Args);
	return;
     }
   
   SLreverse_stack(nargs);

   if (NULL == (mmt = SLang_pop_mmt (DB_Type_Id)))
     {
	SLang_free_mmt (mmt);
	return;
     }  
   p = SLang_object_from_mmt (mmt);
   
   nargs--;

   if (-1 == SLang_pop_datatype (&type))
     {
	SLang_verror(SL_Application_Error, "error in sqlite_get_array");
	SLang_free_mmt (mmt);
	SLdo_pop_n(nargs);
	return;
     }
   
   
   switch(type)
     {
      case SLANG_INT_TYPE:
      case SLANG_DOUBLE_TYPE:
      case SLANG_STRING_TYPE:
      case SLANG_BSTRING_TYPE:
	break;
      default:
	SLang_verror(SL_Usage_Error, "only Integer, Double, String and Bstring types allowed");
	SLdo_pop_n (nargs - 1);
	return;
     }
   
   
   if (-1 == SLang_pop_slstring(&sql))
     {
	SLang_verror(SL_Usage_Error, "usage: sqlite_get_array(type, Sqlite db, String sql, ...)");
	SLang_free_mmt(mmt);
	return;
     }
   
   if (check_error(p->db, sqlite3_prepare(p->db, sql, strlen(sql), &ppStmt, NULL)))
     {
	goto free_return;
     }
   
   if (sqlite_bind(p->db, ppStmt, nargs - 2))
     {
	sqlite3_finalize(ppStmt);
	goto free_return;
     }
   
   switch(type)
     {
      case SLANG_INT_TYPE:
	sqlite_get_integer_array(ppStmt); break;
      case SLANG_DOUBLE_TYPE:
	sqlite_get_double_array(ppStmt); break;
      case SLANG_STRING_TYPE:
	sqlite_get_string_array(ppStmt); break;
      case SLANG_BSTRING_TYPE:
	sqlite_get_bstring_array(ppStmt); break;
     }
free_return:
   check_error(p->db, sqlite3_finalize (ppStmt));
   
   SLang_free_slstring(sql);
   SLang_free_mmt(mmt);
}

/*}}}*/

static void slsqlite_exec(void)
{
   db_type *p;
   SLang_MMT_Type *mmt;
   sqlite3_stmt *ppStmt;
   int nargs;
   char *sql;
   
   nargs = SLang_Num_Function_Args;
   
   if (nargs < 2)
     {
	SLang_verror(SL_Usage_Error, "usage: sqlite_exec(Sqlite db, String sql, ...)");
	return;
     }
   
   SLreverse_stack(nargs);
   if (NULL == (mmt = SLang_pop_mmt (DB_Type_Id)))
     {
	SLang_free_mmt (mmt);
	return;
     }  
   p = SLang_object_from_mmt (mmt);
   
   if (-1 == SLang_pop_slstring(&sql))
     {
	SLang_verror(SL_Usage_Error, "usage: sqlite_get_row(Sqlite db, String sql, ...)");
	SLang_free_mmt(mmt);
	return;
     }
   
   if (check_error(p->db, sqlite3_prepare(p->db, sql, strlen(sql), &ppStmt, NULL)))
     {
	goto free_return;
     }
   
   if (sqlite_bind(p->db, ppStmt, nargs - 2))
     {
	sqlite3_finalize(ppStmt);
	goto free_return;
     }
   
   if (SQLITE_ERROR == sqlite3_step(ppStmt))
     {
	check_error(p->db, sqlite3_reset(ppStmt));
	sqlite3_finalize(ppStmt);
     }
   else
     check_error(p->db, sqlite3_finalize (ppStmt));
   
free_return:
   SLang_free_slstring(sql);
   SLang_free_mmt(mmt);
}

static int slsqlite_changes()
{
   db_type *p;
   SLang_MMT_Type *mmt;
   int res;
   if (NULL == (mmt = SLang_pop_mmt (DB_Type_Id)))
     {
	SLang_free_mmt (mmt);
	return 0;
     }
   p = SLang_object_from_mmt (mmt);
   res = sqlite3_changes(p->db);
   SLang_free_mmt (mmt);
   return res;
}

/*}}}*/
/*{{{ intrinsics */

static SLang_Intrin_Fun_Type Module_Intrinsics [] =
{
   MAKE_INTRINSIC_S("sqlite_open", slsqlite_open, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_S("sqlite_get_table", slsqlite_get_table, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("sqlite_get_row", slsqlite_get_row, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("sqlite_get_array", slsqlite_get_array, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("sqlite_exec", slsqlite_exec, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("sqlite_changes", slsqlite_changes, SLANG_INT_TYPE),
   SLANG_END_INTRIN_FUN_TABLE
};

static SLang_Intrin_Var_Type Module_Variables [] =
{
   MAKE_VARIABLE("_sqlite_module_version_string", &Module_Version_String, SLANG_STRING_TYPE, 1),
   SLANG_END_INTRIN_VAR_TABLE
};

static SLang_IConstant_Type Module_Constants [] =
{
   MAKE_ICONSTANT("_sqlite_module_version", MODULE_VERSION_NUMBER),
   SLANG_END_ICONST_TABLE
};

/*}}}*/
/*{{{ foreach */

struct _pSLang_Foreach_Context_Type
{
   SLang_MMT_Type *mmt;
   db_type *p;
   sqlite3_stmt *ppStmt;
};


static SLang_Foreach_Context_Type *cl_foreach_open (SLtype type, unsigned int num)
{
   SLang_Foreach_Context_Type *c = NULL;
   SLang_MMT_Type *mmt = NULL;
   char *s;
   
   (void) type;
   
   if (NULL == (mmt = SLang_pop_mmt (DB_Type_Id)))
     return NULL;
   
   if (!num)
     {
	SLang_verror (SL_Usage_Error, "Sqlite_Type requires an sql statement");
	SLang_free_mmt (mmt);
	return NULL;
     }
   
   SLreverse_stack(num);
   if (-1 == SLang_pop_slstring (&s))
     {
	SLang_verror (SL_Usage_Error, "Sqlite_Type requires an sql statement");
	SLang_free_mmt (mmt);
	return NULL;
     }
   
   if (NULL == (c = (SLang_Foreach_Context_Type *) SLmalloc (sizeof (SLang_Foreach_Context_Type))))
     goto free_return;
   
   memset ((char *) c, 0, sizeof (SLang_Foreach_Context_Type));
   
   c->mmt = mmt;
   c->p = (db_type *) SLang_object_from_mmt (mmt);
   if (check_error(c->p->db, sqlite3_prepare(c->p->db, s, strlen(s), &c->ppStmt, NULL)))
     goto free_return;
   
   if (sqlite_bind(c->p->db, c->ppStmt, num - 1))
     {
	sqlite3_finalize(c->ppStmt);
	goto free_return;
     }
   
   return c;
   
free_return:
   SLang_free_slstring (s);
   SLang_free_mmt(mmt);
   return NULL;
}

static void cl_foreach_close (SLtype type, SLang_Foreach_Context_Type *c)
{
   (void) type;
   if (c == NULL) return;
   if (SQLITE_OK != sqlite3_finalize (c->ppStmt))
     SLang_verror (Sqlite_Error, "foreach_close failed");
   SLang_free_mmt (c->mmt);
   SLfree ((char *) c);
}

static int cl_foreach (SLtype type, SLang_Foreach_Context_Type *c)
{
   (void) type;
   
   if (c == NULL)
     return -1;
   return sqlite_step(c->p->db, c->ppStmt);
}

/*}}}*/
/*{{{ register class */

static void patchup_intrinsic_table (SLang_Intrin_Fun_Type *table, 
				     unsigned char dummy, unsigned char type)
{
   while (table->name != NULL)
     {
	unsigned int i, nargs;
	SLtype *args;
	
	nargs = table->num_args;
	args = table->arg_types;
	for (i = 0; i < nargs; i++)
	  {
	     if (args[i] == dummy)
	       args[i] = type;
	  }
	
	/* For completeness */
	if (table->return_type == dummy)
	  table->return_type = type;
	
	table++;
     }
}

static int register_sqlite_type (void)
{
   SLang_Class_Type *cl;
   
   if (DB_Type_Id != 0)
     return 0;

   if (NULL == (cl = SLclass_allocate_class ("Sqlite_Type")))
     return -1;

   if (-1 == SLclass_set_destroy_function (cl, destroy_sqlite))
     return -1;

   if (-1 == SLclass_set_foreach_functions(cl, cl_foreach_open, cl_foreach, cl_foreach_close))
     return -1;

   /* By registering as SLANG_VOID_TYPE, slang will dynamically allocate a
    * type.
    */
   if (-1 == SLclass_register_class (cl, SLANG_VOID_TYPE, sizeof (db_type), SLANG_CLASS_TYPE_MMT))
     return -1;

   DB_Type_Id = SLclass_get_class_id (cl);

   patchup_intrinsic_table (Module_Intrinsics, DUMMY_SQLITE_TYPE, DB_Type_Id);
   
   return 0;
}

/*}}}*/
/*{{{ init */

int init_sqlite_module_ns (char *ns_name)
{
   SLang_NameSpace_Type *ns = SLns_create_namespace (ns_name);
   if (ns == NULL)
     return -1;
   if (-1 == register_sqlite_type ())
     return -1;
   
   if (Sqlite_Error == 0)
     {
	const Sqlite_Exception_Table_Type *b;
	b = Sqlite_Exception_Table;

	if (-1 == (Sqlite_Error = SLerr_new_exception (SL_RunTime_Error, "SqliteError", "Sqlite error")))
	  return -1;
	b++;
	while (b->errcode_ptr != NULL)
	  {
	     *b->errcode_ptr = SLerr_new_exception (Sqlite_Error, b->name, b->description);
	     if (*b->errcode_ptr == -1)
	       return -1;
	     b++;
	  }
     }

   if ((-1 == SLns_add_intrin_fun_table (ns, Module_Intrinsics, NULL))
       || (-1 == SLns_add_intrin_var_table (ns, Module_Variables, NULL))
       || (-1 == SLns_add_iconstant_table (ns, Module_Constants, NULL)))
     return -1;
   
   return 0;
}

/*}}}*/
