Break the sqlite JDBC driver out from our JDBC implementation.

The JDBC driver is from a different source
(http://www.ch-werner.de/javasqlite/overview-summary.html) and is only
really needed for testing.

Bug 2468870 asks that we don't eagerly register the native methods for
these classes. That bug is fixed by this change.

Bug 2198667 asks that we stop shipping this JDBC driver as part of the
base system. That bug is not addressed by this change: the classes and
native code are now in their own, separate, .jar and .so files -- so
they'll be easier to remove in future -- but for now those files are
still in /system/framework and /system/lib respectively.

Bug: 2468870
Bug: 2198667
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..2c3926a
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,23 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	src/main/native/sqlite_jni.c
+
+LOCAL_C_INCLUDES += \
+        $(JNI_H_INCLUDE) \
+	external/sqlite/dist
+
+LOCAL_SHARED_LIBRARIES += \
+	libsqlite
+
+LOCAL_STATIC_LIBRARIES +=
+
+# This name is dictated by the fact that the SQLite code calls
+# loadLibrary("sqlite_jni").
+LOCAL_MODULE := libsqlite_jni
+
+TARGET_PRELINK_MODULE := false
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/MODULE_LICENSE_BSD_LIKE b/MODULE_LICENSE_BSD_LIKE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_BSD_LIKE
diff --git a/src/main/java/SQLite/Authorizer.java b/src/main/java/SQLite/Authorizer.java
new file mode 100644
index 0000000..cdc321d
--- /dev/null
+++ b/src/main/java/SQLite/Authorizer.java
@@ -0,0 +1,25 @@
+package SQLite;
+
+/**
+ * Callback interface for SQLite's authorizer function.
+ */
+
+public interface Authorizer {
+
+    /**
+     * Callback to authorize access.
+     *
+     * @param what integer indicating type of access
+     * @param arg1 first argument (table, view, index, or trigger name)
+     * @param arg2 second argument (file, table, or column name)
+     * @param arg3 third argument (database name)
+     * @param arg4 third argument (trigger name)
+     * @return Constants.SQLITE_OK for success, Constants.SQLITE_IGNORE
+     * for don't allow access but don't raise an error, Constants.SQLITE_DENY
+     * for abort SQL statement with error.
+     */
+
+    public int authorize(int what, String arg1, String arg2, String arg3,
+             String arg4);
+}
+
diff --git a/src/main/java/SQLite/Blob.java b/src/main/java/SQLite/Blob.java
new file mode 100644
index 0000000..3de9f8a
--- /dev/null
+++ b/src/main/java/SQLite/Blob.java
@@ -0,0 +1,323 @@
+package SQLite;
+
+import java.io.*;
+
+/**
+ * Internal class implementing java.io.InputStream on
+ * SQLite 3.4.0 incremental blob I/O interface.
+ */
+
+class BlobR extends InputStream {
+
+    /**
+     * Blob instance
+     */
+
+    private Blob blob;
+
+    /**
+     * Read position, file pointer.
+     */
+
+    private int pos;
+
+    /**
+     * Contruct InputStream from blob instance.
+     */
+
+    BlobR(Blob blob) {
+    this.blob = blob;
+    this.pos = 0;
+    }
+
+    /**
+     * Return number of available bytes for reading.
+     * @return available input bytes
+     */
+
+    public int available() throws IOException {
+    int ret = blob.size - pos;
+    return (ret < 0) ? 0 : ret;
+    }
+
+    /**
+     * Mark method; dummy to satisfy InputStream class.
+     */
+
+    public void mark(int limit) {
+    }
+
+    /**
+     * Reset method; dummy to satisfy InputStream class.
+     */
+
+    public void reset() throws IOException {
+    }
+
+    /**
+     * Mark support; not for this class.
+     * @return always false
+     */
+
+    public boolean markSupported() {
+    return false;
+    }
+
+    /**
+     * Close this blob InputStream.
+     */
+
+    public void close() throws IOException {
+        blob.close();
+    blob = null;
+    pos = 0;
+    }
+
+    /**
+     * Skip over blob data.
+     */
+
+    public long skip(long n) throws IOException {
+    long ret = pos + n;
+    if (ret < 0) {
+        ret = 0;
+        pos = 0;
+    } else if (ret > blob.size) {
+        ret = blob.size;
+        pos = blob.size;
+    } else {
+        pos = (int) ret;
+    }
+    return ret;
+    }
+
+    /**
+     * Read single byte from blob.
+     * @return byte read
+     */
+
+    public int read() throws IOException {
+    byte b[] = new byte[1];
+    int n = blob.read(b, 0, pos, b.length);
+    if (n > 0) {
+        pos += n;
+        return b[0];
+    }
+    return -1;
+    }
+
+    /**
+     * Read byte array from blob.
+     * @param b byte array to be filled
+     * @return number of bytes read
+     */
+
+    public int read(byte b[]) throws IOException {
+    int n = blob.read(b, 0, pos, b.length);
+    if (n > 0) {
+        pos += n;
+        return n;
+    }
+    return -1;
+    }
+
+    /**
+     * Read slice of byte array from blob.
+     * @param b byte array to be filled
+     * @param off offset into byte array
+     * @param len length to be read
+     * @return number of bytes read
+     */
+
+    public int read(byte b[], int off, int len) throws IOException {
+    if (off + len > b.length) {
+        len = b.length - off;
+    }
+    if (len < 0) {
+        return -1;
+    }
+    if (len == 0) {
+        return 0;
+    }
+    int n = blob.read(b, off, pos, len);
+    if (n > 0) {
+        pos += n;
+        return n;
+    }
+    return -1;
+    }
+}
+
+/**
+ * Internal class implementing java.io.OutputStream on
+ * SQLite 3.4.0 incremental blob I/O interface.
+ */
+
+class BlobW extends OutputStream {
+
+    /**
+     * Blob instance
+     */
+
+    private Blob blob;
+
+    /**
+     * Read position, file pointer.
+     */
+
+    private int pos;
+
+    /**
+     * Contruct OutputStream from blob instance.
+     */
+
+    BlobW(Blob blob) {
+    this.blob = blob;
+    this.pos = 0;
+    }
+
+    /**
+     * Flush blob; dummy to satisfy OutputStream class.
+     */
+
+    public void flush() throws IOException {
+    }
+
+    /**
+     * Close this blob OutputStream.
+     */
+
+    public void close() throws IOException {
+        blob.close();
+    blob = null;
+    pos = 0;
+    }
+
+    /**
+     * Write blob data.
+     * @param v byte to be written at current position.
+     */
+
+    public void write(int v) throws IOException {
+    byte b[] = new byte[1];
+    b[0] = (byte) v;
+    pos += blob.write(b, 0, pos, 1);
+    }
+
+    /**
+     * Write blob data.
+     * @param b byte array to be written at current position.
+     */
+
+    public void write(byte[] b) throws IOException {
+    if (b != null && b.length > 0) {
+        pos += blob.write(b, 0, pos, b.length);
+    }
+    }
+
+    /**
+     * Write blob data.
+     * @param b byte array to be written.
+     * @param off offset within byte array
+     * @param len length of data to be written
+     */
+
+    public void write(byte[] b, int off, int len) throws IOException {
+    if (b != null) {
+        if (off + len > b.length) {
+        len = b.length - off;
+        }
+        if (len <= 0) {
+        return;
+        }
+        pos += blob.write(b, off, pos, len);
+    }
+    }
+}
+
+/**
+ * Class to represent SQLite3 3.4.0 incremental blob I/O interface.
+ *
+ * Note, that all native methods of this class are
+ * not synchronized, i.e. it is up to the caller
+ * to ensure that only one thread is in these
+ * methods at any one time.
+ */
+
+public class Blob {
+
+    /**
+     * Internal handle for the SQLite3 blob.
+     */
+
+    private long handle = 0;
+
+    /**
+     * Cached size of blob, setup right after blob
+     * has been opened.
+     */
+
+    protected int size = 0;
+
+    /**
+     * Return InputStream for this blob
+     * @return InputStream
+     */
+
+    public InputStream getInputStream() {
+    return (InputStream) new BlobR(this);
+    }
+
+    /**
+     * Return OutputStream for this blob
+     * @return OutputStream
+     */
+
+    public OutputStream getOutputStream() {
+    return (OutputStream) new BlobW(this);
+    }
+
+    /**
+     * Close blob.
+     */
+
+    public native void close();
+
+    /**
+     * Internal blob write method.
+     * @param b byte array to be written
+     * @param off offset into byte array
+     * @param pos offset into blob
+     * @param len length to be written
+     * @return number of bytes written to blob
+     */
+
+    native int write(byte[] b, int off, int pos, int len) throws IOException;
+
+    /**
+     * Internal blob read method.
+     * @param b byte array to be written
+     * @param off offset into byte array
+     * @param pos offset into blob
+     * @param len length to be written
+     * @return number of bytes written to blob
+     */
+
+    native int read(byte[] b, int off, int pos, int len) throws IOException;
+
+    /**
+     * Destructor for object.
+     */
+
+    protected native void finalize();
+
+    /**
+     * Internal native initializer.
+     */
+
+    private static native void internal_init();
+
+    static {
+    internal_init();
+    }
+}
diff --git a/src/main/java/SQLite/BusyHandler.java b/src/main/java/SQLite/BusyHandler.java
new file mode 100644
index 0000000..c39b39d
--- /dev/null
+++ b/src/main/java/SQLite/BusyHandler.java
@@ -0,0 +1,20 @@
+package SQLite;
+
+/**
+ * Callback interface for SQLite's user defined busy handler.
+ */
+
+public interface BusyHandler {
+
+    /**
+     * Invoked when a table is locked by another process
+     * or thread. The method should return true for waiting
+     * until the table becomes unlocked, or false in order
+     * to abandon the action.<BR><BR>
+     *
+     * @param table the name of the locked table
+     * @param count number of times the table was locked
+     */
+
+    public boolean busy(String table, int count);
+}
diff --git a/src/main/java/SQLite/Callback.java b/src/main/java/SQLite/Callback.java
new file mode 100644
index 0000000..3eeb605
--- /dev/null
+++ b/src/main/java/SQLite/Callback.java
@@ -0,0 +1,68 @@
+package SQLite;
+
+/**
+ * Callback interface for SQLite's query results.
+ * <BR><BR>
+ * Example:<BR>
+ *
+ * <PRE>
+ *   class TableFmt implements SQLite.Callback {
+ *     public void columns(String cols[]) {
+ *       System.out.println("&lt;TH&gt;&lt;TR&gt;");
+ *       for (int i = 0; i &lt; cols.length; i++) {
+ *         System.out.println("&lt;TD&gt;" + cols[i] + "&lt;/TD&gt;");
+ *       }
+ *       System.out.println("&lt;/TR&gt;&lt;/TH&gt;");
+ *     }
+ *     public boolean newrow(String cols[]) {
+ *       System.out.println("&lt;TR&gt;");
+ *       for (int i = 0; i &lt; cols.length; i++) {
+ *         System.out.println("&lt;TD&gt;" + cols[i] + "&lt;/TD&gt;");
+ *       }
+ *       System.out.println("&lt;/TR&gt;");
+ *       return false;
+ *     }
+ *   }
+ *   ...
+ *   SQLite.Database db = new SQLite.Database();
+ *   db.open("db", 0);
+ *   System.out.println("&lt;TABLE&gt;");
+ *   db.exec("select * from TEST", new TableFmt());
+ *   System.out.println("&lt;/TABLE&gt;");
+ *   ...
+ * </PRE>
+ */
+
+public interface Callback {
+
+    /**
+     * Reports column names of the query result.
+     * This method is invoked first (and once) when
+     * the SQLite engine returns the result set.<BR><BR>
+     *
+     * @param coldata string array holding the column names
+     */
+
+    public void columns(String coldata[]);
+
+    /**
+     * Reports type names of the columns of the query result.
+     * This is available from SQLite 2.6.0 on and needs
+     * the PRAGMA show_datatypes to be turned on.<BR><BR>
+     *
+     * @param types string array holding column types
+     */
+
+    public void types(String types[]);
+
+    /**
+     * Reports row data of the query result.
+     * This method is invoked for each row of the
+     * result set. If true is returned the running
+     * SQLite query is aborted.<BR><BR>
+     *
+     * @param rowdata string array holding the column values of the row
+     */
+
+    public boolean newrow(String rowdata[]);
+}
diff --git a/src/main/java/SQLite/Constants.java b/src/main/java/SQLite/Constants.java
new file mode 100644
index 0000000..4e636b9
--- /dev/null
+++ b/src/main/java/SQLite/Constants.java
@@ -0,0 +1,157 @@
+/* DO NOT EDIT */
+
+package SQLite;
+
+/**
+ * Container for SQLite constants.
+ */
+
+public final class Constants {
+    /*
+     * Error code: 0
+     */
+    public static final int SQLITE_OK = 0;
+    /*
+     * Error code: 1
+     */
+    public static final int SQLITE_ERROR = 1;
+    /*
+     * Error code: 2
+     */
+    public static final int SQLITE_INTERNAL = 2;
+    /*
+     * Error code: 3
+     */
+    public static final int SQLITE_PERM = 3;
+    /*
+     * Error code: 4
+     */
+    public static final int SQLITE_ABORT = 4;
+    /*
+     * Error code: 5
+     */
+    public static final int SQLITE_BUSY = 5;
+    /*
+     * Error code: 6
+     */
+    public static final int SQLITE_LOCKED = 6;
+    /*
+     * Error code: 7
+     */
+    public static final int SQLITE_NOMEM = 7;
+    /*
+     * Error code: 8
+     */
+    public static final int SQLITE_READONLY = 8;
+    /*
+     * Error code: 9
+     */
+    public static final int SQLITE_INTERRUPT = 9;
+    /*
+     * Error code: 10
+     */
+    public static final int SQLITE_IOERR = 10;
+    /*
+     * Error code: 11
+     */
+    public static final int SQLITE_CORRUPT = 11;
+    /*
+     * Error code: 12
+     */
+    public static final int SQLITE_NOTFOUND = 12;
+    /*
+     * Error code: 13
+     */
+    public static final int SQLITE_FULL = 13;
+    /*
+     * Error code: 14
+     */
+    public static final int SQLITE_CANTOPEN = 14;
+    /*
+     * Error code: 15
+     */
+    public static final int SQLITE_PROTOCOL = 15;
+    /*
+     * Error code: 16
+     */
+    public static final int SQLITE_EMPTY = 16;
+    /*
+     * Error code: 17
+     */
+    public static final int SQLITE_SCHEMA = 17;
+    /*
+     * Error code: 18
+     */
+    public static final int SQLITE_TOOBIG = 18;
+    /*
+     * Error code: 19
+     */
+    public static final int SQLITE_CONSTRAINT = 19;
+    /*
+     * Error code: 20
+     */
+    public static final int SQLITE_MISMATCH = 20;
+    /*
+     * Error code: 21
+     */
+    public static final int SQLITE_MISUSE = 21;
+    /*
+     * Error code: 22
+     */
+    public static final int SQLITE_NOLFS = 22;
+    /*
+     * Error code: 23
+     */
+    public static final int SQLITE_AUTH = 23;
+    /*
+     * Error code: 24
+     */
+    public static final int SQLITE_FORMAT = 24;
+    /*
+     * Error code: 25
+     */
+    public static final int SQLITE_RANGE = 25;
+    /*
+     * Error code: 26
+     */
+    public static final int SQLITE_NOTADB = 26;
+    public static final int SQLITE_ROW = 100;
+    public static final int SQLITE_DONE = 101;
+    public static final int SQLITE_INTEGER = 1;
+    public static final int SQLITE_FLOAT = 2;
+    public static final int SQLITE_BLOB = 4;
+    public static final int SQLITE_NULL = 5;
+    public static final int SQLITE3_TEXT = 3;
+    public static final int SQLITE_NUMERIC = -1;
+    public static final int SQLITE_TEXT = 3;
+    public static final int SQLITE2_TEXT = -2;
+    public static final int SQLITE_ARGS = -3;
+    public static final int SQLITE_COPY = 0;
+    public static final int SQLITE_CREATE_INDEX = 1;
+    public static final int SQLITE_CREATE_TABLE = 2;
+    public static final int SQLITE_CREATE_TEMP_INDEX = 3;
+    public static final int SQLITE_CREATE_TEMP_TABLE = 4;
+    public static final int SQLITE_CREATE_TEMP_TRIGGER = 5;
+    public static final int SQLITE_CREATE_TEMP_VIEW = 6;
+    public static final int SQLITE_CREATE_TRIGGER = 7;
+    public static final int SQLITE_CREATE_VIEW = 8;
+    public static final int SQLITE_DELETE = 9;
+    public static final int SQLITE_DROP_INDEX = 10;
+    public static final int SQLITE_DROP_TABLE = 11;
+    public static final int SQLITE_DROP_TEMP_INDEX = 12;
+    public static final int SQLITE_DROP_TEMP_TABLE = 13;
+    public static final int SQLITE_DROP_TEMP_TRIGGER = 14;
+    public static final int SQLITE_DROP_TEMP_VIEW = 15;
+    public static final int SQLITE_DROP_TRIGGER = 16;
+    public static final int SQLITE_DROP_VIEW = 17;
+    public static final int SQLITE_INSERT = 18;
+    public static final int SQLITE_PRAGMA = 19;
+    public static final int SQLITE_READ = 20;
+    public static final int SQLITE_SELECT = 21;
+    public static final int SQLITE_TRANSACTION = 22;
+    public static final int SQLITE_UPDATE = 23;
+    public static final int SQLITE_ATTACH = 24;
+    public static final int SQLITE_DETACH = 25;
+    public static final int SQLITE_DENY = 1;
+    public static final int SQLITE_IGNORE = 2;
+}
diff --git a/src/main/java/SQLite/Database.java b/src/main/java/SQLite/Database.java
new file mode 100644
index 0000000..fbb5d29
--- /dev/null
+++ b/src/main/java/SQLite/Database.java
@@ -0,0 +1,615 @@
+package SQLite;
+
+/**
+ * Main class wrapping an SQLite database.
+ */
+
+public class Database {
+
+    /**
+     * Internal handle for the native SQLite API.
+     */
+
+    protected long handle = 0;
+
+    /**
+     * Internal last error code for exec() methods.
+     */
+
+    protected int error_code = 0;
+
+    /**
+     * Open an SQLite database file.
+     *
+     * @param filename the name of the database file
+     * @param mode open mode, currently ignored
+     */
+
+    public void open(String filename, int mode) throws SQLite.Exception {
+    synchronized(this) {
+        _open(filename, mode);
+    }
+    }
+
+    private native void _open(String filename, int mode)
+    throws SQLite.Exception;
+
+    /**
+     * Open SQLite auxiliary database file for temporary
+     * tables.
+     *
+     * @param filename the name of the auxiliary file or null
+     */
+
+    public void open_aux_file(String filename) throws SQLite.Exception {
+    synchronized(this) {
+        _open_aux_file(filename);
+    }
+    }
+
+    private native void _open_aux_file(String filename)
+    throws SQLite.Exception;
+
+    /**
+     * Destructor for object.
+     */
+
+    protected void finalize() {
+    synchronized(this) {
+        _finalize();
+    }
+    }
+
+    private native void _finalize();
+
+    /**
+     * Close the underlying SQLite database file.
+     */
+
+    public void close()    throws SQLite.Exception {
+    synchronized(this) {
+        _close();
+    }
+    }
+
+    private native void _close()
+    throws SQLite.Exception;
+
+    /**
+     * Execute an SQL statement and invoke callback methods
+     * for each row of the result set.<P>
+     *
+     * It the method fails, an SQLite.Exception is thrown and
+     * an error code is set, which later can be retrieved by
+     * the last_error() method.
+     *
+     * @param sql the SQL statement to be executed
+     * @param cb the object implementing the callback methods
+     */
+
+    public void exec(String sql, SQLite.Callback cb) throws SQLite.Exception {
+    synchronized(this) {
+        _exec(sql, cb);
+    }
+    }
+
+    private native void _exec(String sql, SQLite.Callback cb)
+    throws SQLite.Exception;
+
+    /**
+     * Execute an SQL statement and invoke callback methods
+     * for each row of the result set. Each '%q' or %Q in the
+     * statement string is substituted by its corresponding
+     * element in the argument vector.
+     * <BR><BR>
+     * Example:<BR>
+     * <PRE>
+     *   String args[] = new String[1];
+     *   args[0] = "tab%";
+     *   db.exec("select * from sqlite_master where type like '%q'",
+     *           null, args);
+     * </PRE>
+     *
+     * It the method fails, an SQLite.Exception is thrown and
+     * an error code is set, which later can be retrieved by
+     * the last_error() method.
+     *
+     * @param sql the SQL statement to be executed
+     * @param cb the object implementing the callback methods
+     * @param args arguments for the SQL statement, '%q' substitution
+     */
+
+    public void exec(String sql, SQLite.Callback cb,
+             String args[]) throws SQLite.Exception {
+    synchronized(this) {
+        _exec(sql, cb, args);
+    }
+    }
+
+    private native void _exec(String sql, SQLite.Callback cb, String args[])
+    throws SQLite.Exception;
+
+    /**
+     * Return the row identifier of the last inserted
+     * row.
+     */
+
+    public long last_insert_rowid() {
+    synchronized(this) {
+        return _last_insert_rowid();
+    }
+    }
+
+    private native long _last_insert_rowid();
+
+    /**
+     * Abort the current SQLite operation.
+     */
+
+    public void interrupt() {
+    synchronized(this) {
+        _interrupt();
+    }
+    }
+
+    private native void _interrupt();
+
+    /**
+     * Return the number of changed rows for the last statement.
+     */
+
+    public long changes() {
+    synchronized(this) {
+        return _changes();
+    }
+    }
+
+    private native long _changes();
+
+    /**
+     * Establish a busy callback method which gets called when
+     * an SQLite table is locked.
+     *
+     * @param bh the object implementing the busy callback method
+     */
+
+    public void busy_handler(SQLite.BusyHandler bh) {
+    synchronized(this) {
+        _busy_handler(bh);
+    }
+    }
+
+    private native void _busy_handler(SQLite.BusyHandler bh);
+
+    /**
+     * Set the timeout for waiting for an SQLite table to become
+     * unlocked.
+     *
+     * @param ms number of millisecond to wait
+     */
+
+    public void busy_timeout(int ms) {
+    synchronized(this) {
+        _busy_timeout(ms);
+    }
+    }
+
+    private native void _busy_timeout(int ms);
+
+    /**
+     * Convenience method to retrieve an entire result
+     * set into memory.
+     *
+     * @param sql the SQL statement to be executed
+     * @return result set
+     */
+
+    public TableResult get_table(String sql) throws SQLite.Exception {
+    TableResult ret = new TableResult();
+    if (!is3()) {
+        exec(sql, ret);
+    } else {
+        synchronized(this) {
+        /* only one statement !!! */
+        Vm vm = compile(sql);
+        set_last_error(vm.error_code);
+        while (vm.step(ret)) {
+            set_last_error(vm.error_code);
+        }
+        vm.finalize();
+        }
+    }
+    return ret;
+    }
+
+    /**
+     * Convenience method to retrieve an entire result
+     * set into memory.
+     *
+     * @param sql the SQL statement to be executed
+     * @param args arguments for the SQL statement, '%q' substitution
+     * @return result set
+     */
+
+    public TableResult get_table(String sql, String args[])
+    throws SQLite.Exception {
+    TableResult ret = new TableResult();
+    if (!is3()) {
+        exec(sql, ret, args);
+    } else {
+        synchronized(this) {
+        /* only one statement !!! */
+        Vm vm = compile(sql, args);
+        set_last_error(vm.error_code);
+        while (vm.step(ret)) {
+            set_last_error(vm.error_code);
+        }
+        vm.finalize();
+        }
+    }
+    return ret;
+    }
+
+    /**
+     * Convenience method to retrieve an entire result
+     * set into memory.
+     *
+     * @param sql the SQL statement to be executed
+     * @param args arguments for the SQL statement, '%q' substitution
+     * @param tbl TableResult to receive result set
+     * @return result set
+     */
+
+    public void get_table(String sql, String args[], TableResult tbl)
+    throws SQLite.Exception {
+    tbl.clear();
+    if (!is3()) {
+        exec(sql, tbl, args);
+    } else {
+        synchronized(this) {
+        /* only one statement !!! */
+        Vm vm = compile(sql, args);
+        while (vm.step(tbl)) {
+        }
+        vm.finalize();
+        }
+    }
+    }
+
+    /**
+     * See if an SQL statement is complete.
+     * Returns true if the input string comprises
+     * one or more complete SQL statements.
+     *
+     * @param sql the SQL statement to be checked
+     */
+
+    public synchronized static boolean complete(String sql) {
+    return _complete(sql);
+    }
+
+    private native static boolean _complete(String sql);
+
+    /**
+     * Return SQLite version number as string.
+     * Don't rely on this when both SQLite 2 and 3 are compiled
+     * into the native part. Use the class method in this case.
+     */
+
+    public native static String version();
+
+    /**
+     * Return SQLite version number as string.
+     * If the database is not open, <tt>unknown</tt> is returned.
+     */
+
+    public native String dbversion();
+
+    /**
+     * Create regular function.
+     *
+     * @param name the name of the new function
+     * @param nargs number of arguments to function
+     * @param f interface of function
+     */
+
+    public void create_function(String name, int nargs, Function f) {
+    synchronized(this) {
+        _create_function(name, nargs, f);
+    }
+    }
+
+    private native void _create_function(String name, int nargs, Function f);
+
+    /**
+     * Create aggregate function.
+     *
+     * @param name the name of the new function
+     * @param nargs number of arguments to function
+     * @param f interface of function
+     */
+
+    public void create_aggregate(String name, int nargs, Function f) {
+    synchronized(this) {
+        _create_aggregate(name, nargs, f);
+    }
+    }
+
+    private native void _create_aggregate(String name, int nargs, Function f);
+
+    /**
+     * Set function return type. Only available in SQLite 2.6.0 and
+     * above, otherwise a no-op.
+     *
+     * @param name the name of the function whose return type is to be set
+     * @param type return type code, e.g. SQLite.Constants.SQLITE_NUMERIC
+     */
+
+    public void function_type(String name, int type) {
+    synchronized(this) {
+        _function_type(name, type);
+    }
+    }
+
+    private native void _function_type(String name, int type);
+
+    /**
+     * Return the code of the last error occured in
+     * any of the exec() methods. The value is valid
+     * after an Exception has been reported by one of
+     * these methods. See the <A HREF="Constants.html">Constants</A>
+     * class for possible values.
+     *
+     * @return SQLite error code
+     */
+
+    public int last_error() {
+    return error_code;
+    }
+
+    /**
+     * Internal: set error code.
+     * @param error_code new error code
+     */
+
+    protected void set_last_error(int error_code) {
+    this.error_code = error_code;
+    }
+
+    /**
+     * Return last error message of SQLite3 engine.
+     *
+     * @return error string or null
+     */
+
+    public String error_message() {
+    synchronized(this) {
+        return _errmsg();
+    }
+    }
+
+    private native String _errmsg();
+
+    /**
+     * Return error string given SQLite error code (SQLite2).
+     *
+     * @param error_code the error code
+     * @return error string
+     */
+
+    public static native String error_string(int error_code);
+
+    /**
+     * Set character encoding.
+     * @param enc name of encoding
+     */
+
+    public void set_encoding(String enc) throws SQLite.Exception {
+    synchronized(this) {
+        _set_encoding(enc);
+    }
+    }
+
+    private native void _set_encoding(String enc)
+    throws SQLite.Exception;
+
+    /**
+     * Set authorizer function. Only available in SQLite 2.7.6 and
+     * above, otherwise a no-op.
+     *
+     * @param auth the authorizer function
+     */
+
+    public void set_authorizer(Authorizer auth) {
+    synchronized(this) {
+        _set_authorizer(auth);
+    }
+    }
+
+    private native void _set_authorizer(Authorizer auth);
+
+    /**
+     * Set trace function. Only available in SQLite 2.7.6 and above,
+     * otherwise a no-op.
+     *
+     * @param tr the trace function
+     */
+
+    public void trace(Trace tr) {
+    synchronized(this) {
+        _trace(tr);
+    }
+    }
+
+    private native void _trace(Trace tr);
+
+    /**
+     * Compile and return SQLite VM for SQL statement. Only available
+     * in SQLite 2.8.0 and above, otherwise a no-op.
+     *
+     * @param sql SQL statement to be compiled
+     * @return a Vm object
+     */
+
+    public Vm compile(String sql) throws SQLite.Exception {
+    synchronized(this) {
+        Vm vm = new Vm();
+        vm_compile(sql, vm);
+        return vm;
+    }
+    }
+
+    /**
+     * Compile and return SQLite VM for SQL statement. Only available
+     * in SQLite 3.0 and above, otherwise a no-op.
+     *
+     * @param sql SQL statement to be compiled
+     * @param args arguments for the SQL statement, '%q' substitution
+     * @return a Vm object
+     */
+
+    public Vm compile(String sql, String args[]) throws SQLite.Exception {
+    synchronized(this) {
+        Vm vm = new Vm();
+        vm_compile_args(sql, vm, args);
+        return vm;
+    }
+    }
+
+    /**
+     * Prepare and return SQLite3 statement for SQL. Only available
+     * in SQLite 3.0 and above, otherwise a no-op.
+     *
+     * @param sql SQL statement to be prepared
+     * @return a Stmt object
+     */
+
+    public Stmt prepare(String sql) throws SQLite.Exception {
+    synchronized(this) {
+        Stmt stmt = new Stmt();
+        stmt_prepare(sql, stmt);
+        return stmt;
+    }
+    }
+
+    /**
+     * Open an SQLite3 blob. Only available in SQLite 3.4.0 and above.
+     * @param db database name
+     * @param table table name
+     * @param column column name
+     * @param row row identifier
+     * @param rw if true, open for read-write, else read-only
+     * @return a Blob object
+     */
+
+    public Blob open_blob(String db, String table, String column,
+              long row, boolean rw) throws SQLite.Exception {
+    synchronized(this) {
+        Blob blob = new Blob();
+        _open_blob(db, table, column, row, rw, blob);
+        return blob;
+    }
+    }
+
+    /**
+     * Check type of open database.
+     * @return true if SQLite3 database
+     */
+
+    public native boolean is3();
+
+    /**
+     * Internal compile method.
+     * @param sql SQL statement
+     * @param vm Vm object
+     */
+
+    private native void vm_compile(String sql, Vm vm)
+    throws SQLite.Exception;
+
+    /**
+     * Internal compile method, SQLite 3.0 only.
+     * @param sql SQL statement
+     * @param args arguments for the SQL statement, '%q' substitution
+     * @param vm Vm object
+     */
+
+    private native void vm_compile_args(String sql, Vm vm, String args[])
+    throws SQLite.Exception;
+
+    /**
+     * Internal SQLite3 prepare method.
+     * @param sql SQL statement
+     * @param stmt Stmt object
+     */
+
+    private native void stmt_prepare(String sql, Stmt stmt)
+    throws SQLite.Exception;
+
+    /**
+     * Internal SQLite open blob method.
+     * @param db database name
+     * @param table table name
+     * @param column column name
+     * @param row row identifier
+     * @param rw if true, open for read-write, else read-only
+     * @param blob Blob object
+     */
+
+    private native void _open_blob(String db, String table, String column,
+                   long row, boolean rw, Blob blob)
+    throws SQLite.Exception;
+
+    /**
+     * Establish a progress callback method which gets called after
+     * N SQLite VM opcodes.
+     *
+     * @param n number of SQLite VM opcodes until callback is invoked
+     * @param p the object implementing the progress callback method
+     */
+
+    public void progress_handler(int n, SQLite.ProgressHandler p) {
+    synchronized(this) {
+        _progress_handler(n, p);
+    }
+    }
+
+    private native void _progress_handler(int n, SQLite.ProgressHandler p);
+
+    /**
+     * Internal native initializer.
+     */
+
+    private static native void internal_init();
+
+    /**
+     * Static initializer to load the native part.
+     */
+
+    static {
+        try {
+            String path = System.getProperty("SQLite.library.path");
+            if (path == null || path.length() == 0){
+                System.loadLibrary("sqlite_jni");
+            } else {
+                try {
+                    java.lang.reflect.Method mapLibraryName;
+                    Class param[] = new Class[1];
+                    param[0] = String.class;
+                    mapLibraryName = System.class.getMethod("mapLibraryName",
+                                                            param);
+                    Object args[] = new Object[1];
+                    args[0] = "sqlite_jni";
+                    String mapped = (String) mapLibraryName.invoke(null, args);
+                    System.load(path + java.io.File.separator + mapped);
+                } catch (Throwable t) {
+                    System.loadLibrary("sqlite_jni");
+                }
+            }
+            internal_init();
+        } catch (Throwable t) {
+            System.err.println("Unable to load sqlite: " + t);
+        }
+    }
+}
+
diff --git a/src/main/java/SQLite/Exception.java b/src/main/java/SQLite/Exception.java
new file mode 100644
index 0000000..cc26b99
--- /dev/null
+++ b/src/main/java/SQLite/Exception.java
@@ -0,0 +1,18 @@
+package SQLite;
+
+/**
+ * Class for SQLite related exceptions.
+ */
+
+public class Exception extends java.lang.Exception {
+
+    /**
+     * Construct a new SQLite exception.
+     *
+     * @param string error message
+     */
+
+    public Exception(String string) {
+    super(string);
+    }
+}
diff --git a/src/main/java/SQLite/Function.java b/src/main/java/SQLite/Function.java
new file mode 100644
index 0000000..5aa5e33
--- /dev/null
+++ b/src/main/java/SQLite/Function.java
@@ -0,0 +1,59 @@
+package SQLite;
+
+/**
+ * Callback interface for SQLite's user defined functions.
+ * Each callback method receives a
+ * <A HREF="FunctionContext.html">FunctionContext</A> object
+ * which is used to set the function result or error code.
+ * <BR><BR>
+ * Example:<BR>
+ *
+ * <PRE>
+ *   class SinFunc implements SQLite.Function {
+ *     public void function(SQLite.FunctionContext fc, String args[]) {
+ *       try {
+ *         Double d = new Double(args[0]);
+ *         fc.set_result(Math.sin(d.doubleValue()));
+ *       } catch (Exception e) {
+ *         fc.set_error("sin(" + args[0] + "):" + e);
+ *       }
+ *     }
+ *     ...
+ *   }
+ *   SQLite.Database db = new SQLite.Database();
+ *   db.open("db", 0);
+ *   db.create_function("sin", 1, SinFunc);
+ *   ...
+ *   db.exec("select sin(1.0) from test", null);
+ * </PRE>
+ */
+
+public interface Function {
+
+    /**
+     * Callback for regular function.
+     *
+     * @param fc function's context for reporting result
+     * @param args String array of arguments
+     */
+
+    public void function(FunctionContext fc, String args[]);
+
+    /**
+     * Callback for one step in aggregate function.
+     *
+     * @param fc function's context for reporting result
+     * @param args String array of arguments
+     */
+
+    public void step(FunctionContext fc, String args[]);
+
+    /**
+     * Callback for final step in aggregate function.
+     *
+     * @param fc function's context for reporting result
+     */
+
+    public void last_step(FunctionContext fc);
+
+}
diff --git a/src/main/java/SQLite/FunctionContext.java b/src/main/java/SQLite/FunctionContext.java
new file mode 100644
index 0000000..d0b5182
--- /dev/null
+++ b/src/main/java/SQLite/FunctionContext.java
@@ -0,0 +1,82 @@
+package SQLite;
+
+/**
+ * Context for execution of SQLite's user defined functions.
+ * A reference to an instance of this class is passed to
+ * user defined functions.
+ */
+
+public class FunctionContext {
+
+    /**
+     * Internal handle for the native SQLite API.
+     */
+
+    private long handle = 0;
+
+    /**
+     * Set function result from string.
+     *
+     * @param r result string
+     */
+
+    public native void set_result(String r);
+
+    /**
+     * Set function result from integer.
+     *
+     * @param r result integer
+     */
+
+    public native void set_result(int r);
+
+    /**
+     * Set function result from double.
+     *
+     * @param r result double
+     */
+
+    public native void set_result(double r);
+
+    /**
+     * Set function result from error message.
+     *
+     * @param r result string (error message)
+     */
+
+    public native void set_error(String r);
+
+    /**
+     * Set function result from byte array.
+     * Only provided by SQLite3 databases.
+     *
+     * @param r result byte array
+     */
+
+    public native void set_result(byte[] r);
+
+    /**
+     * Set function result as empty blob given size.
+     * Only provided by SQLite3 databases.
+     *
+     * @param n size for empty blob
+     */
+
+    public native void set_result_zeroblob(int n);
+
+    /**
+     * Retrieve number of rows for aggregate function.
+     */
+
+    public native int count();
+
+    /**
+     * Internal native initializer.
+     */
+
+    private static native void internal_init();
+
+    static {
+    internal_init();
+    }
+}
diff --git a/src/main/java/SQLite/JDBC2y/JDBCConnection.java b/src/main/java/SQLite/JDBC2y/JDBCConnection.java
new file mode 100644
index 0000000..20c98e3
--- /dev/null
+++ b/src/main/java/SQLite/JDBC2y/JDBCConnection.java
@@ -0,0 +1,452 @@
+package SQLite.JDBC2y;
+
+import java.sql.*;
+import java.util.*;
+
+public class JDBCConnection
+    implements java.sql.Connection, SQLite.BusyHandler {
+
+    /**
+     * Open database.
+     */
+    protected DatabaseX db;
+
+    /**
+     * Database URL.
+     */
+    protected String url;
+
+    /**
+     * Character encoding.
+     */
+    protected String enc;
+
+    /**
+     * Autocommit flag, true means autocommit.
+     */
+    protected boolean autocommit = true;
+
+    /**
+     * In-transaction flag.
+     * Can be true only when autocommit false.
+     */
+    protected boolean intrans = false;
+
+    /**
+     * Timeout for Database.exec()
+     */
+    protected int timeout = 1000000;
+
+    /**
+     * File name of database.
+     */
+    private String dbfile = null;
+
+    /**
+     * Reference to meta data or null.
+     */
+    private JDBCDatabaseMetaData meta = null;
+
+    /**
+     * Base time value for timeout handling.
+     */
+    private long t0;
+
+    /**
+     * Database in readonly mode.
+     */
+    private boolean readonly = false;
+
+
+    private boolean busy0(DatabaseX db, int count) {
+    if (count <= 1) {
+        t0 = System.currentTimeMillis();
+    }
+    if (db != null) {
+        long t1 = System.currentTimeMillis();
+        if (t1 - t0 > timeout) {
+        return false;
+        }
+        db.wait(100);
+        return true;
+    }
+    return false;
+    }
+
+    public boolean busy(String table, int count) {
+    return busy0(db, count);
+    }
+
+    protected boolean busy3(DatabaseX db, int count) {
+    if (count <= 1) {
+        t0 = System.currentTimeMillis();
+    }
+    if (db != null) {
+        long t1 = System.currentTimeMillis();
+        if (t1 - t0 > timeout) {
+        return false;
+        }
+        return true;
+    }
+    return false;
+    }
+
+    private DatabaseX open(boolean readonly) throws SQLException {
+    DatabaseX db = null;
+    try {
+        db = new DatabaseX();
+        db.open(dbfile, readonly ? 0444 : 0644);
+        db.set_encoding(enc);
+    } catch (SQLite.Exception e) {
+        throw new SQLException(e.toString());
+    }
+    int loop = 0;
+    while (true) {
+        try {
+        db.exec("PRAGMA short_column_names = off;", null);
+        db.exec("PRAGMA full_column_names = on;", null);
+        db.exec("PRAGMA empty_result_callbacks = on;", null);
+        if (SQLite.Database.version().compareTo("2.6.0") >= 0) {
+            db.exec("PRAGMA show_datatypes = on;", null);
+        }
+        } catch (SQLite.Exception e) {
+        if (db.last_error() != SQLite.Constants.SQLITE_BUSY ||
+            !busy0(db, ++loop)) {
+            try {
+            db.close();
+            } catch (SQLite.Exception ee) {
+            }
+            throw new SQLException(e.toString());
+        }
+        continue;
+        }
+        break;
+    }
+    return db;
+    }
+
+    public JDBCConnection(String url, String enc) throws SQLException {
+    if (url.startsWith("sqlite:/")) {
+        dbfile = url.substring(8);
+    } else if (url.startsWith("jdbc:sqlite:/")) {
+        dbfile = url.substring(13);
+    } else {
+        throw new SQLException("unsupported url");
+    }
+    this.url = url;
+    this.enc = enc;
+    try {
+        db = open(readonly);
+        db.busy_handler(this);
+    } catch (SQLException e) {
+        if (db != null) {
+        try {
+            db.close();
+        } catch (SQLite.Exception ee) {
+        }
+        }
+        throw e;
+    }
+    }
+
+    /* non-standard */
+    public SQLite.Database getSQLiteDatabase() {
+    return (SQLite.Database) db;
+    }
+  
+    public Statement createStatement() {
+    JDBCStatement s = new JDBCStatement(this);
+    return s;
+    }
+  
+    public Statement createStatement(int resultSetType,
+                     int resultSetConcurrency)
+    throws SQLException {
+    JDBCStatement s = new JDBCStatement(this);
+    return s;
+    }
+    
+    public DatabaseMetaData getMetaData() throws SQLException {
+    if (meta == null) {
+        meta = new JDBCDatabaseMetaData(this);
+    }
+    return meta;
+    }
+
+    public void close() throws SQLException {
+    try {
+        rollback();
+    } catch (SQLException e) {
+        /* ignored */
+    }
+    intrans = false;
+    if (db != null) {
+        try {
+        db.close();
+        db = null;
+        } catch (SQLite.Exception e) {
+        throw new SQLException(e.toString());
+        }
+    }
+    }
+
+    public boolean isClosed() throws SQLException {
+    return db == null;
+    }
+
+    public boolean isReadOnly() throws SQLException {
+    return readonly;
+    }
+
+    public void clearWarnings() throws SQLException {
+    }
+
+    public void commit() throws SQLException {
+    if (db == null) {
+        throw new SQLException("stale connection");
+    }
+    if (!intrans) {
+        return;
+    }
+    try {
+        db.exec("COMMIT", null);
+        intrans = false;
+    } catch (SQLite.Exception e) {
+        throw new SQLException(e.toString());
+    }
+    }
+
+    public boolean getAutoCommit() throws SQLException {
+    return autocommit;
+    }
+
+    public String getCatalog() throws SQLException {
+    return null;
+    }
+
+    public int getTransactionIsolation() throws SQLException {
+    return TRANSACTION_SERIALIZABLE;
+    }
+
+    public SQLWarning getWarnings() throws SQLException {
+    return null;
+    }
+
+    public String nativeSQL(String sql) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public CallableStatement prepareCall(String sql) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public CallableStatement prepareCall(String sql, int x, int y)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public PreparedStatement prepareStatement(String sql) throws SQLException {
+    JDBCPreparedStatement s = new JDBCPreparedStatement(this, sql);
+    return s;
+    }
+
+    public PreparedStatement prepareStatement(String sql, int resultSetType,
+                          int resultSetConcurrency)
+    throws SQLException {
+    JDBCPreparedStatement s = new JDBCPreparedStatement(this, sql);
+    return s;
+    }
+
+    public void rollback() throws SQLException {
+    if (db == null) {
+        throw new SQLException("stale connection");
+    }
+    if (!intrans) {
+        return;
+    }
+    try {
+        db.exec("ROLLBACK", null);
+        intrans = false;
+    } catch (SQLite.Exception e) {
+        throw new SQLException(e.toString());
+    }
+    }
+
+    public void setAutoCommit(boolean ac) throws SQLException {
+    if (ac && intrans && db != null) {
+        try {
+        db.exec("ROLLBACK", null);
+        } catch (SQLite.Exception e) {
+        throw new SQLException(e.toString());
+        }
+    }
+    intrans = false;
+    autocommit = ac;
+    }
+
+    public void setCatalog(String catalog) throws SQLException {
+    }
+
+    public void setReadOnly(boolean ro) throws SQLException {
+    if (intrans) {
+        throw new SQLException("incomplete transaction");
+    }
+    if (ro != readonly) {
+        DatabaseX db = null;
+        try {
+        db = open(ro);
+        this.db.close();
+        this.db = db;
+        db = null;
+        readonly = ro;
+        } catch (SQLException e) {
+        throw e;
+        } catch (SQLite.Exception ee) {
+        if (db != null) {
+            try {
+            db.close();
+            } catch (SQLite.Exception eee) {
+            }
+        }
+        throw new SQLException(ee.toString());
+        }
+    }
+    }
+
+    public void setTransactionIsolation(int level) throws SQLException {
+    if (level != TRANSACTION_SERIALIZABLE) {
+        throw new SQLException("not supported");
+    }
+    }
+
+    public java.util.Map<String, Class<?>> getTypeMap() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setTypeMap(java.util.Map map) throws SQLException {
+    throw new SQLException("not supported");
+    }
+  
+    public int getHoldability() throws SQLException {
+    return ResultSet.HOLD_CURSORS_OVER_COMMIT;
+    }
+
+    public void setHoldability(int holdability) throws SQLException {
+    if (holdability == ResultSet.HOLD_CURSORS_OVER_COMMIT) {
+        return;
+    }
+    throw new SQLException("not supported");
+    }
+
+    public Savepoint setSavepoint() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public Savepoint setSavepoint(String name) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void rollback(Savepoint x) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void releaseSavepoint(Savepoint x) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public Statement createStatement(int resultSetType,
+                     int resultSetConcurrency,
+                     int resultSetHoldability)
+    throws SQLException {
+    if (resultSetHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
+        throw new SQLException("not supported");
+    }
+    return createStatement(resultSetType, resultSetConcurrency);
+    }
+
+    public PreparedStatement prepareStatement(String sql, int resultSetType,
+                          int resultSetConcurrency,
+                          int resultSetHoldability)
+    throws SQLException {
+    if (resultSetHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
+        throw new SQLException("not supported");
+    }
+    return prepareStatement(sql, resultSetType, resultSetConcurrency);
+    }
+
+    public CallableStatement prepareCall(String sql, int x, int y, int z)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public PreparedStatement prepareStatement(String sql, int autokeys)
+    throws SQLException {
+    if (autokeys != Statement.NO_GENERATED_KEYS) {
+        throw new SQLException("not supported");
+    }
+    return prepareStatement(sql);
+    }
+
+    public PreparedStatement prepareStatement(String sql, int colIndexes[])
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public PreparedStatement prepareStatement(String sql, String columns[])
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+}
+
+class DatabaseX extends SQLite.Database {
+
+    static Object lock = new Object();
+
+    public DatabaseX() {
+    super();
+    }
+
+    void wait(int ms) {
+    try {
+        synchronized (lock) {
+        lock.wait(ms);
+        }
+    } catch (java.lang.Exception e) {
+    }
+    }
+
+    public void exec(String sql, SQLite.Callback cb)
+    throws SQLite.Exception {
+    super.exec(sql, cb);
+    synchronized (lock) {
+        lock.notifyAll();
+    }
+    }
+
+    public void exec(String sql, SQLite.Callback cb, String args[])
+    throws SQLite.Exception {
+    super.exec(sql, cb, args);
+    synchronized (lock) {
+        lock.notifyAll();
+    }
+    }
+
+    public SQLite.TableResult get_table(String sql, String args[])
+    throws SQLite.Exception {
+    SQLite.TableResult ret = super.get_table(sql, args);
+    synchronized (lock) {
+        lock.notifyAll();
+    }
+    return ret;
+    }
+
+    public void get_table(String sql, String args[], SQLite.TableResult tbl)
+    throws SQLite.Exception {
+    super.get_table(sql, args, tbl);
+    synchronized (lock) {
+        lock.notifyAll();
+    }
+    }
+
+}
diff --git a/src/main/java/SQLite/JDBC2y/JDBCDatabaseMetaData.java b/src/main/java/SQLite/JDBC2y/JDBCDatabaseMetaData.java
new file mode 100644
index 0000000..8c14d1d
--- /dev/null
+++ b/src/main/java/SQLite/JDBC2y/JDBCDatabaseMetaData.java
@@ -0,0 +1,1578 @@
+package SQLite.JDBC2y;
+
+import java.sql.*;
+import java.util.Hashtable;
+
+public class JDBCDatabaseMetaData implements DatabaseMetaData {
+
+    private JDBCConnection conn;
+
+    public JDBCDatabaseMetaData(JDBCConnection conn) {
+    this.conn = conn;
+    }
+
+    public boolean allProceduresAreCallable() throws SQLException {
+    return false;
+    }
+
+    public boolean allTablesAreSelectable() throws SQLException {
+    return true;
+    }
+
+    public String getURL() throws SQLException {
+    return conn.url;
+    }
+
+    public String getUserName() throws SQLException {
+    return "";
+    }
+
+    public boolean isReadOnly() throws SQLException {
+    return false;
+    }
+
+    public boolean nullsAreSortedHigh() throws SQLException {
+    return false;
+    }
+
+    public boolean nullsAreSortedLow() throws SQLException {
+    return false;
+    }
+
+    public boolean nullsAreSortedAtStart() throws SQLException {
+    return false;
+    }
+
+    public boolean nullsAreSortedAtEnd() throws SQLException {
+    return false;
+    }
+
+    public String getDatabaseProductName() throws SQLException {
+    return "SQLite";
+    }
+
+    public String getDatabaseProductVersion() throws SQLException {
+    return SQLite.Database.version();
+    }
+
+    public String getDriverName() throws SQLException {
+    return "SQLite/JDBC";
+    }
+
+    public String getDriverVersion() throws SQLException {
+    return "" + SQLite.JDBCDriver.MAJORVERSION + "." +
+        SQLite.JDBCDriver.MINORVERSION;
+    }
+
+    public int getDriverMajorVersion() {
+    return SQLite.JDBCDriver.MAJORVERSION;
+    }
+
+    public int getDriverMinorVersion() {
+    return SQLite.JDBCDriver.MINORVERSION;
+    }
+
+    public boolean usesLocalFiles() throws SQLException {
+    return true;
+    }
+
+    public boolean usesLocalFilePerTable() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsMixedCaseIdentifiers() throws SQLException {
+    return false;
+    }
+
+    public boolean storesUpperCaseIdentifiers() throws SQLException {
+    return false;
+    }
+
+    public boolean storesLowerCaseIdentifiers() throws SQLException {
+    return false;
+    }
+
+    public boolean storesMixedCaseIdentifiers() throws SQLException {
+    return true;
+    }
+
+    public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException {
+    return false;
+    }
+
+    public boolean storesUpperCaseQuotedIdentifiers() throws SQLException {
+    return false;
+    }
+
+    public boolean storesLowerCaseQuotedIdentifiers() throws SQLException {
+    return false;
+    }
+
+    public boolean storesMixedCaseQuotedIdentifiers() throws SQLException {
+    return true;
+    }
+
+    public String getIdentifierQuoteString() throws SQLException {
+    return "\"";
+    }
+
+    public String getSQLKeywords() throws SQLException {
+    return "SELECT,UPDATE,CREATE,TABLE,VIEW,DELETE,FROM,WHERE" +
+        ",COMMIT,ROLLBACK,TRIGGER";
+    }
+
+    public String getNumericFunctions() throws SQLException {
+    return ""; 
+    }
+
+    public String getStringFunctions() throws SQLException {
+    return "";
+    }
+
+    public String getSystemFunctions() throws SQLException {
+    return "";
+    }
+
+    public String getTimeDateFunctions() throws SQLException {
+    return "";
+    }
+
+    public String getSearchStringEscape() throws SQLException {
+    return "\\";
+    }
+
+    public String getExtraNameCharacters() throws SQLException {
+    return "";
+    }
+
+    public boolean supportsAlterTableWithAddColumn() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsAlterTableWithDropColumn() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsColumnAliasing() throws SQLException {
+    return true;
+    }
+
+    public boolean nullPlusNonNullIsNull() throws SQLException {
+    return false;
+    }
+    
+    public boolean supportsConvert() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsConvert(int fromType, int toType)
+    throws SQLException {
+    return false;
+    }
+
+    public boolean supportsTableCorrelationNames() throws SQLException {
+    return true;
+    }
+
+    public boolean supportsDifferentTableCorrelationNames()
+    throws SQLException {
+    return false;
+    }
+
+    public boolean supportsExpressionsInOrderBy() throws SQLException {
+    return true;
+    }
+
+    public boolean supportsOrderByUnrelated() throws SQLException {
+    return true;
+    }
+
+    public boolean supportsGroupBy() throws SQLException {
+    return true;
+    }
+
+    public boolean supportsGroupByUnrelated() throws SQLException {
+    return true;
+    }
+
+    public boolean supportsGroupByBeyondSelect() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsLikeEscapeClause() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsMultipleResultSets() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsMultipleTransactions() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsNonNullableColumns() throws SQLException {
+    return true;
+    }
+
+    public boolean supportsMinimumSQLGrammar() throws SQLException {
+    return true;
+    } 
+
+    public boolean supportsCoreSQLGrammar() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsExtendedSQLGrammar() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsANSI92EntryLevelSQL() throws SQLException {
+    return true;
+    }
+
+    public boolean supportsANSI92IntermediateSQL() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsANSI92FullSQL() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsIntegrityEnhancementFacility()
+    throws SQLException {
+    return false;
+    }
+
+    public boolean supportsOuterJoins() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsFullOuterJoins() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsLimitedOuterJoins() throws SQLException {
+    return false;
+    }
+
+    public String getSchemaTerm() throws SQLException {
+    return "";
+    }
+
+    public String getProcedureTerm() throws SQLException {
+    return "";
+    }
+
+    public String getCatalogTerm() throws SQLException {
+    return "";
+    }
+
+    public boolean isCatalogAtStart() throws SQLException {
+    return false;
+    }
+
+    public String getCatalogSeparator() throws SQLException {
+    return "";
+    }
+
+    public boolean supportsSchemasInDataManipulation() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsSchemasInProcedureCalls() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsSchemasInTableDefinitions() throws SQLException {
+    return false;
+    }
+    
+    public boolean supportsSchemasInIndexDefinitions() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsSchemasInPrivilegeDefinitions()
+    throws SQLException {
+    return false;
+    }
+
+    public boolean supportsCatalogsInDataManipulation() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsCatalogsInProcedureCalls() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsCatalogsInTableDefinitions() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsCatalogsInIndexDefinitions() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsCatalogsInPrivilegeDefinitions()
+    throws SQLException {
+    return false;
+    }
+
+    public boolean supportsPositionedDelete() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsPositionedUpdate() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsSelectForUpdate() throws SQLException {
+    return true;
+    }
+
+    public boolean supportsStoredProcedures() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsSubqueriesInComparisons() throws SQLException {
+    return true;
+    }
+
+    public boolean supportsSubqueriesInExists() throws SQLException {
+    return true;
+    }
+
+    public boolean supportsSubqueriesInIns() throws SQLException {
+    return true;
+    }
+
+    public boolean supportsSubqueriesInQuantifieds() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsCorrelatedSubqueries() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsUnion() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsUnionAll() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsOpenCursorsAcrossCommit() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsOpenCursorsAcrossRollback() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsOpenStatementsAcrossCommit() throws SQLException {
+    return false;
+    }
+
+    public boolean supportsOpenStatementsAcrossRollback() throws SQLException {
+    return false;
+    }
+
+    public int getMaxBinaryLiteralLength() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxCharLiteralLength() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxColumnNameLength() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxColumnsInGroupBy() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxColumnsInIndex() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxColumnsInOrderBy() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxColumnsInSelect() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxColumnsInTable() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxConnections() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxCursorNameLength() throws SQLException {
+    return 8;
+    }
+
+    public int getMaxIndexLength() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxSchemaNameLength() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxProcedureNameLength() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxCatalogNameLength() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxRowSize() throws SQLException {
+    return 0;
+    }
+
+    public boolean doesMaxRowSizeIncludeBlobs() throws SQLException {
+    return true;
+    }
+
+    public int getMaxStatementLength() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxStatements() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxTableNameLength() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxTablesInSelect() throws SQLException {
+    return 0;
+    }
+
+    public int getMaxUserNameLength() throws SQLException {
+    return 0;
+    }
+
+    public int getDefaultTransactionIsolation() throws SQLException {
+    return Connection.TRANSACTION_SERIALIZABLE;
+    }
+
+    public boolean supportsTransactions() throws SQLException {
+    return true;
+    }
+
+    public boolean supportsTransactionIsolationLevel(int level)
+    throws SQLException {
+    return level == Connection.TRANSACTION_SERIALIZABLE;
+    }
+
+    public boolean supportsDataDefinitionAndDataManipulationTransactions()
+    throws SQLException {
+    return true;
+    }
+
+    public boolean supportsDataManipulationTransactionsOnly()
+    throws SQLException {
+    return false;
+    }
+
+    public boolean dataDefinitionCausesTransactionCommit()
+    throws SQLException {
+    return false;
+    }
+
+    public boolean dataDefinitionIgnoredInTransactions() throws SQLException {
+    return false;
+    }
+
+    public ResultSet getProcedures(String catalog, String schemaPattern,
+                   String procedureNamePattern)
+    throws SQLException {
+    return null;
+    }
+
+    public ResultSet getProcedureColumns(String catalog,
+                     String schemaPattern,
+                     String procedureNamePattern, 
+                     String columnNamePattern)
+    throws SQLException {
+    return null;
+    }
+
+    public ResultSet getTables(String catalog, String schemaPattern,
+                   String tableNamePattern, String types[])
+    throws SQLException {
+    JDBCStatement s = new JDBCStatement(conn);
+    StringBuffer sb = new StringBuffer();
+    sb.append("SELECT '' AS 'TABLE_CAT', " +
+          "'' AS 'TABLE_SCHEM', " +
+          "tbl_name AS 'TABLE_NAME', " +
+          "upper(type) AS 'TABLE_TYPE', " +
+          "'' AS REMARKS FROM sqlite_master " +
+          "WHERE tbl_name like ");
+    if (tableNamePattern != null) {
+        sb.append(SQLite.Shell.sql_quote(tableNamePattern));
+    } else {
+        sb.append("'%'");
+    }
+    sb.append(" AND ");
+    if (types == null || types.length == 0) {
+        sb.append("(type = 'table' or type = 'view')");
+    } else {
+        sb.append("(");
+        String sep = ""; 
+        for (int i = 0; i < types.length; i++) {
+        sb.append(sep);
+        sb.append("type = ");
+        sb.append(SQLite.Shell.sql_quote(types[i].toLowerCase()));
+        sep = " or ";
+        }
+        sb.append(")");
+    }
+    ResultSet rs = null;
+    try {
+        rs = s.executeQuery(sb.toString());
+        s.close();
+    } catch (SQLException e) {
+        throw e;
+    } finally {
+        s.close();
+    }
+    return rs;
+    }
+
+    public ResultSet getSchemas() throws SQLException {
+    String cols[] = { "TABLE_SCHEM" };
+    SQLite.TableResult tr = new SQLite.TableResult();
+    tr.columns(cols);
+    String row[] = { "" };
+    tr.newrow(row);
+    JDBCResultSet rs = new JDBCResultSet(tr, null);
+    return (ResultSet) rs;
+    }
+
+    public ResultSet getCatalogs() throws SQLException {
+    String cols[] = { "TABLE_CAT" };
+    SQLite.TableResult tr = new SQLite.TableResult();
+    tr.columns(cols);
+    String row[] = { "" };
+    tr.newrow(row);
+    JDBCResultSet rs = new JDBCResultSet(tr, null);
+    return (ResultSet) rs;
+    }
+
+    public ResultSet getTableTypes() throws SQLException {
+    String cols[] = { "TABLE_TYPE" };
+    SQLite.TableResult tr = new SQLite.TableResult();
+    tr.columns(cols);
+    String row[] = new String[1];
+    row[0] = "TABLE";
+    tr.newrow(row);
+    row = new String[1];
+    row[0] = "VIEW";
+    tr.newrow(row);
+    JDBCResultSet rs = new JDBCResultSet(tr, null);
+    return (ResultSet) rs;
+    }
+
+    public ResultSet getColumns(String catalog, String schemaPattern,
+                String tableNamePattern,
+                String columnNamePattern)
+    throws SQLException {
+    JDBCStatement s = new JDBCStatement(conn);
+    JDBCResultSet rs0 = null;
+    try {
+        rs0 = (JDBCResultSet)
+        (s.executeQuery("PRAGMA table_info(" +
+                SQLite.Shell.sql_quote(tableNamePattern) +
+                ")"));
+        s.close();
+    } catch (SQLException e) {
+        throw e;
+    } finally {
+        s.close();
+    }
+    String cols[] = {
+        "TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME",
+        "COLUMN_NAME", "DATA_TYPE", "TYPE_NAME",
+        "COLUMN_SIZE", "BUFFER_LENGTH", "DECIMAL_POINTS",
+        "NUM_PREC_RADIX", "NULLABLE", "REMARKS",
+        "COLUMN_DEF", "SQL_DATA_TYPE", "SQL_DATETIME_SUB",
+        "CHAR_OCTET_LENGTH", "ORDINAL_POSITION", "IS_NULLABLE"
+    };
+    int types[] = {
+        Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+        Types.VARCHAR, Types.SMALLINT, Types.VARCHAR,
+        Types.INTEGER, Types.INTEGER, Types.INTEGER,
+        Types.INTEGER, Types.INTEGER, Types.VARCHAR,
+        Types.VARCHAR, Types.INTEGER, Types.INTEGER,
+        Types.INTEGER, Types.INTEGER, Types.VARCHAR
+    };
+    TableResultX tr = new TableResultX();
+    tr.columns(cols);
+    tr.sql_types(types);
+    JDBCResultSet rs = new JDBCResultSet((SQLite.TableResult) tr, null);
+    if (rs0 != null && rs0.tr != null && rs0.tr.nrows > 0) {
+        Hashtable<String, Integer> h = new Hashtable<String, Integer>();
+        for (int i = 0; i < rs0.tr.ncolumns; i++) {
+        h.put(rs0.tr.column[i], new Integer(i));
+        }
+        if (columnNamePattern != null &&
+        columnNamePattern.charAt(0) == '%') {
+        columnNamePattern = null;
+        }
+        for (int i = 0; i < rs0.tr.nrows; i++) {
+        String r0[] = (String [])(rs0.tr.rows.elementAt(i));
+        int col = ((Integer) h.get("name")).intValue();
+        if (columnNamePattern != null) {
+            if (r0[col].compareTo(columnNamePattern) != 0) {
+            continue;
+            }
+        }
+        String row[] = new String[cols.length];
+        row[0]  = "";
+        row[1]  = "";
+        row[2]  = tableNamePattern;
+        row[3]  = r0[col];
+        col = ((Integer) h.get("type")).intValue();
+        String typeStr = r0[col];
+        int type = mapSqlType(typeStr);
+        row[4]  = "" + type;
+        row[5]  = mapTypeName(type);
+        row[6]  = "" + getD(typeStr, type);
+        row[7]  = "" + getM(typeStr, type);
+        row[8]  = "10";
+        row[9]  = "0";
+        row[11] = null;
+        col = ((Integer) h.get("dflt_value")).intValue();
+        row[12] = r0[col];
+        row[13] = "0";
+        row[14] = "0";
+        row[15] = "65536";
+        col = ((Integer) h.get("cid")).intValue();
+        Integer cid = new Integer(r0[col]);
+        row[16] = "" + (cid.intValue() + 1);
+        col = ((Integer) h.get("notnull")).intValue();
+        row[17] = (r0[col].charAt(0) == '0') ? "YES" : "NO";
+        row[10] = (r0[col].charAt(0) == '0') ? "" + columnNullable :
+              "" + columnNoNulls;
+        tr.newrow(row);
+        }
+    }
+    return rs;
+    }
+
+    public ResultSet getColumnPrivileges(String catalog, String schema,
+                     String table,
+                     String columnNamePattern)
+    throws SQLException {
+    String cols[] = {
+        "TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME",
+        "COLUMN_NAME", "GRANTOR", "GRANTEE",
+        "PRIVILEGE", "IS_GRANTABLE"
+    };
+    int types[] = {
+        Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+        Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+        Types.VARCHAR, Types.VARCHAR
+    };
+    TableResultX tr = new TableResultX();
+    tr.columns(cols);
+    tr.sql_types(types);
+    JDBCResultSet rs = new JDBCResultSet((SQLite.TableResult) tr, null);
+    return rs;
+    }
+
+    public ResultSet getTablePrivileges(String catalog, String schemaPattern,
+                    String tableNamePattern)
+    throws SQLException {
+    String cols[] = {
+        "TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME",
+        "COLUMN_NAME", "GRANTOR", "GRANTEE",
+        "PRIVILEGE", "IS_GRANTABLE"
+    };
+    int types[] = {
+        Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+        Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+        Types.VARCHAR, Types.VARCHAR
+    };
+    TableResultX tr = new TableResultX();
+    tr.columns(cols);
+    tr.sql_types(types);
+    JDBCResultSet rs = new JDBCResultSet((SQLite.TableResult) tr, null);
+    return rs;
+    }
+
+    public ResultSet getBestRowIdentifier(String catalog, String schema,
+                      String table, int scope,
+                      boolean nullable)
+    throws SQLException {
+    JDBCStatement s0 = new JDBCStatement(conn);
+    JDBCResultSet rs0 = null;
+    JDBCStatement s1 = new JDBCStatement(conn);
+    JDBCResultSet rs1 = null;
+    try {
+        rs0 = (JDBCResultSet)
+        (s0.executeQuery("PRAGMA index_list(" +
+                 SQLite.Shell.sql_quote(table) + ")"));
+        rs1 = (JDBCResultSet)
+        (s1.executeQuery("PRAGMA table_info(" +
+                 SQLite.Shell.sql_quote(table) + ")"));
+    } catch (SQLException e) {
+        throw e;
+    } finally {
+        s0.close();
+        s1.close();
+    }
+    String cols[] = {
+        "SCOPE", "COLUMN_NAME", "DATA_TYPE",
+        "TYPE_NAME", "COLUMN_SIZE", "BUFFER_LENGTH",
+        "DECIMAL_DIGITS", "PSEUDO_COLUMN"
+    };
+    int types[] = {
+        Types.SMALLINT, Types.VARCHAR, Types.SMALLINT,
+        Types.VARCHAR, Types.INTEGER, Types.INTEGER,
+        Types.SMALLINT, Types.SMALLINT
+    };
+    TableResultX tr = new TableResultX();
+    tr.columns(cols);
+    tr.sql_types(types);
+    JDBCResultSet rs = new JDBCResultSet((SQLite.TableResult) tr, null);
+    if (rs0 != null && rs0.tr != null && rs0.tr.nrows > 0 &&
+        rs1 != null && rs1.tr != null && rs1.tr.nrows > 0) {
+        Hashtable<String, Integer> h0 = new Hashtable<String, Integer>();
+        for (int i = 0; i < rs0.tr.ncolumns; i++) {
+        h0.put(rs0.tr.column[i], new Integer(i));
+        }
+        Hashtable<String, Integer> h1 = new Hashtable<String, Integer>();
+        for (int i = 0; i < rs1.tr.ncolumns; i++) {
+        h1.put(rs1.tr.column[i], new Integer(i));
+        }
+        for (int i = 0; i < rs0.tr.nrows; i++) {
+        String r0[] = (String [])(rs0.tr.rows.elementAt(i));
+        int col = ((Integer) h0.get("unique")).intValue();
+        String uniq = r0[col];
+        col = ((Integer) h0.get("name")).intValue();
+        String iname = r0[col];
+        if (uniq.charAt(0) == '0') {
+            continue;
+        }
+        JDBCStatement s2 = new JDBCStatement(conn);
+        JDBCResultSet rs2 = null;
+        try {
+            rs2 = (JDBCResultSet)
+            (s2.executeQuery("PRAGMA index_info(" +
+                     SQLite.Shell.sql_quote(iname) + ")"));
+        } catch (SQLException e) {
+        } finally {
+            s2.close();
+        }
+        if (rs2 == null || rs2.tr == null || rs2.tr.nrows <= 0) {
+            continue;
+        }
+        Hashtable<String, Integer> h2 =
+            new Hashtable<String, Integer>();
+        for (int k = 0; k < rs2.tr.ncolumns; k++) {
+            h2.put(rs2.tr.column[k], new Integer(k));
+        }
+        for (int k = 0; k < rs2.tr.nrows; k++) {
+            String r2[] = (String [])(rs2.tr.rows.elementAt(k));
+            col = ((Integer) h2.get("name")).intValue();
+            String cname = r2[col];
+            for (int m = 0; m < rs1.tr.nrows; m++) {
+            String r1[] = (String [])(rs1.tr.rows.elementAt(m));
+            col = ((Integer) h1.get("name")).intValue();
+            if (cname.compareTo(r1[col]) == 0) {
+                String row[] = new String[cols.length];
+                row[0] = "" + scope;
+                row[1] = cname;
+                row[2] = "" + Types.VARCHAR;
+                row[3] = "VARCHAR";
+                row[4] = "65536";
+                row[5] = "0";
+                row[6] = "0";
+                row[7] = "" + bestRowNotPseudo;
+                tr.newrow(row);
+            }
+            }
+        }
+        }
+    }
+    if (tr.nrows <= 0) {
+        String row[] = new String[cols.length];
+        row[0] = "" + scope;
+        row[1] = "_ROWID_";
+        row[2] = "" + Types.INTEGER;
+        row[3] = "INTEGER";
+        row[4] = "10";
+        row[5] = "0";
+        row[6] = "0";
+        row[7] = "" + bestRowPseudo;
+        tr.newrow(row);
+    }
+    return rs;
+    }
+
+    public ResultSet getVersionColumns(String catalog, String schema,
+                       String table) throws SQLException {
+    String cols[] = {
+        "SCOPE", "COLUMN_NAME", "DATA_TYPE",
+        "TYPE_NAME", "COLUMN_SIZE", "BUFFER_LENGTH",
+        "DECIMAL_DIGITS", "PSEUDO_COLUMN"
+    };
+    int types[] = {
+        Types.SMALLINT, Types.VARCHAR, Types.SMALLINT,
+        Types.VARCHAR, Types.INTEGER, Types.INTEGER,
+        Types.SMALLINT, Types.SMALLINT
+    };
+    TableResultX tr = new TableResultX();
+    tr.columns(cols);
+    tr.sql_types(types);
+    JDBCResultSet rs = new JDBCResultSet((SQLite.TableResult) tr, null);
+    return rs;
+    }
+
+    public ResultSet getPrimaryKeys(String catalog, String schema,
+                    String table) throws SQLException {
+    JDBCStatement s0 = new JDBCStatement(conn);
+    JDBCResultSet rs0 = null;
+    try {
+        rs0 = (JDBCResultSet)
+        (s0.executeQuery("PRAGMA index_list(" +
+                 SQLite.Shell.sql_quote(table) + ")"));
+    } catch (SQLException e) {
+        throw e;
+    } finally {
+        s0.close();
+    }
+    String cols[] = {
+        "TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME",
+        "COLUMN_NAME", "KEY_SEQ", "PK_NAME"
+    };
+    int types[] = {
+        Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+        Types.VARCHAR, Types.SMALLINT, Types.VARCHAR
+    };
+    TableResultX tr = new TableResultX();
+    tr.columns(cols);
+    tr.sql_types(types);
+    JDBCResultSet rs = new JDBCResultSet((SQLite.TableResult) tr, null);
+    if (rs0 != null && rs0.tr != null && rs0.tr.nrows > 0) {
+        Hashtable<String, Integer> h0 = new Hashtable<String, Integer>();
+        for (int i = 0; i < rs0.tr.ncolumns; i++) {
+        h0.put(rs0.tr.column[i], new Integer(i));
+        }
+        for (int i = 0; i < rs0.tr.nrows; i++) {
+        String r0[] = (String [])(rs0.tr.rows.elementAt(i));
+        int col = ((Integer) h0.get("unique")).intValue();
+        String uniq = r0[col];
+        col = ((Integer) h0.get("name")).intValue();
+        String iname = r0[col];
+        if (uniq.charAt(0) == '0') {
+            continue;
+        }
+        JDBCStatement s1 = new JDBCStatement(conn);
+        JDBCResultSet rs1 = null;
+        try {
+            rs1 = (JDBCResultSet)
+            (s1.executeQuery("PRAGMA index_info(" +
+                     SQLite.Shell.sql_quote(iname) + ")"));
+        } catch (SQLException e) {
+        } finally {
+            s1.close();
+        }
+        if (rs1 == null || rs1.tr == null || rs1.tr.nrows <= 0) {
+            continue;
+        }
+        Hashtable<String, Integer> h1 =
+            new Hashtable<String, Integer>();
+        for (int k = 0; k < rs1.tr.ncolumns; k++) {
+            h1.put(rs1.tr.column[k], new Integer(k));
+        }
+        for (int k = 0; k < rs1.tr.nrows; k++) {
+            String r1[] = (String [])(rs1.tr.rows.elementAt(k));
+            String row[] = new String[cols.length];
+            row[0]  = "";
+            row[1]  = "";
+            row[2]  = table;
+            col = ((Integer) h1.get("name")).intValue();
+            row[3] = r1[col];
+            col = ((Integer) h1.get("seqno")).intValue();
+// BEGIN android-changed
+            row[4]  = "" + (Integer.parseInt(r1[col]) + 1);
+// END android-changed
+            row[5]  = iname;
+            tr.newrow(row);
+        }
+        }
+    }
+    JDBCStatement s1 = new JDBCStatement(conn);
+    try {
+        rs0 = (JDBCResultSet)
+        (s1.executeQuery("PRAGMA table_info(" +
+                 SQLite.Shell.sql_quote(table) + ")"));
+    } catch (SQLException e) {
+        throw e;
+    } finally {
+        s1.close();
+    }
+    if (rs0 != null && rs0.tr != null && rs0.tr.nrows > 0) {
+        Hashtable<String, Integer> h0 = new Hashtable<String, Integer>();
+        for (int i = 0; i < rs0.tr.ncolumns; i++) {
+        h0.put(rs0.tr.column[i], new Integer(i));
+        }
+        for (int i = 0; i < rs0.tr.nrows; i++) {
+        String r0[] = (String [])(rs0.tr.rows.elementAt(i));
+        int col = ((Integer) h0.get("type")).intValue();
+        String type = r0[col];
+        if (!type.equalsIgnoreCase("integer")) {
+            continue;
+        }
+        col = ((Integer) h0.get("pk")).intValue();
+        String pk = r0[col];
+        if (pk.charAt(0) == '0') {
+            continue;
+        }
+        String row[] = new String[cols.length];
+        row[0]  = "";
+        row[1]  = "";
+        row[2]  = table;
+        col = ((Integer) h0.get("name")).intValue();
+        row[3] = r0[col];
+        col = ((Integer) h0.get("cid")).intValue();
+// BEGIN android-changed
+        row[4] = "" + (Integer.parseInt(r0[col]) + 1);
+// END android-changed
+        row[5] = "";
+        tr.newrow(row);
+        }
+    }
+    return rs;
+    }
+
+    private void internalImportedKeys(String table, String pktable,
+                      JDBCResultSet in, TableResultX out) {
+    Hashtable<String, Integer> h0 = new Hashtable<String, Integer>();
+    for (int i = 0; i < in.tr.ncolumns; i++) {
+        h0.put(in.tr.column[i], new Integer(i));
+    }
+    for (int i = 0; i < in.tr.nrows; i++) {
+        String r0[] = (String [])(in.tr.rows.elementAt(i));
+        int col = ((Integer) h0.get("table")).intValue();
+        String pktab = r0[col];
+        if (pktable != null && !pktable.equalsIgnoreCase(pktab)) {
+        continue;
+        }
+        col = ((Integer) h0.get("from")).intValue();
+        String pkcol = r0[col];
+        col = ((Integer) h0.get("to")).intValue();
+        String fkcol = r0[col];
+        col = ((Integer) h0.get("seq")).intValue();
+        String seq = r0[col];
+        String row[] = new String[out.ncolumns];
+        row[0]  = "";
+        row[1]  = "";
+        row[2]  = pktab;
+        row[3]  = pkcol;
+        row[4]  = "";
+        row[5]  = "";
+        row[6]  = table;
+        row[7]  = fkcol == null ? pkcol : fkcol;
+// BEGIN android-changed
+        row[8]  = "" + ((Integer.parseInt(seq)) + 1);
+// END android-changed
+        row[9]  =
+        "" + java.sql.DatabaseMetaData.importedKeyNoAction;
+        row[10] =
+        "" + java.sql.DatabaseMetaData.importedKeyNoAction;
+        row[11] = null;
+        row[12] = null;
+        row[13] =
+        "" + java.sql.DatabaseMetaData.importedKeyNotDeferrable;
+        out.newrow(row);
+    }
+    }
+
+    public ResultSet getImportedKeys(String catalog, String schema,
+                     String table) throws SQLException {
+    JDBCStatement s0 = new JDBCStatement(conn);
+    JDBCResultSet rs0 = null;
+    try {
+        rs0 = (JDBCResultSet)
+        (s0.executeQuery("PRAGMA foreign_key_list(" +
+                 SQLite.Shell.sql_quote(table) + ")"));
+    } catch (SQLException e) {
+        throw e;
+    } finally {
+        s0.close();
+    }
+    String cols[] = {
+        "PKTABLE_CAT", "PKTABLE_SCHEM", "PKTABLE_NAME",
+        "PKCOLUMN_NAME", "FKTABLE_CAT", "FKTABLE_SCHEM",
+        "FKTABLE_NAME", "FKCOLUMN_NAME", "KEY_SEQ",
+        "UPDATE_RULE", "DELETE_RULE", "FK_NAME",
+        "PK_NAME", "DEFERRABILITY"
+    };
+    int types[] = {
+        Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+        Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+        Types.VARCHAR, Types.VARCHAR, Types.SMALLINT,
+        Types.SMALLINT, Types.SMALLINT, Types.VARCHAR,
+        Types.VARCHAR, Types.SMALLINT
+    };
+    TableResultX tr = new TableResultX();
+    tr.columns(cols);
+    tr.sql_types(types);
+    JDBCResultSet rs = new JDBCResultSet((SQLite.TableResult) tr, null);
+    if (rs0 != null && rs0.tr != null && rs0.tr.nrows > 0) {
+        internalImportedKeys(table, null, rs0, tr);
+    }
+    return rs;
+    }
+
+    public ResultSet getExportedKeys(String catalog, String schema,
+                     String table) throws SQLException {
+    String cols[] = {
+        "PKTABLE_CAT", "PKTABLE_SCHEM", "PKTABLE_NAME",
+        "PKCOLUMN_NAME", "FKTABLE_CAT", "FKTABLE_SCHEM",
+        "FKTABLE_NAME", "FKCOLUMN_NAME", "KEY_SEQ",
+        "UPDATE_RULE", "DELETE_RULE", "FK_NAME",
+        "PK_NAME", "DEFERRABILITY"
+    };
+    int types[] = {
+        Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+        Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+        Types.VARCHAR, Types.VARCHAR, Types.SMALLINT,
+        Types.SMALLINT, Types.SMALLINT, Types.VARCHAR,
+        Types.VARCHAR, Types.SMALLINT
+    };
+    TableResultX tr = new TableResultX();
+    tr.columns(cols);
+    tr.sql_types(types);
+    JDBCResultSet rs = new JDBCResultSet(tr, null);
+    return rs;
+    }
+
+    public ResultSet getCrossReference(String primaryCatalog,
+                       String primarySchema,
+                       String primaryTable,
+                       String foreignCatalog,
+                       String foreignSchema,
+                       String foreignTable)
+    throws SQLException {
+    JDBCResultSet rs0 = null;
+    if (foreignTable != null && foreignTable.charAt(0) != '%') {
+        JDBCStatement s0 = new JDBCStatement(conn);
+        try {
+        rs0 = (JDBCResultSet)
+            (s0.executeQuery("PRAGMA foreign_key_list(" +
+                     SQLite.Shell.sql_quote(foreignTable) + ")"));
+        } catch (SQLException e) {
+        throw e;
+        } finally {
+        s0.close();
+        }
+    }
+    String cols[] = {
+        "PKTABLE_CAT", "PKTABLE_SCHEM", "PKTABLE_NAME",
+        "PKCOLUMN_NAME", "FKTABLE_CAT", "FKTABLE_SCHEM",
+        "FKTABLE_NAME", "FKCOLUMN_NAME", "KEY_SEQ",
+        "UPDATE_RULE", "DELETE_RULE", "FK_NAME",
+        "PK_NAME", "DEFERRABILITY"
+    };
+    int types[] = {
+        Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+        Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+        Types.VARCHAR, Types.VARCHAR, Types.SMALLINT,
+        Types.SMALLINT, Types.SMALLINT, Types.VARCHAR,
+        Types.VARCHAR, Types.SMALLINT
+    };
+    TableResultX tr = new TableResultX();
+    tr.columns(cols);
+    tr.sql_types(types);
+    JDBCResultSet rs = new JDBCResultSet(tr, null);
+    if (rs0 != null && rs0.tr != null && rs0.tr.nrows > 0) {
+        String pktable = null;
+        if (primaryTable != null && primaryTable.charAt(0) != '%') {
+        pktable = primaryTable;
+        }
+        internalImportedKeys(foreignTable, pktable, rs0, tr);
+    }
+    return rs;
+    }
+
+    public ResultSet getTypeInfo() throws SQLException {
+    String cols[] = {
+        "TYPE_NAME", "DATA_TYPE", "PRECISION",
+        "LITERAL_PREFIX", "LITERAL_SUFFIX", "CREATE_PARAMS",
+        "NULLABLE", "CASE_SENSITIVE", "SEARCHABLE",
+        "UNSIGNED_ATTRIBUTE", "FIXED_PREC_SCALE", "AUTO_INCREMENT",
+        "LOCAL_TYPE_NAME", "MINIMUM_SCALE", "MAXIMUM_SCALE",
+        "SQL_DATA_TYPE", "SQL_DATETIME_SUB", "NUM_PREC_RADIX"
+    };
+    int types[] = {
+        Types.VARCHAR, Types.SMALLINT, Types.INTEGER,
+        Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+        Types.SMALLINT, Types.BIT, Types.SMALLINT,
+        Types.BIT, Types.BIT, Types.BIT,
+        Types.VARCHAR, Types.SMALLINT, Types.SMALLINT,
+        Types.INTEGER, Types.INTEGER, Types.INTEGER
+    };
+    TableResultX tr = new TableResultX();
+    tr.columns(cols);
+    tr.sql_types(types);
+    JDBCResultSet rs = new JDBCResultSet(tr, null);
+    String row1[] = {
+        "VARCHAR", "" + Types.VARCHAR, "65536",
+        "'", "'", null,
+        "" + typeNullable, "1", "" + typeSearchable,
+        "0", "0", "0",
+        null, "0", "0",
+        "0", "0", "0"
+    };
+    tr.newrow(row1);
+    String row2[] = {
+        "INTEGER", "" + Types.INTEGER, "32",
+        null, null, null,
+        "" + typeNullable, "0", "" + typeSearchable,
+        "0", "0", "1",
+        null, "0", "0",
+        "0", "0", "2"
+    };
+    tr.newrow(row2);
+    String row3[] = {
+        "DOUBLE", "" + Types.DOUBLE, "16",
+        null, null, null,
+        "" + typeNullable, "0", "" + typeSearchable,
+        "0", "0", "1",
+        null, "0", "0",
+        "0", "0", "10"
+    };
+    tr.newrow(row3);
+    String row4[] = {
+        "FLOAT", "" + Types.FLOAT, "7",
+        null, null, null,
+        "" + typeNullable, "0", "" + typeSearchable,
+        "0", "0", "1",
+        null, "0", "0",
+        "0", "0", "10"
+    };
+    tr.newrow(row4);
+    String row5[] = {
+        "SMALLINT", "" + Types.SMALLINT, "16",
+        null, null, null,
+        "" + typeNullable, "0", "" + typeSearchable,
+        "0", "0", "1",
+        null, "0", "0",
+        "0", "0", "2"
+    };
+    tr.newrow(row5);
+    String row6[] = {
+        "BIT", "" + Types.BIT, "1",
+        null, null, null,
+        "" + typeNullable, "0", "" + typeSearchable,
+        "0", "0", "1",
+        null, "0", "0",
+        "0", "0", "2"
+    };
+    tr.newrow(row6);
+    String row7[] = {
+        "TIMESTAMP", "" + Types.TIMESTAMP, "30",
+        null, null, null,
+        "" + typeNullable, "0", "" + typeSearchable,
+        "0", "0", "1",
+        null, "0", "0",
+        "0", "0", "0"
+    };
+    tr.newrow(row7);
+    String row8[] = {
+        "DATE", "" + Types.DATE, "10",
+        null, null, null,
+        "" + typeNullable, "0", "" + typeSearchable,
+        "0", "0", "1",
+        null, "0", "0",
+        "0", "0", "0"
+    };
+    tr.newrow(row8);
+    String row9[] = {
+        "TIME", "" + Types.TIME, "8",
+        null, null, null,
+        "" + typeNullable, "0", "" + typeSearchable,
+        "0", "0", "1",
+        null, "0", "0",
+        "0", "0", "0"
+    };
+    tr.newrow(row9);
+    String row10[] = {
+        "BINARY", "" + Types.BINARY, "65536",
+        null, null, null,
+        "" + typeNullable, "0", "" + typeSearchable,
+        "0", "0", "1",
+        null, "0", "0",
+        "0", "0", "0"
+    };
+    tr.newrow(row10);
+    String row11[] = {
+        "VARBINARY", "" + Types.VARBINARY, "65536",
+        null, null, null,
+        "" + typeNullable, "0", "" + typeSearchable,
+        "0", "0", "1",
+        null, "0", "0",
+        "0", "0", "0"
+    };
+    tr.newrow(row11);
+    return rs;
+    }
+
+    public ResultSet getIndexInfo(String catalog, String schema, String table,
+                  boolean unique, boolean approximate)
+    throws SQLException {
+    JDBCStatement s0 = new JDBCStatement(conn);
+    JDBCResultSet rs0 = null;
+    try {
+        rs0 = (JDBCResultSet)
+        (s0.executeQuery("PRAGMA index_list(" +
+                 SQLite.Shell.sql_quote(table) + ")"));
+    } catch (SQLException e) {
+        throw e;
+    } finally {
+        s0.close();
+    }
+    String cols[] = {
+        "TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME",
+        "NON_UNIQUE", "INDEX_QUALIFIER", "INDEX_NAME",
+        "TYPE", "ORDINAL_POSITION", "COLUMN_NAME",
+        "ASC_OR_DESC", "CARDINALITY", "PAGES",
+        "FILTER_CONDITION"
+    };
+    int types[] = {
+        Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+        Types.BIT, Types.VARCHAR, Types.VARCHAR,
+        Types.SMALLINT, Types.SMALLINT, Types.VARCHAR,
+        Types.VARCHAR, Types.INTEGER, Types.INTEGER,
+        Types.VARCHAR
+    };
+    TableResultX tr = new TableResultX();
+    tr.columns(cols);
+    tr.sql_types(types);
+    JDBCResultSet rs = new JDBCResultSet(tr, null);
+    if (rs0 != null && rs0.tr != null && rs0.tr.nrows > 0) {
+        Hashtable<String, Integer> h0 = new Hashtable<String, Integer>();
+        for (int i = 0; i < rs0.tr.ncolumns; i++) {
+        h0.put(rs0.tr.column[i], new Integer(i));
+        }
+        for (int i = 0; i < rs0.tr.nrows; i++) {
+        String r0[] = (String [])(rs0.tr.rows.elementAt(i));
+        int col = ((Integer) h0.get("unique")).intValue();
+        String uniq = r0[col];
+        col = ((Integer) h0.get("name")).intValue();
+        String iname = r0[col];
+        if (unique && uniq.charAt(0) == '0') {
+            continue;
+        }
+        JDBCStatement s1 = new JDBCStatement(conn);
+        JDBCResultSet rs1 = null;
+        try {
+            rs1 = (JDBCResultSet)
+            (s1.executeQuery("PRAGMA index_info(" +
+                     SQLite.Shell.sql_quote(iname) + ")"));
+        } catch (SQLException e) {
+        } finally {
+            s1.close();
+        }
+        if (rs1 == null || rs1.tr == null || rs1.tr.nrows <= 0) {
+            continue;
+        }
+        Hashtable<String, Integer> h1 =
+            new Hashtable<String, Integer>();
+        for (int k = 0; k < rs1.tr.ncolumns; k++) {
+            h1.put(rs1.tr.column[k], new Integer(k));
+        }
+        for (int k = 0; k < rs1.tr.nrows; k++) {
+            String r1[] = (String [])(rs1.tr.rows.elementAt(k));
+            String row[] = new String[cols.length];
+            row[0]  = "";
+            row[1]  = "";
+            row[2]  = table;
+            row[3]  = (uniq.charAt(0) != '0' ||
+            (iname.charAt(0) == '(' &&
+             iname.indexOf(" autoindex ") > 0)) ? "0" : "1";
+            row[4]  = "";
+            row[5]  = iname;
+            row[6]  = "" + tableIndexOther;
+            col = ((Integer) h1.get("seqno")).intValue();
+// BEGIN android-changed
+            row[7]  = "" + (Integer.parseInt(r1[col]) + 1);
+// END android-changed
+            col = ((Integer) h1.get("name")).intValue();
+            row[8]  = r1[col];
+            row[9]  = "A";
+            row[10] = "0";
+            row[11] = "0";
+            row[12] = null;
+            tr.newrow(row);
+        }
+        }
+    }
+    return rs;
+    }
+
+    public boolean supportsResultSetType(int type) throws SQLException {
+    return type == ResultSet.CONCUR_READ_ONLY;
+    }
+
+    public boolean supportsResultSetConcurrency(int type, int concurrency)
+    throws SQLException {
+    return false;
+    }
+
+    public boolean ownUpdatesAreVisible(int type) throws SQLException {
+    return false;
+    }
+
+    public boolean ownDeletesAreVisible(int type) throws SQLException {
+    return false;
+    }
+
+    public boolean ownInsertsAreVisible(int type) throws SQLException {
+    return false;
+    }
+
+    public boolean othersUpdatesAreVisible(int type) throws SQLException {
+    return false;
+    }
+
+    public boolean othersDeletesAreVisible(int type) throws SQLException {
+    return false;
+    }
+
+    public boolean othersInsertsAreVisible(int type) throws SQLException {
+    return false;
+    }
+
+    public boolean updatesAreDetected(int type) throws SQLException {
+    return false;
+    }
+
+    public boolean deletesAreDetected(int type) throws SQLException {
+    return false;
+    }
+
+    public boolean insertsAreDetected(int type) throws SQLException {
+    return false;
+    }
+
+    public boolean supportsBatchUpdates() throws SQLException {
+    return false;
+    }
+
+    public ResultSet getUDTs(String catalog, String schemaPattern, 
+              String typeNamePattern, int[] types) 
+    throws SQLException {
+    return null;
+    }
+
+    public Connection getConnection() throws SQLException {
+    return conn;
+    }
+
+    static String mapTypeName(int type) {
+    switch (type) {
+    case Types.INTEGER:    return "integer";
+    case Types.SMALLINT:    return "smallint";
+    case Types.FLOAT:    return "float";
+    case Types.DOUBLE:    return "double";
+    case Types.TIMESTAMP:    return "timestamp";
+    case Types.DATE:    return "date";
+    case Types.TIME:    return "time";
+    case Types.BINARY:    return "binary";
+    case Types.VARBINARY:    return "varbinary";
+    }
+    return "varchar";
+    }
+
+    static int mapSqlType(String type) {
+    if (type == null) {
+        return Types.VARCHAR;
+    }
+    type = type.toLowerCase();
+    if (type.startsWith("inter")) {
+        return Types.VARCHAR;
+    }
+    if (type.startsWith("numeric") ||
+        type.startsWith("int")) {
+        return Types.INTEGER;
+    }
+    if (type.startsWith("tinyint") ||
+        type.startsWith("smallint")) {
+        return Types.SMALLINT;
+    }
+    if (type.startsWith("float")) {
+        return Types.FLOAT;
+    }
+    if (type.startsWith("double")) {
+        return Types.DOUBLE;
+    }
+    if (type.startsWith("datetime") ||
+        type.startsWith("timestamp")) {
+        return Types.TIMESTAMP;
+    }
+    if (type.startsWith("date")) {
+        return Types.DATE;
+    }
+    if (type.startsWith("time")) {
+        return Types.TIME;
+    }
+    if (type.startsWith("blob")) {
+        return Types.BINARY;
+    }
+    if (type.startsWith("binary")) {
+        return Types.BINARY;
+    }
+    if (type.startsWith("varbinary")) {
+        return Types.VARBINARY;
+    }
+    return Types.VARCHAR;
+    }
+
+    static int getM(String typeStr, int type) {
+    int m = 65536;
+    switch (type) {
+    case Types.INTEGER:    m = 11; break;
+    case Types.SMALLINT:    m = 6;  break;
+    case Types.FLOAT:    m = 25; break;
+    case Types.DOUBLE:    m = 54; break;
+    case Types.TIMESTAMP:    return 30;
+    case Types.DATE:    return 10;
+    case Types.TIME:    return 8;
+    }
+    typeStr = typeStr.toLowerCase();
+    int i1 = typeStr.indexOf('(');
+    if (i1 > 0) {
+        ++i1;
+        int i2 = typeStr.indexOf(',', i1);
+        if (i2 < 0) {
+        i2 = typeStr.indexOf(')', i1);
+        }
+        if (i2 - i1 > 0) {
+        String num = typeStr.substring(i1, i2);
+        try {
+            m = java.lang.Integer.parseInt(num, 10);
+        } catch (NumberFormatException e) {
+        }
+        }
+    }
+    return m;
+    }
+
+    static int getD(String typeStr, int type) {
+    int d = 0;
+    switch (type) {
+    case Types.INTEGER:    d = 10; break;
+    case Types.SMALLINT:    d = 5;  break;
+    case Types.FLOAT:    d = 24; break;
+    case Types.DOUBLE:    d = 53; break;
+    default:        return getM(typeStr, type);
+    }
+    typeStr = typeStr.toLowerCase();
+    int i1 = typeStr.indexOf('(');
+    if (i1 > 0) {
+        ++i1;
+        int i2 = typeStr.indexOf(',', i1);
+        if (i2 < 0) {
+        return getM(typeStr, type);
+        }
+        i1 = i2;
+        i2 = typeStr.indexOf(')', i1);
+        if (i2 - i1 > 0) {
+        String num = typeStr.substring(i1, i2);
+        try {
+            d = java.lang.Integer.parseInt(num, 10);
+        } catch (NumberFormatException e) {
+        }
+        }
+    }
+    return d;
+    }
+
+    public boolean supportsSavepoints() {
+    return false;
+    }
+
+    public boolean supportsNamedParameters() {
+    return false;
+    }
+
+    public boolean supportsMultipleOpenResults() {
+    return false;
+    }
+
+    public boolean supportsGetGeneratedKeys() {
+    return false;
+    }
+
+    public boolean supportsResultSetHoldability(int x) {
+    return false;
+    }
+
+    public boolean supportsStatementPooling() {
+    return false;
+    }
+
+    public boolean locatorsUpdateCopy() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public ResultSet getSuperTypes(String catalog, String schemaPattern,
+                String typeNamePattern)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public ResultSet getSuperTables(String catalog, String schemaPattern,
+                    String tableNamePattern)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public ResultSet getAttributes(String catalog, String schemaPattern,
+                   String typeNamePattern,
+                   String attributeNamePattern)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public int getResultSetHoldability() throws SQLException {
+    return ResultSet.HOLD_CURSORS_OVER_COMMIT;
+    }
+
+    public int getDatabaseMajorVersion() {
+    return SQLite.JDBCDriver.MAJORVERSION;
+    }
+
+    public int getDatabaseMinorVersion() {
+    return SQLite.JDBCDriver.MINORVERSION;
+    }
+
+    public int getJDBCMajorVersion() {
+    return 1;
+    }
+    
+    public int getJDBCMinorVersion() {
+    return 0;
+    }
+
+    public int getSQLStateType() throws SQLException {
+    return sqlStateXOpen;
+    }
+
+}
diff --git a/src/main/java/SQLite/JDBC2y/JDBCPreparedStatement.java b/src/main/java/SQLite/JDBC2y/JDBCPreparedStatement.java
new file mode 100644
index 0000000..ab81867
--- /dev/null
+++ b/src/main/java/SQLite/JDBC2y/JDBCPreparedStatement.java
@@ -0,0 +1,752 @@
+package SQLite.JDBC2y;
+
+import java.sql.*;
+import java.math.BigDecimal;
+import java.util.*;
+
+class BatchArg {
+    String arg;
+    boolean blob;
+
+    BatchArg(String arg, boolean blob) {
+    if (arg == null) {
+        this.arg = null;
+    } else {
+        this.arg = new String(arg);
+    }
+    this.blob = blob;
+    }
+}
+
+public class JDBCPreparedStatement extends JDBCStatement
+    implements java.sql.PreparedStatement {
+
+    private String sql;
+    private String args[];
+    private boolean blobs[];
+    private ArrayList<BatchArg> batch;
+    private static final boolean nullrepl =
+    SQLite.Database.version().compareTo("2.5.0") < 0;
+
+    public JDBCPreparedStatement(JDBCConnection conn, String sql) {
+    super(conn);
+    this.args = null;
+    this.blobs = null;
+    this.batch = null;
+    this.sql = fixup(sql);
+    }
+
+    private String fixup(String sql) {
+    StringBuffer sb = new StringBuffer();
+    boolean inq = false;
+    int nparm = 0;
+    for (int i = 0; i < sql.length(); i++) {
+        char c = sql.charAt(i);
+        if (c == '\'') {
+        if (inq) {
+                    char nextChar = 0;
+                    if(i + 1 < sql.length()) {
+                        nextChar = sql.charAt(i + 1);
+                    }
+            if (nextChar == '\'') {
+                        sb.append(c);
+                        sb.append(nextChar);
+                        i++;
+                    } else {
+            inq = false;
+                        sb.append(c);
+                    }
+        } else {
+            inq = true;
+                    sb.append(c);
+        }
+        } else if (c == '?') {
+        if (inq) {
+            sb.append(c);
+        } else {
+            ++nparm;
+            sb.append(nullrepl ? "'%q'" : "%Q");
+        }
+        } else if (c == ';') {
+        if (!inq) {
+            break;
+        }
+        sb.append(c);
+        } else if (c == '%') {
+        sb.append("%%");
+        } else {
+        sb.append(c);
+        }
+    }
+    args = new String[nparm];
+    blobs = new boolean[nparm];
+    try {
+        clearParameters();
+    } catch (SQLException e) {
+    }
+    return sb.toString();
+    }
+
+    private String fixup2(String sql) {
+    if (!conn.db.is3()) {
+        return sql;
+    }
+    StringBuffer sb = new StringBuffer();
+    int parm = -1;
+    for (int i = 0; i < sql.length(); i++) {
+        char c = sql.charAt(i);
+        if (c == '%') {
+        sb.append(c);
+        ++i;
+        c = sql.charAt(i);
+        if (c == 'Q') {
+            parm++;
+            if (blobs[parm]) {
+            c = 's';
+            }
+        }
+        }
+        sb.append(c);
+    }
+    return sb.toString();
+    }
+
+    public ResultSet executeQuery() throws SQLException {
+    return executeQuery(fixup2(sql), args, false);
+    }
+
+    public int executeUpdate() throws SQLException {
+    executeQuery(fixup2(sql), args, true);
+    return updcnt;
+    }
+
+    public void setNull(int parameterIndex, int sqlType) throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    args[parameterIndex - 1] = nullrepl ? "" : null;
+    blobs[parameterIndex - 1] = false;
+    }
+    
+    public void setBoolean(int parameterIndex, boolean x)
+    throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    args[parameterIndex - 1] = x ? "1" : "0";
+    blobs[parameterIndex - 1] = false;
+    }
+
+    public void setByte(int parameterIndex, byte x) throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    args[parameterIndex - 1] = "" + x;
+    blobs[parameterIndex - 1] = false;
+    }
+
+    public void setShort(int parameterIndex, short x) throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    args[parameterIndex - 1] = "" + x;
+    blobs[parameterIndex - 1] = false;
+    }
+
+    public void setInt(int parameterIndex, int x) throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    args[parameterIndex - 1] = "" + x;
+    blobs[parameterIndex - 1] = false;
+    }
+
+    public void setLong(int parameterIndex, long x) throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    args[parameterIndex - 1] = "" + x;
+    blobs[parameterIndex - 1] = false;
+    }
+
+    public void setFloat(int parameterIndex, float x) throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    args[parameterIndex - 1] = "" + x;
+    blobs[parameterIndex - 1] = false;
+    }
+
+    public void setDouble(int parameterIndex, double x) throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    args[parameterIndex - 1] = "" + x;
+    blobs[parameterIndex - 1] = false;
+    }
+
+    public void setBigDecimal(int parameterIndex, BigDecimal x)
+    throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    if (x == null) {
+        args[parameterIndex - 1] = nullrepl ? "" : null;
+    } else {
+        args[parameterIndex - 1] = "" + x;
+    }
+    blobs[parameterIndex - 1] = false;
+    }
+
+    public void setString(int parameterIndex, String x) throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    if (x == null) {
+        args[parameterIndex - 1] = nullrepl ? "" : null;
+    } else {
+        args[parameterIndex - 1] = x;
+    }
+    blobs[parameterIndex - 1] = false;
+    }
+
+    public void setBytes(int parameterIndex, byte x[]) throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    blobs[parameterIndex - 1] = false;
+    if (x == null) {
+        args[parameterIndex - 1] = nullrepl ? "" : null;
+    } else {
+        if (conn.db.is3()) {
+        args[parameterIndex - 1] = SQLite.StringEncoder.encodeX(x);
+        blobs[parameterIndex - 1] = true;
+        } else {
+        args[parameterIndex - 1] = SQLite.StringEncoder.encode(x);
+        }
+    }
+    }
+
+    public void setDate(int parameterIndex, java.sql.Date x)
+    throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    if (x == null) {
+        args[parameterIndex - 1] = nullrepl ? "" : null;
+    } else {
+        args[parameterIndex - 1] = x.toString();
+    }
+    blobs[parameterIndex - 1] = false;
+    }
+
+    public void setTime(int parameterIndex, java.sql.Time x) 
+    throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    if (x == null) {
+        args[parameterIndex - 1] = nullrepl ? "" : null;
+    } else {
+        args[parameterIndex - 1] = x.toString();
+    }
+    blobs[parameterIndex - 1] = false;
+    }
+
+    public void setTimestamp(int parameterIndex, java.sql.Timestamp x)
+    throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    if (x == null) {
+        args[parameterIndex - 1] = nullrepl ? "" : null;
+    } else {
+        args[parameterIndex - 1] = x.toString();
+    }
+    blobs[parameterIndex - 1] = false;
+    }
+
+    public void setAsciiStream(int parameterIndex, java.io.InputStream x,
+                   int length) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    @Deprecated
+    public void setUnicodeStream(int parameterIndex, java.io.InputStream x, 
+                 int length) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setBinaryStream(int parameterIndex, java.io.InputStream x,
+                int length) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void clearParameters() throws SQLException {
+    for (int i = 0; i < args.length; i++) {
+        args[i] = nullrepl ? "" : null;
+        blobs[i] = false;
+    }
+    }
+
+    public void setObject(int parameterIndex, Object x, int targetSqlType,
+              int scale) throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    if (x == null) {
+        args[parameterIndex - 1] = nullrepl ? "" : null;
+    } else {
+        if (x instanceof byte[]) {
+        byte[] bx = (byte[]) x;
+        if (conn.db.is3()) {
+            args[parameterIndex - 1] =
+              SQLite.StringEncoder.encodeX(bx);
+            blobs[parameterIndex - 1] = true;
+            return;
+        }
+        args[parameterIndex - 1] = SQLite.StringEncoder.encode(bx);
+        } else {
+        args[parameterIndex - 1] = x.toString();
+        }
+    }
+    blobs[parameterIndex - 1] = false;
+    }
+
+    public void setObject(int parameterIndex, Object x, int targetSqlType)
+    throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    if (x == null) {
+        args[parameterIndex - 1] = nullrepl ? "" : null;
+    } else {
+        if (x instanceof byte[]) {
+        byte[] bx = (byte[]) x;
+        if (conn.db.is3()) {
+            args[parameterIndex - 1] =
+            SQLite.StringEncoder.encodeX(bx);
+            blobs[parameterIndex - 1] = true;
+            return;
+        }
+        args[parameterIndex - 1] = SQLite.StringEncoder.encode(bx);
+        } else {
+        args[parameterIndex - 1] = x.toString();
+        }
+    }
+    blobs[parameterIndex - 1] = false;
+    }
+
+    public void setObject(int parameterIndex, Object x) throws SQLException {
+    if (parameterIndex < 1 || parameterIndex > args.length) {
+        throw new SQLException("bad parameter index");
+    }
+    if (x == null) {
+        args[parameterIndex - 1] = nullrepl ? "" : null;
+    } else {
+        if (x instanceof byte[]) {
+        byte[] bx = (byte[]) x;
+        if (conn.db.is3()) {
+            args[parameterIndex - 1] =
+            SQLite.StringEncoder.encodeX(bx);
+            blobs[parameterIndex - 1] = true;
+            return;
+        }
+        args[parameterIndex - 1] = SQLite.StringEncoder.encode(bx);
+        } else {
+        args[parameterIndex - 1] = x.toString();
+        }
+    }
+    blobs[parameterIndex - 1] = false;
+    }
+
+    public boolean execute() throws SQLException {
+    return executeQuery(fixup2(sql), args, false) != null;
+    }
+
+    public void addBatch() throws SQLException {
+    if (batch == null) {
+        batch = new ArrayList<BatchArg>(args.length);
+    }
+    for (int i = 0; i < args.length; i++) {
+        batch.add(new BatchArg(args[i], blobs[i]));
+    }
+    }
+
+    public int[] executeBatch() throws SQLException {
+    if (batch == null) {
+        return new int[0];
+    }
+    int[] ret = new int[batch.size() / args.length];
+    for (int i = 0; i < ret.length; i++) {
+        ret[i] = EXECUTE_FAILED;
+    }
+    int errs = 0;
+    int index = 0;
+    for (int i = 0; i < ret.length; i++) {
+        for (int k = 0; k < args.length; k++) {
+        BatchArg b = (BatchArg) batch.get(index++);
+
+        args[i] = b.arg;
+        blobs[i] = b.blob;
+        }
+        try {
+        ret[i] = executeUpdate();
+        } catch (SQLException e) {
+        ++errs;
+        }
+    }
+    if (errs > 0) {
+        throw new BatchUpdateException("batch failed", ret);
+    }
+    return ret;
+    }
+
+    public void clearBatch() throws SQLException {
+    if (batch != null) {
+        batch.clear();
+        batch = null;
+    }
+    }
+
+    public void setCharacterStream(int parameterIndex,
+                   java.io.Reader reader,
+                   int length) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setRef(int i, Ref x) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setBlob(int i, Blob x) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setClob(int i, Clob x) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setArray(int i, Array x) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public ResultSetMetaData getMetaData() throws SQLException {
+    return rs.getMetaData();
+    }
+
+    public void setDate(int parameterIndex, java.sql.Date x, Calendar cal)
+    throws SQLException {
+    setDate(parameterIndex, x);
+    }
+
+    public void setTime(int parameterIndex, java.sql.Time x, Calendar cal)
+    throws SQLException {
+    setTime(parameterIndex, x);
+    }
+
+    public void setTimestamp(int parameterIndex, java.sql.Timestamp x,
+                 Calendar cal) throws SQLException {
+    setTimestamp(parameterIndex, x);
+    }
+
+    public void setNull(int parameterIndex, int sqlType, String typeName)
+    throws SQLException {
+    setNull(parameterIndex, sqlType);
+    }
+
+    public ParameterMetaData getParameterMetaData() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void registerOutputParameter(String parameterName, int sqlType)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void registerOutputParameter(String parameterName, int sqlType,
+                    int scale)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void registerOutputParameter(String parameterName, int sqlType,
+                    String typeName)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.net.URL getURL(int parameterIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setURL(int parameterIndex, java.net.URL url)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setNull(String parameterName, int sqlType)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setBoolean(String parameterName, boolean val)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setByte(String parameterName, byte val)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setShort(String parameterName, short val)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setInt(String parameterName, int val)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setLong(String parameterName, long val)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setFloat(String parameterName, float val)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setDouble(String parameterName, double val)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setBigDecimal(String parameterName, BigDecimal val)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setString(String parameterName, String val)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setBytes(String parameterName, byte val[])
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setDate(String parameterName, java.sql.Date val)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setTime(String parameterName, java.sql.Time val)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setTimestamp(String parameterName, java.sql.Timestamp val)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setAsciiStream(String parameterName,
+                   java.io.InputStream s, int length)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setBinaryStream(String parameterName,
+                java.io.InputStream s, int length)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setObject(String parameterName, Object val, int targetSqlType,
+              int scale)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setObject(String parameterName, Object val, int targetSqlType)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setObject(String parameterName, Object val)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setCharacterStream(String parameterName,
+                   java.io.Reader r, int length)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setDate(String parameterName, java.sql.Date val,
+            Calendar cal)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setTime(String parameterName, java.sql.Time val,
+            Calendar cal)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setTimestamp(String parameterName, java.sql.Timestamp val,
+                 Calendar cal)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setNull(String parameterName, int sqlType, String typeName)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public String getString(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public boolean getBoolean(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public byte getByte(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public short getShort(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public int getInt(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public long getLong(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public float getFloat(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public double getDouble(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public byte[] getBytes(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Date getDate(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Time getTime(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Timestamp getTimestamp(String parameterName)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public Object getObject(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public Object getObject(int parameterIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public BigDecimal getBigDecimal(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public Object getObject(String parameterName, Map map)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public Object getObject(int parameterIndex, Map map)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public Ref getRef(int parameterIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public Ref getRef(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public Blob getBlob(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public Blob getBlob(int parameterIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public Clob getClob(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public Clob getClob(int parameterIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public Array getArray(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public Array getArray(int parameterIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Date getDate(String parameterName, Calendar cal)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Date getDate(int parameterIndex, Calendar cal)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Time getTime(String parameterName, Calendar cal)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Time getTime(int parameterIndex, Calendar cal)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Timestamp getTimestamp(String parameterName, Calendar cal)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Timestamp getTimestamp(int parameterIndex, Calendar cal)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.net.URL getURL(String parameterName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+}
diff --git a/src/main/java/SQLite/JDBC2y/JDBCResultSet.java b/src/main/java/SQLite/JDBC2y/JDBCResultSet.java
new file mode 100644
index 0000000..06384eb
--- /dev/null
+++ b/src/main/java/SQLite/JDBC2y/JDBCResultSet.java
@@ -0,0 +1,932 @@
+package SQLite.JDBC2y;
+
+import java.sql.*;
+import java.math.BigDecimal;
+
+public class JDBCResultSet implements java.sql.ResultSet {
+
+    /**
+     * Current row to be retrieved.
+     */
+    private int row;
+
+    /**
+     * Table returned by Database.get_table()
+     */
+    protected SQLite.TableResult tr;
+
+    /**
+     * Statement from which result set was produced.
+     */
+    private JDBCStatement s;
+
+    /**
+     * Meta data for result set or null.
+     */
+    private JDBCResultSetMetaData m;
+
+    /**
+     * Last result cell retrieved or null.
+     */
+    private String lastg;
+
+
+    public JDBCResultSet(SQLite.TableResult tr, JDBCStatement s) {
+    this.tr = tr;
+    this.s = s;
+    this.m = null;
+    this.lastg = null;
+    this.row = -1;
+    }
+
+    public boolean next() throws SQLException {
+    if (tr == null) {
+        return false;
+    }
+    row++;
+    return row < tr.nrows;
+    }
+
+    public int findColumn(String columnName) throws SQLException {
+    JDBCResultSetMetaData m = (JDBCResultSetMetaData) getMetaData();
+    return m.findColByName(columnName);
+    }
+  
+    public int getRow() throws SQLException {
+    if (tr == null) {
+        throw new SQLException("no rows");
+    }
+    return row + 1;
+    }
+
+    public boolean previous() throws SQLException {
+    if (tr == null) {
+        return false;
+    }
+    if (row >= 0) {
+        row--;
+    }
+    return row >= 0;
+    }
+
+    public boolean absolute(int row) throws SQLException {
+    if (tr == null) {
+        return false;
+    }
+    if (row < 0) {
+        row = tr.nrows + 1 + row;
+    }
+    row--;
+    if (row < 0 || row > tr.nrows) {
+        return false;
+    }
+    this.row = row;
+    return true;
+    }
+
+    public boolean relative(int row) throws SQLException {
+    if (tr == null) {
+        return false;
+    }
+    if (this.row + row < 0 || this.row + row >= tr.nrows) {
+        return false;
+    }
+    this.row += row;
+    return true;
+    }
+
+    public void setFetchDirection(int dir) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public int getFetchDirection() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setFetchSize(int fsize) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public int getFetchSize() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public String getString(int columnIndex) throws SQLException {
+    if (tr == null || columnIndex < 1 || columnIndex > tr.ncolumns) {
+        throw new SQLException("column " + columnIndex + " not found");
+    }
+    String rd[] = (String []) tr.rows.elementAt(row);
+    lastg = rd[columnIndex - 1];
+    return lastg;
+    }
+
+    public String getString(String columnName) throws SQLException {
+    int col = findColumn(columnName);
+    return getString(col);
+    }
+
+    public int getInt(int columnIndex) throws SQLException {
+    Integer i = internalGetInt(columnIndex);
+    if (i != null) {
+        return i.intValue();
+    }
+    return 0;
+    }
+
+    private Integer internalGetInt(int columnIndex) throws SQLException {
+    if (tr == null || columnIndex < 1 || columnIndex > tr.ncolumns) {
+        throw new SQLException("column " + columnIndex + " not found");
+    }
+    String rd[] = (String []) tr.rows.elementAt(row);
+    lastg = rd[columnIndex - 1];
+    try {
+        return Integer.valueOf(lastg);
+    } catch (java.lang.Exception e) {
+        lastg = null;
+    }
+    return null;
+    }
+
+    public int getInt(String columnName) throws SQLException {
+    int col = findColumn(columnName);
+    return getInt(col);
+    }
+
+    public boolean getBoolean(int columnIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public boolean getBoolean(String columnName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public ResultSetMetaData getMetaData() throws SQLException {
+    if (m == null) {
+        m = new JDBCResultSetMetaData(this);
+    }
+    return m;
+    }
+
+    public short getShort(int columnIndex) throws SQLException {
+    Short s = internalGetShort(columnIndex);
+    if (s != null) {
+        return s.shortValue();
+    }
+    return 0;
+    }
+
+    private Short internalGetShort(int columnIndex) throws SQLException {
+    if (tr == null || columnIndex < 1 || columnIndex > tr.ncolumns) {
+        throw new SQLException("column " + columnIndex + " not found");
+    }
+    String rd[] = (String []) tr.rows.elementAt(row);
+    lastg = rd[columnIndex - 1];
+    try {
+        return Short.valueOf(lastg);
+    } catch (java.lang.Exception e) {
+        lastg = null;
+    }
+    return null;
+    }
+
+    public short getShort(String columnName) throws SQLException {
+    int col = findColumn(columnName);
+    return getShort(col);
+    }
+
+    public java.sql.Time getTime(int columnIndex) throws SQLException {
+    return internalGetTime(columnIndex, null);
+    }
+
+    private java.sql.Time internalGetTime(int columnIndex,
+                      java.util.Calendar cal)
+    throws SQLException {
+    if (tr == null || columnIndex < 1 || columnIndex > tr.ncolumns) {
+        throw new SQLException("column " + columnIndex + " not found");
+    }
+    String rd[] = (String []) tr.rows.elementAt(row);
+    lastg = rd[columnIndex - 1];
+    try {
+        return java.sql.Time.valueOf(lastg);
+    } catch (java.lang.Exception e) {
+        lastg = null;
+    }
+    return null;
+    }
+
+    public java.sql.Time getTime(String columnName) throws SQLException {
+    int col = findColumn(columnName);
+    return getTime(col);
+    }
+
+    public java.sql.Time getTime(int columnIndex, java.util.Calendar cal)
+    throws SQLException {
+    return internalGetTime(columnIndex, cal);
+    }
+
+    public java.sql.Time getTime(String columnName, java.util.Calendar cal)
+    throws SQLException{
+    int col = findColumn(columnName);
+    return getTime(col, cal);
+    }
+
+    public java.sql.Timestamp getTimestamp(int columnIndex)
+    throws SQLException{
+    return internalGetTimestamp(columnIndex, null);
+    }
+
+    private java.sql.Timestamp internalGetTimestamp(int columnIndex,
+                            java.util.Calendar cal)
+    throws SQLException {
+    if (tr == null || columnIndex < 1 || columnIndex > tr.ncolumns) {
+        throw new SQLException("column " + columnIndex + " not found");
+    }
+    String rd[] = (String []) tr.rows.elementAt(row);
+    lastg = rd[columnIndex - 1];
+    try {
+        return java.sql.Timestamp.valueOf(lastg);
+    } catch (java.lang.Exception e) {
+        lastg = null;
+    }
+    return null;
+    }
+
+    public java.sql.Timestamp getTimestamp(String columnName)
+    throws SQLException{
+    int col = findColumn(columnName);
+    return getTimestamp(col);
+    }
+
+    public java.sql.Timestamp getTimestamp(int columnIndex,
+                       java.util.Calendar cal)
+    throws SQLException {
+    return internalGetTimestamp(columnIndex, cal);
+    }
+
+    public java.sql.Timestamp getTimestamp(String columnName,
+                       java.util.Calendar cal)
+    throws SQLException {
+    int col = findColumn(columnName);
+    return getTimestamp(col, cal);
+    }
+
+    public java.sql.Date getDate(int columnIndex) throws SQLException {
+    return internalGetDate(columnIndex, null);
+    }
+
+    private java.sql.Date internalGetDate(int columnIndex,
+                      java.util.Calendar cal)
+    throws SQLException {
+    if (tr == null || columnIndex < 1 || columnIndex > tr.ncolumns) {
+        throw new SQLException("column " + columnIndex + " not found");
+    }
+    String rd[] = (String []) tr.rows.elementAt(row);
+    lastg = rd[columnIndex - 1];
+    try {
+        return java.sql.Date.valueOf(lastg);
+    } catch (java.lang.Exception e) {
+        lastg = null;
+    }
+    return null;
+    }
+
+    public java.sql.Date getDate(String columnName) throws SQLException {
+    int col = findColumn(columnName);
+    return getDate(col);
+    }
+
+    public java.sql.Date getDate(int columnIndex, java.util.Calendar cal)
+    throws SQLException{
+    return internalGetDate(columnIndex, cal);
+    }
+
+    public java.sql.Date getDate(String columnName, java.util.Calendar cal)
+    throws SQLException{
+    int col = findColumn(columnName);
+    return getDate(col, cal);
+    }
+
+    public double getDouble(int columnIndex) throws SQLException {
+    Double d = internalGetDouble(columnIndex);
+    if (d != null) {
+        return d.doubleValue();
+    }
+    return 0;
+    }
+
+    private Double internalGetDouble(int columnIndex) throws SQLException {
+    if (tr == null || columnIndex < 1 || columnIndex > tr.ncolumns) {
+        throw new SQLException("column " + columnIndex + " not found");
+    }
+    String rd[] = (String []) tr.rows.elementAt(row);
+    lastg = rd[columnIndex - 1];
+    try {
+        return  Double.valueOf(lastg);
+    } catch (java.lang.Exception e) {
+        lastg = null;
+    }
+    return null;
+    }
+    
+    public double getDouble(String columnName) throws SQLException {
+    int col = findColumn(columnName);
+    return getDouble(col);
+    }
+
+    public float getFloat(int columnIndex) throws SQLException {
+    Float f = internalGetFloat(columnIndex);
+    if (f != null) {
+        return f.floatValue();
+    }
+    return 0;
+    }
+
+    private Float internalGetFloat(int columnIndex) throws SQLException {
+    if (tr == null || columnIndex < 1 || columnIndex > tr.ncolumns) {
+        throw new SQLException("column " + columnIndex + " not found");
+    }
+    String rd[] = (String []) tr.rows.elementAt(row);
+    lastg = rd[columnIndex - 1];
+    try {
+        return Float.valueOf(lastg);
+    } catch (java.lang.Exception e) {
+        lastg = null;
+    }
+    return null;
+    }
+
+    public float getFloat(String columnName) throws SQLException {
+    int col = findColumn(columnName);
+    return getFloat(col);
+    }
+
+    public long getLong(int columnIndex) throws SQLException {
+    Long l = internalGetLong(columnIndex);
+    if (l != null) {
+        return l.longValue();
+    }
+    return 0;
+    }
+
+    private Long internalGetLong(int columnIndex) throws SQLException {
+    if (tr == null || columnIndex < 1 || columnIndex > tr.ncolumns) {
+        throw new SQLException("column " + columnIndex + " not found");
+    }
+    String rd[] = (String []) tr.rows.elementAt(row);
+    lastg = rd[columnIndex - 1];
+    try {
+        return Long.valueOf(lastg);
+    } catch (java.lang.Exception e) {
+        lastg = null;
+    }
+    return null;
+    }
+
+    public long getLong(String columnName) throws SQLException {
+    int col = findColumn(columnName);
+    return getLong(col);
+    }
+
+    @Deprecated
+    public java.io.InputStream getUnicodeStream(int columnIndex)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    @Deprecated
+    public java.io.InputStream getUnicodeStream(String columnName)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.io.InputStream getAsciiStream(String columnName)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.io.InputStream getAsciiStream(int columnIndex)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public BigDecimal getBigDecimal(String columnName)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    @Deprecated
+    public BigDecimal getBigDecimal(String columnName, int scale)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public BigDecimal getBigDecimal(int columnIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    @Deprecated
+    public BigDecimal getBigDecimal(int columnIndex, int scale)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.io.InputStream getBinaryStream(int columnIndex)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.io.InputStream getBinaryStream(String columnName)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public byte getByte(int columnIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public byte getByte(String columnName) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public byte[] getBytes(int columnIndex) throws SQLException {
+    if (tr == null || columnIndex < 1 || columnIndex > tr.ncolumns) {
+        throw new SQLException("column " + columnIndex + " not found");
+    }
+    byte ret[] = null;
+    String rd[] = (String []) tr.rows.elementAt(row);
+    lastg = rd[columnIndex - 1];
+    if (lastg != null) {
+        ret = SQLite.StringEncoder.decode(lastg);
+    }
+    return ret;
+    }
+
+    public byte[] getBytes(String columnName) throws SQLException {
+    int col = findColumn(columnName);
+    return getBytes(col);
+    }
+
+    public String getCursorName() throws SQLException {
+    return null;
+    }
+
+    public Object getObject(int columnIndex) throws SQLException {
+    if (tr == null || columnIndex < 1 || columnIndex > tr.ncolumns) {
+        throw new SQLException("column " + columnIndex + " not found");
+    }
+    String rd[] = (String []) tr.rows.elementAt(row);
+    lastg = rd[columnIndex - 1];
+    Object ret = lastg;
+    if (tr instanceof TableResultX) {
+        switch (((TableResultX) tr).sql_type[columnIndex - 1]) {
+        case Types.SMALLINT:
+        ret = internalGetShort(columnIndex);
+        break;
+        case Types.INTEGER:
+        ret = internalGetInt(columnIndex);
+        break;
+        case Types.DOUBLE:
+        ret = internalGetDouble(columnIndex);
+        break;
+        case Types.FLOAT:
+        ret = internalGetFloat(columnIndex);
+        break;
+        case Types.BIGINT:
+        ret = internalGetLong(columnIndex);
+        break;
+        case Types.BINARY:
+        case Types.VARBINARY:
+        case Types.LONGVARBINARY:
+        ret = getBytes(columnIndex);
+        break;
+        case Types.NULL:
+        ret = null;
+        break;
+        /* defaults to String below */
+        }
+    }
+    return ret;
+    }
+
+    public Object getObject(String columnName) throws SQLException {
+    int col = findColumn(columnName);
+    return getObject(col);
+    }
+
+    public Object getObject(int columnIndex, java.util.Map map) 
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public Object getObject(String columnIndex, java.util.Map map)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Ref getRef(int columnIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Ref getRef(String columnIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Blob getBlob(int columnIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Blob getBlob(String columnIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Clob getClob(int columnIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Clob getClob(String columnIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Array getArray(int columnIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.sql.Array getArray(String columnIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.io.Reader getCharacterStream(int columnIndex)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public java.io.Reader getCharacterStream(String columnName)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public SQLWarning getWarnings() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public boolean wasNull() throws SQLException {
+    return lastg == null;
+    }
+    
+    public void clearWarnings() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public boolean isFirst() throws SQLException {
+    if (tr == null) {
+        return true;
+    }
+    return row == 0;
+    }
+
+    public boolean isBeforeFirst() throws SQLException {
+    if (tr == null || tr.nrows <= 0) {
+        return false;
+    }
+    return row < 0;
+    }
+
+    public void beforeFirst() throws SQLException {
+    if (tr == null) {
+        return;
+    }
+    row = -1;
+    }
+
+    public boolean first() throws SQLException {
+    if (tr == null || tr.nrows <= 0) {
+        return false;
+    }
+    row = 0;
+    return true;
+    }
+
+    public boolean isAfterLast() throws SQLException {
+    if (tr == null || tr.nrows <= 0) {
+        return false;
+    }
+    return row >= tr.nrows;
+    }
+
+    public void afterLast() throws SQLException {
+    if (tr == null) {
+        return;
+    }
+    row = tr.nrows;
+    }
+
+    public boolean isLast() throws SQLException {
+    if (tr == null) {
+        return true;
+    }
+    return row == tr.nrows - 1;
+    }
+
+    public boolean last() throws SQLException {
+    if (tr == null || tr.nrows <= 0) {
+        return false;
+    }
+    row = tr.nrows -1;
+    return true;
+    }
+
+    public int getType() throws SQLException {
+    return TYPE_SCROLL_INSENSITIVE;
+    }
+
+    public int getConcurrency() throws SQLException {
+    return CONCUR_READ_ONLY;
+    }
+
+    public boolean rowUpdated() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public boolean rowInserted() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public boolean rowDeleted() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void insertRow() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateRow() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void deleteRow() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void refreshRow() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void cancelRowUpdates() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void moveToInsertRow() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void moveToCurrentRow() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateNull(int colIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateBoolean(int colIndex, boolean b) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateByte(int colIndex, byte b) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateShort(int colIndex, short b) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateInt(int colIndex, int b) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateLong(int colIndex, long b) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateFloat(int colIndex, float f) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateDouble(int colIndex, double f) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateBigDecimal(int colIndex, BigDecimal f)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateString(int colIndex, String s) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateBytes(int colIndex, byte[] s) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateDate(int colIndex, java.sql.Date d) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateTime(int colIndex, java.sql.Time t) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateTimestamp(int colIndex, java.sql.Timestamp t)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateAsciiStream(int colIndex, java.io.InputStream in, int s)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateBinaryStream(int colIndex, java.io.InputStream in, int s)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateCharacterStream(int colIndex, java.io.Reader in, int s)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateObject(int colIndex, Object obj) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateObject(int colIndex, Object obj, int s)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateNull(String colIndex) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateBoolean(String colIndex, boolean b) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateByte(String colIndex, byte b) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateShort(String colIndex, short b) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateInt(String colIndex, int b) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateLong(String colIndex, long b) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateFloat(String colIndex, float f) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateDouble(String colIndex, double f) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateBigDecimal(String colIndex, BigDecimal f)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateString(String colIndex, String s) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateBytes(String colIndex, byte[] s) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateDate(String colIndex, java.sql.Date d)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateTime(String colIndex, java.sql.Time t)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateTimestamp(String colIndex, java.sql.Timestamp t)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateAsciiStream(String colIndex, java.io.InputStream in,
+                  int s)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateBinaryStream(String colIndex, java.io.InputStream in,
+                   int s)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateCharacterStream(String colIndex, java.io.Reader in,
+                      int s)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateObject(String colIndex, Object obj)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateObject(String colIndex, Object obj, int s)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public Statement getStatement() throws SQLException {
+    if (s == null) {
+        throw new SQLException("stale result set");
+    }
+    return s;
+    }
+
+    public void close() throws SQLException {
+    s = null;
+    tr = null;
+    lastg = null;
+    row = -1;
+    }
+
+    public java.net.URL getURL(int colIndex) throws SQLException {
+    if (tr == null || colIndex < 1 || colIndex > tr.ncolumns) {
+        throw new SQLException("column " + colIndex + " not found");
+    }
+    String rd[] = (String []) tr.rows.elementAt(row);
+    lastg = rd[colIndex - 1];
+    java.net.URL url = null;
+    if (lastg == null) {
+        return url;
+    }
+    try {
+        url = new java.net.URL(lastg);
+    } catch (java.lang.Exception e) {
+        url = null;
+    }
+    return url;
+    }
+
+    public java.net.URL getURL(String colIndex) throws SQLException {
+    int col = findColumn(colIndex);
+    return getURL(col);
+    }
+
+    public void updateRef(int colIndex, java.sql.Ref x) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateRef(String colIndex, java.sql.Ref x)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateBlob(int colIndex, java.sql.Blob x)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateBlob(String colIndex, java.sql.Blob x)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateClob(int colIndex, java.sql.Clob x)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateClob(String colIndex, java.sql.Clob x)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateArray(int colIndex, java.sql.Array x)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void updateArray(String colIndex, java.sql.Array x)
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+}
diff --git a/src/main/java/SQLite/JDBC2y/JDBCResultSetMetaData.java b/src/main/java/SQLite/JDBC2y/JDBCResultSetMetaData.java
new file mode 100644
index 0000000..934ca78
--- /dev/null
+++ b/src/main/java/SQLite/JDBC2y/JDBCResultSetMetaData.java
@@ -0,0 +1,212 @@
+package SQLite.JDBC2y;
+
+import java.sql.*;
+
+public class JDBCResultSetMetaData implements java.sql.ResultSetMetaData {
+
+    private JDBCResultSet r;
+    
+    public JDBCResultSetMetaData(JDBCResultSet r) {
+    this.r = r;
+    }
+ 
+    public String getCatalogName(int column) throws java.sql.SQLException {
+    return null;
+    }
+
+    public String getColumnClassName(int column) throws java.sql.SQLException {
+    column--;
+    if (r != null && r.tr != null) {
+        if (column < 0 || column >= r.tr.ncolumns) {
+        return null;
+        }
+        if (r.tr instanceof TableResultX) {
+        switch (((TableResultX) r.tr).sql_type[column]) {
+        case Types.SMALLINT:    return "java.lang.Short";
+        case Types.INTEGER:    return "java.lang.Integer";
+        case Types.DOUBLE:    return "java.lang.Double";
+        case Types.FLOAT:    return "java.lang.Float";
+        case Types.BIGINT:    return "java.lang.Long";
+        case Types.DATE:    return "java.sql.Date";
+        case Types.TIME:    return "java.sql.Time";
+        case Types.TIMESTAMP:    return "java.sql.Timestamp";
+        case Types.BINARY:
+        case Types.VARBINARY:    return "[B";
+        /* defaults to varchar below */
+        }
+        }
+        return "java.lang.String";
+    }
+    return null;
+    }
+
+    public int getColumnCount() throws java.sql.SQLException {
+    if (r != null && r.tr != null) {
+        return r.tr.ncolumns;
+    }
+    return 0;
+    }
+
+    public int getColumnDisplaySize(int column) throws java.sql.SQLException {
+    return 0;
+    }
+
+    public String getColumnLabel(int column) throws java.sql.SQLException {
+    column--;
+    String c = null;
+    if (r != null && r.tr != null) {
+        if (column < 0 || column >= r.tr.ncolumns) {
+        return c;
+        }
+        c = r.tr.column[column];
+    }
+    return c;
+    }
+
+    public String getColumnName(int column) throws java.sql.SQLException {
+    column--;
+    String c = null;
+    if (r != null && r.tr != null) {
+        if (column < 0 || column >= r.tr.ncolumns) {
+        return c;
+        }
+        c = r.tr.column[column];
+        if (c != null) {
+        int i = c.indexOf('.');
+        if (i > 0) {
+            return c.substring(i + 1);
+        }
+        }
+    }
+    return c;
+    }
+
+    public int getColumnType(int column) throws java.sql.SQLException {
+    column--;
+    if (r != null && r.tr != null) {
+        if (column >= 0 && column < r.tr.ncolumns) {
+        if (r.tr instanceof TableResultX) {
+            return ((TableResultX) r.tr).sql_type[column];
+        }
+        return Types.VARCHAR;
+        }
+    }
+    throw new SQLException("bad column index");
+    }
+
+    public String getColumnTypeName(int column) throws java.sql.SQLException {
+    column--;
+    if (r != null && r.tr != null) {
+        if (column >= 0 && column < r.tr.ncolumns) {
+        if (r.tr instanceof TableResultX) {
+            switch (((TableResultX) r.tr).sql_type[column]) {
+            case Types.SMALLINT:    return "smallint";
+            case Types.INTEGER:        return "integer";
+            case Types.DOUBLE:        return "double";
+            case Types.FLOAT:        return "float";
+            case Types.BIGINT:        return "bigint";
+            case Types.DATE:        return "date";
+            case Types.TIME:        return "time";
+            case Types.TIMESTAMP:    return "timestamp";
+            case Types.BINARY:        return "binary";
+            case Types.VARBINARY:    return "varbinary";
+            /* defaults to varchar below */
+            }
+        }
+        return "varchar";
+        }
+    }
+    throw new SQLException("bad column index");
+    }
+
+    public int getPrecision(int column) throws java.sql.SQLException {
+    return 0;
+    }
+
+    public int getScale(int column) throws java.sql.SQLException {
+    return 0;
+    }
+
+    public String getSchemaName(int column) throws java.sql.SQLException {
+    return null;
+    }
+
+    public String getTableName(int column) throws java.sql.SQLException {
+    column--;
+    String c = null;
+    if (r != null && r.tr != null) {
+        if (column < 0 || column >= r.tr.ncolumns) {
+        return c;
+        }
+        c = r.tr.column[column];
+        if (c != null) {
+        int i = c.indexOf('.');
+        if (i > 0) {
+            return c.substring(0, i);
+        }
+        c = null;
+        }
+    }
+    return c;
+    }
+
+    public boolean isAutoIncrement(int column) throws java.sql.SQLException {
+    return false;
+    }
+
+    public boolean isCaseSensitive(int column) throws java.sql.SQLException {
+    return false;
+    }
+
+    public boolean isCurrency(int column) throws java.sql.SQLException {
+    return false;
+    }
+
+    public boolean isDefinitelyWritable(int column) 
+    throws java.sql.SQLException {
+    return true;
+    }
+
+    public int isNullable(int column) throws java.sql.SQLException {
+    return columnNullableUnknown;
+    }
+
+    public boolean isReadOnly(int column) throws java.sql.SQLException {
+    return false;
+    }
+
+    public boolean isSearchable(int column) throws java.sql.SQLException {
+    return true;
+    }
+
+    public boolean isSigned(int column) throws java.sql.SQLException {
+    return false;
+    }
+
+    public boolean isWritable(int column) throws java.sql.SQLException {
+    return true;
+    }
+
+    int findColByName(String columnName) throws java.sql.SQLException {
+    String c = null;
+    if (r != null && r.tr != null) {
+        for (int i = 0; i < r.tr.ncolumns; i++) {
+        c = r.tr.column[i];
+        if (c != null) {
+            if (c.compareToIgnoreCase(columnName) == 0) {
+            return i + 1;
+            }
+            int k = c.indexOf('.');
+            if (k > 0) {
+            c = c.substring(k + 1);
+            if (c.compareToIgnoreCase(columnName) == 0) {
+                return i + 1;
+            }
+            }
+        }
+        c = null;
+        }
+    }
+    throw new SQLException("column " + columnName + " not found");
+    }
+}
diff --git a/src/main/java/SQLite/JDBC2y/JDBCStatement.java b/src/main/java/SQLite/JDBC2y/JDBCStatement.java
new file mode 100644
index 0000000..99d12d3
--- /dev/null
+++ b/src/main/java/SQLite/JDBC2y/JDBCStatement.java
@@ -0,0 +1,287 @@
+package SQLite.JDBC2y;
+
+import java.sql.*;
+import java.util.*;
+
+public class JDBCStatement implements java.sql.Statement {
+
+    protected JDBCConnection conn;
+    protected JDBCResultSet rs;
+    protected int updcnt;
+    private ArrayList<String> batch;
+
+    public JDBCStatement(JDBCConnection conn) {
+    this.conn = conn;
+    this.updcnt = 0;
+    this.rs = null;
+    this.batch = null;    
+    }
+
+    public void setFetchSize(int fetchSize) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public int getFetchSize() throws SQLException {
+    return 1;
+    }
+
+    public int getMaxRows() throws SQLException {
+    return 0;
+    }
+
+    public void setMaxRows(int max) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setFetchDirection(int fetchDirection) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public int getFetchDirection() throws SQLException {
+    return ResultSet.FETCH_UNKNOWN;
+    }
+
+    public int getResultSetConcurrency() throws SQLException {
+    return ResultSet.CONCUR_READ_ONLY;
+    }
+
+    public int getResultSetType() throws SQLException {
+    return ResultSet.TYPE_SCROLL_INSENSITIVE;
+    }
+
+    public void setQueryTimeout(int seconds) throws SQLException {
+    conn.timeout = seconds * 1000;
+    if (conn.timeout < 0) {
+        conn.timeout = 120000;
+    } else if (conn.timeout < 1000) {
+        conn.timeout = 5000;
+    }
+    }
+
+    public int getQueryTimeout() throws SQLException {
+    return conn.timeout;
+    }
+
+    public ResultSet getResultSet() throws SQLException {
+    return rs;
+    }
+
+    ResultSet executeQuery(String sql, String args[], boolean updonly)
+    throws SQLException {
+    SQLite.TableResult tr = null;
+    if (rs != null) {
+        rs.close();
+        rs = null;
+    }
+    updcnt = -1;
+    if (conn == null || conn.db == null) {
+        throw new SQLException("stale connection");
+    }
+    int busy = 0;
+    boolean starttrans = !conn.autocommit && !conn.intrans;
+    while (true) {
+        try {
+        if (starttrans) {
+            conn.db.exec("BEGIN TRANSACTION", null);
+            conn.intrans = true;
+        }
+        if (args == null) {
+            if (updonly) {
+            conn.db.exec(sql, null);
+            } else {
+            tr = conn.db.get_table(sql);
+            }
+        } else {
+            if (updonly) {
+            conn.db.exec(sql, null, args);
+            } else {
+            tr = conn.db.get_table(sql, args);
+            }
+        }
+        updcnt = (int) conn.db.changes();
+        } catch (SQLite.Exception e) {
+        if (conn.db.is3() &&
+            conn.db.last_error() == SQLite.Constants.SQLITE_BUSY &&
+            conn.busy3(conn.db, ++busy)) {
+            try {
+            if (starttrans && conn.intrans) {
+                conn.db.exec("ROLLBACK", null);
+                conn.intrans = false;
+            }
+            } catch (SQLite.Exception ee) {
+            }
+            try {
+            int ms = 20 + busy * 10;
+            if (ms > 1000) {
+                ms = 1000;
+            }
+            synchronized (this) {
+                this.wait(ms);
+            }
+            } catch (java.lang.Exception eee) {
+            }
+            continue;
+        }
+        throw new SQLException(e.toString());
+        }
+        break;
+    }
+    if (!updonly && tr == null) {
+        throw new SQLException("no result set produced");
+    }
+    if (!updonly && tr != null) {
+        rs = new JDBCResultSet(new TableResultX(tr), this);
+    }
+    return rs;
+    }
+
+    public ResultSet executeQuery(String sql) throws SQLException {
+    return executeQuery(sql, null, false);
+    }
+
+    public boolean execute(String sql) throws SQLException {
+    return executeQuery(sql) != null;
+    }
+
+    public void cancel() throws SQLException {
+    if (conn == null || conn.db == null) {
+        throw new SQLException("stale connection");
+    }
+    conn.db.interrupt();
+    }
+
+    public void clearWarnings() throws SQLException {
+    }
+
+    public Connection getConnection() throws SQLException {
+    return conn;
+    }
+
+    public void addBatch(String sql) throws SQLException {
+    if (batch == null) {
+        batch = new ArrayList<String>(1);
+    }
+    batch.add(sql);
+    }
+
+    public int[] executeBatch() throws SQLException {
+    if (batch == null) {
+        return new int[0];
+    }
+    int[] ret = new int[batch.size()];
+    for (int i = 0; i < ret.length; i++) {
+        ret[i] = EXECUTE_FAILED;
+    }
+    int errs = 0;
+    for (int i = 0; i < ret.length; i++) {
+        try {
+        execute((String) batch.get(i));
+        ret[i] = updcnt;
+        } catch (SQLException e) {
+        ++errs;
+        }
+    }
+    if (errs > 0) {
+        throw new BatchUpdateException("batch failed", ret);
+    }
+    return ret;
+    }
+
+    public void clearBatch() throws SQLException {
+    if (batch != null) {
+        batch.clear();
+        batch = null;
+    }
+    }
+
+    public void close() throws SQLException {
+    clearBatch();
+    conn = null;
+    }
+
+    public int executeUpdate(String sql) throws SQLException {
+    executeQuery(sql, null, true);
+    return updcnt;
+    }
+
+    public int getMaxFieldSize() throws SQLException {
+    return 0;
+    }
+
+    public boolean getMoreResults() throws SQLException {
+    if (rs != null) {
+        rs.close();
+        rs = null;
+    }
+    return false;
+    }
+
+    public int getUpdateCount() throws SQLException {
+    return updcnt;
+    }
+
+    public SQLWarning getWarnings() throws SQLException {
+    return null;
+    }
+
+    public void setCursorName(String name) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setEscapeProcessing(boolean enable) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public void setMaxFieldSize(int max) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public boolean getMoreResults(int x) throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public ResultSet getGeneratedKeys() throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public int executeUpdate(String sql, int autokeys)
+    throws SQLException {
+    if (autokeys != Statement.NO_GENERATED_KEYS) {
+        throw new SQLException("not supported");
+    }
+    return executeUpdate(sql);
+    }
+
+    public int executeUpdate(String sql, int colIndexes[])
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public int executeUpdate(String sql, String colIndexes[])
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public boolean execute(String sql, int autokeys)
+    throws SQLException {
+    if (autokeys != Statement.NO_GENERATED_KEYS) {
+        throw new SQLException("not supported");
+    }
+    return execute(sql);
+    }
+
+    public boolean execute(String sql, int colIndexes[])
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public boolean execute(String sql, String colIndexes[])
+    throws SQLException {
+    throw new SQLException("not supported");
+    }
+
+    public int getResultSetHoldability() throws SQLException {
+    return ResultSet.HOLD_CURSORS_OVER_COMMIT;
+    }
+
+}
diff --git a/src/main/java/SQLite/JDBC2y/TableResultX.java b/src/main/java/SQLite/JDBC2y/TableResultX.java
new file mode 100644
index 0000000..205372f
--- /dev/null
+++ b/src/main/java/SQLite/JDBC2y/TableResultX.java
@@ -0,0 +1,37 @@
+package SQLite.JDBC2y;
+
+import java.sql.Types;
+import java.util.Vector;
+
+public class TableResultX extends SQLite.TableResult {
+    public int sql_type[];
+
+    public TableResultX() {
+    super();
+    sql_type = new int[this.ncolumns];
+    for (int i = 0; i < this.ncolumns; i++) {
+        sql_type[i] = Types.VARCHAR;
+    }
+    }
+
+    public TableResultX(SQLite.TableResult tr) {
+    this.column = tr.column;
+    this.rows = tr.rows;
+    this.ncolumns = tr.ncolumns;
+    this.nrows = tr.nrows;
+    this.types = tr.types;
+    sql_type = new int[tr.ncolumns];
+    for (int i = 0; i < this.ncolumns; i++) {
+        sql_type[i] = Types.VARCHAR;
+    }
+    if (tr.types != null) {
+        for (int i = 0; i < tr.types.length; i++) {
+        sql_type[i] = JDBCDatabaseMetaData.mapSqlType(tr.types[i]);
+        }
+    }    
+    }
+
+    void sql_types(int types[]) {
+    sql_type = types;
+    } 
+}
diff --git a/src/main/java/SQLite/JDBCDriver.java b/src/main/java/SQLite/JDBCDriver.java
new file mode 100644
index 0000000..63b95ee
--- /dev/null
+++ b/src/main/java/SQLite/JDBCDriver.java
@@ -0,0 +1,109 @@
+package SQLite;
+
+import java.sql.*;
+import java.util.Properties;
+
+public class JDBCDriver implements java.sql.Driver {
+
+    public static final int MAJORVERSION = 1;
+    public static final int MINORVERSION = 2;
+
+    private static java.lang.reflect.Constructor makeConn = null;
+
+    protected Connection conn;
+
+    static {
+    try {
+        Class connClass = null;
+        Class args[] = new Class[2];
+        args[0] = Class.forName("java.lang.String");
+        args[1] = args[0];
+        String jvers = java.lang.System.getProperty("java.version");
+        String cvers;
+        if (jvers == null || jvers.startsWith("1.0")) {
+        throw new java.lang.Exception("unsupported java version");
+        } else if (jvers.startsWith("1.1")) {
+        cvers = "SQLite.JDBC1.JDBCConnection";
+        } else if (jvers.startsWith("1.2") || jvers.startsWith("1.3")) {
+        cvers = "SQLite.JDBC2.JDBCConnection";
+        } else if (jvers.startsWith("1.4")) {
+        cvers = "SQLite.JDBC2x.JDBCConnection";
+        } else if (jvers.startsWith("1.5")) {
+        cvers = "SQLite.JDBC2y.JDBCConnection";
+        try {
+            Class.forName(cvers);
+        } catch (java.lang.Exception e) {
+            cvers = "SQLite.JDBC2x.JDBCConnection";
+        }
+        } else {
+        cvers = "SQLite.JDBC2z.JDBCConnection";
+        try {
+            Class.forName(cvers);
+        } catch (java.lang.Exception e) {
+            cvers = "SQLite.JDBC2y.JDBCConnection";
+            try {
+            Class.forName(cvers);
+            } catch (java.lang.Exception ee) {
+            cvers = "SQLite.JDBC2x.JDBCConnection";
+            }
+        }
+        }
+        connClass = Class.forName(cvers);
+        makeConn = connClass.getConstructor(args);
+        java.sql.DriverManager.registerDriver(new JDBCDriver());
+    } catch (java.lang.Exception e) {
+        System.err.println(e);
+    }
+    }
+
+    public JDBCDriver() {
+    }
+    
+    public boolean acceptsURL(String url) throws SQLException {
+    return url.startsWith("sqlite:/") ||
+        url.startsWith("jdbc:sqlite:/");
+    }
+
+    public Connection connect(String url, Properties info)
+    throws SQLException {
+    if (!acceptsURL(url)) {
+        return null;
+    }
+    Object args[] = new Object[2];
+    args[0] = url;
+    if (info != null) {
+        args[1] = info.getProperty("encoding");
+    }
+    if (args[1] == null) {
+        args[1] = java.lang.System.getProperty("SQLite.encoding");
+    }
+    try {
+        conn = (Connection) makeConn.newInstance(args);
+    } catch (java.lang.reflect.InvocationTargetException ie) {
+        throw new SQLException(ie.getTargetException().toString());
+    } catch (java.lang.Exception e) {
+        throw new SQLException(e.toString());
+    }
+    return conn;
+    }
+
+    public int getMajorVersion() {
+    return MAJORVERSION;
+    }
+
+    public int getMinorVersion() {
+    return MINORVERSION;
+    }
+
+    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info)
+    throws SQLException {
+    DriverPropertyInfo p[] = new DriverPropertyInfo[1];
+    DriverPropertyInfo pp = new DriverPropertyInfo("encoding", "");
+    p[0] = pp;
+    return p;
+    }
+
+    public boolean jdbcCompliant() {
+    return false;
+    }
+}
diff --git a/src/main/java/SQLite/ProgressHandler.java b/src/main/java/SQLite/ProgressHandler.java
new file mode 100644
index 0000000..b2ec7c0
--- /dev/null
+++ b/src/main/java/SQLite/ProgressHandler.java
@@ -0,0 +1,17 @@
+package SQLite;
+
+/**
+ * Callback interface for SQLite's user defined progress handler.
+ */
+
+public interface ProgressHandler {
+
+    /**
+     * Invoked for N SQLite VM opcodes.
+     * The method should return true to continue the
+     * current query, or false in order
+     * to abandon the action.<BR><BR>
+     */
+
+    public boolean progress();
+}
diff --git a/src/main/java/SQLite/Shell.java b/src/main/java/SQLite/Shell.java
new file mode 100644
index 0000000..78d37a1
--- /dev/null
+++ b/src/main/java/SQLite/Shell.java
@@ -0,0 +1,669 @@
+package SQLite;
+
+import SQLite.*;
+import java.io.*;
+import java.util.*;
+
+/**
+ * SQLite command line shell. This is a partial reimplementaion
+ * of sqlite/src/shell.c and can be invoked by:<P>
+ *
+ * <verb>
+ *     java SQLite.Shell [OPTIONS] database [SHELLCMD]
+ * or
+ *     java -jar sqlite.jar [OPTIONS] database [SHELLCMD]
+ * </verb>
+ */
+
+public class Shell implements Callback {
+    Database db;
+    boolean echo;
+    int count;
+    int mode;
+    boolean showHeader;
+    String tableName;
+    String sep;
+    String cols[];
+    int colwidth[];
+    String destTable;
+    PrintWriter pw;
+    PrintWriter err;
+
+    static final int MODE_Line = 0;
+    static final int MODE_Column = 1;
+    static final int MODE_List = 2;
+    static final int MODE_Semi = 3;
+    static final int MODE_Html = 4;
+    static final int MODE_Insert = 5;
+    static final int MODE_Insert2 = 6;
+
+    public Shell(PrintWriter pw, PrintWriter err) {
+    this.pw = pw;
+    this.err = err;
+    }
+
+    public Shell(PrintStream ps, PrintStream errs) {
+    pw = new PrintWriter(ps);
+    err = new PrintWriter(errs);
+    }
+
+    protected Object clone() {
+        Shell s = new Shell(this.pw, this.err);
+    s.db = db;
+    s.echo = echo;
+    s.mode = mode;
+    s.count = 0;
+    s.showHeader = showHeader;
+    s.tableName = tableName;
+    s.sep = sep;
+    s.colwidth = colwidth;
+    return s;
+    }
+
+    static public String sql_quote_dbl(String str) {
+    if (str == null) {
+        return "NULL";
+    }
+    int i, single = 0, dbl = 0;
+    for (i = 0; i < str.length(); i++) {
+        if (str.charAt(i) == '\'') {
+        single++;
+        } else if (str.charAt(i) == '"') {
+        dbl++;
+        }
+    }
+    if (dbl == 0) {
+        return "\"" + str + "\"";
+    }
+    StringBuffer sb = new StringBuffer("\"");
+    for (i = 0; i < str.length(); i++) {
+        char c = str.charAt(i);
+        if (c == '"') {
+        sb.append("\"\"");
+        } else {
+        sb.append(c);
+        }
+    }
+    return sb.toString();
+    }
+
+    static public String sql_quote(String str) {
+    if (str == null) {
+        return "NULL";
+    }
+    int i, single = 0, dbl = 0;
+    for (i = 0; i < str.length(); i++) {
+        if (str.charAt(i) == '\'') {
+        single++;
+        } else if (str.charAt(i) == '"') {
+        dbl++;
+        }
+    }
+    if (single == 0) {
+        return "'" + str + "'";
+    }
+    if (dbl == 0) {
+        return "\"" + str + "\"";
+    }
+    StringBuffer sb = new StringBuffer("'");
+    for (i = 0; i < str.length(); i++) {
+        char c = str.charAt(i);
+        if (c == '\'') {
+        sb.append("''");
+        } else {
+        sb.append(c);
+        }
+    }
+    return sb.toString();
+    }
+
+    static String html_quote(String str) {
+    if (str == null) {
+        return "NULL";
+    }
+    StringBuffer sb = new StringBuffer();
+    for (int i = 0; i < str.length(); i++) {
+        char c = str.charAt(i);
+        if (c == '<') {
+        sb.append("&lt;");
+        } else if (c == '>') {
+        sb.append("&gt;");
+        } else if (c == '&') {
+        sb.append("&amp;");
+        } else {
+        int x = c;
+        if (x < 32 || x > 127) {
+            sb.append("&#" + x + ";");
+        } else {
+            sb.append(c);
+        }
+        }
+    }
+    return sb.toString();
+    }
+
+    static boolean is_numeric(String str) {
+    try {
+        Double d = Double.valueOf(str);
+    } catch (java.lang.Exception e) {
+        return false;
+    }
+    return true;
+    }
+
+    void set_table_name(String str) {
+    if (str == null) {
+        tableName = "";
+        return;
+    }
+    tableName = Shell.sql_quote(str);
+    }
+
+    public void columns(String args[]) {
+    cols = args;
+    }
+
+    public void types(String args[]) {
+    /* Empty body to satisfy SQLite.Callback interface. */
+    }
+
+    public boolean newrow(String args[]) {
+    int i;
+    String tname;
+    switch (mode) {
+    case Shell.MODE_Line:
+        if (args.length == 0) {
+        break;
+        }
+        if (count++ > 0) {
+        pw.println("");
+        }
+        for (i = 0; i < args.length; i++) {
+        pw.println(cols[i] + " = " +
+               args[i] == null ? "NULL" : args[i]);
+        }
+        break;
+    case Shell.MODE_Column:
+        String csep = "";
+        if (count++ == 0) {
+        colwidth = new int[args.length];
+        for (i = 0; i < args.length; i++) {
+            int w, n;
+            w = cols[i].length();
+            if (w < 10) {
+            w = 10;
+            }
+            colwidth[i] = w;
+            if (showHeader) {
+            pw.print(csep + cols[i]);
+            csep = " ";
+            }
+        }
+        if (showHeader) {
+            pw.println("");
+        }
+        }
+        if (args.length == 0) {
+        break;
+        }
+        csep = "";
+        for (i = 0; i < args.length; i++) {
+        pw.print(csep + (args[i] == null ? "NULL" : args[i]));
+        csep = " ";
+        }
+        pw.println("");
+        break;
+    case Shell.MODE_Semi:
+    case Shell.MODE_List:
+        if (count++ == 0 && showHeader) {
+        for (i = 0; i < args.length; i++) {
+            pw.print(cols[i] +
+                 (i == args.length - 1 ? "\n" : sep));
+        }
+        }
+        if (args.length == 0) {
+        break;
+        }
+        for (i = 0; i < args.length; i++) {
+        pw.print(args[i] == null ? "NULL" : args[i]);
+        if (mode == Shell.MODE_Semi) {
+            pw.print(";");
+        } else if (i < args.length - 1) {
+            pw.print(sep);
+        }
+        }
+        pw.println("");
+        break;
+    case MODE_Html:
+        if (count++ == 0 && showHeader) {
+        pw.print("<TR>");
+        for (i = 0; i < args.length; i++) {
+            pw.print("<TH>" + html_quote(cols[i]) + "</TH>");
+        }
+        pw.println("</TR>");
+        }
+        if (args.length == 0) {
+        break;
+        }
+        pw.print("<TR>");
+        for (i = 0; i < args.length; i++) {
+        pw.print("<TD>" + html_quote(args[i]) + "</TD>");
+        }
+        pw.println("</TR>");
+        break;
+    case MODE_Insert:
+        if (args.length == 0) {
+        break;
+        }
+        tname = tableName;
+        if (destTable != null) {
+            tname = destTable;
+        }
+        pw.print("INSERT INTO " + tname + " VALUES(");
+        for (i = 0; i < args.length; i++) {
+            String tsep = i > 0 ? "," : "";
+        if (args[i] == null) {
+            pw.print(tsep + "NULL");
+        } else if (is_numeric(args[i])) {
+            pw.print(tsep + args[i]);
+        } else {
+            pw.print(tsep + sql_quote(args[i]));
+        }
+        }
+        pw.println(");");
+        break;
+    case MODE_Insert2:
+        if (args.length == 0) {
+        break;
+        }
+        tname = tableName;
+        if (destTable != null) {
+            tname = destTable;
+        }
+        pw.print("INSERT INTO " + tname + " VALUES(");
+        for (i = 0; i < args.length; i++) {
+            String tsep = i > 0 ? "," : "";
+        pw.print(tsep + args[i]);
+        }
+        pw.println(");");
+        break;
+    }
+    return false;
+    }
+
+    void do_meta(String line) {
+        StringTokenizer st = new StringTokenizer(line.toLowerCase());
+    int n = st.countTokens();
+    if (n <= 0) {
+        return;
+    }
+    String cmd = st.nextToken();
+    String args[] = new String[n - 1];
+    int i = 0;
+    while (st.hasMoreTokens()) {
+        args[i] = st.nextToken();
+        ++i;
+    }
+    if (cmd.compareTo(".dump") == 0) {
+        new DBDump(this, args);
+        return;
+    }
+    if (cmd.compareTo(".echo") == 0) {
+        if (args.length > 0 &&
+        (args[0].startsWith("y") || args[0].startsWith("on"))) {
+        echo = true;
+        }
+        return;
+    }
+    if (cmd.compareTo(".exit") == 0) {
+        try {
+        db.close();
+        } catch (Exception e) {
+        }
+        System.exit(0);
+    }
+    if (cmd.compareTo(".header") == 0) {
+        if (args.length > 0 &&
+        (args[0].startsWith("y") || args[0].startsWith("on"))) {
+        showHeader = true;
+        }
+        return;
+    }
+    if (cmd.compareTo(".help") == 0) {
+        pw.println(".dump ?TABLE? ...  Dump database in text fmt");
+        pw.println(".echo ON|OFF       Command echo on or off");
+        pw.println(".enc ?NAME?        Change encoding");
+        pw.println(".exit              Exit program");
+        pw.println(".header ON|OFF     Display headers on or off");
+        pw.println(".help              This message");
+        pw.println(".mode MODE         Set output mode to\n" +
+               "                   line, column, insert\n" +
+               "                   list, or html");
+        pw.println(".mode insert TABLE Generate SQL insert stmts");
+        pw.println(".schema ?PATTERN?  List table schema");
+        pw.println(".separator STRING  Set separator string");
+        pw.println(".tables ?PATTERN?  List table names");
+        return;
+    }
+    if (cmd.compareTo(".mode") == 0) {
+        if (args.length > 0) {
+        if (args[0].compareTo("line") == 0) {
+            mode = Shell.MODE_Line;
+        } else if (args[0].compareTo("column") == 0) {
+            mode = Shell.MODE_Column;
+        } else if (args[0].compareTo("list") == 0) {
+            mode = Shell.MODE_List;
+        } else if (args[0].compareTo("html") == 0) {
+            mode = Shell.MODE_Html;
+        } else if (args[0].compareTo("insert") == 0) {
+            mode = Shell.MODE_Insert;
+            if (args.length > 1) {
+            destTable = args[1];
+            }
+        }
+        }
+        return;
+    }
+    if (cmd.compareTo(".separator") == 0) {
+        if (args.length > 0) {
+        sep = args[0];
+        }
+        return;
+    }
+    if (cmd.compareTo(".tables") == 0) {
+        TableResult t = null;
+        if (args.length > 0) {
+        try {
+            String qarg[] = new String[1];
+            qarg[0] = args[0];
+            t = db.get_table("SELECT name FROM sqlite_master " +
+                     "WHERE type='table' AND " +
+                     "name LIKE '%%%q%%' " +
+                     "ORDER BY name", qarg);
+        } catch (Exception e) {
+            err.println("SQL Error: " + e);
+            err.flush();
+        }
+        } else {
+        try {
+            t = db.get_table("SELECT name FROM sqlite_master " +
+                     "WHERE type='table' ORDER BY name");
+        } catch (Exception e) {
+            err.println("SQL Error: " + e);
+            err.flush();
+        }
+        }
+        if (t != null) {
+        for (i = 0; i < t.nrows; i++) {
+            String tab = ((String[]) t.rows.elementAt(i))[0];
+            if (tab != null) {
+            pw.println(tab);
+            }
+        }
+        }
+        return;
+    }
+    if (cmd.compareTo(".schema") == 0) {
+        if (args.length > 0) {
+        try {
+            String qarg[] = new String[1];
+            qarg[0] = args[0];
+            db.exec("SELECT sql FROM sqlite_master " +
+                "WHERE type!='meta' AND " +
+                "name LIKE '%%%q%%' AND " +
+                "sql NOTNULL " +
+                "ORDER BY type DESC, name",
+                this, qarg);
+        } catch (Exception e) {
+            err.println("SQL Error: " + e);
+            err.flush();
+        }
+        } else {
+        try {
+            db.exec("SELECT sql FROM sqlite_master " +
+                "WHERE type!='meta' AND " +
+                "sql NOTNULL " +
+                "ORDER BY tbl_name, type DESC, name",
+                this);
+        } catch (Exception e) {
+            err.println("SQL Error: " + e);
+            err.flush();
+        }
+        }
+        return;
+    }
+    if (cmd.compareTo(".enc") == 0) {
+        try {
+        db.set_encoding(args.length > 0 ? args[0] : null);
+        } catch (Exception e) {
+        err.println("" + e);
+        err.flush();
+        }
+        return;
+    }
+    err.println("Unknown command '" + cmd + "'");
+    err.flush();
+    }
+
+    String read_line(BufferedReader is, String prompt) {
+    try {
+        if (prompt != null) {
+        pw.print(prompt);
+        pw.flush();
+        }
+        String line = is.readLine();
+        return line;
+    } catch (IOException e) {
+        return null;
+    }
+    }
+
+    void do_input(BufferedReader is) {
+    String line, sql = null;
+    String prompt = "SQLITE> ";
+    while ((line = read_line(is, prompt)) != null) {
+        if (echo) {
+        pw.println(line);
+        }
+        if (line.length() > 0 && line.charAt(0) == '.') {
+            do_meta(line);
+        } else {
+        if (sql == null) {
+            sql = line;
+        } else {
+            sql = sql + " " + line;
+        }
+        if (Database.complete(sql)) {
+            try {
+            db.exec(sql, this);
+            } catch (Exception e) {
+            if (!echo) {
+                err.println(sql);
+            }
+            err.println("SQL Error: " + e);
+            err.flush();
+            }
+            sql = null;
+            prompt = "SQLITE> ";
+        } else {
+            prompt = "SQLITE? ";
+        }
+        }
+        pw.flush();
+    }
+    if (sql != null) {
+        err.println("Incomplete SQL: " + sql);
+        err.flush();
+    }
+    }
+
+    void do_cmd(String sql) {
+        if (db == null) {
+        return;
+    }
+        if (sql.length() > 0 && sql.charAt(0) == '.') {
+        do_meta(sql);
+    } else {
+        try {
+            db.exec(sql, this);
+        } catch (Exception e) {
+        err.println("SQL Error: " + e);
+        err.flush();
+        }
+    }
+    }
+
+    public static void main(String args[]) {
+    Shell s = new Shell(System.out, System.err);
+    s.mode = Shell.MODE_List;
+    s.sep = "|";
+    s.showHeader = false;
+    s.db = new Database();
+    String dbname = null, sql = null;
+    for (int i = 0; i < args.length; i++) {
+        if(args[i].compareTo("-html") ==0) {
+        s.mode = Shell.MODE_Html;
+        } else if (args[i].compareTo("-list") == 0) {
+        s.mode = Shell.MODE_List;
+        } else if (args[i].compareTo("-line") == 0) {
+        s.mode = Shell.MODE_Line;
+        } else if (i < args.length - 1 &&
+               args[i].compareTo("-separator") == 0) {
+        ++i;
+        s.sep = args[i];
+        } else if (args[i].compareTo("-header") == 0) {
+        s.showHeader = true;
+        } else if (args[i].compareTo("-noheader") == 0) {
+        s.showHeader = false;
+        } else if (args[i].compareTo("-echo") == 0) {
+        s.echo = true;
+        } else if (dbname == null) {
+        dbname = args[i];
+        } else if (sql == null) {
+        sql = args[i];
+        } else {
+        System.err.println("Arguments: ?OPTIONS? FILENAME ?SQL?");
+        System.exit(1);
+        }
+    }
+    if (dbname == null) {
+        System.err.println("No database file given");
+        System.exit(1);
+    }
+    try {
+        s.db.open(dbname, 0);
+    } catch (Exception e) {
+        System.err.println("Unable to open database: " + e);
+        System.exit(1);
+    }
+    if (sql != null) {
+        s.do_cmd(sql);
+    } else {
+        // BEGIN android-modified
+        BufferedReader is =
+            new BufferedReader(new InputStreamReader(System.in), 8192);
+        // END android-modified
+        s.do_input(is);
+    }
+    try {
+        s.db.close();
+    } catch (Exception ee) {
+    }
+    }
+}
+
+/**
+ * Internal class for dumping an entire database.
+ * It contains a special callback interface to traverse the
+ * tables of the current database and output create SQL statements
+ * and for the data insert SQL statements.
+ */
+
+class DBDump implements Callback {
+    Shell s;
+
+    DBDump(Shell s, String tables[]) {
+        this.s = s;
+    s.pw.println("BEGIN TRANSACTION;");
+        if (tables == null || tables.length == 0) {
+        try {
+            s.db.exec("SELECT name, type, sql FROM sqlite_master " +
+              "WHERE type!='meta' AND sql NOT NULL " +
+              "ORDER BY substr(type,2,1), name", this);
+        } catch (Exception e) {
+            s.err.println("SQL Error: " + e);
+        s.err.flush();
+        }
+    } else {
+        String arg[] = new String[1];
+        for (int i = 0; i < tables.length; i++) {
+            arg[0] = tables[i];
+        try {
+            s.db.exec("SELECT name, type, sql FROM sqlite_master " +
+                  "WHERE tbl_name LIKE '%q' AND type!='meta' " +
+                  " AND sql NOT NULL " +
+                  " ORDER BY substr(type,2,1), name",
+                  this, arg);
+        } catch (Exception e) {
+            s.err.println("SQL Error: " + e);
+            s.err.flush();
+        }
+        }
+    }
+    s.pw.println("COMMIT;");
+    }
+
+    public void columns(String col[]) {
+    /* Empty body to satisfy SQLite.Callback interface. */
+    }
+
+    public void types(String args[]) {
+    /* Empty body to satisfy SQLite.Callback interface. */
+    }
+
+    public boolean newrow(String args[]) {
+        if (args.length != 3) {
+        return true;
+    }
+    s.pw.println(args[2] + ";");
+    if (args[1].compareTo("table") == 0) {
+        Shell s2 = (Shell) s.clone();
+        s2.mode = Shell.MODE_Insert;
+        s2.set_table_name(args[0]);
+        String qargs[] = new String[1];
+        qargs[0] = args[0];
+        try {
+            if (s2.db.is3()) {
+            TableResult t = null;
+            t = s2.db.get_table("PRAGMA table_info('%q')", qargs);
+            String query;
+            if (t != null) {
+                StringBuffer sb = new StringBuffer();
+            String sep = "";
+
+            sb.append("SELECT ");
+            for (int i = 0; i < t.nrows; i++) {
+                String col = ((String[]) t.rows.elementAt(i))[1];
+                sb.append(sep + "quote(" +
+                      Shell.sql_quote_dbl(col) + ")");
+                sep = ",";
+            }
+            sb.append(" from '%q'");
+            query = sb.toString();
+            s2.mode = Shell.MODE_Insert2;
+            } else {
+                query = "SELECT * from '%q'";
+            }
+            s2.db.exec(query, s2, qargs);
+        } else {
+            s2.db.exec("SELECT * from '%q'", s2, qargs);
+        }
+        } catch (Exception e) {
+            s.err.println("SQL Error: " + e);
+        s.err.flush();
+        return true;
+        }
+    }
+    return false;
+    }
+}
diff --git a/src/main/java/SQLite/Stmt.java b/src/main/java/SQLite/Stmt.java
new file mode 100644
index 0000000..c4f72ed
--- /dev/null
+++ b/src/main/java/SQLite/Stmt.java
@@ -0,0 +1,288 @@
+package SQLite;
+
+/**
+ * Class to represent compiled SQLite3 statement.
+ *
+ * Note, that all native methods of this class are
+ * not synchronized, i.e. it is up to the caller
+ * to ensure that only one thread is in these
+ * methods at any one time.
+ */
+
+public class Stmt {
+
+    /**
+     * Internal handle for the SQLite3 statement.
+     */
+
+    private long handle = 0;
+
+    /**
+     * Internal last error code for prepare()/step() methods.
+     */
+
+    protected int error_code = 0;
+
+    /**
+     * Prepare the next SQL statement for the Stmt instance.
+     * @return true when the next piece of the SQL statement sequence
+     * has been prepared, false on end of statement sequence.
+     */
+
+    public native boolean prepare() throws SQLite.Exception;
+
+    /**
+     * Perform one step of compiled SQLite3 statement.
+     *
+     * Example:<BR>
+     * <PRE>
+     *   ...
+     *   try {
+     *     Stmt s = db.prepare("select * from x; select * from y;");
+     *     s.bind(...);
+     *     ...
+     *     s.bind(...);
+     *     while (s.step(cb)) {
+     *       Object o = s.value(...);
+     *       ...
+     *     }
+     *     // s.reset() for re-execution or
+     *     // s.prepare() for the next piece of SQL
+     *     while (s.prepare()) {
+     *       s.bind(...);
+     *       ...
+     *       s.bind(...);
+     *       while (s.step(cb)) {
+     *         Object o = s.value(...);
+     *         ...
+     *       }
+     *     }
+     *   } catch (SQLite.Exception e) {
+     *     s.close();
+     *   }
+     * </PRE>
+     *
+     * @return true when row data is available, false on end
+     * of result set.
+     */
+
+    public native boolean step() throws SQLite.Exception;
+
+    /**
+     * Close the compiled SQLite3 statement.
+     */
+
+    public native void close() throws SQLite.Exception;
+
+    /**
+     * Reset the compiled SQLite3 statement without
+     * clearing parameter bindings.
+     */
+
+    public native void reset() throws SQLite.Exception;
+
+    /**
+     * Clear all bound parameters of the compiled SQLite3 statement.
+     */
+
+    public native void clear_bindings() throws SQLite.Exception;
+
+    /**
+     * Bind positional integer value to compiled SQLite3 statement.
+     * @param pos parameter index, 1-based
+     * @param value value of parameter
+     */
+
+    public native void bind(int pos, int value) throws SQLite.Exception;
+
+    /**
+     * Bind positional long value to compiled SQLite3 statement.
+     * @param pos parameter index, 1-based
+     * @param value value of parameter
+     */
+
+    public native void bind(int pos, long value) throws SQLite.Exception;
+
+    /**
+     * Bind positional double value to compiled SQLite3 statement.
+     * @param pos parameter index, 1-based
+     * @param value value of parameter
+     */
+
+    public native void bind(int pos, double value) throws SQLite.Exception;
+
+    /**
+     * Bind positional byte array to compiled SQLite3 statement.
+     * @param pos parameter index, 1-based
+     * @param value value of parameter, may be null
+     */
+
+    public native void bind(int pos, byte[] value) throws SQLite.Exception;
+
+    /**
+     * Bind positional String to compiled SQLite3 statement.
+     * @param pos parameter index, 1-based
+     * @param value value of parameter, may be null
+     */
+
+    public native void bind(int pos, String value) throws SQLite.Exception;
+
+    /**
+     * Bind positional SQL null to compiled SQLite3 statement.
+     * @param pos parameter index, 1-based
+     */
+
+    public native void bind(int pos) throws SQLite.Exception;
+
+    /**
+     * Bind positional zero'ed blob to compiled SQLite3 statement.
+     * @param pos parameter index, 1-based
+     * @param length byte size of zero blob
+     */
+
+    public native void bind_zeroblob(int pos, int length)
+    throws SQLite.Exception;
+
+    /**
+     * Return number of parameters in compiled SQLite3 statement.
+     * @return int number of parameters
+     */
+
+    public native int bind_parameter_count() throws SQLite.Exception;
+
+    /**
+     * Return name of parameter in compiled SQLite3 statement.
+     * @param pos parameter index, 1-based
+     * @return String parameter name
+     */
+
+    public native String bind_parameter_name(int pos) throws SQLite.Exception;
+
+    /**
+     * Return index of named parameter in compiled SQLite3 statement.
+     * @param name of parameter
+     * @return int index of parameter, 1-based
+     */
+
+    public native int bind_parameter_index(String name)
+    throws SQLite.Exception;
+
+
+    /**
+     * Retrieve integer column from exec'ed SQLite3 statement.
+     * @param col column number, 0-based
+     * @return int column value
+     */
+
+    public native int column_int(int col) throws SQLite.Exception;
+
+    /**
+     * Retrieve long column from exec'ed SQLite3 statement.
+     * @param col column number, 0-based
+     * @return long column value
+     */
+    public native long column_long(int col) throws SQLite.Exception;
+
+    /**
+     * Retrieve double column from exec'ed SQLite3 statement.
+     * @param col column number, 0-based
+     * @return double column value
+     */
+    public native double column_double(int col) throws SQLite.Exception;
+
+    /**
+     * Retrieve blob column from exec'ed SQLite3 statement.
+     * @param col column number, 0-based
+     * @return byte[] column value
+     */
+    public native byte[] column_bytes(int col) throws SQLite.Exception;
+
+    /**
+     * Retrieve string column from exec'ed SQLite3 statement.
+     * @param col column number, 0-based
+     * @return String column value
+     */
+    public native String column_string(int col) throws SQLite.Exception;
+
+    /**
+     * Retrieve column type from exec'ed SQLite3 statement.
+     * @param col column number, 0-based
+     * @return column type code, e.g. SQLite.Constants.SQLITE_INTEGER
+     */
+    public native int column_type(int col) throws SQLite.Exception;
+
+    /**
+     * Retrieve number of columns of exec'ed SQLite3 statement.
+     * @return int number of columns
+     */
+
+    public native int column_count() throws SQLite.Exception;
+
+    /**
+     * Retrieve column data as object from exec'ed SQLite3 statement.
+     * @param col column number, 0-based
+     * @return Object or null
+     */
+
+    public Object column(int col) throws SQLite.Exception {
+        switch (column_type(col)) {
+    case Constants.SQLITE_INTEGER:
+        return new Long(column_long(col));
+    case Constants.SQLITE_FLOAT:
+        return new Double(column_double(col));
+    case Constants.SQLITE_BLOB:
+        return column_bytes(col);
+    case Constants.SQLITE3_TEXT:
+        return column_string(col);
+    }
+    return null;
+    }
+
+    /**
+     * Return table name of column of SQLite3 statement.
+     * @param col column number, 0-based
+     * @return String or null
+     */
+
+    public native String column_table_name(int col) throws SQLite.Exception;
+
+    /**
+     * Return database name of column of SQLite3 statement.
+     * @param col column number, 0-based
+     * @return String or null
+     */
+
+    public native String column_database_name(int col) throws SQLite.Exception;
+
+    /**
+     * Return declared column type of SQLite3 statement.
+     * @param col column number, 0-based
+     * @return String or null
+     */
+
+    public native String column_decltype(int col) throws SQLite.Exception;
+
+    /**
+     * Return origin column name of column of SQLite3 statement.
+     * @param col column number, 0-based
+     * @return String or null
+     */
+
+    public native String column_origin_name(int col) throws SQLite.Exception;
+
+    /**
+     * Destructor for object.
+     */
+
+    protected native void finalize();
+
+    /**
+     * Internal native initializer.
+     */
+
+    private static native void internal_init();
+
+    static {
+    internal_init();
+    }
+}
diff --git a/src/main/java/SQLite/StringEncoder.java b/src/main/java/SQLite/StringEncoder.java
new file mode 100644
index 0000000..c2f20ad
--- /dev/null
+++ b/src/main/java/SQLite/StringEncoder.java
@@ -0,0 +1,201 @@
+package SQLite;
+
+/**
+ * String encoder/decoder for SQLite.
+ *
+ * This module was kindly donated by Eric van der Maarel of Nedap N.V.
+ *
+ * This encoder was implemented based on an original idea from an anonymous
+ * author in the source code of the SQLite distribution.
+ * I feel obliged to provide a quote from the original C-source code:
+ *
+ * "The author disclaims copyright to this source code.  In place of
+ *  a legal notice, here is a blessing:
+ *
+ *     May you do good and not evil.
+ *     May you find forgiveness for yourself and forgive others.
+ *     May you share freely, never taking more than you give."
+ *
+ */
+
+public class StringEncoder {
+
+    /**
+     * Encodes the given byte array into a string that can be used by
+     * the SQLite database. The database cannot handle null (0x00) and
+     * the character '\'' (0x27). The encoding consists of escaping
+     * these characters with a reserved character (0x01). The escaping
+     * is applied after determining and applying a shift that minimizes
+     * the number of escapes required.
+     * With this encoding the data of original size n is increased to a
+     * maximum of 1+(n*257)/254.
+     * For sufficiently large n the overhead is thus less than 1.2%.
+     * @param a the byte array to be encoded. A null reference is handled as
+     *     an empty array.
+     * @return the encoded bytes as a string. When an empty array is
+     *     provided a string of length 1 is returned, the value of
+     *     which is bogus.
+     *     When decoded with this class' <code>decode</code> method
+     *     a string of size 1 will return an empty byte array.
+     */
+
+    public static String encode(byte[] a) {
+    // check input
+    if (a == null || a.length == 0) {
+        // bogus shift, no data
+        return "x";
+    }
+    // determine count
+    int[] cnt = new int[256];
+    for (int i = 0 ; i < a.length; i++) {
+        cnt[a[i] & 0xff]++;
+    }
+    // determine shift for minimum number of escapes
+    int shift = 1;
+    int nEscapes = a.length;
+    for (int i = 1; i < 256; i++) {
+        if (i == '\'') {
+        continue;
+        }
+        int sum = cnt[i] + cnt[(i + 1) & 0xff] + cnt[(i + '\'') & 0xff];
+        if (sum < nEscapes) {
+        nEscapes = sum;
+        shift = i;
+        if (nEscapes == 0) {
+            // cannot become smaller
+            break;
+        }
+        }
+    }
+    // construct encoded output
+    int outLen = a.length + nEscapes + 1;
+    StringBuffer out = new StringBuffer(outLen);
+    out.append((char)shift);
+    for (int i = 0; i < a.length; i++) {
+        // apply shift
+        char c = (char)((a[i] - shift)&0xff);
+        // insert escapes
+        if (c == 0) { // forbidden
+        out.append((char)1);
+        out.append((char)1);
+        } else if (c == 1) { // escape character
+        out.append((char)1);
+        out.append((char)2);
+        } else if (c == '\'') { // forbidden
+        out.append((char)1);
+        out.append((char)3);
+        } else {
+        out.append(c);
+        }
+    }
+    return out.toString();
+    }
+
+    /**
+     * Decodes the given string that is assumed to be a valid encoding
+     * of a byte array. Typically the given string is generated by
+     * this class' <code>encode</code> method.
+     * @param s the given string encoding.
+     * @return the byte array obtained from the decoding.
+     * @throws IllegalArgumentException when the string given is not
+     *    a valid encoded string for this encoder.
+     */
+
+    public static byte[] decode(String s) {
+    char[] a = s.toCharArray();
+    if (a.length > 2 && a[0] == 'X' &&
+        a[1] == '\'' && a[a.length-1] == '\'') {
+        // SQLite3 BLOB syntax
+        byte[] result = new byte[(a.length-3)/2];
+        for (int i = 2, k = 0; i < a.length - 1; i += 2, k++) {
+        byte tmp = (byte) (a[i] - '0');
+        if (tmp > 15) {
+            tmp -= 0x20;
+        }
+        result[k] = (byte) (tmp << 4);
+        tmp = (byte) (a[i+1] - '0');
+        if (tmp > 15) {
+            tmp -= 0x20;
+        }
+        result[k] |= tmp;
+        }
+        return result;
+    }
+    // first element is the shift
+    byte[] result = new byte[a.length-1];
+    int i = 0;
+    int shift = s.charAt(i++);
+    int j = 0;
+    while (i < s.length()) {
+        int c;
+        if ((c = s.charAt(i++)) == 1) { // escape character found
+        if ((c = s.charAt(i++)) == 1) {
+            c = 0;
+        } else if (c == 2) {
+            c = 1;
+        } else if (c == 3) {
+            c = '\'';
+        } else {
+            throw new IllegalArgumentException(
+            "invalid string passed to decoder: " + j);
+        }
+        }
+        // do shift
+        result[j++] = (byte)((c + shift) & 0xff);
+    }
+    int outLen = j;
+    // provide array of correct length
+    if (result.length != outLen) {
+        result = byteCopy(result, 0, outLen, new byte[outLen]);
+    }
+    return result;
+    }
+
+    /**
+     * Copies count elements from source, starting at element with
+     * index offset, to the given target.
+     * @param source the source.
+     * @param offset the offset.
+     * @param count the number of elements to be copied.
+     * @param target the target to be returned.
+     * @return the target being copied to.
+     */
+
+    private static byte[] byteCopy(byte[] source, int offset,
+                   int count, byte[] target) {
+    for (int i = offset, j = 0; i < offset + count; i++, j++) {
+        target[j] = source[i];
+    }
+    return target;
+    }
+
+
+    static final char[] xdigits = {
+    '0', '1', '2', '3', '4', '5', '6', '7',
+    '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+    };
+
+    /**
+     * Encodes the given byte array into SQLite3 blob notation, ie X'..'
+     * @param a the byte array to be encoded. A null reference is handled as
+     *     an empty array.
+     * @return the encoded bytes as a string.
+     */
+
+    public static String encodeX(byte[] a) {
+    // check input
+    if (a == null || a.length == 0) {
+        return "X''";
+    }
+    int outLen = a.length + 3;
+    StringBuffer out = new StringBuffer(outLen);
+    out.append('X');
+    out.append('\'');
+    for (int i = 0; i < a.length; i++) {
+        out.append(xdigits[a[i] >> 4]);
+        out.append(xdigits[a[i] & 0x0F]);
+    }
+    out.append('\'');
+    return out.toString();
+    }
+}
diff --git a/src/main/java/SQLite/TableResult.java b/src/main/java/SQLite/TableResult.java
new file mode 100644
index 0000000..1a7fb57
--- /dev/null
+++ b/src/main/java/SQLite/TableResult.java
@@ -0,0 +1,133 @@
+package SQLite;
+
+import java.util.Vector;
+
+/**
+ * Class representing an SQLite result set as
+ * returned by the
+ * <A HREF="Database.html#get_table(java.lang.String)">Database.get_table</A>
+ * convenience method.
+ * <BR><BR>
+ * Example:<BR>
+ *
+ * <PRE>
+ *   ...
+ *   SQLite.Database db = new SQLite.Database();
+ *   db.open("db", 0);
+ *   System.out.print(db.get_table("select * from TEST"));
+ *   ...
+ * </PRE>
+ * Example output:<BR>
+ *
+ * <PRE>
+ *   id|firstname|lastname|
+ *   0|John|Doe|
+ *   1|Speedy|Gonzales|
+ *   ...
+ * </PRE>
+ */
+
+public class TableResult implements Callback {
+
+    /**
+     * Number of columns in the result set.
+     */
+
+    public int ncolumns;
+
+    /**
+     * Number of rows in the result set.
+     */
+
+    public int nrows;
+
+    /**
+     * Column names of the result set.
+     */
+
+    public String column[];
+
+    /**
+     * Types of columns of the result set or null.
+     */
+
+    public String types[];
+
+    /**
+     * Rows of the result set. Each row is stored as a String array.
+     */
+
+    public Vector rows;
+
+    /**
+     * Create an empty result set.
+     */
+
+    public TableResult() {
+    clear();
+    }
+
+    /**
+     * Clear result set.
+     */
+
+    public void clear() {
+    column = new String[0];
+    types = null;
+    rows = new Vector();
+    ncolumns = nrows = 0;
+    }
+
+    /**
+     * Callback method used while the query is executed.
+     */
+
+    public void columns(String coldata[]) {
+    column = coldata;
+    ncolumns = column.length;
+    }
+
+    /**
+     * Callback method used while the query is executed.
+     */
+
+    public void types(String types[]) {
+    this.types = types;
+    }
+
+    /**
+     * Callback method used while the query is executed.
+     */
+
+    public boolean newrow(String rowdata[]) {
+    if (rowdata != null) {
+        rows.addElement(rowdata);
+        nrows++;
+    }
+    return false;
+    }
+
+    /**
+     * Make String representation of result set.
+     */
+
+    public String toString() {
+    StringBuffer sb = new StringBuffer();
+    int i;
+    for (i = 0; i < ncolumns; i++) {
+        sb.append(column[i] == null ? "NULL" : column[i]);
+        sb.append('|');
+    }
+    sb.append('\n');
+    for (i = 0; i < nrows; i++) {
+        int k;
+        String row[] = (String[]) rows.elementAt(i);
+        for (k = 0; k < ncolumns; k++) {
+        sb.append(row[k] == null ? "NULL" : row[k]);
+        sb.append('|');
+        }
+        sb.append('\n');
+    }
+    return sb.toString();
+    }
+}
diff --git a/src/main/java/SQLite/Trace.java b/src/main/java/SQLite/Trace.java
new file mode 100644
index 0000000..19ed2a1
--- /dev/null
+++ b/src/main/java/SQLite/Trace.java
@@ -0,0 +1,17 @@
+package SQLite;
+
+/**
+ * Callback interface for SQLite's trace function.
+ */
+
+public interface Trace {
+
+    /**
+     * Callback to trace (ie log) one SQL statement.
+     *
+     * @param stmt SQL statement string
+     */
+
+    public void trace(String stmt);
+}
+
diff --git a/src/main/java/SQLite/Vm.java b/src/main/java/SQLite/Vm.java
new file mode 100644
index 0000000..9856ed0
--- /dev/null
+++ b/src/main/java/SQLite/Vm.java
@@ -0,0 +1,78 @@
+package SQLite;
+
+/**
+ * Class to represent compiled SQLite VM.
+ */
+
+public class Vm {
+
+    /**
+     * Internal handle for the compiled SQLite VM.
+     */
+
+    private long handle = 0;
+
+    /**
+     * Internal last error code for compile()/step() methods.
+     */
+
+    protected int error_code = 0;
+
+    /**
+     * Perform one step on compiled SQLite VM.
+     * The result row is passed to the given callback interface.<BR><BR>
+     *
+     * Example:<BR>
+     * <PRE>
+     *   ...
+     *   try {
+     *     Vm vm = db.compile("select * from x; select * from y;");
+     *     while (vm.step(cb)) {
+     *       ...
+     *     }
+     *     while (vm.compile()) {
+     *       while (vm.step(cb)) {
+     *         ...
+     *       }
+     *     }
+     *   } catch (SQLite.Exception e) {
+     *   }
+     * </PRE>
+     *
+     * @param cb the object implementing the callback methods.
+     * @return true as long as more row data can be retrieved,
+     * false, otherwise.
+     */
+
+    public native boolean step(Callback cb) throws SQLite.Exception;
+
+    /**
+     * Compile the next SQL statement for the SQLite VM instance.
+     * @return true when SQL statement has been compiled, false
+     * on end of statement sequence.
+     */
+
+    public native boolean compile() throws SQLite.Exception;
+
+    /**
+     * Abort the compiled SQLite VM.
+     */
+
+    public native void stop() throws SQLite.Exception;
+
+    /**
+     * Destructor for object.
+     */
+
+    protected native void finalize();
+
+    /**
+     * Internal native initializer.
+     */
+
+    private static native void internal_init();
+
+    static {
+    internal_init();
+    }
+}
diff --git a/src/main/native/sqlite_jni.c b/src/main/native/sqlite_jni.c
new file mode 100644
index 0000000..1333d24
--- /dev/null
+++ b/src/main/native/sqlite_jni.c
@@ -0,0 +1,4404 @@
+#include "JNIHelp.h"
+#include "sqlite_jni_defs.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.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 jsize utfLength = (*env)->GetStringUTFLength(env, src);
+        dest->result = dest->tofree = malloc(utfLength + 1);
+        if (!dest->tofree) {
+            throwoom(env, "string translation failed");
+            return dest->result;
+        }
+        (*env)->GetStringUTFRegion(env, src, 0, utfLength, dest->result);
+        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) {
+	    (*env)->DeleteLocalRef(env, cls);
+	    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);
+	(*env)->DeleteLocalRef(env, cls);
+    }
+    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) {
+	    (*env)->DeleteLocalRef(env, cls);
+	    return ret;
+	}
+	ret = (*env)->CallBooleanMethod(env, h->bh, mid, 0, (jint) count)
+	    != JNI_FALSE;
+	(*env)->DeleteLocalRef(env, cls);
+    }
+    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) {
+	    (*env)->DeleteLocalRef(env, cls);
+	    return ret;
+	}
+	ret = (*env)->CallBooleanMethod(env, h->ph, mid) != JNI_TRUE;
+	(*env)->DeleteLocalRef(env, cls);
+    }
+    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) {
+	    jthrowable exc;
+	    int rc = SQLITE_ERROR, nargs, i;
+	    char *err = 0, *p;
+	    const char *str = (*env)->GetStringUTFChars(env, sql, NULL);
+	    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) {
+	    (*env)->DeleteLocalRef(env, cls);
+	    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) {
+	    (*env)->DeleteLocalRef(env, cls);
+	    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) {
+	    (*env)->DeleteLocalRef(env, cls);
+	    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) {
+	    (*env)->DeleteLocalRef(env, cls);
+	    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;
+	int rc = SQLITE_ERROR, nargs, i;
+	char *p;
+	const char *str = (*env)->GetStringUTFChars(env, sql, NULL);
+	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 < (jsize) 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) {
+	    const jsize charCount = (*env)->GetStringLength(env, val);
+	    len = charCount * sizeof(jchar);
+	    if (len > 0) {
+		data = sqlite3_malloc(len);
+		if (!data) {
+		    throwoom(env, "unable to get blob parameter");
+		    return;
+		}
+		
+		(*env)->GetStringRegion(env, val, 0, charCount, (jchar*) data);
+		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
diff --git a/src/main/native/sqlite_jni.h b/src/main/native/sqlite_jni.h
new file mode 100644
index 0000000..cdb7692
--- /dev/null
+++ b/src/main/native/sqlite_jni.h
@@ -0,0 +1,657 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class SQLite_Database */
+
+#ifndef _Included_SQLite_Database
+#define _Included_SQLite_Database
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     SQLite_Database
+ * Method:    _open
+ * Signature: (Ljava/lang/String;I)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1open
+  (JNIEnv *, jobject, jstring, jint);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _open_aux_file
+ * Signature: (Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1open_1aux_1file
+  (JNIEnv *, jobject, jstring);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _finalize
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1finalize
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _close
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1close
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _exec
+ * Signature: (Ljava/lang/String;LSQLite/Callback;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1exec__Ljava_lang_String_2LSQLite_Callback_2
+  (JNIEnv *, jobject, jstring, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _exec
+ * Signature: (Ljava/lang/String;LSQLite/Callback;[Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1exec__Ljava_lang_String_2LSQLite_Callback_2_3Ljava_lang_String_2
+  (JNIEnv *, jobject, jstring, jobject, jobjectArray);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _last_insert_rowid
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_SQLite_Database__1last_1insert_1rowid
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _interrupt
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1interrupt
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _changes
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_SQLite_Database__1changes
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _busy_handler
+ * Signature: (LSQLite/BusyHandler;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1busy_1handler
+  (JNIEnv *, jobject, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _busy_timeout
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1busy_1timeout
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _complete
+ * Signature: (Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_SQLite_Database__1complete
+  (JNIEnv *, jclass, jstring);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    version
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_SQLite_Database_version
+  (JNIEnv *, jclass);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    dbversion
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_SQLite_Database_dbversion
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _create_function
+ * Signature: (Ljava/lang/String;ILSQLite/Function;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1create_1function
+  (JNIEnv *, jobject, jstring, jint, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _create_aggregate
+ * Signature: (Ljava/lang/String;ILSQLite/Function;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1create_1aggregate
+  (JNIEnv *, jobject, jstring, jint, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _function_type
+ * Signature: (Ljava/lang/String;I)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1function_1type
+  (JNIEnv *, jobject, jstring, jint);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _errmsg
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_SQLite_Database__1errmsg
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    error_string
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_SQLite_Database_error_1string
+  (JNIEnv *, jclass, jint);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _set_encoding
+ * Signature: (Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1set_1encoding
+  (JNIEnv *, jobject, jstring);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _set_authorizer
+ * Signature: (LSQLite/Authorizer;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1set_1authorizer
+  (JNIEnv *, jobject, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _trace
+ * Signature: (LSQLite/Trace;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1trace
+  (JNIEnv *, jobject, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    is3
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_SQLite_Database_is3
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    vm_compile
+ * Signature: (Ljava/lang/String;LSQLite/Vm;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database_vm_1compile
+  (JNIEnv *, jobject, jstring, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    vm_compile_args
+ * Signature: (Ljava/lang/String;LSQLite/Vm;[Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database_vm_1compile_1args
+  (JNIEnv *, jobject, jstring, jobject, jobjectArray);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    stmt_prepare
+ * Signature: (Ljava/lang/String;LSQLite/Stmt;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database_stmt_1prepare
+  (JNIEnv *, jobject, jstring, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _open_blob
+ * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JZLSQLite/Blob;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1open_1blob
+  (JNIEnv *, jobject, jstring, jstring, jstring, jlong, jboolean, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    _progress_handler
+ * Signature: (ILSQLite/ProgressHandler;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database__1progress_1handler
+  (JNIEnv *, jobject, jint, jobject);
+
+/*
+ * Class:     SQLite_Database
+ * Method:    internal_init
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Database_internal_1init
+  (JNIEnv *, jclass);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* Header for class SQLite_Vm */
+
+#ifndef _Included_SQLite_Vm
+#define _Included_SQLite_Vm
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     SQLite_Vm
+ * Method:    step
+ * Signature: (LSQLite/Callback;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_SQLite_Vm_step
+  (JNIEnv *, jobject, jobject);
+
+/*
+ * Class:     SQLite_Vm
+ * Method:    compile
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_SQLite_Vm_compile
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Vm
+ * Method:    stop
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Vm_stop
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Vm
+ * Method:    finalize
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Vm_finalize
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Vm
+ * Method:    internal_init
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Vm_internal_1init
+  (JNIEnv *, jclass);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* Header for class SQLite_FunctionContext */
+
+#ifndef _Included_SQLite_FunctionContext
+#define _Included_SQLite_FunctionContext
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     SQLite_FunctionContext
+ * Method:    set_result
+ * Signature: (Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_FunctionContext_set_1result__Ljava_lang_String_2
+  (JNIEnv *, jobject, jstring);
+
+/*
+ * Class:     SQLite_FunctionContext
+ * Method:    set_result
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_FunctionContext_set_1result__I
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     SQLite_FunctionContext
+ * Method:    set_result
+ * Signature: (D)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_FunctionContext_set_1result__D
+  (JNIEnv *, jobject, jdouble);
+
+/*
+ * Class:     SQLite_FunctionContext
+ * Method:    set_error
+ * Signature: (Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_FunctionContext_set_1error
+  (JNIEnv *, jobject, jstring);
+
+/*
+ * Class:     SQLite_FunctionContext
+ * Method:    set_result
+ * Signature: ([B)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_FunctionContext_set_1result___3B
+  (JNIEnv *, jobject, jbyteArray);
+
+/*
+ * Class:     SQLite_FunctionContext
+ * Method:    set_result_zeroblob
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_FunctionContext_set_1result_1zeroblob
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     SQLite_FunctionContext
+ * Method:    count
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_SQLite_FunctionContext_count
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_FunctionContext
+ * Method:    internal_init
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_SQLite_FunctionContext_internal_1init
+  (JNIEnv *, jclass);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* Header for class SQLite_Stmt */
+
+#ifndef _Included_SQLite_Stmt
+#define _Included_SQLite_Stmt
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     SQLite_Stmt
+ * Method:    prepare
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_SQLite_Stmt_prepare
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    step
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_SQLite_Stmt_step
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    close
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Stmt_close
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    reset
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Stmt_reset
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    clear_bindings
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Stmt_clear_1bindings
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    bind
+ * Signature: (II)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Stmt_bind__II
+  (JNIEnv *, jobject, jint, jint);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    bind
+ * Signature: (IJ)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Stmt_bind__IJ
+  (JNIEnv *, jobject, jint, jlong);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    bind
+ * Signature: (ID)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Stmt_bind__ID
+  (JNIEnv *, jobject, jint, jdouble);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    bind
+ * Signature: (I[B)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Stmt_bind__I_3B
+  (JNIEnv *, jobject, jint, jbyteArray);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    bind
+ * Signature: (ILjava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Stmt_bind__ILjava_lang_String_2
+  (JNIEnv *, jobject, jint, jstring);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    bind
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Stmt_bind__I
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    bind_zeroblob
+ * Signature: (II)V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Stmt_bind_1zeroblob
+  (JNIEnv *, jobject, jint, jint);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    bind_parameter_count
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_SQLite_Stmt_bind_1parameter_1count
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    bind_parameter_name
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_SQLite_Stmt_bind_1parameter_1name
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    bind_parameter_index
+ * Signature: (Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_SQLite_Stmt_bind_1parameter_1index
+  (JNIEnv *, jobject, jstring);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    column_int
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_SQLite_Stmt_column_1int
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    column_long
+ * Signature: (I)J
+ */
+JNIEXPORT jlong JNICALL Java_SQLite_Stmt_column_1long
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    column_double
+ * Signature: (I)D
+ */
+JNIEXPORT jdouble JNICALL Java_SQLite_Stmt_column_1double
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    column_bytes
+ * Signature: (I)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_SQLite_Stmt_column_1bytes
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    column_string
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_SQLite_Stmt_column_1string
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    column_type
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_SQLite_Stmt_column_1type
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    column_count
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_SQLite_Stmt_column_1count
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    column_table_name
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_SQLite_Stmt_column_1table_1name
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    column_database_name
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_SQLite_Stmt_column_1database_1name
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    column_decltype
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_SQLite_Stmt_column_1decltype
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    column_origin_name
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_SQLite_Stmt_column_1origin_1name
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    finalize
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Stmt_finalize
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Stmt
+ * Method:    internal_init
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Stmt_internal_1init
+  (JNIEnv *, jclass);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* Header for class SQLite_Blob */
+
+#ifndef _Included_SQLite_Blob
+#define _Included_SQLite_Blob
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     SQLite_Blob
+ * Method:    close
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Blob_close
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Blob
+ * Method:    write
+ * Signature: ([BIII)I
+ */
+JNIEXPORT jint JNICALL Java_SQLite_Blob_write
+  (JNIEnv *, jobject, jbyteArray, jint, jint, jint);
+
+/*
+ * Class:     SQLite_Blob
+ * Method:    read
+ * Signature: ([BIII)I
+ */
+JNIEXPORT jint JNICALL Java_SQLite_Blob_read
+  (JNIEnv *, jobject, jbyteArray, jint, jint, jint);
+
+/*
+ * Class:     SQLite_Blob
+ * Method:    finalize
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Blob_finalize
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     SQLite_Blob
+ * Method:    internal_init
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_SQLite_Blob_internal_1init
+  (JNIEnv *, jclass);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/src/main/native/sqlite_jni_defs.h b/src/main/native/sqlite_jni_defs.h
new file mode 100644
index 0000000..91b2378
--- /dev/null
+++ b/src/main/native/sqlite_jni_defs.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2007, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0 
+ *
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+#define HAVE_SQLITE2 0
+#define HAVE_SQLITE3 1
+#define HAVE_SQLITE_FUNCTION_TYPE 0
+#define HAVE_SQLITE_OPEN_AUX_FILE 0
+#define HAVE_SQLITE_SET_AUTHORIZER 0
+#define HAVE_SQLITE_TRACE 0
+#define HAVE_SQLITE_COMPILE 0
+#define HAVE_SQLITE_PROGRESS_HANDLER 0
+#define HAVE_SQLITE3_MALLOC 1
+#define HAVE_SQLITE3_PREPARE_V2 0
+#define HAVE_SQLITE3_PREPARE16_V2 0
+#define HAVE_SQLITE3_BIND_ZEROBLOB 0
+#define HAVE_SQLITE3_CLEAR_BINDINGS 0
+#define HAVE_SQLITE3_COLUMN_TABLE_NAME16 0
+#define HAVE_SQLITE3_COLUMN_DATABASE_NAME16 0
+#define HAVE_SQLITE3_COLUMN_ORIGIN_NAME16 0
+#define HAVE_SQLITE3_BIND_PARAMETER_COUNT 1
+#define HAVE_SQLITE3_BIND_PARAMETER_NAME 1
+#define HAVE_SQLITE3_BIND_PARAMETER_INDEX 1
+#define HAVE_SQLITE3_RESULT_ZEROBLOB 0
+#define HAVE_SQLITE3_INCRBLOBIO 0
+
+