#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "sqlite_jni_defs.h"

#if HAVE_SQLITE2
#include "sqlite.h"
#endif

#if HAVE_SQLITE3
#include "sqlite3.h"
#undef  HAVE_SQLITE_COMPILE
#define HAVE_SQLITE_COMPILE 1
#undef  HAVE_SQLITE_PROGRESS_HANDLER
#define HAVE_SQLITE_PROGRESS_HANDLER 1
#undef  HAVE_SQLITE_TRACE
#define HAVE_SQLITE_TRACE 1
#if !HAVE_SQLITE3_MALLOC
#define sqlite3_malloc malloc
#define sqlite3_free   free
#endif
#if !HAVE_SQLITE3_BIND_PARAMETER_COUNT
#define sqlite3_bind_parameter_count(dummy) (1000)
#endif
#endif

#if HAVE_SQLITE2 && HAVE_SQLITE3
#define HAVE_BOTH_SQLITE 1
#endif

#define CANT_PASS_VALIST_AS_CHARPTR

#include "sqlite_jni.h"

#if defined(_WIN32) || !defined(CANT_PASS_VALIST_AS_CHARPTR)
#define MAX_PARAMS 256
#else
#define MAX_PARAMS 32
#endif

/* free memory proc */

typedef void (freemem)(void *);

/* internal handle for SQLite database */

typedef struct {
    void *sqlite;		/* SQLite handle */
#if HAVE_BOTH_SQLITE
    int is3;			/* True for SQLITE3 handle */
#endif
    int ver;			/* version code */
    jobject bh;			/* BusyHandler object */
    jobject cb;			/* Callback object */
    jobject ai;			/* Authorizer object */
    jobject tr;			/* Trace object */
    jobject ph;			/* ProgressHandler object */
    JNIEnv *env;		/* Java environment for callbacks */
    int row1;			/* true while processing first row */
    int haveutf;		/* true for SQLite UTF-8 support */
    jstring enc;		/* encoding or 0 */
    struct hfunc *funcs;	/* SQLite user defined function handles */
#if HAVE_SQLITE_COMPILE
    struct hvm *vms;		/* Compiled SQLite VMs */
#endif
#if HAVE_SQLITE3
    sqlite3_stmt *stmt;		/* For callback() */
#endif
#if HAVE_SQLITE3 && HAVE_SQLITE3_INCRBLOBIO
  struct hbl *blobs;		/* SQLite3 blob handles */
#endif
} handle;

/* internal handle for SQLite user defined function */

typedef struct hfunc {
    struct hfunc *next;		/* next function */
#if HAVE_BOTH_SQLITE
    int is3;			/* True for SQLITE3 handle */
#endif
    jobject fc;			/* FunctionContext object */
    jobject fi;			/* Function object */
    jobject db;			/* Database object */
    handle *h;			/* SQLite database handle */
    void *sf;			/* SQLite function handle */
    JNIEnv *env;		/* Java environment for callbacks */
} hfunc;

#if HAVE_SQLITE_COMPILE
/* internal handle for SQLite VM (sqlite_compile()) */

typedef struct hvm {
    struct hvm *next;		/* next vm handle */
#if HAVE_BOTH_SQLITE
    int is3;			/* True for SQLITE3 handle */
#endif
    void *vm;			/* SQLite 2/3 VM/statement */
    char *tail;			/* tail SQL string */
    int tail_len;		/* only for SQLite3/prepare */
    handle *h;			/* SQLite database handle */
    handle hh;			/* fake SQLite database handle */
} hvm;
#endif

#if HAVE_SQLITE3 && HAVE_SQLITE3_INCRBLOBIO
/* internal handle for sqlite3_blob */

typedef struct hbl {
    struct hbl *next;		/* next blob handle */
    sqlite3_blob *blob;		/* SQLite3 blob */
    handle *h;			/* SQLite database handle */
} hbl;
#endif

/* ISO to/from UTF-8 translation */

typedef struct {
    char *result;		/* translated C string result */
    char *tofree;		/* memory to be free'd, or 0 */
    jstring jstr;		/* resulting Java string or 0 */
} transstr;

/* static cached weak class refs, field and method ids */

static jclass C_java_lang_String = 0;

static jfieldID F_SQLite_Database_handle = 0;
static jfieldID F_SQLite_Database_error_code = 0;
static jfieldID F_SQLite_FunctionContext_handle = 0;
static jfieldID F_SQLite_Vm_handle = 0;
static jfieldID F_SQLite_Vm_error_code = 0;
static jfieldID F_SQLite_Stmt_handle = 0;
static jfieldID F_SQLite_Stmt_error_code = 0;
static jfieldID F_SQLite_Blob_handle = 0;
static jfieldID F_SQLite_Blob_size = 0;

static jmethodID M_java_lang_String_getBytes = 0;
static jmethodID M_java_lang_String_getBytes2 = 0;
static jmethodID M_java_lang_String_initBytes = 0;
static jmethodID M_java_lang_String_initBytes2 = 0;

static const char xdigits[] = "0123456789ABCDEF";

static void
seterr(JNIEnv *env, jobject obj, int err)
{
    jvalue v;

    v.j = 0;
    v.i = (jint) err;
    (*env)->SetIntField(env, obj, F_SQLite_Database_error_code, v.i);
}

#if HAVE_SQLITE_COMPILE
static void
setvmerr(JNIEnv *env, jobject obj, int err)
{
    jvalue v;

    v.j = 0;
    v.i = (jint) err;
    (*env)->SetIntField(env, obj, F_SQLite_Vm_error_code, v.i);
}

#if HAVE_SQLITE3
static void
setstmterr(JNIEnv *env, jobject obj, int err)
{
    jvalue v;

    v.j = 0;
    v.i = (jint) err;
    (*env)->SetIntField(env, obj, F_SQLite_Stmt_error_code, v.i);
}

static int
jstrlen(const jchar *jstr)
{
    int len = 0;

    if (jstr) {
        while (*jstr++) {
	    len++;
	}
    }
    return len;
}
#endif
#endif

static void *
gethandle(JNIEnv *env, jobject obj)
{
    jvalue v;

    v.j = (*env)->GetLongField(env, obj, F_SQLite_Database_handle);
    return (void *) v.l;
}

#if HAVE_SQLITE_COMPILE
static void *
gethvm(JNIEnv *env, jobject obj)
{
    jvalue v;

    v.j = (*env)->GetLongField(env, obj, F_SQLite_Vm_handle);
    return (void *) v.l;
}

#if HAVE_SQLITE3
static void *
gethstmt(JNIEnv *env, jobject obj)
{
    jvalue v;

    v.j = (*env)->GetLongField(env, obj, F_SQLite_Stmt_handle);
    return (void *) v.l;
}
#endif
#endif

#if HAVE_SQLITE3 && HAVE_SQLITE3_INCRBLOBIO
static void *
gethbl(JNIEnv *env, jobject obj)
{
    jvalue v;

    v.j = (*env)->GetLongField(env, obj, F_SQLite_Blob_handle);
    return (void *) v.l;
}
#endif

static void
delglobrefp(JNIEnv *env, jobject *obj)
{
    if (*obj) {
	(*env)->DeleteGlobalRef(env, *obj);
	*obj = 0;
    }
}

static jobject
globrefpop(JNIEnv *env, jobject *obj)
{
    jobject ret = 0;

    if (*obj) {
	ret = *obj;
	*obj = 0;
    }
    return ret;
}

static void
globrefset(JNIEnv *env, jobject obj, jobject *ref)
{
    if (ref) {
	if (obj) {	
	    *ref = (*env)->NewGlobalRef(env, obj);
	} else {
	    *ref = 0;
	}
    }
}

static void
freep(char **strp)
{
    if (strp && *strp) {
	free(*strp);
	*strp = 0;
    }
}

static void
throwex(JNIEnv *env, const char *msg)
{
    jclass except = (*env)->FindClass(env, "SQLite/Exception");

    (*env)->ExceptionClear(env);
    if (except) {
	(*env)->ThrowNew(env, except, msg);
    }
}

static void
throwoom(JNIEnv *env, const char *msg)
{
    jclass except = (*env)->FindClass(env, "java/lang/OutOfMemoryError");

    (*env)->ExceptionClear(env);
    if (except) {
	(*env)->ThrowNew(env, except, msg);
    }
}

static void
throwclosed(JNIEnv *env)
{
    throwex(env, "database already closed");
}

#if HAVE_SQLITE3 && HAVE_SQLITE3_INCRBLOBIO
static void
throwioex(JNIEnv *env, const char *msg)
{
    jclass except = (*env)->FindClass(env, "java/io/IOException");

    (*env)->ExceptionClear(env);
    if (except) {
	(*env)->ThrowNew(env, except, msg);
    }
}
#endif

static void
transfree(transstr *dest)
{
    dest->result = 0;
    freep(&dest->tofree);
}

static char *
trans2iso(JNIEnv *env, int haveutf, jstring enc, jstring src,
	  transstr *dest)
{
    jbyteArray bytes = 0;
    jthrowable exc;

    dest->result = 0;
    dest->tofree = 0;
    if (haveutf) {
	const char *utf = (*env)->GetStringUTFChars(env, src, 0);

	if (!utf) {
	    return dest->result;
	}
	dest->tofree = malloc(strlen(utf) + 1);
	dest->result = dest->tofree;
	strcpy(dest->result, utf);
	(*env)->ReleaseStringUTFChars(env, src, utf);
	return dest->result;
    }
    if (enc) {
	bytes = (*env)->CallObjectMethod(env, src,
					 M_java_lang_String_getBytes2, enc);
    } else {
	bytes = (*env)->CallObjectMethod(env, src,
					 M_java_lang_String_getBytes);
    }
    exc = (*env)->ExceptionOccurred(env);
    if (!exc) {
	jint len = (*env)->GetArrayLength(env, bytes);
	dest->tofree = malloc(len + 1);
	if (!dest->tofree) {
	    throwoom(env, "string translation failed");
	    return dest->result;
	}
	dest->result = dest->tofree;	
	(*env)->GetByteArrayRegion(env, bytes, 0, len, (jbyte *) dest->result);
	dest->result[len] = '\0';
    } else {
	(*env)->DeleteLocalRef(env, exc);
    }
    return dest->result;
}

static jstring
trans2utf(JNIEnv *env, int haveutf, jstring enc, const char *src,
	  transstr *dest)
{
    jbyteArray bytes = 0;
    int len;

    dest->result = 0;
    dest->tofree = 0;
    dest->jstr = 0;
    if (!src) {
	return dest->jstr;
    }
    if (haveutf) {
	dest->jstr = (*env)->NewStringUTF(env, src);
	return dest->jstr;
    }
    len = strlen(src);
    bytes = (*env)->NewByteArray(env, len);
    if (bytes) {
	(*env)->SetByteArrayRegion(env, bytes, 0, len, (jbyte *) src);
	if (enc) {
	    dest->jstr =
		(*env)->NewObject(env, C_java_lang_String,
				  M_java_lang_String_initBytes2, bytes, enc);
	} else {
	    dest->jstr =
		(*env)->NewObject(env, C_java_lang_String,
				  M_java_lang_String_initBytes, bytes);
	}
	(*env)->DeleteLocalRef(env, bytes);
	return dest->jstr;
    }
    throwoom(env, "string translation failed");
    return dest->jstr;
}

#if HAVE_SQLITE2
static int
busyhandler(void *udata, const char *table, int count)
{
    handle *h = (handle *) udata;
    JNIEnv *env = h->env;
    int ret = 0;

    if (env && h->bh) {
	transstr tabstr;
	jclass cls = (*env)->GetObjectClass(env, h->bh);
	jmethodID mid = (*env)->GetMethodID(env, cls, "busy",
					    "(Ljava/lang/String;I)Z");

	if (mid == 0) {
	    return ret;
	}
	trans2utf(env, h->haveutf, h->enc, table, &tabstr);
	ret = (*env)->CallBooleanMethod(env, h->bh, mid, tabstr.jstr,
					(jint) count)
	      != JNI_FALSE;
	(*env)->DeleteLocalRef(env, tabstr.jstr);
    }
    return ret;
}
#endif

#if HAVE_SQLITE3
static int
busyhandler3(void *udata, int count)
{
    handle *h = (handle *) udata;
    JNIEnv *env = h->env;
    int ret = 0;

    if (env && h->bh) {
	jclass cls = (*env)->GetObjectClass(env, h->bh);
	jmethodID mid = (*env)->GetMethodID(env, cls, "busy",
					    "(Ljava/lang/String;I)Z");

	if (mid == 0) {
	    return ret;
	}
	ret = (*env)->CallBooleanMethod(env, h->bh, mid, 0, (jint) count)
	    != JNI_FALSE;
    }
    return ret;
}
#endif

static int
progresshandler(void *udata)
{
    handle *h = (handle *) udata;
    JNIEnv *env = h->env;
    int ret = 0;

    if (env && h->ph) {
	jclass cls = (*env)->GetObjectClass(env, h->ph);
	jmethodID mid = (*env)->GetMethodID(env, cls, "progress", "()Z");

	if (mid == 0) {
	    return ret;
	}
	ret = (*env)->CallBooleanMethod(env, h->ph, mid) != JNI_TRUE;
    }
    return ret;
}

static int
callback(void *udata, int ncol, char **data, char **cols)
{
    handle *h = (handle *) udata;
    JNIEnv *env = h->env;

    if (env && h->cb) {
	jthrowable exc;
	jclass cls = (*env)->GetObjectClass(env, h->cb);
	jmethodID mid;
	jobjectArray arr = 0;
	jint i;

	if (h->row1) {
	    mid = (*env)->GetMethodID(env, cls, "columns",
				      "([Ljava/lang/String;)V");

	    if (mid) {
		arr = (*env)->NewObjectArray(env, ncol, C_java_lang_String, 0);
		for (i = 0; i < ncol; i++) {
		    if (cols[i]) {
			transstr col;

			trans2utf(env, h->haveutf, h->enc, cols[i], &col);
			(*env)->SetObjectArrayElement(env, arr, i, col.jstr);
			exc = (*env)->ExceptionOccurred(env);
			if (exc) {
			    (*env)->DeleteLocalRef(env, exc);
			    return 1;
			}
			(*env)->DeleteLocalRef(env, col.jstr);
		    }
		}
		h->row1 = 0;
		(*env)->CallVoidMethod(env, h->cb, mid, arr);
		exc = (*env)->ExceptionOccurred(env);
		if (exc) {
		    (*env)->DeleteLocalRef(env, exc);
		    return 1;
		}
		(*env)->DeleteLocalRef(env, arr);
	    }
#if HAVE_BOTH_SQLITE
	    if (h->is3) {
		mid = (*env)->GetMethodID(env, cls, "types",
					  "([Ljava/lang/String;)V");

		if (mid && h->stmt) {
		    arr = (*env)->NewObjectArray(env, ncol,
						 C_java_lang_String, 0);
		    for (i = 0; i < ncol; i++) {
			const char *ctype =
			    sqlite3_column_decltype(h->stmt, i);

			if (!ctype) {
			    switch (sqlite3_column_type(h->stmt, i)) {
			    case SQLITE_INTEGER: ctype = "integer"; break;
			    case SQLITE_FLOAT:   ctype = "double";  break;
			    default:
#if defined(SQLITE_TEXT) && defined(SQLITE3_TEXT) && (SQLITE_TEXT != SQLITE3_TEXT)
			    case SQLITE_TEXT:
#else
#ifdef SQLITE3_TEXT
			    case SQLITE3_TEXT:
#endif
#endif
						 ctype = "text";    break;
			    case SQLITE_BLOB:    ctype = "blob";    break;
			    case SQLITE_NULL:    ctype = "null";    break;
			    }
			}
			if (ctype) {
			    transstr ty;

			    trans2utf(env, 1, 0, ctype, &ty);
			    (*env)->SetObjectArrayElement(env, arr, i,
							  ty.jstr);
			    exc = (*env)->ExceptionOccurred(env);
			    if (exc) {
				(*env)->DeleteLocalRef(env, exc);
				return 1;
			    }
			    (*env)->DeleteLocalRef(env, ty.jstr);
			}
		    }
		    (*env)->CallVoidMethod(env, h->cb, mid, arr);
		    exc = (*env)->ExceptionOccurred(env);
		    if (exc) {
			(*env)->DeleteLocalRef(env, exc);
			return 1;
		    }
		    (*env)->DeleteLocalRef(env, arr);
		}
	    } else {
		if (h->ver >= 0x020506 && cols[ncol]) {
		    mid = (*env)->GetMethodID(env, cls, "types",
					      "([Ljava/lang/String;)V");

		    if (mid) {
			arr = (*env)->NewObjectArray(env, ncol,
						     C_java_lang_String, 0);
			for (i = 0; i < ncol; i++) {
			    if (cols[i + ncol]) {
				transstr ty;

				trans2utf(env, h->haveutf, h->enc,
					  cols[i + ncol], &ty);
				(*env)->SetObjectArrayElement(env, arr, i,
							      ty.jstr);
				exc = (*env)->ExceptionOccurred(env);
				if (exc) {
				    (*env)->DeleteLocalRef(env, exc);
				    return 1;
				}
				(*env)->DeleteLocalRef(env, ty.jstr);
			    }
			}
			(*env)->CallVoidMethod(env, h->cb, mid, arr);
			exc = (*env)->ExceptionOccurred(env);
			if (exc) {
			    (*env)->DeleteLocalRef(env, exc);
			    return 1;
			}
			(*env)->DeleteLocalRef(env, arr);
		    }
		}
	    }
#else
#if HAVE_SQLITE2
	    if (h->ver >= 0x020506 && cols[ncol]) {
		mid = (*env)->GetMethodID(env, cls, "types",
					  "([Ljava/lang/String;)V");

		if (mid) {
		    arr = (*env)->NewObjectArray(env, ncol,
						 C_java_lang_String, 0);
		    for (i = 0; i < ncol; i++) {
			if (cols[i + ncol]) {
			    transstr ty;

			    trans2utf(env, h->haveutf, h->enc,
				      cols[i + ncol], &ty);
			    (*env)->SetObjectArrayElement(env, arr, i,
							  ty.jstr);
			    exc = (*env)->ExceptionOccurred(env);
			    if (exc) {
				(*env)->DeleteLocalRef(env, exc);
				return 1;
			    }
			    (*env)->DeleteLocalRef(env, ty.jstr);
			}
		    }
		    (*env)->CallVoidMethod(env, h->cb, mid, arr);
		    exc = (*env)->ExceptionOccurred(env);
		    if (exc) {
			(*env)->DeleteLocalRef(env, exc);
			return 1;
		    }
		    (*env)->DeleteLocalRef(env, arr);
		}
	    }
#endif
#if HAVE_SQLITE3
	    mid = (*env)->GetMethodID(env, cls, "types",
				      "([Ljava/lang/String;)V");

	    if (mid && h->stmt) {
		arr = (*env)->NewObjectArray(env, ncol,
					     C_java_lang_String, 0);
		for (i = 0; i < ncol; i++) {
		    const char *ctype = sqlite3_column_decltype(h->stmt, i);

		    if (!ctype) {
			switch (sqlite3_column_type(h->stmt, i)) {
			case SQLITE_INTEGER: ctype = "integer"; break;
			case SQLITE_FLOAT:   ctype = "double";  break;
			default:
#if defined(SQLITE_TEXT) && defined(SQLITE3_TEXT) && (SQLITE_TEXT != SQLITE3_TEXT)
			case SQLITE_TEXT:
#else
#ifdef SQLITE3_TEXT
			case SQLITE3_TEXT:
#endif
#endif
					     ctype = "text";    break;
			case SQLITE_BLOB:    ctype = "blob";    break;
			case SQLITE_NULL:    ctype = "null";    break;
			}
		    }
		    if (ctype) {
			transstr ty;

			trans2utf(env, 1, 0, ctype, &ty);
			(*env)->SetObjectArrayElement(env, arr, i, ty.jstr);
			exc = (*env)->ExceptionOccurred(env);
			if (exc) {
			    (*env)->DeleteLocalRef(env, exc);
			    return 1;
			}
			(*env)->DeleteLocalRef(env, ty.jstr);
		    }
		}
		(*env)->CallVoidMethod(env, h->cb, mid, arr);
		exc = (*env)->ExceptionOccurred(env);
		if (exc) {
		    (*env)->DeleteLocalRef(env, exc);
		    return 1;
		}
		(*env)->DeleteLocalRef(env, arr);
	    }
#endif
#endif
	}
	mid = (*env)->GetMethodID(env, cls, "newrow",
				  "([Ljava/lang/String;)Z");
	if (mid) {
	    jboolean rc;

	    if (data) {
		arr = (*env)->NewObjectArray(env, ncol, C_java_lang_String, 0);
	    } else {
		arr = 0;
	    }
	    for (i = 0; arr && i < ncol; i++) {
		if (data[i]) {
		    transstr dats;

		    trans2utf(env, h->haveutf, h->enc, data[i], &dats);
		    (*env)->SetObjectArrayElement(env, arr, i, dats.jstr);
		    exc = (*env)->ExceptionOccurred(env);
		    if (exc) {
			(*env)->DeleteLocalRef(env, exc);
			return 1;
		    }
		    (*env)->DeleteLocalRef(env, dats.jstr);
		}
	    }
	    rc = (*env)->CallBooleanMethod(env, h->cb, mid, arr);
	    exc = (*env)->ExceptionOccurred(env);
	    if (exc) {
		(*env)->DeleteLocalRef(env, exc);
		return 1;
	    }
	    if (arr) {
		(*env)->DeleteLocalRef(env, arr);
	    }
	    (*env)->DeleteLocalRef(env, cls);
	    return rc != JNI_FALSE;
	}
    }
    return 0;
}

static void
doclose(JNIEnv *env, jobject obj, int final)
{
    handle *h = gethandle(env, obj);

    if (h) {
	hfunc *f;
#if HAVE_SQLITE3 && HAVE_SQLITE3_INCRBLOBIO
	hbl *bl;
#endif
#if HAVE_SQLITE_COMPILE
	hvm *v;

	while ((v = h->vms)) {
	    h->vms = v->next;
	    v->next = 0;
	    v->h = 0;
	    if (v->vm) {
#if HAVE_BOTH_SQLITE
		if (h->is3) {
		    sqlite3_finalize((sqlite3_stmt *) v->vm);
		} else {
		    sqlite_finalize((sqlite_vm *) v->vm, 0);
		}
#else
#if HAVE_SQLITE2
		sqlite_finalize((sqlite_vm *) v->vm, 0);
#endif
#if HAVE_SQLITE3
		sqlite3_finalize((sqlite3_stmt *) v->vm);
#endif
#endif
		v->vm = 0;
	    }
	}
#endif
	if (h->sqlite) {
#if HAVE_BOTH_SQLITE
	    if (h->is3) {
		sqlite3_close((sqlite3 *) h->sqlite);
	    } else {
		sqlite_close((sqlite *) h->sqlite);
	    }
#else
#if HAVE_SQLITE2
	    sqlite_close((sqlite *) h->sqlite);
#endif
#if HAVE_SQLITE3
	    sqlite3_close((sqlite3 *) h->sqlite);
#endif
#endif
	    h->sqlite = 0;
	}
	while ((f = h->funcs)) {
	    h->funcs = f->next;
	    f->h = 0;
	    f->sf = 0;
	    f->env = 0;
	    if (f->fc) {
		(*env)->SetLongField(env, f->fc,
				     F_SQLite_FunctionContext_handle, 0);
	    }
	    delglobrefp(env, &f->db);
	    delglobrefp(env, &f->fi);
	    delglobrefp(env, &f->fc);
	    free(f);
	}
#if HAVE_SQLITE3 && HAVE_SQLITE3_INCRBLOBIO
	while ((bl = h->blobs)) {
	    h->blobs = bl->next;
	    bl->next = 0;
	    bl->h = 0;
	    if (bl->blob) {
	        sqlite3_blob_close(bl->blob);
	    }
	    bl->blob = 0;
	}
#endif
	delglobrefp(env, &h->bh);
	delglobrefp(env, &h->cb);
	delglobrefp(env, &h->ai);
	delglobrefp(env, &h->tr);
	delglobrefp(env, &h->ph);
	delglobrefp(env, &h->enc);
	free(h);
	(*env)->SetLongField(env, obj, F_SQLite_Database_handle, 0);
	return;
    }
    if (!final) {
	throwclosed(env);
    }
}

JNIEXPORT void JNICALL
Java_SQLite_Database__1close(JNIEnv *env, jobject obj)
{
    doclose(env, obj, 0);
}

JNIEXPORT void JNICALL
Java_SQLite_Database__1finalize(JNIEnv *env, jobject obj)
{
    doclose(env, obj, 1);
}

JNIEXPORT void JNICALL
Java_SQLite_Database__1busy_1timeout(JNIEnv *env, jobject obj, jint ms)
{
    handle *h = gethandle(env, obj);

    if (h && h->sqlite) {
#if HAVE_BOTH_SQLITE
	if (h->is3) {
	    sqlite3_busy_timeout((sqlite3 * ) h->sqlite, ms);
	} else {
	    sqlite_busy_timeout((sqlite *) h->sqlite, ms);
	}
#else
#if HAVE_SQLITE2
	sqlite_busy_timeout((sqlite *) h->sqlite, ms);
#endif
#if HAVE_SQLITE3
	sqlite3_busy_timeout((sqlite3 * ) h->sqlite, ms);
#endif
#endif
	return;
    }
    throwclosed(env);
}

JNIEXPORT jstring JNICALL
Java_SQLite_Database_version(JNIEnv *env, jclass cls)
{
    /* CHECK THIS */
#if HAVE_BOTH_SQLITE
    return (*env)->NewStringUTF(env, sqlite_libversion());
#else
#if HAVE_SQLITE2
    return (*env)->NewStringUTF(env, sqlite_libversion());
#else
    return (*env)->NewStringUTF(env, sqlite3_libversion());
#endif
#endif
}

JNIEXPORT jstring JNICALL
Java_SQLite_Database_dbversion(JNIEnv *env, jobject obj)
{
    handle *h = gethandle(env, obj);

    if (h && h->sqlite) {
#if HAVE_BOTH_SQLITE
	if (h->is3) {
	    return (*env)->NewStringUTF(env, sqlite3_libversion());
	} else {
	    return (*env)->NewStringUTF(env, sqlite_libversion());
	}
#else
#if HAVE_SQLITE2
	return (*env)->NewStringUTF(env, sqlite_libversion());
#else
	return (*env)->NewStringUTF(env, sqlite3_libversion());
#endif
#endif
    }
    return (*env)->NewStringUTF(env, "unknown");
}

JNIEXPORT jlong JNICALL
Java_SQLite_Database__1last_1insert_1rowid(JNIEnv *env, jobject obj)
{
    handle *h = gethandle(env, obj);

    if (h && h->sqlite) {
#if HAVE_BOTH_SQLITE
	if (h->is3) {
	    return (jlong) sqlite3_last_insert_rowid((sqlite3 *) h->sqlite);
	} else {
	    return (jlong) sqlite_last_insert_rowid((sqlite *) h->sqlite);
	}
#else
#if HAVE_SQLITE2
	return (jlong) sqlite_last_insert_rowid((sqlite *) h->sqlite);
#endif
#if HAVE_SQLITE3
	return (jlong) sqlite3_last_insert_rowid((sqlite3 *) h->sqlite);
#endif
#endif
    }
    throwclosed(env);
    return (jlong) 0;
}

JNIEXPORT jlong JNICALL
Java_SQLite_Database__1changes(JNIEnv *env, jobject obj)
{
    handle *h = gethandle(env, obj);

    if (h && h->sqlite) {
#if HAVE_BOTH_SQLITE
	if (h->is3) {
	    return (jlong) sqlite3_changes((sqlite3 *) h->sqlite);
	} else {
	    return (jlong) sqlite_changes((sqlite *) h->sqlite);
	}
#else
#if HAVE_SQLITE2
	return (jlong) sqlite_changes((sqlite *) h->sqlite);
#endif
#if HAVE_SQLITE3
	return (jlong) sqlite3_changes((sqlite3 *) h->sqlite);
#endif
#endif
    }
    throwclosed(env);
    return (jlong) 0;
}

JNIEXPORT jboolean JNICALL
Java_SQLite_Database__1complete(JNIEnv *env, jclass cls, jstring sql)
{
    transstr sqlstr;
    jboolean result;

    if (!sql) {
	return JNI_FALSE;
    }
#if HAVE_BOTH_SQLITE || HAVE_SQLITE3
    /* CHECK THIS */
    trans2iso(env, 1, 0, sql, &sqlstr);
    result = sqlite3_complete(sqlstr.result) ? JNI_TRUE : JNI_FALSE;
#else
    trans2iso(env, strcmp(sqlite_libencoding(), "UTF-8") == 0, 0,
	      sql, &sqlstr);
    result = sqlite_complete(sqlstr.result) ? JNI_TRUE : JNI_FALSE;
#endif
    transfree(&sqlstr);
    return result;
}

JNIEXPORT void JNICALL
Java_SQLite_Database__1interrupt(JNIEnv *env, jobject obj)
{
    handle *h = gethandle(env, obj);

    if (h && h->sqlite) {
#if HAVE_BOTH_SQLITE
	if (h->is3) {
	    sqlite3_interrupt((sqlite3 *) h->sqlite);
	} else {
	    sqlite_interrupt((sqlite *) h->sqlite);
	}
#else
#if HAVE_SQLITE2
	sqlite_interrupt((sqlite *) h->sqlite);
#endif
#if HAVE_SQLITE3
	sqlite3_interrupt((sqlite3 *) h->sqlite);
#endif
#endif
	return;
    }
    throwclosed(env);
}

JNIEXPORT void JNICALL
Java_SQLite_Database__1open(JNIEnv *env, jobject obj, jstring file, jint mode)
{
    handle *h = gethandle(env, obj);
    jthrowable exc;
    char *err = 0;
    transstr filename;
    int maj, min, lev;

    if (h) {
	if (h->sqlite) {
#if HAVE_BOTH_SQLITE
	    if (h->is3) {
		sqlite3_close((sqlite3 *) h->sqlite);
	    } else {
		sqlite_close((sqlite *) h->sqlite);
	    }
	    h->is3 = 0;
#else
#if HAVE_SQLITE2
	    sqlite_close((sqlite *) h->sqlite);
#endif
#if HAVE_SQLITE3
	    sqlite3_close((sqlite3 *) h->sqlite);
#endif
#endif
	    h->sqlite = 0;
	}
    } else {
	h = malloc(sizeof (handle));
	if (!h) {
	    throwoom(env, "unable to get SQLite handle");
	    return;
	}
	h->sqlite = 0;
	h->bh = h->cb = h->ai = h->tr = h->ph = 0;
	/* CHECK THIS */
#if HAVE_BOTH_SQLITE
	h->is3 = 0;
	h->stmt = 0;
	h->haveutf = 1;
#else
#if HAVE_SQLITE2
	h->haveutf = strcmp(sqlite_libencoding(), "UTF-8") == 0;
#endif
#if HAVE_SQLITE3
	h->stmt = 0;
	h->haveutf = 1;
#endif
#endif
	h->enc = 0;
	h->funcs = 0;
	h->ver = 0;
#if HAVE_SQLITE_COMPILE
	h->vms = 0;
#endif
#if HAVE_SQLITE3 && HAVE_SQLITE3_INCRBLOBIO
	h->blobs = 0;
#endif
    }
    h->env = 0;
    if (!file) {
	throwex(env, err ? err : "invalid file name");
	return;
    }
    trans2iso(env, h->haveutf, h->enc, file, &filename);
    exc = (*env)->ExceptionOccurred(env);
    if (exc) {
	(*env)->DeleteLocalRef(env, exc);
	return;
    }
#if HAVE_BOTH_SQLITE
    {
	FILE *f = fopen(filename.result, "rb");
	int c_0 = EOF;

	if (f) {
	    c_0 = fgetc(f);
	    fclose(f);
	}
	if (c_0 != '*') {
	    int rc = sqlite3_open(filename.result, (sqlite3 **) &h->sqlite);

	    if (rc == SQLITE_OK) {
		h->is3 = 1;
	    } else if (h->sqlite) {
	        sqlite3_close((sqlite3 *) h->sqlite);
		h->sqlite = 0;
	    }
	} else {
	    h->sqlite = (void *) sqlite_open(filename.result,
					     (int) mode, &err);
	}
    }
#else
#if HAVE_SQLITE2
    h->sqlite = (void *) sqlite_open(filename.result, (int) mode, &err);
#endif
#if HAVE_SQLITE3
    if (sqlite3_open(filename.result, (sqlite3 **) &h->sqlite) != SQLITE_OK) {
        if (h->sqlite) {
	    sqlite3_close((sqlite3 *) h->sqlite);
	    h->sqlite = 0;
	}
    }
#endif
#endif
    transfree(&filename);
    exc = (*env)->ExceptionOccurred(env);
    if (exc) {
	(*env)->DeleteLocalRef(env, exc);
#if HAVE_SQLITE2
	if (err) {
	    sqlite_freemem(err);
	}
#endif
	if (h->sqlite) {
#if HAVE_BOTH_SQLITE
	    if (h->is3) {
		sqlite3_close((sqlite3 *) h->sqlite);
		h->is3 = 0;
	    } else {
		sqlite_close((sqlite *) h->sqlite);
	    }
#else
#if HAVE_SQLITE2
	    sqlite_close((sqlite *) h->sqlite);
#endif
#if HAVE_SQLITE3
	    sqlite3_close((sqlite3 *) h->sqlite);
#endif
#endif
	}
	h->sqlite = 0;
	return;
    }
    if (h->sqlite) {
	jvalue v;

	v.j = 0;
	v.l = (jobject) h;
	(*env)->SetLongField(env, obj, F_SQLite_Database_handle, v.j);
#if HAVE_SQLITE2
	if (err) {
	    sqlite_freemem(err);
	}
#endif
#if HAVE_BOTH_SQLITE
	if (h->is3) {
	    sscanf(sqlite3_libversion(), "%d.%d.%d", &maj, &min, &lev);
	} else {
	    sscanf(sqlite_libversion(), "%d.%d.%d", &maj, &min, &lev);
	}
#else
#if HAVE_SQLITE2
	sscanf(sqlite_libversion(), "%d.%d.%d", &maj, &min, &lev);
#endif
#if HAVE_SQLITE3
	sscanf(sqlite3_libversion(), "%d.%d.%d", &maj, &min, &lev);
#endif
#endif
	h->ver = ((maj & 0xFF) << 16) | ((min & 0xFF) << 8) | (lev & 0xFF);
	return;
    }
    throwex(env, err ? err : "unknown error in open");
#if HAVE_SQLITE2
    if (err) {
	sqlite_freemem(err);
    }
#endif
}

JNIEXPORT void JNICALL
Java_SQLite_Database__1open_1aux_1file(JNIEnv *env, jobject obj, jstring file)
{
    handle *h = gethandle(env, obj);
#if HAVE_SQLITE_OPEN_AUX_FILE
    jboolean b;
    jthrowable exc;
    char *err = 0;
    transstr filename;
    int ret;
#endif

    if (h && h->sqlite) {
#if HAVE_SQLITE_OPEN_AUX_FILE
#if HAVE_BOTH_SQLITE
	if (h->is3) {
	    throwex(env, "unsupported");
	}
#endif
	trans2iso(env, h->haveutf, h->enc, file, &filename);
	exc = (*env)->ExceptionOccurred(env);
	if (exc) {
	    (*env)->DeleteLocalRef(env, exc);
	    return;
	}
	ret = sqlite_open_aux_file((sqlite *) h->sqlite,
				   filename.result, &err);
	transfree(&filename);
	exc = (*env)->ExceptionOccurred(env);
	if (exc) {
	    (*env)->DeleteLocalRef(env, exc);
	    if (err) {
		sqlite_freemem(err);
	    }
	    return;
	}
	if (ret != SQLITE_OK) {
	    throwex(env, err ? err : sqlite_error_string(ret));
	}
	if (err) {
	    sqlite_freemem(err);
	}
#else
	throwex(env, "unsupported");
#endif
	return;
    }
    throwclosed(env);
}

JNIEXPORT void JNICALL
Java_SQLite_Database__1busy_1handler(JNIEnv *env, jobject obj, jobject bh)
{
    handle *h = gethandle(env, obj);

    if (h && h->sqlite) {
	delglobrefp(env, &h->bh);
	globrefset(env, bh, &h->bh);
#if HAVE_BOTH_SQLITE
	if (h->is3) {
	    sqlite3_busy_handler((sqlite3 *) h->sqlite, busyhandler3, h);
	} else {
	    sqlite_busy_handler((sqlite *) h->sqlite, busyhandler, h);
	}
#else
#if HAVE_SQLITE2
	sqlite_busy_handler((sqlite *) h->sqlite, busyhandler, h);
#endif
#if HAVE_SQLITE3
	sqlite3_busy_handler((sqlite3 *) h->sqlite, busyhandler3, h);
#endif
#endif
	return;
    }
    throwclosed(env);
}

JNIEXPORT void JNICALL
Java_SQLite_Database__1exec__Ljava_lang_String_2LSQLite_Callback_2
    (JNIEnv *env, jobject obj, jstring sql, jobject cb)
{
    handle *h = gethandle(env, obj);
    freemem *freeproc;

    if (!sql) {
	throwex(env, "invalid SQL statement");
	return;
    }
    if (h) {
	if (h->sqlite) {
	    jthrowable exc;
	    int rc;
	    char *err = 0;
	    transstr sqlstr;
	    jobject oldcb = globrefpop(env, &h->cb);

	    globrefset(env, cb, &h->cb);
	    h->env = env;
	    h->row1 = 1;
	    trans2iso(env, h->haveutf, h->enc, sql, &sqlstr);
	    exc = (*env)->ExceptionOccurred(env);
	    if (exc) {
		(*env)->DeleteLocalRef(env, exc);
		return;
	    }
#if HAVE_BOTH_SQLITE
	    if (h->is3) {
		rc = sqlite3_exec((sqlite3 *) h->sqlite, sqlstr.result,
				  callback, h, &err);
		freeproc = (freemem *) sqlite3_free;
	    } else {
		rc = sqlite_exec((sqlite *) h->sqlite, sqlstr.result,
				 callback, h, &err);
		freeproc = (freemem *) sqlite_freemem;
	    }
#else
#if HAVE_SQLITE2
	    rc = sqlite_exec((sqlite *) h->sqlite, sqlstr.result,
			     callback, h, &err);
	    freeproc = (freemem *) sqlite_freemem;
#endif
#if HAVE_SQLITE3
	    rc = sqlite3_exec((sqlite3 *) h->sqlite, sqlstr.result,
			      callback, h, &err);
	    freeproc = (freemem *) sqlite3_free;
#endif
#endif
	    transfree(&sqlstr);
	    exc = (*env)->ExceptionOccurred(env);
	    delglobrefp(env, &h->cb);
	    h->cb = oldcb;
	    if (exc) {
		(*env)->DeleteLocalRef(env, exc);
		if (err) {
		    freeproc(err);
		}
		return;
	    }
	    if (rc != SQLITE_OK) {
		char msg[128];

		seterr(env, obj, rc);
		if (!err) {
		    sprintf(msg, "error %d in sqlite*_exec", rc);
		}
		throwex(env, err ? err : msg);
	    }
	    if (err) {
		freeproc(err);
	    }
	    return;
	}
    }
    throwclosed(env);
}

JNIEXPORT void JNICALL
Java_SQLite_Database__1exec__Ljava_lang_String_2LSQLite_Callback_2_3Ljava_lang_String_2
    (JNIEnv *env, jobject obj, jstring sql, jobject cb, jobjectArray args)
{
    handle *h = gethandle(env, obj);
    freemem *freeproc = 0;

    if (!sql) {
	throwex(env, "invalid SQL statement");
	return;
    }
    if (h) {
	if (h->sqlite) {
	    jboolean b;
	    jthrowable exc;
	    int rc = SQLITE_ERROR, nargs, i;
	    char *err = 0, *p;
	    const char *str = (*env)->GetStringUTFChars(env, sql, &b);
	    transstr sqlstr;
	    struct args {
		char *arg;
		jobject obj;
		transstr trans;
	    } *argv = 0;
	    char **cargv = 0;
	    jobject oldcb = globrefpop(env, &h->cb);

	    globrefset(env, cb, &h->cb);
	    p = (char *) str;
	    nargs = 0;
	    while (*p) {
		if (*p == '%') {
		    ++p;
		    if (*p == 'q' || *p == 's') {
			nargs++;
			if (nargs > MAX_PARAMS) {
			    (*env)->ReleaseStringUTFChars(env, sql, str);
			    delglobrefp(env, &h->cb);
			    h->cb = oldcb;
			    throwex(env, "too much SQL parameters");
			    return;
			}
		    } else if (h->ver >= 0x020500 && *p == 'Q') {
			nargs++;
			if (nargs > MAX_PARAMS) {
			    (*env)->ReleaseStringUTFChars(env, sql, str);
			    delglobrefp(env, &h->cb);
			    h->cb = oldcb;
			    throwex(env, "too much SQL parameters");
			    return;
			}
		    } else if (*p != '%') {
			(*env)->ReleaseStringUTFChars(env, sql, str);
			delglobrefp(env, &h->cb);
			h->cb = oldcb;
			throwex(env, "bad % specification in query");
			return;
		    }
		}
		++p;
	    }
	    cargv = malloc((sizeof (*argv) + sizeof (char *))
			   * MAX_PARAMS);
	    if (!cargv) {
		(*env)->ReleaseStringUTFChars(env, sql, str);
		delglobrefp(env, &h->cb);
		h->cb = oldcb;
		throwoom(env, "unable to allocate arg vector");
		return;
	    }
	    argv = (struct args *) (cargv + MAX_PARAMS);
	    for (i = 0; i < MAX_PARAMS; i++) {
		cargv[i] = 0;
		argv[i].arg = 0;
		argv[i].obj = 0;
		argv[i].trans.result = argv[i].trans.tofree = 0;
	    }
	    exc = 0;
	    for (i = 0; i < nargs; i++) {
		jobject so = (*env)->GetObjectArrayElement(env, args, i);

		exc = (*env)->ExceptionOccurred(env);
		if (exc) {
		    (*env)->DeleteLocalRef(env, exc);
		    break;
		}
		if (so) {
		    argv[i].obj = so;
		    argv[i].arg = cargv[i] =
			trans2iso(env, h->haveutf, h->enc, argv[i].obj,
				  &argv[i].trans);
		}
	    }
	    if (exc) {
		for (i = 0; i < nargs; i++) {
		    if (argv[i].obj) {
			transfree(&argv[i].trans);
		    }
		}
		freep((char **) &cargv);
		(*env)->ReleaseStringUTFChars(env, sql, str);
		delglobrefp(env, &h->cb);
		h->cb = oldcb;
		return;
	    }
	    h->env = env;
	    h->row1 = 1;
	    trans2iso(env, h->haveutf, h->enc, sql, &sqlstr);
	    exc = (*env)->ExceptionOccurred(env);
	    if (!exc) {
#if HAVE_BOTH_SQLITE
		if (h->is3) {
#if defined(_WIN32) || !defined(CANT_PASS_VALIST_AS_CHARPTR)
		    char *s = sqlite3_vmprintf(sqlstr.result, (char *) cargv);
#else
		    char *s = sqlite3_mprintf(sqlstr.result,
					      cargv[0], cargv[1],
					      cargv[2], cargv[3],
					      cargv[4], cargv[5],
					      cargv[6], cargv[7],
					      cargv[8], cargv[9],
					      cargv[10], cargv[11],
					      cargv[12], cargv[13],
					      cargv[14], cargv[15],
					      cargv[16], cargv[17],
					      cargv[18], cargv[19],
					      cargv[20], cargv[21],
					      cargv[22], cargv[23],
					      cargv[24], cargv[25],
					      cargv[26], cargv[27],
					      cargv[28], cargv[29],
					      cargv[30], cargv[31]);
#endif

		    if (s) {
			rc = sqlite3_exec((sqlite3 *) h->sqlite, s, callback,
					  h, &err);
			sqlite3_free(s);
		    } else {
			rc = SQLITE_NOMEM;
		    }
		    freeproc = (freemem *) sqlite3_free;
		} else {
#if defined(_WIN32) || !defined(CANT_PASS_VALIST_AS_CHARPTR)
		    rc = sqlite_exec_vprintf((sqlite *) h->sqlite,
					     sqlstr.result, callback, h, &err,
					     (char *) cargv);
#else
		    rc = sqlite_exec_printf((sqlite *) h->sqlite,
					    sqlstr.result, callback,
					    h, &err,
					    cargv[0], cargv[1],
					    cargv[2], cargv[3],
					    cargv[4], cargv[5],
					    cargv[6], cargv[7],
					    cargv[8], cargv[9],
					    cargv[10], cargv[11],
					    cargv[12], cargv[13],
					    cargv[14], cargv[15],
					    cargv[16], cargv[17],
					    cargv[18], cargv[19],
					    cargv[20], cargv[21],
					    cargv[22], cargv[23],
					    cargv[24], cargv[25],
					    cargv[26], cargv[27],
					    cargv[28], cargv[29],
					    cargv[30], cargv[31]);
#endif
		    freeproc = (freemem *) sqlite_freemem;
		}
#else
#if HAVE_SQLITE2
#if defined(_WIN32) || !defined(CANT_PASS_VALIST_AS_CHARPTR)
		rc = sqlite_exec_vprintf((sqlite *) h->sqlite, sqlstr.result,
					 callback, h, &err, (char *) cargv);
#else
		rc = sqlite_exec_printf((sqlite *) h->sqlite, sqlstr.result,
					callback, h, &err,
					cargv[0], cargv[1],
					cargv[2], cargv[3],
					cargv[4], cargv[5],
					cargv[6], cargv[7],
					cargv[8], cargv[9],
					cargv[10], cargv[11],
					cargv[12], cargv[13],
					cargv[14], cargv[15],
					cargv[16], cargv[17],
					cargv[18], cargv[19],
					cargv[20], cargv[21],
					cargv[22], cargv[23],
					cargv[24], cargv[25],
					cargv[26], cargv[27],
					cargv[28], cargv[29],
					cargv[30], cargv[31]);
#endif
		freeproc = (freemem *) sqlite_freemem;
#endif
#if HAVE_SQLITE3
#if defined(_WIN32) || !defined(CANT_PASS_VALIST_AS_CHARPTR)
		char *s = sqlite3_vmprintf(sqlstr.result, (char *) cargv);
#else
		char *s = sqlite3_mprintf(sqlstr.result,
					  cargv[0], cargv[1],
					  cargv[2], cargv[3],
					  cargv[4], cargv[5],
					  cargv[6], cargv[7],
					  cargv[8], cargv[9],
					  cargv[10], cargv[11],
					  cargv[12], cargv[13],
					  cargv[14], cargv[15],
					  cargv[16], cargv[17],
					  cargv[18], cargv[19],
					  cargv[20], cargv[21],
					  cargv[22], cargv[23],
					  cargv[24], cargv[25],
					  cargv[26], cargv[27],
					  cargv[28], cargv[29],
					  cargv[30], cargv[31]);
#endif

		if (s) {
		    rc = sqlite3_exec((sqlite3 *) h->sqlite, s, callback,
				      h, &err);
		    sqlite3_free(s);
		} else {
		    rc = SQLITE_NOMEM;
		}
		freeproc = (freemem *) sqlite3_free;
#endif
#endif
		exc = (*env)->ExceptionOccurred(env);
	    }
	    for (i = 0; i < nargs; i++) {
		if (argv[i].obj) {
		    transfree(&argv[i].trans);
		}
	    }
	    transfree(&sqlstr);
	    (*env)->ReleaseStringUTFChars(env, sql, str);
	    freep((char **) &cargv);
	    delglobrefp(env, &h->cb);
	    h->cb = oldcb;
	    if (exc) {
		(*env)->DeleteLocalRef(env, exc);
		if (err && freeproc) {
		    freeproc(err);
		}
		return;
	    }
	    if (rc != SQLITE_OK) {
		char msg[128];

		seterr(env, obj, rc);
		if (!err) {
		    sprintf(msg, "error %d in sqlite*_exec", rc);
		}
		throwex(env, err ? err : msg);
	    }
	    if (err && freeproc) {
		freeproc(err);
	    }
	    return;
	}
    }
    throwclosed(env);
}

static hfunc *
getfunc(JNIEnv *env, jobject obj)
{
    jvalue v;

    v.j = (*env)->GetLongField(env, obj, F_SQLite_FunctionContext_handle);
    return (hfunc *) v.l;
}

#if HAVE_SQLITE2
static void
call_common(sqlite_func *sf, int isstep, int nargs, const char **args)
{
    hfunc *f = (hfunc *) sqlite_user_data(sf);

    if (f && f->env && f->fi) {
	JNIEnv *env = f->env;
	jclass cls = (*env)->GetObjectClass(env, f->fi);
	jmethodID mid =
	    (*env)->GetMethodID(env, cls,
				isstep ? "step" : "function",
				"(LSQLite/FunctionContext;[Ljava/lang/String;)V");
	jobjectArray arr;
	int i;

	if (mid == 0) {
	    return;
	}
	arr = (*env)->NewObjectArray(env, nargs, C_java_lang_String, 0);
	for (i = 0; i < nargs; i++) {
	    if (args[i]) {
		transstr arg;
		jthrowable exc;

		trans2utf(env, f->h->haveutf, f->h->enc, args[i], &arg);
		(*env)->SetObjectArrayElement(env, arr, i, arg.jstr);
		exc = (*env)->ExceptionOccurred(env);
		if (exc) {
		    (*env)->DeleteLocalRef(env, exc);
		    return;
		}
		(*env)->DeleteLocalRef(env, arg.jstr);
	    }
	}
	f->sf = sf;
	(*env)->CallVoidMethod(env, f->fi, mid, f->fc, arr);
	(*env)->DeleteLocalRef(env, arr);
	(*env)->DeleteLocalRef(env, cls);
    }
}

static void
call_func(sqlite_func *sf, int nargs, const char **args)
{
    call_common(sf, 0, nargs, args);
}

static void
call_step(sqlite_func *sf, int nargs, const char **args)
{
    call_common(sf, 1, nargs, args);
}

static void
call_final(sqlite_func *sf)
{
    hfunc *f = (hfunc *) sqlite_user_data(sf);

    if (f && f->env && f->fi) {
	JNIEnv *env = f->env;
	jclass cls = (*env)->GetObjectClass(env, f->fi);
	jmethodID mid = (*env)->GetMethodID(env, cls, "last_step",
					    "(LSQLite/FunctionContext;)V");
	if (mid == 0) {
	    return;
	}
	f->sf = sf;
	(*env)->CallVoidMethod(env, f->fi, mid, f->fc);
	(*env)->DeleteLocalRef(env, cls);
    }
}
#endif

#if HAVE_SQLITE3
static void
call3_common(sqlite3_context *sf, int isstep, int nargs, sqlite3_value **args)
{
    hfunc *f = (hfunc *) sqlite3_user_data(sf);

    if (f && f->env && f->fi) {
	JNIEnv *env = f->env;
	jclass cls = (*env)->GetObjectClass(env, f->fi);
	jmethodID mid =
	    (*env)->GetMethodID(env, cls,
				isstep ? "step" : "function",
				"(LSQLite/FunctionContext;[Ljava/lang/String;)V");
	jobjectArray arr;
	int i;

	if (mid == 0) {
	    return;
	}
	arr = (*env)->NewObjectArray(env, nargs, C_java_lang_String, 0);
	for (i = 0; i < nargs; i++) {
	    if (args[i]) {
		transstr arg;
		jthrowable exc;

		trans2utf(env, 1, 0, (char *) sqlite3_value_text(args[i]),
			  &arg);
		(*env)->SetObjectArrayElement(env, arr, i, arg.jstr);
		exc = (*env)->ExceptionOccurred(env);
		if (exc) {
		    (*env)->DeleteLocalRef(env, exc);
		    return;
		}
		(*env)->DeleteLocalRef(env, arg.jstr);
	    }
	}
	f->sf = sf;
	(*env)->CallVoidMethod(env, f->fi, mid, f->fc, arr);
	(*env)->DeleteLocalRef(env, arr);
	(*env)->DeleteLocalRef(env, cls);
    }
}

static void
call3_func(sqlite3_context *sf, int nargs, sqlite3_value **args)
{
    call3_common(sf, 0, nargs, args);
}

static void
call3_step(sqlite3_context *sf, int nargs, sqlite3_value **args)
{
    call3_common(sf, 1, nargs, args);
}

static void
call3_final(sqlite3_context *sf)
{
    hfunc *f = (hfunc *) sqlite3_user_data(sf);

    if (f && f->env && f->fi) {
	JNIEnv *env = f->env;
	jclass cls = (*env)->GetObjectClass(env, f->fi);
	jmethodID mid = (*env)->GetMethodID(env, cls, "last_step",
					    "(LSQLite/FunctionContext;)V");
	if (mid == 0) {
	    return;
	}
	f->sf = sf;
	(*env)->CallVoidMethod(env, f->fi, mid, f->fc);
	(*env)->DeleteLocalRef(env, cls);
    }
}
#endif

static void
mkfunc_common(JNIEnv *env, int isagg, jobject obj, jstring name,
	      jint nargs, jobject fi)
{
    handle *h = gethandle(env, obj);

    if (h && h->sqlite) {
	jclass cls = (*env)->FindClass(env, "SQLite/FunctionContext");
	jobject fc;
	hfunc *f;
	int ret;
	transstr namestr;
	jvalue v;
	jthrowable exc;

	fc = (*env)->AllocObject(env, cls);
	if (!fi) {
	    throwex(env, "null SQLite.Function not allowed");
	    return;
	}
	f = malloc(sizeof (hfunc));
	if (!f) {
	    throwoom(env, "unable to get SQLite.FunctionContext handle");
	    return;
	}
	globrefset(env, fc, &f->fc);
	globrefset(env, fi, &f->fi);
	globrefset(env, obj, &f->db);
	f->h = h;
	f->next = h->funcs;
	h->funcs = f;
	f->sf = 0;
	f->env = env;
	v.j = 0;
	v.l = (jobject) f;
	(*env)->SetLongField(env, f->fc, F_SQLite_FunctionContext_handle, v.j);
	trans2iso(env, h->haveutf, h->enc, name, &namestr);
	exc = (*env)->ExceptionOccurred(env);
	if (exc) {
	    (*env)->DeleteLocalRef(env, exc);
	    return;
	}
#if HAVE_BOTH_SQLITE
	f->is3 = h->is3;
	if (h->is3) {
	    ret = sqlite3_create_function((sqlite3 *) h->sqlite,
					  namestr.result,
					  (int) nargs,
					  SQLITE_UTF8, f,
					  isagg ? NULL : call3_func,
					  isagg ? call3_step : NULL,
					  isagg ? call3_final : NULL);

	} else {
	    if (isagg) {
		ret = sqlite_create_aggregate((sqlite *) h->sqlite,
					      namestr.result,
					      (int) nargs,
					      call_step, call_final, f);
	    } else {
		ret = sqlite_create_function((sqlite *) h->sqlite,
					     namestr.result,
					     (int) nargs,
					     call_func, f);
	    }
	}
#else
#if HAVE_SQLITE2
	if (isagg) {
	    ret = sqlite_create_aggregate((sqlite *) h->sqlite, namestr.result,
					  (int) nargs,
					  call_step, call_final, f);
	} else {
	    ret = sqlite_create_function((sqlite *) h->sqlite, namestr.result,
					 (int) nargs,
					 call_func, f);
	}
#endif
#if HAVE_SQLITE3
	ret = sqlite3_create_function((sqlite3 *) h->sqlite,
				      namestr.result,
				      (int) nargs,
				      SQLITE_UTF8, f,
				      isagg ? NULL : call3_func,
				      isagg ? call3_step : NULL,
				      isagg ? call3_final : NULL);
#endif
#endif
	transfree(&namestr);
	if (ret != SQLITE_OK) {
	    throwex(env, "error creating function/aggregate");
	}
	return;
    }
    throwclosed(env);
}

JNIEXPORT void JNICALL
Java_SQLite_Database__1create_1aggregate(JNIEnv *env, jobject obj,
					 jstring name, jint nargs, jobject fi)
{
    mkfunc_common(env, 1, obj, name, nargs, fi);
}

JNIEXPORT void JNICALL
Java_SQLite_Database__1create_1function(JNIEnv *env, jobject obj,
					jstring name, jint nargs, jobject fi)
{
    mkfunc_common(env, 0, obj, name, nargs, fi);
}

JNIEXPORT void JNICALL
Java_SQLite_Database__1function_1type(JNIEnv *env, jobject obj,
				      jstring name, jint type)
{
    handle *h = gethandle(env, obj);

    if (h && h->sqlite) {
#if HAVE_BOTH_SQLITE
	if (h->is3) {
	    return;
	}
#endif
#if HAVE_SQLITE2
#if HAVE_SQLITE_FUNCTION_TYPE
	{
	    int ret;
	    transstr namestr;
	    jthrowable exc;

	    trans2iso(env, h->haveutf, h->enc, name, &namestr);
	    exc = (*env)->ExceptionOccurred(env);
	    if (exc) {
		(*env)->DeleteLocalRef(env, exc);
		return;
	    }
	    ret = sqlite_function_type(h->sqlite, namestr.result, (int) type);
	    transfree(&namestr);
	    if (ret != SQLITE_OK) {
		throwex(env, sqlite_error_string(ret));
	    }
	}
#endif
#endif
	return;
    }
    throwclosed(env);
}

JNIEXPORT jint JNICALL
Java_SQLite_FunctionContext_count(JNIEnv *env, jobject obj)
{
    hfunc *f = getfunc(env, obj);
    jint r = 0;

    if (f && f->sf) {
#if HAVE_SQLITE_BOTH
	if (f->is3) {
	    r = (jint) sqlite3_aggregate_count((sqlite3_context *) f->sf);
	} else {
	    r = (jint) sqlite_aggregate_count((sqlite_func *) f->sf);
	}
#else
#if HAVE_SQLITE2
	r = (jint) sqlite_aggregate_count((sqlite_func *) f->sf);
#endif
#if HAVE_SQLITE3
	r = (jint) sqlite3_aggregate_count((sqlite3_context *) f->sf);
#endif
#endif
    }
    return r;
}

JNIEXPORT void JNICALL
Java_SQLite_FunctionContext_set_1error(JNIEnv *env, jobject obj, jstring err)
{
    hfunc *f = getfunc(env, obj);

    if (f && f->sf) {
#if HAVE_BOTH_SQLITE
	if (!f->is3) {
	    transstr errstr;
	    jthrowable exc;

	    trans2iso(env, f->h->haveutf, f->h->enc, err, &errstr);
	    exc = (*env)->ExceptionOccurred(env);
	    if (exc) {
	        (*env)->DeleteLocalRef(env, exc);
		return;
	    }
	    sqlite_set_result_error((sqlite_func *) f->sf,
				    errstr.result, -1);
	    transfree(&errstr);
	} else if (err) {
	    jsize len = (*env)->GetStringLength(env, err) * sizeof (jchar);
	    const jchar *str = (*env)->GetStringChars(env, err, 0);

	    sqlite3_result_error16((sqlite3_context *) f->sf, str, len);
	    (*env)->ReleaseStringChars(env, err, str);
	} else {
	    sqlite3_result_error((sqlite3_context *) f->sf,
				 "null error text", -1);
	}
#else
#if HAVE_SQLITE2
	transstr errstr;
	jthrowable exc;

	trans2iso(env, f->h->haveutf, f->h->enc, err, &errstr);
	exc = (*env)->ExceptionOccurred(env);
	if (exc) {
	    (*env)->DeleteLocalRef(env, exc);
	    return;
	}
	sqlite_set_result_error((sqlite_func *) f->sf, errstr.result, -1);
	transfree(&errstr);
#endif
#if HAVE_SQLITE3
	if (err) {
	    jsize len = (*env)->GetStringLength(env, err) * sizeof (jchar);
	    const jchar *str = (*env)->GetStringChars(env, err, 0);

	    sqlite3_result_error16((sqlite3_context *) f->sf, str, len);
	    (*env)->ReleaseStringChars(env, err, str);
	} else {
	    sqlite3_result_error((sqlite3_context *) f->sf,
				 "null error text", -1);
	}
#endif
#endif
    }
}

JNIEXPORT void JNICALL
Java_SQLite_FunctionContext_set_1result__D(JNIEnv *env, jobject obj, jdouble d)
{
    hfunc *f = getfunc(env, obj);

    if (f && f->sf) {
#if HAVE_BOTH_SQLITE
	if (f->is3) {
	    sqlite3_result_double((sqlite3_context *) f->sf, (double) d);
	} else {
	    sqlite_set_result_double((sqlite_func *) f->sf, (double) d);
	}
#else
#if HAVE_SQLITE2
	sqlite_set_result_double((sqlite_func *) f->sf, (double) d);
#endif
#if HAVE_SQLITE3
	sqlite3_result_double((sqlite3_context *) f->sf, (double) d);
#endif
#endif
    }
}

JNIEXPORT void JNICALL
Java_SQLite_FunctionContext_set_1result__I(JNIEnv *env, jobject obj, jint i)
{
    hfunc *f = getfunc(env, obj);

    if (f && f->sf) {
#if HAVE_BOTH_SQLITE
	if (f->is3) {
	    sqlite3_result_int((sqlite3_context *) f->sf, (int) i);
	} else {
	    sqlite_set_result_int((sqlite_func *) f->sf, (int) i);
	}
#else
#if HAVE_SQLITE2
	sqlite_set_result_int((sqlite_func *) f->sf, (int) i);
#endif
#if HAVE_SQLITE3
	sqlite3_result_int((sqlite3_context *) f->sf, (int) i);
#endif
#endif
    }
}

JNIEXPORT void JNICALL
Java_SQLite_FunctionContext_set_1result__Ljava_lang_String_2(JNIEnv *env,
							     jobject obj,
							     jstring ret)
{
    hfunc *f = getfunc(env, obj);

    if (f && f->sf) {
#if HAVE_BOTH_SQLITE
	if (!f->is3) {
	    transstr retstr;
	    jthrowable exc;

	    trans2iso(env, f->h->haveutf, f->h->enc, ret, &retstr);
	    exc = (*env)->ExceptionOccurred(env);
	    if (exc) {
	        (*env)->DeleteLocalRef(env, exc);
		return;
	    }
	    sqlite_set_result_string((sqlite_func *) f->sf,
				     retstr.result, -1);
	    transfree(&retstr);
	} else if (ret) {
	    jsize len = (*env)->GetStringLength(env, ret) * sizeof (jchar);
	    const jchar *str = (*env)->GetStringChars(env, ret, 0);

	    sqlite3_result_text16((sqlite3_context *) f->sf, str, len,
				  SQLITE_TRANSIENT);
	    (*env)->ReleaseStringChars(env, ret, str);
	} else {
	    sqlite3_result_null((sqlite3_context *) f->sf);
	}
#else
#if HAVE_SQLITE2
	transstr retstr;
	jthrowable exc;

	trans2iso(env, f->h->haveutf, f->h->enc, ret, &retstr);
	exc = (*env)->ExceptionOccurred(env);
	if (exc) {
	    (*env)->DeleteLocalRef(env, exc);
	    return;
	}
	sqlite_set_result_string((sqlite_func *) f->sf, retstr.result, -1);
	transfree(&retstr);
#endif
#if HAVE_SQLITE3
	if (ret) {
	    jsize len = (*env)->GetStringLength(env, ret) * sizeof (jchar);
	    const jchar *str = (*env)->GetStringChars(env, ret, 0);

	    sqlite3_result_text16((sqlite3_context *) f->sf, str, len,
				  SQLITE_TRANSIENT);
	    (*env)->ReleaseStringChars(env, ret, str);
	} else {
	    sqlite3_result_null((sqlite3_context *) f->sf);
	}
#endif
#endif
    }
}

JNIEXPORT void JNICALL
Java_SQLite_FunctionContext_set_1result___3B(JNIEnv *env, jobject obj,
					     jbyteArray b)
{
#if HAVE_SQLITE3
    hfunc *f = getfunc(env, obj);

    if (f && f->sf) {
#if HAVE_BOTH_SQLITE
	if (!f->is3) {
	    /* silently ignored */
	    return;
	}
#endif
	if (b) {
	    jsize len;
	    jbyte *data;

	    len = (*env)->GetArrayLength(env, b);
	    data = (*env)->GetByteArrayElements(env, b, 0);
	    sqlite3_result_blob((sqlite3_context *) f->sf,
				data, len, SQLITE_TRANSIENT);
	    (*env)->ReleaseByteArrayElements(env, b, data, 0);
	} else {
	    sqlite3_result_null((sqlite3_context *) f->sf);
	}
    }
#endif
}

JNIEXPORT void JNICALL
Java_SQLite_FunctionContext_set_1result_1zeroblob(JNIEnv *env, jobject obj,
						  jint n)
{
#if HAVE_SQLITE3 && HAVE_SQLITE3_RESULT_ZEROBLOB
    hfunc *f = getfunc(env, obj);

    if (f && f->sf) {
#if HAVE_BOTH_SQLITE
	if (!f->is3) {
	    /* silently ignored */
	    return;
	}
#endif
	sqlite3_result_zeroblob((sqlite3_context *) f->sf, n);
    }
#endif
}

JNIEXPORT jstring JNICALL
Java_SQLite_Database_error_1string(JNIEnv *env, jclass c, jint err)
{
#if HAVE_SQLITE2
    return (*env)->NewStringUTF(env, sqlite_error_string((int) err));
#else
    return (*env)->NewStringUTF(env, "unkown error");
#endif
}

JNIEXPORT jstring JNICALL
Java_SQLite_Database__1errmsg(JNIEnv *env, jobject obj)
{
#if HAVE_SQLITE3
    handle *h = gethandle(env, obj);

    if (h && h->sqlite) {
#if HAVE_BOTH_SQLITE
	if (!h->is3) {
	    return 0;
	}
#endif
	return (*env)->NewStringUTF(env,
				    sqlite3_errmsg((sqlite3 *) h->sqlite));
    }
#endif
    return 0;
}

JNIEXPORT void JNICALL
Java_SQLite_Database__1set_1encoding(JNIEnv *env, jobject obj, jstring enc)
{
    handle *h = gethandle(env, obj);

    if (h && !h->haveutf) {
#if HAVE_BOTH_SQLITE
	if (!h->is3) {
	    delglobrefp(env, &h->enc);
	    h->enc = enc;
	    globrefset(env, enc, &h->enc);
	}
#else
#if HAVE_SQLITE2
	delglobrefp(env, &h->enc);
	h->enc = enc;
	globrefset(env, enc, &h->enc);
#endif
#endif
    }
}

#if HAVE_SQLITE_SET_AUTHORIZER
static int
doauth(void *arg, int what, const char *arg1, const char *arg2,
       const char *arg3, const char *arg4)
{
    handle *h = (handle *) arg;
    JNIEnv *env = h->env;

    if (env && h->ai) {
	jthrowable exc;
	jclass cls = (*env)->GetObjectClass(env, h->ai);
	jmethodID mid;
	jint i = what;

	mid = (*env)->GetMethodID(env, cls, "authorize",
				  "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I");
	if (mid) {
	    jstring s1 = 0, s2 = 0, s3 = 0, s4 = 0;
	    transstr tr;

	    if (arg1) {
		trans2utf(env, h->haveutf, h->enc, arg1, &tr);
		s1 = tr.jstr;
	    }
	    exc = (*env)->ExceptionOccurred(env);
	    if (exc) {
		(*env)->DeleteLocalRef(env, exc);
		return SQLITE_DENY;
	    }
	    if (arg2) {
		trans2utf(env, h->haveutf, h->enc, arg2, &tr);
		s2 = tr.jstr;
	    }
	    if (arg3) {
		trans2utf(env, h->haveutf, h->enc, arg3, &tr);
		s3 = tr.jstr;
	    }
	    if (arg4) {
		trans2utf(env, h->haveutf, h->enc, arg4, &tr);
		s4 = tr.jstr;
	    }
	    exc = (*env)->ExceptionOccurred(env);
	    if (exc) {
		(*env)->DeleteLocalRef(env, exc);
		return SQLITE_DENY;
	    }
	    i = (*env)->CallIntMethod(env, h->ai, mid, i, s1, s2, s3, s4);
	    exc = (*env)->ExceptionOccurred(env);
	    if (exc) {
		(*env)->DeleteLocalRef(env, exc);
		return SQLITE_DENY;
	    }
	    (*env)->DeleteLocalRef(env, s4);
	    (*env)->DeleteLocalRef(env, s3);
	    (*env)->DeleteLocalRef(env, s2);
	    (*env)->DeleteLocalRef(env, s1);
	    if (i != SQLITE_OK && i != SQLITE_IGNORE) {
		i = SQLITE_DENY;
	    }
	    return (int) i;
	}
    }
    return SQLITE_DENY;
}
#endif

JNIEXPORT void JNICALL
Java_SQLite_Database__1set_1authorizer(JNIEnv *env, jobject obj, jobject auth)
{
    handle *h = gethandle(env, obj);

    if (h && h->sqlite) {
	delglobrefp(env, &h->ai);
	globrefset(env, auth, &h->ai);
#if HAVE_SQLITE_SET_AUTHORIZER
	h->env = env;
#if HAVE_BOTH_SQLITE
	if (h->is3) {
	    sqlite3_set_authorizer((sqlite3 *) h->sqlite,
				   h->ai ? doauth : 0, h);
	} else {
	    sqlite_set_authorizer((sqlite *) h->sqlite,
				  h->ai ? doauth : 0, h);
	}
#else
#if HAVE_SQLITE2
	sqlite_set_authorizer((sqlite *) h->sqlite, h->ai ? doauth : 0, h);
#endif
#if HAVE_SQLITE3
	sqlite3_set_authorizer((sqlite3 *) h->sqlite, h->ai ? doauth : 0, h);
#endif
#endif
#endif
	return;
    }
    throwclosed(env);
}

#if HAVE_SQLITE_TRACE
static void
dotrace(void *arg, const char *msg)
{
    handle *h = (handle *) arg;
    JNIEnv *env = h->env;

    if (env && h->tr && msg) {
	jthrowable exc;
	jclass cls = (*env)->GetObjectClass(env, h->tr);
	jmethodID mid;

	mid = (*env)->GetMethodID(env, cls, "trace", "(Ljava/lang/String;)V");
	if (mid) {
	    transstr tr;

	    trans2utf(env, h->haveutf, h->enc, msg, &tr);
	    exc = (*env)->ExceptionOccurred(env);
	    if (exc) {
		(*env)->DeleteLocalRef(env, exc);
		(*env)->ExceptionClear(env);
		return;
	    }
	    (*env)->CallVoidMethod(env, h->tr, mid, tr.jstr);
	    (*env)->ExceptionClear(env);
	    (*env)->DeleteLocalRef(env, tr.jstr);
	    return;
	}
    }
    return;
}
#endif

JNIEXPORT void JNICALL
Java_SQLite_Database__1trace(JNIEnv *env, jobject obj, jobject tr)
{
    handle *h = gethandle(env, obj);

    if (h && h->sqlite) {
	delglobrefp(env, &h->tr);
	globrefset(env, tr, &h->tr);
#if HAVE_BOTH_SQLITE
	if (h->is3) {
	    sqlite3_trace((sqlite3 *) h->sqlite, h->tr ? dotrace : 0, h);
	} else {
#if HAVE_SQLITE_TRACE
	    sqlite_trace((sqlite *) h->sqlite, h->tr ? dotrace : 0, h);
#endif
	}
#else
#if HAVE_SQLITE2
#if HAVE_SQLITE_TRACE
	sqlite_trace((sqlite *) h->sqlite, h->tr ? dotrace : 0, h);
#endif
#endif
#if HAVE_SQLITE3
	sqlite3_trace((sqlite3 *) h->sqlite, h->tr ? dotrace : 0, h);
#endif
#endif
	return;
    }
    throwclosed(env);
}

#if HAVE_SQLITE_COMPILE
static void
dovmfinal(JNIEnv *env, jobject obj, int final)
{
    hvm *v = gethvm(env, obj);

    if (v) {
	if (v->h) {
	    handle *h = v->h;
	    hvm *vv, **vvp;

	    vvp = &h->vms;
	    vv = *vvp;
	    while (vv) {
		if (vv == v) {
		    *vvp = vv->next;
		    break;
		}
		vvp = &vv->next;
		vv = *vvp;
	    }
	}
	if (v->vm) {
#if HAVE_BOTH_SQLITE
	    if (v->is3) {
		sqlite3_finalize((sqlite3_stmt *) v->vm);
	    } else {
		sqlite_finalize((sqlite_vm *) v->vm, 0);
	    }
#else
#if HAVE_SQLITE2
	    sqlite_finalize((sqlite_vm *) v->vm, 0);
#endif
#if HAVE_SQLITE3
	    sqlite3_finalize((sqlite3_stmt *) v->vm);
#endif
#endif
	    v->vm = 0;
	}
	free(v);
	(*env)->SetLongField(env, obj, F_SQLite_Vm_handle, 0);
	return;
    }
    if (!final) {
	throwex(env, "vm already closed");
    }
}
#endif

#if HAVE_SQLITE3
static void
dostmtfinal(JNIEnv *env, jobject obj)
{
    hvm *v = gethstmt(env, obj);

    if (v) {
	if (v->h) {
	    handle *h = v->h;
	    hvm *vv, **vvp;

	    vvp = &h->vms;
	    vv = *vvp;
	    while (vv) {
		if (vv == v) {
		    *vvp = vv->next;
		    break;
		}
		vvp = &vv->next;
		vv = *vvp;
	    }
	}
	if (v->vm) {
	    sqlite3_finalize((sqlite3_stmt *) v->vm);
	}
	v->vm = 0;
	free(v);
	(*env)->SetLongField(env, obj, F_SQLite_Stmt_handle, 0);
    }
}
#endif

#if HAVE_SQLITE3 && HAVE_SQLITE3_INCRBLOBIO
static void
doblobfinal(JNIEnv *env, jobject obj)
{
    hbl *bl = gethbl(env, obj);

    if (bl) {
	if (bl->h) {
	    handle *h = bl->h;
	    hbl *blc, **blp;

	    blp = &h->blobs;
	    blc = *blp;
	    while (blc) {
		if (blc == bl) {
		    *blp = blc->next;
		    break;
		}
		blp = &blc->next;
		blc = *blp;
	    }
	}
	if (bl->blob) {
	    sqlite3_blob_close(bl->blob);
	}
	bl->blob = 0;
	free(bl);
	(*env)->SetLongField(env, obj, F_SQLite_Blob_handle, 0);
	(*env)->SetIntField(env, obj, F_SQLite_Blob_size, 0);
    }
}
#endif

JNIEXPORT void JNICALL
Java_SQLite_Vm_stop(JNIEnv *env, jobject obj)
{
#if HAVE_SQLITE_COMPILE
    dovmfinal(env, obj, 0);
#else
    throwex(env, "unsupported");
#endif
}

JNIEXPORT void JNICALL
Java_SQLite_Vm_finalize(JNIEnv *env, jobject obj)
{
#if HAVE_SQLITE_COMPILE
    dovmfinal(env, obj, 1);
#endif
}

#if HAVE_SQLITE_COMPILE
#if HAVE_SQLITE3
static void
free_tab(void *mem)
{
    char **p = (char **) mem;
    int i, n;

    if (!p) {
	return;
    }
    p -= 1;
    mem = (void *) p;
    n = ((int *) p)[0];
    p += n * 2 + 2 + 1;
    for (i = 0; i < n; i++) {
	if (p[i]) {
	    free(p[i]);
	}
    }
    free(mem);
}
#endif
#endif

JNIEXPORT jboolean JNICALL
Java_SQLite_Vm_step(JNIEnv *env, jobject obj, jobject cb)
{
#if HAVE_SQLITE_COMPILE
    hvm *v = gethvm(env, obj);

    if (v && v->vm && v->h) {
	jthrowable exc;
	int ret, ncol = 0;
#if HAVE_SQLITE3
	freemem *freeproc = 0;
	const char **blob = 0;
#endif
	const char **data = 0, **cols = 0;

	v->h->env = env;
#if HAVE_BOTH_SQLITE
	if (v->is3) {
	    ret = sqlite3_step((sqlite3_stmt *) v->vm);
	    if (ret == SQLITE_ROW) {
		ncol = sqlite3_data_count((sqlite3_stmt *) v->vm);
		if (ncol > 0) {
		    data = calloc(ncol * 3 + 3 + 1, sizeof (char *));
		    if (data) {
			data[0] = (const char *) ncol;
			++data;
			cols = data + ncol + 1;
			blob = cols + ncol + 1;
			freeproc = free_tab;
		    } else {
			ret = SQLITE_NOMEM;
		    }
		}
		if (ret != SQLITE_NOMEM) {
		    int i;

		    for (i = 0; i < ncol; i++) {
			cols[i] =
			    sqlite3_column_name((sqlite3_stmt *) v->vm, i);
			if (sqlite3_column_type((sqlite3_stmt *) v->vm, i)
			    == SQLITE_BLOB) {
			    unsigned char *src = (unsigned char *)
				sqlite3_column_blob((sqlite3_stmt *) v->vm, i);
			    int n =
				sqlite3_column_bytes((sqlite3_stmt *) v->vm,
						     i);

			    if (src) {
				data[i] = malloc(n * 2 + 4);
				if (data[i]) {
				    int k;
				    char *p = (char *) data[i];

				    blob[i] = data[i];
				    *p++ = 'X';
				    *p++ = '\'';
				    for (k = 0; k < n; k++) {
					*p++ = xdigits[src[k] >> 4];
					*p++ = xdigits[src[k] & 0x0F];
				    }
				    *p++ = '\'';
				    *p++ = '\0';
				}
			    }
			} else {
			    data[i] = (const char *)
				sqlite3_column_text((sqlite3_stmt *) v->vm, i);
			}
		    }
		}
	    }
	} else {
	    ret = sqlite_step((sqlite_vm *) v->vm, &ncol, &data, &cols);
	}
#else
#if HAVE_SQLITE2
	ret = sqlite_step((sqlite_vm *) v->vm, &ncol, &data, &cols);
#endif
#if HAVE_SQLITE3
	ret = sqlite3_step((sqlite3_stmt *) v->vm);
	if (ret == SQLITE_ROW) {
	    ncol = sqlite3_data_count((sqlite3_stmt *) v->vm);
	    if (ncol > 0) {
		data = calloc(ncol * 3 + 3 + 1, sizeof (char *));
		if (data) {
		    data[0] = (const char *) ncol;
		    ++data;
		    cols = data + ncol + 1;
		    blob = cols + ncol + 1;
		    freeproc = free_tab;
		} else {
		    ret = SQLITE_NOMEM;
		}
	    }
	    if (ret != SQLITE_NOMEM) {
		int i;

		for (i = 0; i < ncol; i++) {
		    cols[i] = sqlite3_column_name((sqlite3_stmt *) v->vm, i);
		    if (sqlite3_column_type((sqlite3_stmt *) v->vm, i)
			== SQLITE_BLOB) {
			unsigned char *src = (unsigned char *)
			    sqlite3_column_blob((sqlite3_stmt *) v->vm, i);
			int n =
			    sqlite3_column_bytes((sqlite3_stmt *) v->vm, i);

			if (src) {
			    data[i] = malloc(n * 2 + 4);
			    if (data[i]) {
				int k;
				char *p = (char *) data[i];

				blob[i] = data[i];
				*p++ = 'X';
				*p++ = '\'';
				for (k = 0; k < n; k++) {
				    *p++ = xdigits[src[k] >> 4];
				    *p++ = xdigits[src[k] & 0x0F];
				}
				*p++ = '\'';
				*p++ = '\0';
			    }
			}
		    } else {
			data[i] = (char *)
			    sqlite3_column_text((sqlite3_stmt *) v->vm, i);
		    }
		}
	    }
	}
#endif
#endif
	if (ret == SQLITE_ROW) {
	    v->hh.cb = cb;
	    v->hh.env = env;
#if HAVE_BOTH_SQLITE
	    if (v->is3) {
		v->hh.stmt = (sqlite3_stmt *) v->vm;
	    }
#else
#if HAVE_SQLITE3
	    v->hh.stmt = (sqlite3_stmt *) v->vm;
#endif
#endif
	    callback((void *) &v->hh, ncol, (char **) data, (char **) cols);
#if HAVE_SQLITE3
	    if (data && freeproc) {
		freeproc((void *) data);
	    }
#endif
	    exc = (*env)->ExceptionOccurred(env);
	    if (exc) {
		(*env)->DeleteLocalRef(env, exc);
		goto dofin;
	    }
	    return JNI_TRUE;
	} else if (ret == SQLITE_DONE) {
dofin:
#if HAVE_BOTH_SQLITE
	    if (v->is3) {
		sqlite3_finalize((sqlite3_stmt *) v->vm);
	    } else {
		sqlite_finalize((sqlite_vm *) v->vm, 0);
	    }
#else
#if HAVE_SQLITE2
	    sqlite_finalize((sqlite_vm *) v->vm, 0);
#endif
#if HAVE_SQLITE3
	    sqlite3_finalize((sqlite3_stmt *) v->vm);
#endif
#endif
	    v->vm = 0;
	    return JNI_FALSE;
	}
#if HAVE_BOTH_SQLITE
	if (v->is3) {
	    sqlite3_finalize((sqlite3_stmt *) v->vm);
	} else {
	    sqlite_finalize((sqlite_vm *) v->vm, 0);
	}
#else
#if HAVE_SQLITE2
	sqlite_finalize((sqlite_vm *) v->vm, 0);
#endif
#if HAVE_SQLITE3
	sqlite3_finalize((sqlite3_stmt *) v->vm);
#endif
#endif
	setvmerr(env, obj, ret);
	v->vm = 0;
	throwex(env, "error in step");
	return JNI_FALSE;
    }
    throwex(env, "vm already closed");
#else
    throwex(env, "unsupported");
#endif
    return JNI_FALSE;
}

JNIEXPORT jboolean JNICALL
Java_SQLite_Vm_compile(JNIEnv *env, jobject obj)
{
#if HAVE_SQLITE_COMPILE
    hvm *v = gethvm(env, obj);
    void *svm = 0;
    char *err = 0;
    const char *tail;
    int ret;

    if (v && v->vm) {
#if HAVE_BOTH_SQLITE
	if (v->is3) {
	    sqlite3_finalize((sqlite3_stmt *) v->vm);
	} else {
	    sqlite_finalize((sqlite_vm *) v->vm, 0);
	}
#else
#if HAVE_SQLITE2
	sqlite_finalize((sqlite_vm *) v->vm, 0);
#endif
#if HAVE_SQLITE3
	sqlite3_finalize((sqlite3_stmt *) v->vm);
#endif
#endif
	v->vm = 0;
    }
    if (v && v->h && v->h->sqlite) {
	if (!v->tail) {
	    return JNI_FALSE;
	}
	v->h->env = env;
#if HAVE_BOTH_SQLITE
	if (v->is3) {
#if HAVE_SQLITE3_PREPARE_V2
	    ret = sqlite3_prepare_v2((sqlite3 *) v->h->sqlite, v->tail, -1,
				     (sqlite3_stmt **) &svm, &tail);
#else
	    ret = sqlite3_prepare((sqlite3 *) v->h->sqlite, v->tail, -1,
				  (sqlite3_stmt **) &svm, &tail);
#endif
	    if (ret != SQLITE_OK) {
		if (svm) {
		    sqlite3_finalize((sqlite3_stmt *) svm);
		    svm = 0;
		}
	    }
	} else {
	    ret = sqlite_compile((sqlite *) v->h->sqlite, v->tail,
				 &tail, (sqlite_vm **) &svm, &err);
	    if (ret != SQLITE_OK) {
		if (svm) {
		    sqlite_finalize((sqlite_vm *) svm, 0);
		    svm = 0;
		}
	    }
	}
#else
#if HAVE_SQLITE2
	ret = sqlite_compile((sqlite *) v->h->sqlite, v->tail,
			     &tail, (sqlite_vm **) &svm, &err);
	if (ret != SQLITE_OK) {
	    if (svm) {
		sqlite_finalize((sqlite_vm *) svm, 0);
		svm = 0;
	    }
	}
#endif
#if HAVE_SQLITE3
#if HAVE_SQLITE3_PREPARE_V2
	ret = sqlite3_prepare_v2((sqlite3 *) v->h->sqlite,
				 v->tail, -1, (sqlite3_stmt **) &svm, &tail);
#else
	ret = sqlite3_prepare((sqlite3 *) v->h->sqlite,
			      v->tail, -1, (sqlite3_stmt **) &svm, &tail);
#endif
	if (ret != SQLITE_OK) {
	    if (svm) {
		sqlite3_finalize((sqlite3_stmt *) svm);
		svm = 0;
	    }
	}
#endif
#endif
	if (ret != SQLITE_OK) {
	    setvmerr(env, obj, ret);
	    v->tail = 0;
	    throwex(env, err ? err : "error in compile/prepare");
#if HAVE_SQLITE2
	    if (err) {
		sqlite_freemem(err);
	    }
#endif
	    return JNI_FALSE;
	}
#if HAVE_SQLITE2
	if (err) {
	    sqlite_freemem(err);
	}
#endif
	if (!svm) {
	    v->tail = 0;
	    return JNI_FALSE;
	}
	v->vm = svm;
	v->tail = (char *) tail;
	v->hh.row1 = 1;
	return JNI_TRUE;
    }
    throwex(env, "vm already closed");
#else
    throwex(env, "unsupported");
#endif
    return JNI_FALSE;
}

JNIEXPORT void JNICALL
Java_SQLite_Database_vm_1compile(JNIEnv *env, jobject obj, jstring sql,
				 jobject vm)
{
#if HAVE_SQLITE_COMPILE
    handle *h = gethandle(env, obj);
    void *svm = 0;
    hvm *v;
    char *err = 0;
    const char *tail;
    transstr tr;
    jvalue vv;
    int ret;
    jthrowable exc;

    if (!h) {
	throwclosed(env);
	return;
    }
    if (!vm) {
	throwex(env, "null vm");
	return;
    }
    if (!sql) {
	throwex(env, "null sql");
	return;
    }
    trans2iso(env, h->haveutf, h->enc, sql, &tr);
    exc = (*env)->ExceptionOccurred(env);
    if (exc) {
	(*env)->DeleteLocalRef(env, exc);
	return;
    }
    h->env = env;
#if HAVE_BOTH_SQLITE
    if (h->is3) {
#if HAVE_SQLITE3_PREPARE_V2
	ret = sqlite3_prepare_v2((sqlite3 *) h->sqlite, tr.result, -1,
				 (sqlite3_stmt **) &svm, &tail);
#else
	ret = sqlite3_prepare((sqlite3 *) h->sqlite, tr.result, -1,
			      (sqlite3_stmt **) &svm, &tail);
#endif
	if (ret != SQLITE_OK) {
	    if (svm) {
		sqlite3_finalize((sqlite3_stmt *) svm);
		svm = 0;
	    }
	}
    } else {
	ret = sqlite_compile((sqlite *) h->sqlite, tr.result, &tail,
			     (sqlite_vm **) &svm, &err);
	if (ret != SQLITE_OK) {
	    if (svm) {
		sqlite_finalize((sqlite_vm *) svm, 0);
	    }
	}
    }
#else
#if HAVE_SQLITE2
    ret = sqlite_compile((sqlite *) h->sqlite, tr.result, &tail,
			 (sqlite_vm **) &svm, &err);
    if (ret != SQLITE_OK) {
	if (svm) {
	    sqlite_finalize((sqlite_vm *) svm, 0);
	    svm = 0;
	}
    }
#endif
#if HAVE_SQLITE3
#if HAVE_SQLITE3_PREPARE_V2
    ret = sqlite3_prepare_v2((sqlite3 *) h->sqlite, tr.result, -1,
			     (sqlite3_stmt **) &svm, &tail);
#else
    ret = sqlite3_prepare((sqlite3 *) h->sqlite, tr.result, -1,
			  (sqlite3_stmt **) &svm, &tail);
#endif
    if (ret != SQLITE_OK) {
	if (svm) {
	    sqlite3_finalize((sqlite3_stmt *) svm);
	    svm = 0;
	}
    }
#endif
#endif
    if (ret != SQLITE_OK) {
	transfree(&tr);
	setvmerr(env, vm, ret);
	throwex(env, err ? err : "error in prepare/compile");
#if HAVE_SQLITE2
	if (err) {
	    sqlite_freemem(err);
	}
#endif
	return;
    }
#if HAVE_SQLITE2
    if (err) {
	sqlite_freemem(err);
    }
#endif
    if (!svm) {
	transfree(&tr);
	return;
    }
    v = malloc(sizeof (hvm) + strlen(tail) + 1);
    if (!v) {
	transfree(&tr);
#if HAVE_BOTH_SQLITE
	if (h->is3) {
	    sqlite3_finalize((sqlite3_stmt *) svm);
	} else {
	    sqlite_finalize((sqlite_vm *) svm, 0);
	}
#else
#if HAVE_SQLITE2
	sqlite_finalize((sqlite_vm *) svm, 0);
#endif
#if HAVE_SQLITE3
	sqlite3_finalize((sqlite3_stmt *) svm);
#endif
#endif
	throwoom(env, "unable to get SQLite handle");
	return;
    }
    v->next = h->vms;
    h->vms = v;
    v->vm = svm;
    v->h = h;
    v->tail = (char *) (v + 1);
#if HAVE_BOTH_SQLITE
    v->is3 = v->hh.is3 = h->is3;
#endif
    strcpy(v->tail, tail);
    v->hh.sqlite = 0;
    v->hh.haveutf = h->haveutf;
    v->hh.ver = h->ver;
    v->hh.bh = v->hh.cb = v->hh.ai = v->hh.tr = v->hh.ph = 0;
    v->hh.row1 = 1;
    v->hh.enc = h->enc;
    v->hh.funcs = 0;
    v->hh.vms = 0;
    v->hh.env = 0;
    vv.j = 0;
    vv.l = (jobject) v;
    (*env)->SetLongField(env, vm, F_SQLite_Vm_handle, vv.j);
#else
    throwex(env, "unsupported");
#endif
}

JNIEXPORT void JNICALL
Java_SQLite_Database_vm_1compile_1args(JNIEnv *env,
				       jobject obj, jstring sql,
				       jobject vm, jobjectArray args)
{
#if HAVE_SQLITE_COMPILE
#if HAVE_SQLITE3
    handle *h = gethandle(env, obj);
#endif

#if HAVE_BOTH_SQLITE
    if (h && !h->is3) {
	throwex(env, "unsupported");
	return;
    }
#else
#if HAVE_SQLITE2
    throwex(env, "unsupported");
#endif
#endif
#if HAVE_SQLITE3 
    if (!h || !h->sqlite) {
	throwclosed(env);
	return;
    }
    if (!vm) {
	throwex(env, "null vm");
	return;
    }
    if (!sql) {
	throwex(env, "null sql");
	return;
    } else {
	void *svm = 0;
	hvm *v;
	jvalue vv;
	jthrowable exc;
	jboolean b;
	int rc = SQLITE_ERROR, nargs, i;
	char *p;
	const char *str = (*env)->GetStringUTFChars(env, sql, &b);
	const char *tail;
	transstr sqlstr;
	struct args {
	    char *arg;
	    jobject obj;
	    transstr trans;
	} *argv = 0;
	char **cargv = 0;

	p = (char *) str;
	nargs = 0;
	while (*p) {
	    if (*p == '%') {
		++p;
		if (*p == 'q' || *p == 'Q' || *p == 's') {
		    nargs++;
		    if (nargs > MAX_PARAMS) {
			(*env)->ReleaseStringUTFChars(env, sql, str);
			throwex(env, "too much SQL parameters");
			return;
		    }
		} else if (*p != '%') {
		    (*env)->ReleaseStringUTFChars(env, sql, str);
		    throwex(env, "bad % specification in query");
		    return;
		}
	    }
	    ++p;
	}
	cargv = malloc((sizeof (*argv) + sizeof (char *)) * MAX_PARAMS);
	if (!cargv) {
	    (*env)->ReleaseStringUTFChars(env, sql, str);
	    throwoom(env, "unable to allocate arg vector");
	    return;
	}
	argv = (struct args *) (cargv + MAX_PARAMS);
	for (i = 0; i < MAX_PARAMS; i++) {
	    cargv[i] = 0;
	    argv[i].arg = 0;
	    argv[i].obj = 0;
	    argv[i].trans.result = argv[i].trans.tofree = 0;
	}
	exc = 0;
	for (i = 0; i < nargs; i++) {
	    jobject so = (*env)->GetObjectArrayElement(env, args, i);

	    exc = (*env)->ExceptionOccurred(env);
	    if (exc) {
		(*env)->DeleteLocalRef(env, exc);
		break;
	    }
	    if (so) {
		argv[i].obj = so;
		argv[i].arg = cargv[i] =
		    trans2iso(env, 1, 0, argv[i].obj, &argv[i].trans);
	    }
	}
	if (exc) {
	    for (i = 0; i < nargs; i++) {
		if (argv[i].obj) {
		    transfree(&argv[i].trans);
		}
	    }
	    freep((char **) &cargv);
	    (*env)->ReleaseStringUTFChars(env, sql, str);
	    return;
	}
	h->row1 = 1;
	trans2iso(env, 1, 0, sql, &sqlstr);
	exc = (*env)->ExceptionOccurred(env);
	if (!exc) {
#if defined(_WIN32) || !defined(CANT_PASS_VALIST_AS_CHARPTR)
	    char *s = sqlite3_vmprintf(sqlstr.result, (char *) cargv);
#else
	    char *s = sqlite3_mprintf(sqlstr.result,
				      cargv[0], cargv[1],
				      cargv[2], cargv[3],
				      cargv[4], cargv[5],
				      cargv[6], cargv[7],
				      cargv[8], cargv[9],
				      cargv[10], cargv[11],
				      cargv[12], cargv[13],
				      cargv[14], cargv[15],
				      cargv[16], cargv[17],
				      cargv[18], cargv[19],
				      cargv[20], cargv[21],
				      cargv[22], cargv[23],
				      cargv[24], cargv[25],
				      cargv[26], cargv[27],
				      cargv[28], cargv[29],
				      cargv[30], cargv[31]);
#endif
	    if (!s) {
		rc = SQLITE_NOMEM;
	    } else {
#if HAVE_SQLITE3_PREPARE_V2
		rc = sqlite3_prepare_v2((sqlite3 *) h->sqlite, s, -1,
					(sqlite3_stmt **) &svm, &tail);
#else
		rc = sqlite3_prepare((sqlite3 *) h->sqlite, s, -1,
				      (sqlite3_stmt **) &svm, &tail);
#endif
		if (rc != SQLITE_OK) {
		    if (svm) {
			sqlite3_finalize((sqlite3_stmt *) svm);
			svm = 0;
		    }
		}
	    }
	    if (rc != SQLITE_OK) {
		sqlite3_free(s);
		for (i = 0; i < nargs; i++) {
		    if (argv[i].obj) {
			transfree(&argv[i].trans);
		    }
		}
		freep((char **) &cargv);
		transfree(&sqlstr);
		(*env)->ReleaseStringUTFChars(env, sql, str);
		setvmerr(env, vm, rc);
		throwex(env, "error in prepare");
		return;
	    }
	    v = malloc(sizeof (hvm) + strlen(tail) + 1);
	    if (!v) {
		sqlite3_free(s);
		for (i = 0; i < nargs; i++) {
		    if (argv[i].obj) {
			transfree(&argv[i].trans);
		    }
		}
		freep((char **) &cargv);
		transfree(&sqlstr);
		(*env)->ReleaseStringUTFChars(env, sql, str);
		sqlite3_finalize((sqlite3_stmt *) svm);
		setvmerr(env, vm, SQLITE_NOMEM);
		throwoom(env, "unable to get SQLite handle");
		return;
	    }
	    v->next = h->vms;
	    h->vms = v;
	    v->vm = svm;
	    v->h = h;
	    v->tail = (char *) (v + 1);
#if HAVE_BOTH_SQLITE
	    v->is3 = v->hh.is3 = h->is3;
#endif
	    strcpy(v->tail, tail);
	    sqlite3_free(s);
	    v->hh.sqlite = 0;
	    v->hh.haveutf = h->haveutf;
	    v->hh.ver = h->ver;
	    v->hh.bh = v->hh.cb = v->hh.ai = v->hh.tr = v->hh.ph = 0;
	    v->hh.row1 = 1;
	    v->hh.enc = h->enc;
	    v->hh.funcs = 0;
	    v->hh.vms = 0;
	    v->hh.env = 0;
	    vv.j = 0;
	    vv.l = (jobject) v;
	    (*env)->SetLongField(env, vm, F_SQLite_Vm_handle, vv.j);
	}
	for (i = 0; i < nargs; i++) {
	    if (argv[i].obj) {
		transfree(&argv[i].trans);
	    }
	}
	freep((char **) &cargv);
	transfree(&sqlstr);
	(*env)->ReleaseStringUTFChars(env, sql, str);
	if (exc) {
	    (*env)->DeleteLocalRef(env, exc);
	}
    }
#endif
#else
    throwex(env, "unsupported");
#endif
}

JNIEXPORT void JNICALL
Java_SQLite_FunctionContext_internal_1init(JNIEnv *env, jclass cls)
{
    F_SQLite_FunctionContext_handle =
	(*env)->GetFieldID(env, cls, "handle", "J");
}

JNIEXPORT void JNICALL
Java_SQLite_Database__1progress_1handler(JNIEnv *env, jobject obj, jint n,
					 jobject ph)
{
    handle *h = gethandle(env, obj);

    if (h && h->sqlite) {
	/* CHECK THIS */
#if HAVE_SQLITE_PROGRESS_HANDLER
	delglobrefp(env, &h->ph);
#if HAVE_BOTH_SQLITE
	if (h->is3) {
	    if (ph) {
		globrefset(env, ph, &h->ph);
		sqlite3_progress_handler((sqlite3 *) h->sqlite,
					 n, progresshandler, h);
	    } else {
		sqlite3_progress_handler((sqlite3 *) h->sqlite,
					 0, 0, 0);
	    }
	} else {
	    if (ph) {
		globrefset(env, ph, &h->ph);
		sqlite_progress_handler((sqlite *) h->sqlite,
					n, progresshandler, h);
	    } else {
		sqlite_progress_handler((sqlite *) h->sqlite,
					0, 0, 0);
	    }
	}
#else
#if HAVE_SQLITE2
	if (ph) {
	    globrefset(env, ph, &h->ph);
	    sqlite_progress_handler((sqlite *) h->sqlite,
				    n, progresshandler, h);
	} else {
	    sqlite_progress_handler((sqlite *) h->sqlite,
				    0, 0, 0);
	}
#endif
#if HAVE_SQLITE3
	if (ph) {
	    globrefset(env, ph, &h->ph);
	    sqlite3_progress_handler((sqlite3 *) h->sqlite,
				     n, progresshandler, h);
	} else {
	    sqlite3_progress_handler((sqlite3 *) h->sqlite,
				     0, 0, 0);
	}
#endif
#endif
	return;
#else
	throwex(env, "unsupported");
	return;
#endif
    }
    throwclosed(env);
}

JNIEXPORT jboolean JNICALL
Java_SQLite_Database_is3(JNIEnv *env, jobject obj)
{
#if HAVE_BOTH_SQLITE
    handle *h = gethandle(env, obj);

    if (h) {
	return h->is3 ? JNI_TRUE : JNI_FALSE;
    }
    return JNI_FALSE;
#else
#if HAVE_SQLITE2
    return JNI_FALSE;
#endif
#if HAVE_SQLITE3
    return JNI_TRUE;
#endif
#endif
}

JNIEXPORT jboolean JNICALL
Java_SQLite_Stmt_prepare(JNIEnv *env, jobject obj)
{
#if HAVE_SQLITE3
    hvm *v = gethstmt(env, obj);
    void *svm = 0;
    char *tail;
    int ret;

    if (v && v->vm) {
	sqlite3_finalize((sqlite3_stmt *) v->vm);
	v->vm = 0;
    }
    if (v && v->h && v->h->sqlite) {
	if (!v->tail) {
	    return JNI_FALSE;
	}
	v->h->env = env;
#if HAVE_SQLITE3_PREPARE16_V2
	ret = sqlite3_prepare16_v2((sqlite3 *) v->h->sqlite,
				   v->tail, -1, (sqlite3_stmt **) &svm,
				   (const void **) &tail);
#else
	ret = sqlite3_prepare16((sqlite3 *) v->h->sqlite,
				v->tail, -1, (sqlite3_stmt **) &svm,
				(const void **) &tail);
#endif
	if (ret != SQLITE_OK) {
	    if (svm) {
		sqlite3_finalize((sqlite3_stmt *) svm);
		svm = 0;
	    }
	}
	if (ret != SQLITE_OK) {
	    const char *err = sqlite3_errmsg(v->h->sqlite);

	    setstmterr(env, obj, ret);
	    v->tail = 0;
	    throwex(env, err ? err : "error in compile/prepare");
	    return JNI_FALSE;
	}
	if (!svm) {
	    v->tail = 0;
	    return JNI_FALSE;
	}
	v->vm = svm;
	v->tail = (char *) tail;
	v->hh.row1 = 1;
	return JNI_TRUE;
    }
    throwex(env, "stmt already closed");
#else
    throwex(env, "unsupported");
#endif
    return JNI_FALSE;
}

JNIEXPORT void JNICALL
Java_SQLite_Database_stmt_1prepare(JNIEnv *env, jobject obj, jstring sql,
				   jobject stmt)
{
#if HAVE_SQLITE3
    handle *h = gethandle(env, obj);
    void *svm = 0;
    hvm *v;
    jvalue vv;
    jsize len16;
    const jchar *sql16, *tail = 0;
    int ret;

    if (!h) {
	throwclosed(env);
	return;
    }
    if (!stmt) {
	throwex(env, "null stmt");
	return;
    }
    if (!sql) {
	throwex(env, "null sql");
	return;
    }
#ifdef HAVE_BOTH_SQLITE
    if (!h->is3) {
	throwex(env, "only on SQLite3 database");
	return;
    }
#endif
    len16 = (*env)->GetStringLength(env, sql) * sizeof (jchar);
    if (len16 < 1) {
        return;
    }
    h->env = env;
    sql16 = (*env)->GetStringChars(env, sql, 0);
#if HAVE_SQLITE3_PREPARE16_V2
    ret = sqlite3_prepare16_v2((sqlite3 *) h->sqlite, sql16, len16,
			       (sqlite3_stmt **) &svm, (const void **) &tail);
#else
    ret = sqlite3_prepare16((sqlite3 *) h->sqlite, sql16, len16,
			    (sqlite3_stmt **) &svm, (const void **) &tail);
#endif
    if (ret != SQLITE_OK) {
	if (svm) {
	    sqlite3_finalize((sqlite3_stmt *) svm);
	    svm = 0;
	}
    }
    if (ret != SQLITE_OK) {
	const char *err = sqlite3_errmsg(h->sqlite);

        (*env)->ReleaseStringChars(env, sql, sql16);
	setstmterr(env, stmt, ret);
	throwex(env, err ? err : "error in prepare");
	return;
    }
    if (!svm) {
        (*env)->ReleaseStringChars(env, sql, sql16);
	return;
    }
    len16 = len16 + sizeof (jchar) - ((char *) tail - (char *) sql16);
    if (len16 < sizeof (jchar)) {
        len16 = sizeof (jchar);
    }
    v = malloc(sizeof (hvm) + len16);
    if (!v) {
        (*env)->ReleaseStringChars(env, sql, sql16);
	sqlite3_finalize((sqlite3_stmt *) svm);
	throwoom(env, "unable to get SQLite handle");
	return;
    }
    v->next = h->vms;
    h->vms = v;
    v->vm = svm;
    v->h = h;
    v->tail = (char *) (v + 1);
#if HAVE_BOTH_SQLITE
    v->is3 = v->hh.is3 = 1;
#endif
    memcpy(v->tail, tail, len16);
    len16 /= sizeof (jchar);
    ((jchar *) v->tail)[len16 - 1] = 0;
    (*env)->ReleaseStringChars(env, sql, sql16);
    v->hh.sqlite = 0;
    v->hh.haveutf = h->haveutf;
    v->hh.ver = h->ver;
    v->hh.bh = v->hh.cb = v->hh.ai = v->hh.tr = v->hh.ph = 0;
    v->hh.row1 = 1;
    v->hh.enc = h->enc;
    v->hh.funcs = 0;
    v->hh.vms = 0;
    v->hh.env = 0;
    vv.j = 0;
    vv.l = (jobject) v;
    (*env)->SetLongField(env, stmt, F_SQLite_Stmt_handle, vv.j);
#else
    throwex(env, "unsupported");
#endif
}

JNIEXPORT jboolean JNICALL
Java_SQLite_Stmt_step(JNIEnv *env, jobject obj)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int ret;

	ret = sqlite3_step((sqlite3_stmt *) v->vm);
	if (ret == SQLITE_ROW) {
	    return JNI_TRUE;
	}
	if (ret != SQLITE_DONE) {
	    const char *err = sqlite3_errmsg(v->h->sqlite);

	    setstmterr(env, obj, ret);
	    throwex(env, err ? err : "error in step");
	}
	return JNI_FALSE;
    }
    throwex(env, "stmt already closed");
#else
    throwex(env, "unsupported");
#endif
    return JNI_FALSE;
}

JNIEXPORT void JNICALL
Java_SQLite_Stmt_close(JNIEnv *env, jobject obj)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int ret;

	ret = sqlite3_finalize((sqlite3_stmt *) v->vm);
	v->vm = 0;
	if (ret != SQLITE_OK) {
	    const char *err = sqlite3_errmsg(v->h->sqlite);

	    setstmterr(env, obj, ret);
	    throwex(env, err ? err : "error in close");
	}
	return;
    }
    throwex(env, "stmt already closed");
#else
    throwex(env, "unsupported");
#endif
    return;
}

JNIEXPORT void JNICALL
Java_SQLite_Stmt_reset(JNIEnv *env, jobject obj)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	sqlite3_reset((sqlite3_stmt *) v->vm);
    } else {
	throwex(env, "stmt already closed");
    }
#else
    throwex(env, "unsupported");
#endif
}

JNIEXPORT void JNICALL
Java_SQLite_Stmt_clear_1bindings(JNIEnv *env, jobject obj)
{
#if HAVE_SQLITE3 && HAVE_SQLITE3_CLEAR_BINDINGS
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	sqlite3_clear_bindings((sqlite3_stmt *) v->vm);
    } else {
	throwex(env, "stmt already closed");
    }
#else
    throwex(env, "unsupported");
#endif
}

JNIEXPORT void JNICALL
Java_SQLite_Stmt_bind__II(JNIEnv *env, jobject obj, jint pos, jint val)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int npar = sqlite3_bind_parameter_count((sqlite3_stmt *) v->vm);
	int ret;

	if (pos < 1 || pos > npar) {
	    throwex(env, "parameter position out of bounds");
	    return;
	}
	ret = sqlite3_bind_int((sqlite3_stmt *) v->vm, pos, val);
	if (ret != SQLITE_OK) {
	    setstmterr(env, obj, ret);
	    throwex(env, "bind failed");
	}
    } else {
        throwex(env, "stmt already closed");
    }
#else
    throwex(env, "unsupported");
#endif
}

JNIEXPORT void JNICALL
Java_SQLite_Stmt_bind__IJ(JNIEnv *env, jobject obj, jint pos, jlong val)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int npar = sqlite3_bind_parameter_count((sqlite3_stmt *) v->vm);
	int ret;

	if (pos < 1 || pos > npar) {
	    throwex(env, "parameter position out of bounds");
	    return;
	}
	ret = sqlite3_bind_int64((sqlite3_stmt *) v->vm, pos, val);
	if (ret != SQLITE_OK) {
	    setstmterr(env, obj, ret);
	    throwex(env, "bind failed");
	}
    } else {
        throwex(env, "stmt already closed");
    }
#else
    throwex(env, "unsupported");
#endif
}

JNIEXPORT void JNICALL
Java_SQLite_Stmt_bind__ID(JNIEnv *env, jobject obj, jint pos, jdouble val)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int npar = sqlite3_bind_parameter_count((sqlite3_stmt *) v->vm);
	int ret;

	if (pos < 1 || pos > npar) {
	    throwex(env, "parameter position out of bounds");
	    return;
	}
	ret = sqlite3_bind_double((sqlite3_stmt *) v->vm, pos, val);
	if (ret != SQLITE_OK) {
	    setstmterr(env, obj, ret);
	    throwex(env, "bind failed");
	}
    } else {
        throwex(env, "stmt already closed");
    }
#else
    throwex(env, "unsupported");
#endif
}

JNIEXPORT void JNICALL
Java_SQLite_Stmt_bind__I_3B(JNIEnv *env, jobject obj, jint pos, jbyteArray val)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int npar = sqlite3_bind_parameter_count((sqlite3_stmt *) v->vm);
	int ret;
	jint len;
	char *data = 0;

	if (pos < 1 || pos > npar) {
	    throwex(env, "parameter position out of bounds");
	    return;
	}
	if (val) {
	    len = (*env)->GetArrayLength(env, val);
	    if (len > 0) {
	        data = sqlite3_malloc(len);
		if (!data) {
		    throwoom(env, "unable to get blob parameter");
		    return;
		}
		(*env)->GetByteArrayRegion(env, val, 0, len, (jbyte *) data);
		ret = sqlite3_bind_blob((sqlite3_stmt *) v->vm,
					pos, data, len, sqlite3_free);
	    } else {
		ret = sqlite3_bind_blob((sqlite3_stmt *) v->vm,
					pos, "", 0, SQLITE_STATIC);
	    }
	} else {
	    ret = sqlite3_bind_null((sqlite3_stmt *) v->vm, pos);
	}
	if (ret != SQLITE_OK) {
	    if (data) {
	        sqlite3_free(data);
	    }
	    setstmterr(env, obj, ret);
	    throwex(env, "bind failed");
	}
    } else {
        throwex(env, "stmt already closed");
    }
#else
    throwex(env, "unsupported");
#endif
}

JNIEXPORT void JNICALL
Java_SQLite_Stmt_bind__ILjava_lang_String_2(JNIEnv *env, jobject obj,
					    jint pos, jstring val)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int npar = sqlite3_bind_parameter_count((sqlite3_stmt *) v->vm);
	int ret;
	jsize len;
	char *data = 0;

	if (pos < 1 || pos > npar) {
	    throwex(env, "parameter position out of bounds");
	    return;
	}
	if (val) {
	    len = (*env)->GetStringLength(env, val);
	    if (len > 0) {
	        const jchar *ch;

		len *= sizeof (jchar);
		data = sqlite3_malloc(len);
		if (!data) {
		    throwoom(env, "unable to get blob parameter");
		    return;
		}
		ch = (*env)->GetStringChars(env, val, 0);
		memcpy(data, ch, len);
		(*env)->ReleaseStringChars(env, val, ch);
		ret = sqlite3_bind_text16((sqlite3_stmt *) v->vm,
					  pos, data, len, sqlite3_free);
	    } else {
	        ret = sqlite3_bind_text16((sqlite3_stmt *) v->vm, pos, "", 0,
					  SQLITE_STATIC);
	    }
	} else {
	    ret = sqlite3_bind_null((sqlite3_stmt *) v->vm, pos);
	}
	if (ret != SQLITE_OK) {
	    if (data) {
	        sqlite3_free(data);
	    }
	    setstmterr(env, obj, ret);
	    throwex(env, "bind failed");
	}
    } else {
        throwex(env, "stmt already closed");
    }
#else
    throwex(env, "unsupported");
#endif
}

JNIEXPORT void JNICALL
Java_SQLite_Stmt_bind__I(JNIEnv *env, jobject obj, jint pos)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int npar = sqlite3_bind_parameter_count((sqlite3_stmt *) v->vm);
	int ret;

	if (pos < 1 || pos > npar) {
	    throwex(env, "parameter position out of bounds");
	    return;
	}
	ret = sqlite3_bind_null((sqlite3_stmt *) v->vm, pos);
	if (ret != SQLITE_OK) {
	    setstmterr(env, obj, ret);
	    throwex(env, "bind failed");
	}
    } else {
        throwex(env, "stmt already closed");
    }
#else
    throwex(env, "unsupported");
#endif
}

JNIEXPORT void JNICALL
Java_SQLite_Stmt_bind_1zeroblob(JNIEnv *env, jobject obj, jint pos, jint len)
{
#if HAVE_SQLITE3 && HAVE_SQLITE3_BIND_ZEROBLOB
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int npar = sqlite3_bind_parameter_count((sqlite3_stmt *) v->vm);
	int ret;

	if (pos < 1 || pos > npar) {
	    throwex(env, "parameter position out of bounds");
	    return;
	}
	ret = sqlite3_bind_zeroblob((sqlite3_stmt *) v->vm, pos, len);
	if (ret != SQLITE_OK) {
	    setstmterr(env, obj, ret);
	    throwex(env, "bind failed");
	}
    } else {
        throwex(env, "stmt already closed");
    }
#else
    throwex(env, "unsupported");
#endif
}

JNIEXPORT jint JNICALL
Java_SQLite_Stmt_bind_1parameter_1count(JNIEnv *env, jobject obj)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	return sqlite3_bind_parameter_count((sqlite3_stmt *) v->vm);
    }
    throwex(env, "stmt already closed");
#else
    throwex(env, "unsupported");
#endif
    return 0;
}

JNIEXPORT jstring JNICALL
Java_SQLite_Stmt_bind_1parameter_1name(JNIEnv *env, jobject obj, jint pos)
{
#if HAVE_SQLITE3 && HAVE_SQLITE3_BIND_PARAMETER_NAME
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int npar = sqlite3_bind_parameter_count((sqlite3_stmt *) v->vm);
	const char *name;

	if (pos < 1 || pos > npar) {
	    throwex(env, "parameter position out of bounds");
	    return 0;
	}
	name = sqlite3_bind_parameter_name((sqlite3_stmt *) v->vm, pos);
	if (name) {
	    return (*env)->NewStringUTF(env, name);
	}
    } else {
        throwex(env, "stmt already closed");
    }
#else
    throwex(env, "unsupported");
#endif
    return 0;
}

JNIEXPORT jint JNICALL
Java_SQLite_Stmt_bind_1parameter_1index(JNIEnv *env, jobject obj,
					jstring name)
{
#if HAVE_SQLITE3 && HAVE_SQLITE3_BIND_PARAMETER_INDEX
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
        int pos;
	const char *n;
	transstr namestr;
	jthrowable exc;

	n = trans2iso(env, 1, 0, name, &namestr);
	exc = (*env)->ExceptionOccurred(env);
	if (exc) {
	    (*env)->DeleteLocalRef(env, exc);
	    return -1;
	}
        pos = sqlite3_bind_parameter_index((sqlite3_stmt *) v->vm, n);
	transfree(&namestr);
	return pos;
    } else {
        throwex(env, "stmt already closed");
    }
#else
    throwex(env, "unsupported");
#endif
    return -1;
}

JNIEXPORT jint JNICALL
Java_SQLite_Stmt_column_1int(JNIEnv *env, jobject obj, jint col)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int ncol = sqlite3_data_count((sqlite3_stmt *) v->vm);

	if (col < 0 || col >= ncol) {
	    throwex(env, "column out of bounds");
	    return 0;
	}
	return sqlite3_column_int((sqlite3_stmt *) v->vm, col);
    }
    throwex(env, "stmt already closed");
#else
    throwex(env, "unsupported");
#endif
    return 0;
}

JNIEXPORT jlong JNICALL
Java_SQLite_Stmt_column_1long(JNIEnv *env, jobject obj, jint col)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int ncol = sqlite3_data_count((sqlite3_stmt *) v->vm);

	if (col < 0 || col >= ncol) {
	    throwex(env, "column out of bounds");
	    return 0;
	}
	return sqlite3_column_int64((sqlite3_stmt *) v->vm, col);
    }
    throwex(env, "stmt already closed");
#else
    throwex(env, "unsupported");
#endif
    return 0;
}

JNIEXPORT jdouble JNICALL
Java_SQLite_Stmt_column_1double(JNIEnv *env, jobject obj, jint col)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int ncol = sqlite3_data_count((sqlite3_stmt *) v->vm);

	if (col < 0 || col >= ncol) {
	    throwex(env, "column out of bounds");
	    return 0;
	}
	return sqlite3_column_double((sqlite3_stmt *) v->vm, col);
    }
    throwex(env, "stmt already closed");
#else
    throwex(env, "unsupported");
#endif
    return 0;
}

JNIEXPORT jbyteArray JNICALL
Java_SQLite_Stmt_column_1bytes(JNIEnv *env, jobject obj, jint col)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int ncol = sqlite3_data_count((sqlite3_stmt *) v->vm);
	int nbytes;
	const jbyte *data;
	jbyteArray b = 0;

	if (col < 0 || col >= ncol) {
	    throwex(env, "column out of bounds");
	    return 0;
	}
	data = sqlite3_column_blob((sqlite3_stmt *) v->vm, col);
	if (data) {
	    nbytes = sqlite3_column_bytes((sqlite3_stmt *) v->vm, col);
	} else {
	    return 0;
	}
	b = (*env)->NewByteArray(env, nbytes);
	if (!b) {
	    throwoom(env, "unable to get blob column data");
	    return 0;
	}
	(*env)->SetByteArrayRegion(env, b, 0, nbytes, data);
	return b;
    }
    throwex(env, "stmt already closed");
#else
    throwex(env, "unsupported");
#endif
    return 0;
}

JNIEXPORT jstring JNICALL
Java_SQLite_Stmt_column_1string(JNIEnv *env, jobject obj, jint col)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int ncol = sqlite3_data_count((sqlite3_stmt *) v->vm);
	int nbytes;
	const jchar *data;
	jstring b = 0;

	if (col < 0 || col >= ncol) {
	    throwex(env, "column out of bounds");
	    return 0;
	}
	data = sqlite3_column_text16((sqlite3_stmt *) v->vm, col);
	if (data) {
	    nbytes = sqlite3_column_bytes16((sqlite3_stmt *) v->vm, col);
	} else {
	    return 0;
	}
	nbytes /= sizeof (jchar);
	b = (*env)->NewString(env, data, nbytes);
	if (!b) {
	    throwoom(env, "unable to get string column data");
	    return 0;
	}
	return b;
    }
    throwex(env, "stmt already closed");
#else
    throwex(env, "unsupported");
#endif
    return 0;
}

JNIEXPORT jint JNICALL
Java_SQLite_Stmt_column_1type(JNIEnv *env, jobject obj, jint col)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int ncol = sqlite3_data_count((sqlite3_stmt *) v->vm);

	if (col < 0 || col >= ncol) {
	    throwex(env, "column out of bounds");
	    return 0;
	}
	return sqlite3_column_type((sqlite3_stmt *) v->vm, col);
    }
    throwex(env, "stmt already closed");
#else
    throwex(env, "unsupported");
#endif
    return 0;
}

JNIEXPORT jint JNICALL
Java_SQLite_Stmt_column_1count(JNIEnv *env, jobject obj)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	return sqlite3_column_count((sqlite3_stmt *) v->vm);
    }
    throwex(env, "stmt already closed");
#else
    throwex(env, "unsupported");
#endif
    return 0;
}

JNIEXPORT jstring JNICALL
Java_SQLite_Stmt_column_1table_1name(JNIEnv *env, jobject obj, jint col)
{
#if HAVE_SQLITE3 && HAVE_SQLITE3_COLUMN_TABLE_NAME16
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int ncol = sqlite3_column_count((sqlite3_stmt *) v->vm);
        const jchar *str;

	if (col < 0 || col >= ncol) {
	    throwex(env, "column out of bounds");
	    return 0;
	}
	str = sqlite3_column_table_name16((sqlite3_stmt *) v->vm, col);
	if (str) {
	    return (*env)->NewString(env, str, jstrlen(str));
	}
	return 0;
    }
    throwex(env, "stmt already closed");
#else
    throwex(env, "unsupported");
#endif
    return 0;
}

JNIEXPORT jstring JNICALL
Java_SQLite_Stmt_column_1database_1name(JNIEnv *env, jobject obj, jint col)
{
#if HAVE_SQLITE3 && HAVE_SQLITE3_COLUMN_DATABASE_NAME16
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int ncol = sqlite3_column_count((sqlite3_stmt *) v->vm);
        const jchar *str;

	if (col < 0 || col >= ncol) {
	    throwex(env, "column out of bounds");
	    return 0;
	}
	str = sqlite3_column_database_name16((sqlite3_stmt *) v->vm, col);
	if (str) {
	    return (*env)->NewString(env, str, jstrlen(str));
	}
	return 0;
    }
    throwex(env, "stmt already closed");
#else
    throwex(env, "unsupported");
#endif
    return 0;
}

JNIEXPORT jstring JNICALL
Java_SQLite_Stmt_column_1decltype(JNIEnv *env, jobject obj, jint col)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int ncol = sqlite3_column_count((sqlite3_stmt *) v->vm);
        const jchar *str;

	if (col < 0 || col >= ncol) {
	    throwex(env, "column out of bounds");
	    return 0;
	}
	str = sqlite3_column_decltype16((sqlite3_stmt *) v->vm, col);
	if (str) {
	    return (*env)->NewString(env, str, jstrlen(str));
	}
	return 0;
    }
    throwex(env, "stmt already closed");
#else
    throwex(env, "unsupported");
#endif
    return 0;
}

JNIEXPORT jstring JNICALL
Java_SQLite_Stmt_column_1origin_1name(JNIEnv *env, jobject obj, jint col)
{
#if HAVE_SQLITE3 && HAVE_SQLITE3_COLUMN_ORIGIN_NAME16
    hvm *v = gethstmt(env, obj);

    if (v && v->vm && v->h) {
	int ncol = sqlite3_column_count((sqlite3_stmt *) v->vm);
        const jchar *str;

	if (col < 0 || col >= ncol) {
	    throwex(env, "column out of bounds");
	    return 0;
	}
	str = sqlite3_column_origin_name16((sqlite3_stmt *) v->vm, col);
	if (str) {
	    return (*env)->NewString(env, str, jstrlen(str));
	}
	return 0;
    }
    throwex(env, "stmt already closed");
#else
    throwex(env, "unsupported");
#endif
    return 0;
}

JNIEXPORT void JNICALL
Java_SQLite_Stmt_finalize(JNIEnv *env, jobject obj)
{
#if HAVE_SQLITE3 && HAVE_SQLITE_COMPILE
    dostmtfinal(env, obj);
#endif
}

JNIEXPORT void JNICALL
Java_SQLite_Database__1open_1blob(JNIEnv *env, jobject obj,
				  jstring dbname, jstring table,
				  jstring column, jlong row,
				  jboolean rw, jobject blobj)
{
#if HAVE_SQLITE3 && HAVE_SQLITE3_INCRBLOBIO
    handle *h = gethandle(env, obj);
    hbl *bl;
    jthrowable exc;
    transstr dbn, tbl, col;
    sqlite3_blob *blob;
    jvalue vv;
    int ret;

    if (!blobj) {
	throwex(env, "null blob");
	return;
    }
    if (h && h->sqlite) {
        trans2iso(env, h->haveutf, h->enc, dbname, &dbn);
	exc = (*env)->ExceptionOccurred(env);
	if (exc) {
	    (*env)->DeleteLocalRef(env, exc);
	    return;
	}
        trans2iso(env, h->haveutf, h->enc, table, &tbl);
	exc = (*env)->ExceptionOccurred(env);
	if (exc) {
	    transfree(&dbn);
	    (*env)->DeleteLocalRef(env, exc);
	    return;
	}
        trans2iso(env, h->haveutf, h->enc, column, &col);
	exc = (*env)->ExceptionOccurred(env);
	if (exc) {
	    transfree(&tbl);
	    transfree(&dbn);
	    (*env)->DeleteLocalRef(env, exc);
	    return;
	}
	ret = sqlite3_blob_open(h->sqlite,
				dbn.result, tbl.result, col.result,
				row, rw, &blob);
	transfree(&col);
	transfree(&tbl);
	transfree(&dbn);
	if (ret != SQLITE_OK) {
	    const char *err = sqlite3_errmsg(h->sqlite);

	    seterr(env, obj, ret);
	    throwex(env, err ? err : "error in blob open");
	    return;
	}
	bl = malloc(sizeof (hbl));
	if (!bl) {
	    sqlite3_blob_close(blob);
	    throwoom(env, "unable to get SQLite blob handle");
	    return;
	}
	bl->next = h->blobs;
	h->blobs = bl;
	bl->blob = blob;
	bl->h = h;
	vv.j = 0;
	vv.l = (jobject) bl;
	(*env)->SetLongField(env, blobj, F_SQLite_Blob_handle, vv.j);
	(*env)->SetIntField(env, blobj, F_SQLite_Blob_size,
			    sqlite3_blob_bytes(blob));
	return;
    }
    throwex(env, "not an open database");
#else
    throwex(env, "unsupported");
#endif
}

JNIEXPORT jint JNICALL
Java_SQLite_Blob_write(JNIEnv *env , jobject obj, jbyteArray b, jint off,
		       jint pos, jint len)
{
#if HAVE_SQLITE3 && HAVE_SQLITE3_INCRBLOBIO
    hbl *bl = gethbl(env, obj);

    if (bl && bl->h && bl->blob) {
        jbyte *buf;
	jthrowable exc;
	int ret;

	if (len <= 0) {
	    return 0;
	}
	buf = malloc(len);
	if (!buf) {
	    throwoom(env, "out of buffer space for blob");
	    return 0;
	}
	(*env)->GetByteArrayRegion(env, b, off, len, buf);
	exc = (*env)->ExceptionOccurred(env);
	if (exc) {
	    free(buf);
	    return 0;
	}
	ret = sqlite3_blob_write(bl->blob, buf, len, pos);
	free(buf);
	if (ret != SQLITE_OK) {
	    throwioex(env, "blob write error");
	    return 0;
	}
	return len;
    }
    throwex(env, "blob already closed");
#else
    throwex(env, "unsupported");
#endif
    return 0;
}

JNIEXPORT jint JNICALL
Java_SQLite_Blob_read(JNIEnv *env , jobject obj, jbyteArray b, jint off,
		      jint pos, jint len)
{
#if HAVE_SQLITE3 && HAVE_SQLITE3_INCRBLOBIO
    hbl *bl = gethbl(env, obj);

    if (bl && bl->h && bl->blob) {
        jbyte *buf;
	jthrowable exc;
	int ret;

	if (len <= 0) {
	    return 0;
	}
	buf = malloc(len);
	if (!buf) {
	    throwoom(env, "out of buffer space for blob");
	    return 0;
	}
	ret = sqlite3_blob_read(bl->blob, buf, len, pos);
	if (ret != SQLITE_OK) {
	    free(buf);
	    throwioex(env, "blob read error");
	    return 0;
	}
	(*env)->SetByteArrayRegion(env, b, off, len, buf);
	free(buf);
	exc = (*env)->ExceptionOccurred(env);
	if (exc) {
	    return 0;
	}
	return len;
    }
    throwex(env, "blob already closed");
#else
    throwex(env, "unsupported");
#endif
    return 0;
}

JNIEXPORT void JNICALL
Java_SQLite_Blob_close(JNIEnv *env, jobject obj)
{
#if HAVE_SQLITE3 && HAVE_SQLITE3_INCRBLOBIO
    doblobfinal(env, obj);
#endif
}

JNIEXPORT void JNICALL
Java_SQLite_Blob_finalize(JNIEnv *env, jobject obj)
{
#if HAVE_SQLITE3 && HAVE_SQLITE3_INCRBLOBIO
    doblobfinal(env, obj);
#endif
}

JNIEXPORT void JNICALL
Java_SQLite_Stmt_internal_1init(JNIEnv *env, jclass cls)
{
    F_SQLite_Stmt_handle =
	(*env)->GetFieldID(env, cls, "handle", "J");
    F_SQLite_Stmt_error_code =
	(*env)->GetFieldID(env, cls, "error_code", "I");
}

JNIEXPORT void JNICALL
Java_SQLite_Vm_internal_1init(JNIEnv *env, jclass cls)
{
    F_SQLite_Vm_handle =
	(*env)->GetFieldID(env, cls, "handle", "J");
    F_SQLite_Vm_error_code =
	(*env)->GetFieldID(env, cls, "error_code", "I");
}

JNIEXPORT void JNICALL
Java_SQLite_Blob_internal_1init(JNIEnv *env, jclass cls)
{
    F_SQLite_Blob_handle =
	(*env)->GetFieldID(env, cls, "handle", "J");
    F_SQLite_Blob_size =
	(*env)->GetFieldID(env, cls, "size", "I");
}

JNIEXPORT void JNICALL
Java_SQLite_Database_internal_1init(JNIEnv *env, jclass cls)
{
//#ifndef JNI_VERSION_1_2
    jclass jls = (*env)->FindClass(env, "java/lang/String");

    C_java_lang_String = (*env)->NewGlobalRef(env, jls);
//#endif
    F_SQLite_Database_handle =
	(*env)->GetFieldID(env, cls, "handle", "J");
    F_SQLite_Database_error_code =
	(*env)->GetFieldID(env, cls, "error_code", "I");
    M_java_lang_String_getBytes =
	(*env)->GetMethodID(env, C_java_lang_String, "getBytes", "()[B");
    M_java_lang_String_getBytes2 =
	(*env)->GetMethodID(env, C_java_lang_String, "getBytes",
			    "(Ljava/lang/String;)[B");
    M_java_lang_String_initBytes =
	(*env)->GetMethodID(env, C_java_lang_String, "<init>", "([B)V");
    M_java_lang_String_initBytes2 =
	(*env)->GetMethodID(env, C_java_lang_String, "<init>",
			    "([BLjava/lang/String;)V");
}

#ifdef JNI_VERSION_1_2
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved)
{
    JNIEnv *env;
    jclass cls;

#ifndef _WIN32
#if HAVE_SQLITE2
    if (strcmp(sqlite_libencoding(), "UTF-8") != 0) {
	fprintf(stderr, "WARNING: using non-UTF SQLite2 engine\n");
    }
#endif
#endif
    if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_2)) {
	return JNI_ERR;
    }
    cls = (*env)->FindClass(env, "java/lang/String");
    if (!cls) {
	return JNI_ERR;
    }
    C_java_lang_String = (*env)->NewWeakGlobalRef(env, cls);
    return JNI_VERSION_1_2;
}

JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *vm, void *reserved)
{
    JNIEnv *env;

    if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_2)) {
	return;
    }
    if (C_java_lang_String) {
	(*env)->DeleteWeakGlobalRef(env, C_java_lang_String);
	C_java_lang_String = 0;
    }
}
#endif
