OSDN Git Service

incorporated sqlitejdbc sources
authorleo <leo@ae02f08e-27ec-0310-ae8c-8ba02fe2eafd>
Fri, 25 Jan 2008 06:09:56 +0000 (06:09 +0000)
committerleo <leo@ae02f08e-27ec-0310-ae8c-8ba02fe2eafd>
Fri, 25 Jan 2008 06:09:56 +0000 (06:09 +0000)
git-svn-id: http://www.xerial.org/svn/project/XerialJ/trunk/sqlite-jdbc@1977 ae02f08e-27ec-0310-ae8c-8ba02fe2eafd

16 files changed:
pom.xml
src/main/java/org/sqlite/Codes.java [new file with mode: 0644]
src/main/java/org/sqlite/Conn.java [new file with mode: 0644]
src/main/java/org/sqlite/DB.java [new file with mode: 0644]
src/main/java/org/sqlite/Function.java [new file with mode: 0644]
src/main/java/org/sqlite/JDBC.java [new file with mode: 0644]
src/main/java/org/sqlite/MetaData.java [new file with mode: 0644]
src/main/java/org/sqlite/NativeDB.c [new file with mode: 0644]
src/main/java/org/sqlite/NativeDB.java [new file with mode: 0644]
src/main/java/org/sqlite/NestedDB.c [new file with mode: 0644]
src/main/java/org/sqlite/PrepStmt.java [new file with mode: 0644]
src/main/java/org/sqlite/RS.java [new file with mode: 0644]
src/main/java/org/sqlite/Stmt.java [new file with mode: 0644]
src/main/java/org/sqlite/Unused.java [new file with mode: 0644]
src/main/java/org/xerial/db/sql/sqlite/SQLiteJDBCLoader.java
src/test/java/org/xerial/db/sql/sqlite/SQLiteJDBCLoaderTest.java

diff --git a/pom.xml b/pom.xml
index 406a95a..20f7f1a 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -12,7 +12,7 @@
   <modelVersion>4.0.0</modelVersion>\r
   <groupId>org.xerial</groupId>\r
   <artifactId>sqlite-jdbc</artifactId>\r
-  <version>v038</version>\r
+  <version>v038.1</version>\r
   <name>SQLite JDBC</name>\r
   <description>SQLite JDBC library</description>\r
 \r
diff --git a/src/main/java/org/sqlite/Codes.java b/src/main/java/org/sqlite/Codes.java
new file mode 100644 (file)
index 0000000..03b3e5d
--- /dev/null
@@ -0,0 +1,92 @@
+/* Copyright 2006 David Crawshaw, see LICENSE file for licensing [BSD]. */
+package org.sqlite;
+
+interface Codes
+{
+    /** Successful result */
+    public static final int SQLITE_OK         =   0;
+
+    /** SQL error or missing database */
+    public static final int SQLITE_ERROR      =   1;
+
+    /** An internal logic error in SQLite */
+    public static final int SQLITE_INTERNAL   =   2;
+
+    /** Access permission denied */
+    public static final int SQLITE_PERM       =   3;
+
+    /** Callback routine requested an abort */
+    public static final int SQLITE_ABORT      =   4;
+
+    /** The database file is locked */
+    public static final int SQLITE_BUSY       =   5;
+
+    /** A table in the database is locked */
+    public static final int SQLITE_LOCKED     =   6;
+
+    /** A malloc() failed */
+    public static final int SQLITE_NOMEM      =   7;
+
+    /** Attempt to write a readonly database */
+    public static final int SQLITE_READONLY   =   8;
+
+    /** Operation terminated by sqlite_interrupt() */
+    public static final int SQLITE_INTERRUPT  =   9;
+
+    /** Some kind of disk I/O error occurred */
+    public static final int SQLITE_IOERR      =  10;
+
+    /** The database disk image is malformed */
+    public static final int SQLITE_CORRUPT    =  11;
+
+    /** (Internal Only) Table or record not found */
+    public static final int SQLITE_NOTFOUND   =  12;
+
+    /** Insertion failed because database is full */
+    public static final int SQLITE_FULL       =  13;
+
+    /** Unable to open the database file */
+    public static final int SQLITE_CANTOPEN   =  14;
+
+    /** Database lock protocol error */
+    public static final int SQLITE_PROTOCOL   =  15;
+
+    /** (Internal Only) Database table is empty */
+    public static final int SQLITE_EMPTY      =  16;
+
+    /** The database schema changed */
+    public static final int SQLITE_SCHEMA     =  17;
+
+    /** Too much data for one row of a table */
+    public static final int SQLITE_TOOBIG     =  18;
+
+    /** Abort due to constraint violation */
+    public static final int SQLITE_CONSTRAINT =  19;
+
+    /** Data type mismatch */
+    public static final int SQLITE_MISMATCH   =  20;
+
+    /** Library used incorrectly */
+    public static final int SQLITE_MISUSE     =  21;
+
+    /** Uses OS features not supported on host */
+    public static final int SQLITE_NOLFS      =  22;
+
+    /** Authorization denied */
+    public static final int SQLITE_AUTH       =  23;
+
+    /** sqlite_step() has another row ready */
+    public static final int SQLITE_ROW        =  100;
+
+    /** sqlite_step() has finished executing */
+    public static final int SQLITE_DONE       =  101;
+
+
+    // types returned by sqlite3_column_type()
+
+    public static final int SQLITE_INTEGER    =  1;
+    public static final int SQLITE_FLOAT      =  2;
+    public static final int SQLITE_TEXT       =  3;
+    public static final int SQLITE_BLOB       =  4;
+    public static final int SQLITE_NULL       =  5;
+}
diff --git a/src/main/java/org/sqlite/Conn.java b/src/main/java/org/sqlite/Conn.java
new file mode 100644 (file)
index 0000000..9b6237c
--- /dev/null
@@ -0,0 +1,436 @@
+/* Copyright 2006 David Crawshaw, see LICENSE file for licensing [BSD]. */
+package org.sqlite;
+
+import java.io.File;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.NClob;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLClientInfoException;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.sql.Struct;
+import java.util.Map;
+import java.util.Properties;
+
+class Conn implements Connection
+{
+    private final String url;
+    private DB db = null;
+    private MetaData meta = null;
+    private boolean autoCommit = true;
+    private int timeout = 0;
+
+    public Conn(String url, String filename) throws SQLException
+    {
+        // check the path to the file exists
+        if (!":memory:".equals(filename))
+        {
+            File file = new File(filename).getAbsoluteFile();
+            File parent = file.getParentFile();
+            if (parent != null && !parent.exists())
+            {
+                for (File up = parent; up != null && !up.exists();)
+                {
+                    parent = up;
+                    up = up.getParentFile();
+                }
+                throw new SQLException("path to '" + filename + "': '" + parent + "' does not exist");
+            }
+
+            // check write access if file does not exist
+            try
+            {
+                if (file.createNewFile())
+                    file.delete();
+            }
+            catch (Exception e)
+            {
+                throw new SQLException("opening db: '" + filename + "': " + e.getMessage());
+            }
+            filename = file.getAbsolutePath();
+        }
+
+        // TODO: library variable to explicitly control load type
+        // attempt to use the Native library first
+        try
+        {
+            Class nativedb = Class.forName("org.sqlite.NativeDB");
+            if (((Boolean) nativedb.getDeclaredMethod("load", null).invoke(null, null)).booleanValue())
+                db = (DB) nativedb.newInstance();
+        }
+        catch (Exception e)
+        {} // fall through to nested library
+
+        // load nested library
+        if (db == null)
+        {
+            try
+            {
+                db = (DB) Class.forName("org.sqlite.NestedDB").newInstance();
+            }
+            catch (Exception e)
+            {
+                throw new SQLException("no SQLite library found");
+            }
+        }
+
+        this.url = url;
+        db.open(filename);
+        setTimeout(3000);
+    }
+
+    int getTimeout()
+    {
+        return timeout;
+    }
+
+    void setTimeout(int ms) throws SQLException
+    {
+        timeout = ms;
+        db.busy_timeout(ms);
+    }
+
+    String url()
+    {
+        return url;
+    }
+
+    String libversion() throws SQLException
+    {
+        return db.libversion();
+    }
+
+    DB db()
+    {
+        return db;
+    }
+
+    private void checkOpen() throws SQLException
+    {
+        if (db == null)
+            throw new SQLException("database connection closed");
+    }
+
+    private void checkCursor(int rst, int rsc, int rsh) throws SQLException
+    {
+        if (rst != ResultSet.TYPE_FORWARD_ONLY)
+            throw new SQLException("SQLite only supports TYPE_FORWARD_ONLY cursors");
+        if (rsc != ResultSet.CONCUR_READ_ONLY)
+            throw new SQLException("SQLite only supports CONCUR_READ_ONLY cursors");
+        if (rsh != ResultSet.CLOSE_CURSORS_AT_COMMIT)
+            throw new SQLException("SQLite only supports closing cursors at commit");
+    }
+
+    public void finalize() throws SQLException
+    {
+        close();
+    }
+
+    public void close() throws SQLException
+    {
+        if (db == null)
+            return;
+        if (meta != null)
+            meta.close();
+
+        db.close();
+        db = null;
+    }
+
+    public boolean isClosed() throws SQLException
+    {
+        return db == null;
+    }
+
+    public String getCatalog() throws SQLException
+    {
+        checkOpen();
+        return null;
+    }
+
+    public void setCatalog(String catalog) throws SQLException
+    {
+        checkOpen();
+    }
+
+    public int getHoldability() throws SQLException
+    {
+        checkOpen();
+        return ResultSet.CLOSE_CURSORS_AT_COMMIT;
+    }
+
+    public void setHoldability(int h) throws SQLException
+    {
+        checkOpen();
+        if (h != ResultSet.CLOSE_CURSORS_AT_COMMIT)
+            throw new SQLException("SQLite only supports CLOSE_CURSORS_AT_COMMIT");
+    }
+
+    public int getTransactionIsolation()
+    {
+        return TRANSACTION_SERIALIZABLE;
+    }
+
+    public void setTransactionIsolation(int level) throws SQLException
+    {
+        if (level != TRANSACTION_SERIALIZABLE)
+            throw new SQLException("SQLite supports only TRANSACTION_SERIALIZABLE");
+    }
+
+    public Map getTypeMap() throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    public void setTypeMap(Map<String, Class< ? >> map) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    public boolean isReadOnly() throws SQLException
+    {
+        return false;
+    } // FIXME
+
+    public void setReadOnly(boolean ro) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    public DatabaseMetaData getMetaData()
+    {
+        if (meta == null)
+            meta = new MetaData(this);
+        return meta;
+    }
+
+    public String nativeSQL(String sql)
+    {
+        return sql;
+    }
+
+    public void clearWarnings() throws SQLException
+    {}
+
+    public SQLWarning getWarnings() throws SQLException
+    {
+        return null;
+    }
+
+    // TODO: optimise with direct jni calls for begin/commit/rollback
+    public boolean getAutoCommit() throws SQLException
+    {
+        checkOpen();
+        return autoCommit;
+    }
+
+    public void setAutoCommit(boolean ac) throws SQLException
+    {
+        checkOpen();
+        if (autoCommit == ac)
+            return;
+        autoCommit = ac;
+        db.exec(autoCommit ? "COMMIT;" : "BEGIN DEFERRED;");
+    }
+
+    public void commit() throws SQLException
+    {
+        checkOpen();
+        if (autoCommit)
+            throw new SQLException("database in auto-commit mode");
+        db.exec("COMMIT;");
+        db.exec("BEGIN DEFERRED;");
+    }
+
+    public void rollback() throws SQLException
+    {
+        checkOpen();
+        if (autoCommit)
+            throw new SQLException("database in auto-commit mode");
+        db.exec("ROLLBACK;");
+        db.exec("BEGIN DEFERRED;");
+    }
+
+    public Statement createStatement() throws SQLException
+    {
+        return createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY,
+                ResultSet.CLOSE_CURSORS_AT_COMMIT);
+    }
+
+    public Statement createStatement(int rsType, int rsConcurr) throws SQLException
+    {
+        return createStatement(rsType, rsConcurr, ResultSet.CLOSE_CURSORS_AT_COMMIT);
+    }
+
+    public Statement createStatement(int rst, int rsc, int rsh) throws SQLException
+    {
+        checkCursor(rst, rsc, rsh);
+        return new Stmt(this);
+    }
+
+    public CallableStatement prepareCall(String sql) throws SQLException
+    {
+        return prepareCall(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY,
+                ResultSet.CLOSE_CURSORS_AT_COMMIT);
+    }
+
+    public CallableStatement prepareCall(String sql, int rst, int rsc) throws SQLException
+    {
+        return prepareCall(sql, rst, rsc, ResultSet.CLOSE_CURSORS_AT_COMMIT);
+    }
+
+    public CallableStatement prepareCall(String sql, int rst, int rsc, int rsh) throws SQLException
+    {
+        throw new SQLException("SQLite does not support Stored Procedures");
+    }
+
+    public PreparedStatement prepareStatement(String sql) throws SQLException
+    {
+        return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+    }
+
+    public PreparedStatement prepareStatement(String sql, int autoC) throws SQLException
+    {
+        throw new SQLException("NYI");
+    }
+
+    public PreparedStatement prepareStatement(String sql, int[] colInds) throws SQLException
+    {
+        throw new SQLException("NYI");
+    }
+
+    public PreparedStatement prepareStatement(String sql, String[] colNames) throws SQLException
+    {
+        throw new SQLException("NYI");
+    }
+
+    public PreparedStatement prepareStatement(String sql, int rst, int rsc) throws SQLException
+    {
+        return prepareStatement(sql, rst, rsc, ResultSet.CLOSE_CURSORS_AT_COMMIT);
+    }
+
+    public PreparedStatement prepareStatement(String sql, int rst, int rsc, int rsh) throws SQLException
+    {
+        checkCursor(rst, rsc, rsh);
+        return new PrepStmt(this, sql);
+    }
+
+    // UNUSED FUNCTIONS /////////////////////////////////////////////
+
+    public Savepoint setSavepoint() throws SQLException
+    {
+        throw new SQLException("unsupported by SQLite: savepoints");
+    }
+
+    public Savepoint setSavepoint(String name) throws SQLException
+    {
+        throw new SQLException("unsupported by SQLite: savepoints");
+    }
+
+    public void releaseSavepoint(Savepoint savepoint) throws SQLException
+    {
+        throw new SQLException("unsupported by SQLite: savepoints");
+    }
+
+    public void rollback(Savepoint savepoint) throws SQLException
+    {
+        throw new SQLException("unsupported by SQLite: savepoints");
+    }
+
+    @Override
+    public Array createArrayOf(String typeName, Object[] elements) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public Blob createBlob() throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public Clob createClob() throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public NClob createNClob() throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public SQLXML createSQLXML() throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public Struct createStruct(String typeName, Object[] attributes) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public Properties getClientInfo() throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public String getClientInfo(String name) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public boolean isValid(int timeout) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setClientInfo(Properties properties) throws SQLClientInfoException
+    {
+        throw new SQLClientInfoException();
+    }
+
+    @Override
+    public void setClientInfo(String name, String value) throws SQLClientInfoException
+    {
+        throw new SQLClientInfoException();
+    }
+
+    @Override
+    public boolean isWrapperFor(Class< ? > iface) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public <T> T unwrap(Class<T> iface) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+}
diff --git a/src/main/java/org/sqlite/DB.java b/src/main/java/org/sqlite/DB.java
new file mode 100644 (file)
index 0000000..4b00c15
--- /dev/null
@@ -0,0 +1,254 @@
+/* Copyright 2006 David Crawshaw, see LICENSE file for licensing [BSD]. */
+package org.sqlite;
+
+import java.lang.ref.*;
+import java.io.File;
+import java.sql.*;
+import java.util.*;
+
+abstract class DB implements Codes
+{
+    /** database pointer */
+    long pointer = 0;
+
+    /** tracer for statements to avoid unfinalized statements on db close */
+    private Map stmts = new Hashtable();
+
+    // WRAPPER FUNCTIONS ////////////////////////////////////////////
+
+    abstract void open(String filename) throws SQLException;
+    abstract void interrupt() throws SQLException;
+    abstract void busy_timeout(int ms) throws SQLException;
+    abstract String errmsg() throws SQLException;
+    abstract String libversion() throws SQLException;
+    abstract int changes() throws SQLException;
+
+    final synchronized void exec(String sql) throws SQLException {
+        long pointer = 0;
+        try {
+            pointer = prepare(sql);
+            if (step(pointer) == SQLITE_ERROR)
+                throwex();
+        } finally {
+            finalize(pointer);
+        }
+    }
+
+    final synchronized void close() throws SQLException {
+        // finalize any remaining statements before closing db
+        synchronized (stmts) {
+            Iterator i = stmts.entrySet().iterator();
+            while (i.hasNext()) {
+                Map.Entry entry = (Map.Entry)i.next();
+                RS stmt = (RS)((WeakReference)entry.getValue()).get();
+                finalize(((Long)entry.getKey()).longValue());
+                if (stmt != null) {
+                    stmt.pointer = 0;
+                    stmt.db = null;
+                }
+                i.remove();
+            }
+        }
+
+        // remove memory used by user-defined functions
+        free_functions();
+
+        _close();
+    }
+
+    final synchronized void prepare(RS stmt) throws SQLException {
+        if (stmt.pointer != 0)
+            finalize(stmt);
+        stmt.pointer = prepare(stmt.sql);
+        stmts.put(new Long(stmt.pointer),
+                  new WeakReference(stmt));
+    }
+
+    final synchronized int finalize(RS stmt) throws SQLException {
+        if (stmt.pointer == 0) return 0;
+        int rc = SQLITE_ERROR;
+        try {
+            rc = finalize(stmt.pointer);
+        } finally {
+            stmts.remove(new Long(stmt.pointer));
+            stmt.pointer = 0;
+        }
+        return rc;
+    }
+
+    final synchronized int step(RS stmt) throws SQLException {
+        int rc = step(stmt.pointer);
+
+        // deal with goofy interface
+        if (rc == SQLITE_ERROR)
+            rc = reset(stmt.pointer);
+        if (rc == SQLITE_SCHEMA) {
+            prepare(stmt);
+            return step(stmt);
+        }
+        return rc;
+    }
+
+    protected abstract void _close() throws SQLException;
+    protected abstract long prepare(String sql) throws SQLException;
+    protected abstract int finalize(long stmt) throws SQLException;
+    protected abstract int step(long stmt) throws SQLException;
+    protected abstract int reset(long stmt) throws SQLException;
+
+    abstract int clear_bindings(long stmt) throws SQLException; // TODO remove?
+    abstract int bind_parameter_count(long stmt) throws SQLException;
+
+    abstract int    column_count      (long stmt) throws SQLException;
+    abstract int    column_type       (long stmt, int col) throws SQLException;
+    abstract String column_decltype   (long stmt, int col) throws SQLException;
+    abstract String column_table_name (long stmt, int col) throws SQLException;
+    abstract String column_name       (long stmt, int col) throws SQLException;
+    abstract String column_text       (long stmt, int col) throws SQLException;
+    abstract byte[] column_blob       (long stmt, int col) throws SQLException;
+    abstract double column_double     (long stmt, int col) throws SQLException;
+    abstract long   column_long       (long stmt, int col) throws SQLException;
+    abstract int    column_int        (long stmt, int col) throws SQLException;
+
+    abstract int bind_null  (long stmt, int pos) throws SQLException;
+    abstract int bind_int   (long stmt, int pos, int    v) throws SQLException;
+    abstract int bind_long  (long stmt, int pos, long   v) throws SQLException;
+    abstract int bind_double(long stmt, int pos, double v) throws SQLException;
+    abstract int bind_text  (long stmt, int pos, String v) throws SQLException;
+    abstract int bind_blob  (long stmt, int pos, byte[] v) throws SQLException;
+
+    abstract void result_null  (long context) throws SQLException;
+    abstract void result_text  (long context, String val) throws SQLException;
+    abstract void result_blob  (long context, byte[] val) throws SQLException;
+    abstract void result_double(long context, double val) throws SQLException;
+    abstract void result_long  (long context, long   val) throws SQLException;
+    abstract void result_int   (long context, int    val) throws SQLException;
+    abstract void result_error (long context, String err) throws SQLException;
+
+    abstract int    value_bytes (Function f, int arg) throws SQLException;
+    abstract String value_text  (Function f, int arg) throws SQLException;
+    abstract byte[] value_blob  (Function f, int arg) throws SQLException;
+    abstract double value_double(Function f, int arg) throws SQLException;
+    abstract long   value_long  (Function f, int arg) throws SQLException;
+    abstract int    value_int   (Function f, int arg) throws SQLException;
+    abstract int    value_type  (Function f, int arg) throws SQLException;
+
+    abstract int create_function(String name, Function f) throws SQLException;
+    abstract int destroy_function(String name) throws SQLException;
+    abstract void free_functions() throws SQLException;
+
+    /** Provides metadata for the columns of a statement. Returns:
+     *   res[col][0] = true if column constrained NOT NULL
+     *   res[col][1] = true if column is part of the primary key
+     *   res[col][2] = true if column is auto-increment
+     */
+    abstract boolean[][] column_metadata(long stmt) throws SQLException;
+
+
+    // COMPOUND FUNCTIONS ////////////////////////////////////////////
+
+    final synchronized String[] column_names(long stmt) throws SQLException {
+        String[] names = new String[column_count(stmt)];
+        for (int i=0; i < names.length; i++)
+            names[i] = column_name(stmt, i);
+        return names;
+    }
+
+    final synchronized int sqlbind(long stmt, int pos, Object v)
+            throws SQLException {
+        pos++;
+        if (v == null) {
+            return bind_null(stmt, pos);
+        } else if (v instanceof Integer) {
+            return bind_int(stmt, pos, ((Integer)v).intValue());
+        } else if (v instanceof Long) {
+            return bind_long(stmt, pos, ((Long)v).longValue());
+        } else if (v instanceof Double) {
+            return bind_double(stmt, pos, ((Double)v).doubleValue());
+        } else if (v instanceof String) {
+            return bind_text(stmt, pos, (String)v);
+        } else if (v instanceof byte[]) {
+            return bind_blob(stmt, pos, (byte[])v);
+        } else {
+            throw new SQLException("unexpected param type: "+v.getClass());
+        }
+    }
+
+    final synchronized int[] executeBatch(long stmt, int count, Object[] vals)
+            throws SQLException {
+        if (count < 1) throw new SQLException("count (" + count + ") < 1");
+
+        final int params = bind_parameter_count(stmt);
+
+        int rc;
+        int[] changes = new int[count];
+
+        for (int i=0; i < count; i++) {
+            reset(stmt);
+            for (int j=0; j < params; j++)
+                if (sqlbind(stmt, j, vals[(i * params) + j]) != SQLITE_OK)
+                    throwex();
+
+            rc = step(stmt);
+            // TODO: handle SQLITE_SCHEMA
+            if (rc != SQLITE_DONE) {
+                reset(stmt);
+                if (rc == SQLITE_ROW) throw new BatchUpdateException(
+                    "batch entry "+i+": query returns results", changes);
+                throwex();
+            }
+
+            changes[i] = changes();
+        }
+
+        reset(stmt);
+        return changes;
+    }
+
+    final synchronized boolean execute(RS stmt, Object[] vals)
+            throws SQLException {
+        if (vals != null) {
+            final int params = bind_parameter_count(stmt.pointer);
+            if (params != vals.length)
+                throw new SQLException("assertion failure: param count ("
+                        + params + ") != value count (" + vals.length + ")");
+
+            for (int i=0; i < params; i++)
+                if (sqlbind(stmt.pointer, i, vals[i]) != SQLITE_OK) throwex();
+        }
+
+        switch (step(stmt)) {
+            case SQLITE_DONE:
+                reset(stmt.pointer);
+                return false;
+            case SQLITE_ROW: return true;
+            case SQLITE_BUSY: throw new SQLException("database locked");
+            case SQLITE_MISUSE:
+                throw new SQLException(errmsg());
+                //throw new SQLException("jdbc internal consistency error");
+            case SQLITE_SCHEMA:
+                throw new SQLException("jdbc internal consistency error");
+            case SQLITE_INTERNAL: // TODO: be specific
+            case SQLITE_PERM: case SQLITE_ABORT: case SQLITE_NOMEM:
+            case SQLITE_READONLY: case SQLITE_INTERRUPT: case SQLITE_IOERR:
+            case SQLITE_CORRUPT:
+            default:
+                finalize(stmt);
+                throw new SQLException(errmsg());
+        }
+    }
+
+    final synchronized int executeUpdate(RS stmt, Object[] vals)
+            throws SQLException {
+        if (execute(stmt, vals))
+            throw new SQLException("query returns results");
+        reset(stmt.pointer);
+        return changes();
+    }
+
+
+    // HELPER FUNCTIONS /////////////////////////////////////////////
+
+    final void throwex() throws SQLException {
+        throw new SQLException(errmsg());
+    }
+}
diff --git a/src/main/java/org/sqlite/Function.java b/src/main/java/org/sqlite/Function.java
new file mode 100644 (file)
index 0000000..1706a2e
--- /dev/null
@@ -0,0 +1,167 @@
+package org.sqlite;
+
+import java.sql.*;
+
+/** Provides an interface for creating SQLite user-defined functions.
+ *
+ * <p>A subclass of <tt>org.sqlite.Function</tt> can be registered with
+ * <tt>Function.create()</tt> and called by the name it was given. All
+ * functions must implement <tt>xFunc()</tt>, which is called when SQLite
+ * runs the custom function.</p>
+ *
+ * Eg.
+ *
+ * <pre>
+ *      Class.forName("org.sqlite.JDBC");
+ *      Connection conn = DriverManager.getConnection("jdbc:sqlite:");
+ *
+ *      Function.create(conn, "myFunc", new Function() {
+ *          protected void xFunc() {
+ *              System.out.println("myFunc called!");
+ *          }
+ *      });
+ *
+ *      conn.createStatement().execute("select myFunc();");
+ *  </pre>
+ *
+ *  <p>Arguments passed to a custom function can be accessed using the
+ *  <tt>protected</tt> functions provided. <tt>args()</tt> returns
+ *  the number of arguments passed, while
+ *  <tt>value_&lt;type&gt;(int)</tt> returns the value of the specific
+ *  argument. Similarly a function can return a value using the
+ *  <tt>result(&lt;type&gt;)</tt> function.</p>
+ *
+ *  <p>Aggregate functions are not yet supported, but coming soon.</p>
+ *
+ */
+public abstract class Function
+{
+    private Conn conn;
+    private DB db;
+
+    long context = 0;     // pointer sqlite3_context*
+    long value = 0;       // pointer sqlite3_value**
+    int args = 0;
+
+    /** Registers the given function with the Connection using the
+     *  provided name. */
+    public static final void create(Connection conn, String name, Function f)
+            throws SQLException {
+        if (conn == null || !(conn instanceof Conn))
+            throw new SQLException("connection must be to an SQLite db");
+        if (conn.isClosed())
+            throw new SQLException("connection closed");
+
+        f.conn = (Conn)conn;
+        f.db = f.conn.db();
+
+        if (name == null || name.length() > 255)
+            throw new SQLException("invalid function name: '"+name+"'");
+
+        if (f.db.create_function(name, f) != Codes.SQLITE_OK)
+            throw new SQLException("error creating function");
+    }
+
+    /** Removes the named function form the Connection. */
+    public static final void destroy(Connection conn, String name)
+            throws SQLException {
+        if (conn == null || !(conn instanceof Conn))
+            throw new SQLException("connection must be to an SQLite db");
+        ((Conn)conn).db().destroy_function(name);
+    }
+
+
+    /** Called by SQLite as a custom function. Should access arguments
+     *  through <tt>value_*(int)</tt>, return results with
+     *  <tt>result(*)</tt> and throw errors with <tt>error(String)</tt>. */
+    protected abstract void xFunc() throws SQLException;
+
+
+    /** Returns the number of arguments passed to the function.
+     *  Can only be called from <tt>xFunc()</tt>. */
+    protected synchronized final int args()
+        throws SQLException { checkContext(); return args; }
+
+    /** Called by <tt>xFunc</tt> to return a value. */
+    protected synchronized final void result(byte[] value)
+        throws SQLException { checkContext(); db.result_blob(context, value); }
+
+    /** Called by <tt>xFunc</tt> to return a value. */
+    protected synchronized final void result(double value)
+        throws SQLException { checkContext(); db.result_double(context,value);}
+
+    /** Called by <tt>xFunc</tt> to return a value. */
+    protected synchronized final void result(int value)
+        throws SQLException { checkContext(); db.result_int(context, value); }
+
+    /** Called by <tt>xFunc</tt> to return a value. */
+    protected synchronized final void result(long value)
+        throws SQLException { checkContext(); db.result_long(context, value); }
+
+    /** Called by <tt>xFunc</tt> to return a value. */
+    protected synchronized final void result()
+        throws SQLException { checkContext(); db.result_null(context); }
+
+    /** Called by <tt>xFunc</tt> to return a value. */
+    protected synchronized final void result(String value)
+        throws SQLException { checkContext(); db.result_text(context, value); }
+
+    /** Called by <tt>xFunc</tt> to throw an error. */
+    protected synchronized final void error(String err)
+        throws SQLException { checkContext(); db.result_error(context, err); }
+
+    /** Called by <tt>xFunc</tt> to access the value of an argument. */
+    protected synchronized final int value_bytes(int arg)
+        throws SQLException {checkValue(arg); return db.value_bytes(this,arg);}
+
+    /** Called by <tt>xFunc</tt> to access the value of an argument. */
+    protected synchronized final String value_text(int arg)
+        throws SQLException {checkValue(arg); return db.value_text(this,arg);}
+
+    /** Called by <tt>xFunc</tt> to access the value of an argument. */
+    protected synchronized final byte[] value_blob(int arg)
+        throws SQLException {checkValue(arg); return db.value_blob(this,arg); }
+
+    /** Called by <tt>xFunc</tt> to access the value of an argument. */
+    protected synchronized final double value_double(int arg)
+        throws SQLException {checkValue(arg); return db.value_double(this,arg);}
+
+    /** Called by <tt>xFunc</tt> to access the value of an argument. */
+    protected synchronized final int value_int(int arg)
+        throws SQLException {checkValue(arg); return db.value_int(this, arg); }
+
+    /** Called by <tt>xFunc</tt> to access the value of an argument. */
+    protected synchronized final long value_long(int arg)
+        throws SQLException { checkValue(arg); return db.value_long(this,arg); }
+
+    /** Called by <tt>xFunc</tt> to access the value of an argument. */
+    protected synchronized final int value_type(int arg)
+        throws SQLException {checkValue(arg); return db.value_type(this,arg); }
+
+
+    private void checkContext() throws SQLException {
+        if (conn == null || conn.db() == null || context == 0)
+            throw new SQLException("no context, not allowed to read value");
+    }
+
+    private void checkValue(int arg) throws SQLException {
+        if (conn == null || conn.db() == null || value == 0)
+            throw new SQLException("not in value access state");
+        if (arg >= args)
+            throw new SQLException("arg "+arg+" out bounds [0,"+args+")");
+    }
+
+
+    public static abstract class Aggregate
+            extends Function
+            implements Cloneable
+    {
+        protected final void xFunc() {}
+        protected abstract void xStep() throws SQLException;
+        protected abstract void xFinal() throws SQLException;
+
+        public Object clone() throws CloneNotSupportedException {
+            return super.clone();
+        }
+    }
+}
diff --git a/src/main/java/org/sqlite/JDBC.java b/src/main/java/org/sqlite/JDBC.java
new file mode 100644 (file)
index 0000000..b59e47a
--- /dev/null
@@ -0,0 +1,41 @@
+/* Copyright 2006 David Crawshaw, see LICENSE file for licensing [BSD]. */
+package org.sqlite;
+
+import java.sql.*;
+import java.util.*;
+
+public class JDBC implements Driver
+{
+    private static final String PREFIX = "jdbc:sqlite:";
+
+    static {
+        try {
+            DriverManager.registerDriver(new JDBC());
+        } catch (SQLException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public int getMajorVersion() { return 1; }
+    public int getMinorVersion() { return 1; }
+
+    public boolean jdbcCompliant() { return false; }
+
+    public boolean acceptsURL(String url) {
+        return url != null && url.toLowerCase().startsWith(PREFIX);
+    }
+
+    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info)
+            throws SQLException {
+        return new DriverPropertyInfo[] {};
+    }
+
+    public Connection connect(String url, Properties info) throws SQLException {
+        if (!acceptsURL(url)) return null;
+        url = url.trim();
+
+        // if no file name is given use a memory database
+        return new Conn(url, PREFIX.equalsIgnoreCase(url) ?
+            ":memory:" : url.substring(PREFIX.length()));
+    }
+}
diff --git a/src/main/java/org/sqlite/MetaData.java b/src/main/java/org/sqlite/MetaData.java
new file mode 100644 (file)
index 0000000..4d2f4d5
--- /dev/null
@@ -0,0 +1,1235 @@
+package org.sqlite;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.RowIdLifetime;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+
+class MetaData implements DatabaseMetaData
+{
+    private Conn conn;
+    private PreparedStatement getTables = null, getTableTypes = null, getTypeInfo = null, getCrossReference = null,
+            getCatalogs = null, getSchemas = null, getUDTs = null, getColumnsTblName = null, getSuperTypes = null,
+            getSuperTables = null, getTablePrivileges = null, getExportedKeys = null, getProcedures = null,
+            getProcedureColumns = null, getAttributes = null, getBestRowIdentifier = null, getVersionColumns = null,
+            getColumnPrivileges = null;
+
+    /** Used by PrepStmt to save generating a new statement every call. */
+    private PreparedStatement getGeneratedKeys = null;
+
+    MetaData(Conn conn)
+    {
+        this.conn = conn;
+    }
+
+    void checkOpen() throws SQLException
+    {
+        if (conn == null)
+            throw new SQLException("connection closed");
+    }
+
+    synchronized void close() throws SQLException
+    {
+        if (conn == null)
+            return;
+
+        try
+        {
+            if (getTables != null)
+                getTables.close();
+            if (getTableTypes != null)
+                getTableTypes.close();
+            if (getTypeInfo != null)
+                getTypeInfo.close();
+            if (getCrossReference != null)
+                getCrossReference.close();
+            if (getCatalogs != null)
+                getCatalogs.close();
+            if (getSchemas != null)
+                getSchemas.close();
+            if (getUDTs != null)
+                getUDTs.close();
+            if (getColumnsTblName != null)
+                getColumnsTblName.close();
+            if (getSuperTypes != null)
+                getSuperTypes.close();
+            if (getSuperTables != null)
+                getSuperTables.close();
+            if (getTablePrivileges != null)
+                getTablePrivileges.close();
+            if (getExportedKeys != null)
+                getExportedKeys.close();
+            if (getProcedures != null)
+                getProcedures.close();
+            if (getProcedureColumns != null)
+                getProcedureColumns.close();
+            if (getAttributes != null)
+                getAttributes.close();
+            if (getBestRowIdentifier != null)
+                getBestRowIdentifier.close();
+            if (getVersionColumns != null)
+                getVersionColumns.close();
+            if (getColumnPrivileges != null)
+                getColumnPrivileges.close();
+            if (getGeneratedKeys != null)
+                getGeneratedKeys.close();
+
+            getTables = null;
+            getTableTypes = null;
+            getTypeInfo = null;
+            getCrossReference = null;
+            getCatalogs = null;
+            getSchemas = null;
+            getUDTs = null;
+            getColumnsTblName = null;
+            getSuperTypes = null;
+            getSuperTables = null;
+            getTablePrivileges = null;
+            getExportedKeys = null;
+            getProcedures = null;
+            getProcedureColumns = null;
+            getAttributes = null;
+            getBestRowIdentifier = null;
+            getVersionColumns = null;
+            getColumnPrivileges = null;
+            getGeneratedKeys = null;
+        }
+        finally
+        {
+            conn = null;
+        }
+    }
+
+    public Connection getConnection()
+    {
+        return conn;
+    }
+
+    public int getDatabaseMajorVersion()
+    {
+        return 3;
+    }
+
+    public int getDatabaseMinorVersion()
+    {
+        return 0;
+    }
+
+    public int getDriverMajorVersion()
+    {
+        return 1;
+    }
+
+    public int getDriverMinorVersion()
+    {
+        return 1;
+    }
+
+    public int getJDBCMajorVersion()
+    {
+        return 2;
+    }
+
+    public int getJDBCMinorVersion()
+    {
+        return 1;
+    }
+
+    public int getDefaultTransactionIsolation()
+    {
+        return Connection.TRANSACTION_SERIALIZABLE;
+    }
+
+    public int getMaxBinaryLiteralLength()
+    {
+        return 0;
+    }
+
+    public int getMaxCatalogNameLength()
+    {
+        return 0;
+    }
+
+    public int getMaxCharLiteralLength()
+    {
+        return 0;
+    }
+
+    public int getMaxColumnNameLength()
+    {
+        return 0;
+    }
+
+    public int getMaxColumnsInGroupBy()
+    {
+        return 0;
+    }
+
+    public int getMaxColumnsInIndex()
+    {
+        return 0;
+    }
+
+    public int getMaxColumnsInOrderBy()
+    {
+        return 0;
+    }
+
+    public int getMaxColumnsInSelect()
+    {
+        return 0;
+    }
+
+    public int getMaxColumnsInTable()
+    {
+        return 0;
+    }
+
+    public int getMaxConnections()
+    {
+        return 0;
+    }
+
+    public int getMaxCursorNameLength()
+    {
+        return 0;
+    }
+
+    public int getMaxIndexLength()
+    {
+        return 0;
+    }
+
+    public int getMaxProcedureNameLength()
+    {
+        return 0;
+    }
+
+    public int getMaxRowSize()
+    {
+        return 0;
+    }
+
+    public int getMaxSchemaNameLength()
+    {
+        return 0;
+    }
+
+    public int getMaxStatementLength()
+    {
+        return 0;
+    }
+
+    public int getMaxStatements()
+    {
+        return 0;
+    }
+
+    public int getMaxTableNameLength()
+    {
+        return 0;
+    }
+
+    public int getMaxTablesInSelect()
+    {
+        return 0;
+    }
+
+    public int getMaxUserNameLength()
+    {
+        return 0;
+    }
+
+    public int getResultSetHoldability()
+    {
+        return ResultSet.CLOSE_CURSORS_AT_COMMIT;
+    }
+
+    public int getSQLStateType()
+    {
+        return sqlStateSQL99;
+    }
+
+    public String getDatabaseProductName()
+    {
+        return "SQLite";
+    }
+
+    public String getDatabaseProductVersion() throws SQLException
+    {
+        return conn.libversion();
+    }
+
+    public String getDriverName()
+    {
+        return "SQLiteJDBC";
+    }
+
+    public String getDriverVersion()
+    {
+        return "1";
+    }
+
+    public String getExtraNameCharacters()
+    {
+        return "";
+    }
+
+    public String getCatalogSeparator()
+    {
+        return ".";
+    }
+
+    public String getCatalogTerm()
+    {
+        return "catalog";
+    }
+
+    public String getSchemaTerm()
+    {
+        return "schema";
+    }
+
+    public String getProcedureTerm()
+    {
+        return "not_implemented";
+    }
+
+    public String getSearchStringEscape()
+    {
+        return null;
+    }
+
+    public String getIdentifierQuoteString()
+    {
+        return " ";
+    }
+
+    public String getSQLKeywords()
+    {
+        return "";
+    }
+
+    public String getNumericFunctions()
+    {
+        return "";
+    }
+
+    public String getStringFunctions()
+    {
+        return "";
+    }
+
+    public String getSystemFunctions()
+    {
+        return "";
+    }
+
+    public String getTimeDateFunctions()
+    {
+        return "";
+    }
+
+    public String getURL()
+    {
+        return conn.url();
+    }
+
+    public String getUserName()
+    {
+        return null;
+    }
+
+    public boolean allProceduresAreCallable()
+    {
+        return false;
+    }
+
+    public boolean allTablesAreSelectable()
+    {
+        return true;
+    }
+
+    public boolean dataDefinitionCausesTransactionCommit()
+    {
+        return false;
+    }
+
+    public boolean dataDefinitionIgnoredInTransactions()
+    {
+        return false;
+    }
+
+    public boolean doesMaxRowSizeIncludeBlobs()
+    {
+        return false;
+    }
+
+    public boolean deletesAreDetected(int type)
+    {
+        return false;
+    }
+
+    public boolean insertsAreDetected(int type)
+    {
+        return false;
+    }
+
+    public boolean isCatalogAtStart()
+    {
+        return true;
+    }
+
+    public boolean locatorsUpdateCopy()
+    {
+        return false;
+    }
+
+    public boolean nullPlusNonNullIsNull()
+    {
+        return true;
+    }
+
+    public boolean nullsAreSortedAtEnd()
+    {
+        return !nullsAreSortedAtStart();
+    }
+
+    public boolean nullsAreSortedAtStart()
+    {
+        return true;
+    }
+
+    public boolean nullsAreSortedHigh()
+    {
+        return true;
+    }
+
+    public boolean nullsAreSortedLow()
+    {
+        return !nullsAreSortedHigh();
+    }
+
+    public boolean othersDeletesAreVisible(int type)
+    {
+        return false;
+    }
+
+    public boolean othersInsertsAreVisible(int type)
+    {
+        return false;
+    }
+
+    public boolean othersUpdatesAreVisible(int type)
+    {
+        return false;
+    }
+
+    public boolean ownDeletesAreVisible(int type)
+    {
+        return false;
+    }
+
+    public boolean ownInsertsAreVisible(int type)
+    {
+        return false;
+    }
+
+    public boolean ownUpdatesAreVisible(int type)
+    {
+        return false;
+    }
+
+    public boolean storesLowerCaseIdentifiers()
+    {
+        return false;
+    }
+
+    public boolean storesLowerCaseQuotedIdentifiers()
+    {
+        return false;
+    }
+
+    public boolean storesMixedCaseIdentifiers()
+    {
+        return true;
+    }
+
+    public boolean storesMixedCaseQuotedIdentifiers()
+    {
+        return false;
+    }
+
+    public boolean storesUpperCaseIdentifiers()
+    {
+        return false;
+    }
+
+    public boolean storesUpperCaseQuotedIdentifiers()
+    {
+        return false;
+    }
+
+    public boolean supportsAlterTableWithAddColumn()
+    {
+        return false;
+    }
+
+    public boolean supportsAlterTableWithDropColumn()
+    {
+        return false;
+    }
+
+    public boolean supportsANSI92EntryLevelSQL()
+    {
+        return false;
+    }
+
+    public boolean supportsANSI92FullSQL()
+    {
+        return false;
+    }
+
+    public boolean supportsANSI92IntermediateSQL()
+    {
+        return false;
+    }
+
+    public boolean supportsBatchUpdates()
+    {
+        return true;
+    }
+
+    public boolean supportsCatalogsInDataManipulation()
+    {
+        return false;
+    }
+
+    public boolean supportsCatalogsInIndexDefinitions()
+    {
+        return false;
+    }
+
+    public boolean supportsCatalogsInPrivilegeDefinitions()
+    {
+        return false;
+    }
+
+    public boolean supportsCatalogsInProcedureCalls()
+    {
+        return false;
+    }
+
+    public boolean supportsCatalogsInTableDefinitions()
+    {
+        return false;
+    }
+
+    public boolean supportsColumnAliasing()
+    {
+        return true;
+    }
+
+    public boolean supportsConvert()
+    {
+        return false;
+    }
+
+    public boolean supportsConvert(int fromType, int toType)
+    {
+        return false;
+    }
+
+    public boolean supportsCorrelatedSubqueries()
+    {
+        return false;
+    }
+
+    public boolean supportsDataDefinitionAndDataManipulationTransactions()
+    {
+        return true;
+    }
+
+    public boolean supportsDataManipulationTransactionsOnly()
+    {
+        return false;
+    }
+
+    public boolean supportsDifferentTableCorrelationNames()
+    {
+        return false;
+    }
+
+    public boolean supportsExpressionsInOrderBy()
+    {
+        return true;
+    }
+
+    public boolean supportsMinimumSQLGrammar()
+    {
+        return true;
+    }
+
+    public boolean supportsCoreSQLGrammar()
+    {
+        return true;
+    }
+
+    public boolean supportsExtendedSQLGrammar()
+    {
+        return false;
+    }
+
+    public boolean supportsLimitedOuterJoins()
+    {
+        return true;
+    }
+
+    public boolean supportsFullOuterJoins()
+    {
+        return false;
+    }
+
+    public boolean supportsGetGeneratedKeys()
+    {
+        return false;
+    }
+
+    public boolean supportsGroupBy()
+    {
+        return true;
+    }
+
+    public boolean supportsGroupByBeyondSelect()
+    {
+        return false;
+    }
+
+    public boolean supportsGroupByUnrelated()
+    {
+        return false;
+    }
+
+    public boolean supportsIntegrityEnhancementFacility()
+    {
+        return false;
+    }
+
+    public boolean supportsLikeEscapeClause()
+    {
+        return false;
+    }
+
+    public boolean supportsMixedCaseIdentifiers()
+    {
+        return true;
+    }
+
+    public boolean supportsMixedCaseQuotedIdentifiers()
+    {
+        return false;
+    }
+
+    public boolean supportsMultipleOpenResults()
+    {
+        return false;
+    }
+
+    public boolean supportsMultipleResultSets()
+    {
+        return false;
+    }
+
+    public boolean supportsMultipleTransactions()
+    {
+        return true;
+    }
+
+    public boolean supportsNamedParameters()
+    {
+        return true;
+    }
+
+    public boolean supportsNonNullableColumns()
+    {
+        return true;
+    }
+
+    public boolean supportsOpenCursorsAcrossCommit()
+    {
+        return false;
+    }
+
+    public boolean supportsOpenCursorsAcrossRollback()
+    {
+        return false;
+    }
+
+    public boolean supportsOpenStatementsAcrossCommit()
+    {
+        return false;
+    }
+
+    public boolean supportsOpenStatementsAcrossRollback()
+    {
+        return false;
+    }
+
+    public boolean supportsOrderByUnrelated()
+    {
+        return false;
+    }
+
+    public boolean supportsOuterJoins()
+    {
+        return true;
+    }
+
+    public boolean supportsPositionedDelete()
+    {
+        return false;
+    }
+
+    public boolean supportsPositionedUpdate()
+    {
+        return false;
+    }
+
+    public boolean supportsResultSetConcurrency(int t, int c)
+    {
+        return t == ResultSet.TYPE_FORWARD_ONLY && c == ResultSet.CONCUR_READ_ONLY;
+    }
+
+    public boolean supportsResultSetHoldability(int h)
+    {
+        return h == ResultSet.CLOSE_CURSORS_AT_COMMIT;
+    }
+
+    public boolean supportsResultSetType(int t)
+    {
+        return t == ResultSet.TYPE_FORWARD_ONLY;
+    }
+
+    public boolean supportsSavepoints()
+    {
+        return false;
+    }
+
+    public boolean supportsSchemasInDataManipulation()
+    {
+        return false;
+    }
+
+    public boolean supportsSchemasInIndexDefinitions()
+    {
+        return false;
+    }
+
+    public boolean supportsSchemasInPrivilegeDefinitions()
+    {
+        return false;
+    }
+
+    public boolean supportsSchemasInProcedureCalls()
+    {
+        return false;
+    }
+
+    public boolean supportsSchemasInTableDefinitions()
+    {
+        return false;
+    }
+
+    public boolean supportsSelectForUpdate()
+    {
+        return false;
+    }
+
+    public boolean supportsStatementPooling()
+    {
+        return false;
+    }
+
+    public boolean supportsStoredProcedures()
+    {
+        return false;
+    }
+
+    public boolean supportsSubqueriesInComparisons()
+    {
+        return false;
+    }
+
+    public boolean supportsSubqueriesInExists()
+    {
+        return true;
+    } // TODO:
+                                                                    // check
+
+    public boolean supportsSubqueriesInIns()
+    {
+        return true;
+    } // TODO: check
+
+    public boolean supportsSubqueriesInQuantifieds()
+    {
+        return false;
+    }
+
+    public boolean supportsTableCorrelationNames()
+    {
+        return false;
+    }
+
+    public boolean supportsTransactionIsolationLevel(int level)
+    {
+        return level == Connection.TRANSACTION_SERIALIZABLE;
+    }
+
+    public boolean supportsTransactions()
+    {
+        return true;
+    }
+
+    public boolean supportsUnion()
+    {
+        return true;
+    }
+
+    public boolean supportsUnionAll()
+    {
+        return true;
+    }
+
+    public boolean updatesAreDetected(int type)
+    {
+        return false;
+    }
+
+    public boolean usesLocalFilePerTable()
+    {
+        return false;
+    }
+
+    public boolean usesLocalFiles()
+    {
+        return true;
+    }
+
+    public boolean isReadOnly() throws SQLException
+    {
+        return conn.isReadOnly();
+    }
+
+    public ResultSet getAttributes(String c, String s, String t, String a) throws SQLException
+    {
+        if (getAttributes == null)
+            getAttributes = conn.prepareStatement("select " + "null as TYPE_CAT, " + "null as TYPE_SCHEM, "
+                    + "null as TYPE_NAME, " + "null as ATTR_NAME, " + "null as DATA_TYPE, "
+                    + "null as ATTR_TYPE_NAME, " + "null as ATTR_SIZE, " + "null as DECIMAL_DIGITS, "
+                    + "null as NUM_PREC_RADIX, " + "null as NULLABLE, " + "null as REMARKS, " + "null as ATTR_DEF, "
+                    + "null as SQL_DATA_TYPE, " + "null as SQL_DATETIME_SUB, " + "null as CHAR_OCTET_LENGTH, "
+                    + "null as ORDINAL_POSITION, " + "null as IS_NULLABLE, " + "null as SCOPE_CATALOG, "
+                    + "null as SCOPE_SCHEMA, " + "null as SCOPE_TABLE, " + "null as SOURCE_DATA_TYPE limit 0;");
+        return getAttributes.executeQuery();
+    }
+
+    public ResultSet getBestRowIdentifier(String c, String s, String t, int scope, boolean n) throws SQLException
+    {
+        if (getBestRowIdentifier == null)
+            getBestRowIdentifier = conn.prepareStatement("select " + "null as SCOPE, " + "null as COLUMN_NAME, "
+                    + "null as DATA_TYPE, " + "null as TYPE_NAME, " + "null as COLUMN_SIZE, "
+                    + "null as BUFFER_LENGTH, " + "null as DECIMAL_DIGITS, " + "null as PSEUDO_COLUMN limit 0;");
+        return getBestRowIdentifier.executeQuery();
+    }
+
+    public ResultSet getColumnPrivileges(String c, String s, String t, String colPat) throws SQLException
+    {
+        if (getColumnPrivileges == null)
+            getColumnPrivileges = conn.prepareStatement("select " + "null as TABLE_CAT, " + "null as TABLE_SCHEM, "
+                    + "null as TABLE_NAME, " + "null as COLUMN_NAME, " + "null as GRANTOR, " + "null as GRANTEE, "
+                    + "null as PRIVILEGE, " + "null as IS_GRANTABLE limit 0;");
+        return getColumnPrivileges.executeQuery();
+    }
+
+    public ResultSet getColumns(String c, String s, String tbl, String colPat) throws SQLException
+    {
+        Statement stat = conn.createStatement();
+        ResultSet rs;
+        String sql;
+
+        checkOpen();
+
+        if (getColumnsTblName == null)
+            getColumnsTblName = conn.prepareStatement("select tbl_name from sqlite_master where tbl_name like ?;");
+
+        // determine exact table name
+        getColumnsTblName.setString(1, tbl);
+        rs = getColumnsTblName.executeQuery();
+        if (!rs.next())
+            return null;
+        tbl = rs.getString(1);
+        rs.close();
+
+        sql = "select " + "null as TABLE_CAT, " + "null as TABLE_SCHEM, " + "'" + escape(tbl) + "' as TABLE_NAME, "
+                + "cn as COLUMN_NAME, " + "-1 as DATA_TYPE, " + "tn as TYPE_NAME, " + "2000000000 as COLUMN_SIZE, "
+                + "2000000000 as BUFFER_LENGTH, " + "10   as DECIMAL_DIGITS, " + "10   as NUM_PREC_RADIX, "
+                + "colnullable as NULLABLE, " + "null as REMARKS, " + "null as COLUMN_DEF, "
+                + "0    as SQL_DATA_TYPE, " + "0    as SQL_DATETIME_SUB, " + "2000000000 as CHAR_OCTET_LENGTH, "
+                + "ordpos as ORDINAL_POSITION, " + "(case colnullable when 0 then 'N' when 1 then 'Y' else '' end)"
+                + "    as IS_NULLABLE, " + "null as SCOPE_CATLOG, " + "null as SCOPE_SCHEMA, "
+                + "null as SCOPE_TABLE, " + "null as SOURCE_DATA_TYPE from (";
+
+        // the command "pragma table_info('tablename')" does not embed
+        // like a normal select statement so we must extract the information
+        // and then build a resultset from unioned select statements
+        rs = stat.executeQuery("pragma table_info ('" + escape(tbl) + "');");
+
+        boolean colFound = false;
+        for (int i = 0; rs.next(); i++)
+        {
+            String colName = rs.getString(2);
+            String colType = rs.getString(3);
+            String colNotNull = rs.getString(4);
+
+            int colNullable = 2;
+            if (colType == null)
+                colType = "TEXT";
+            if (colNotNull != null)
+                colNullable = colNotNull.equals("0") ? 1 : 0;
+            if (colFound)
+                sql += " union all ";
+            colFound = true;
+
+            sql += "select " + i + " as ordpos, " + colNullable + " as colnullable, '" + escape(colName) + "' as cn, '"
+                    + escape(colType) + "' as tn";
+
+            if (colPat != null)
+                sql += " where upper(cn) like upper('" + escape(colPat) + "')";
+        }
+        sql += colFound ? ");" : "select null as ordpos, null as colnullable, " + "null as cn, null as tn) limit 0;";
+        rs.close();
+
+        return stat.executeQuery(sql);
+    }
+
+    public ResultSet getCrossReference(String pc, String ps, String pt, String fc, String fs, String ft)
+            throws SQLException
+    {
+        if (getCrossReference == null)
+            getCrossReference = conn.prepareStatement("select " + "null as PKTABLE_CAT, " + "null as PKTABLE_SCHEM, "
+                    + "null as PKTABLE_NAME, " + "null as PKCOLUMN_NAME, " + "null as FKTABLE_CAT, "
+                    + "null as FKTABLE_SCHEM, " + "null as FKTABLE_NAME, " + "null as FKCOLUMN_NAME, "
+                    + "null as KEY_SEQ, " + "null as UPDATE_RULE, " + "null as DELETE_RULE, " + "null as FK_NAME, "
+                    + "null as PK_NAME, " + "null as DEFERRABILITY " + "limit 0;");
+        getCrossReference.clearParameters();
+        return getCrossReference.executeQuery();
+    }
+
+    public ResultSet getSchemas() throws SQLException
+    {
+        if (getSchemas == null)
+            getSchemas = conn.prepareStatement("select " + "null as TABLE_SCHEM, " + "null as TABLE_CATALOG "
+                    + "limit 0;");
+        getSchemas.clearParameters();
+        return getSchemas.executeQuery();
+    }
+
+    public ResultSet getCatalogs() throws SQLException
+    {
+        if (getCatalogs == null)
+            getCatalogs = conn.prepareStatement("select null as TABLE_CAT limit 0;");
+        getCatalogs.clearParameters();
+        return getCatalogs.executeQuery();
+    }
+
+    public ResultSet getPrimaryKeys(String c, String s, String table) throws SQLException
+    {
+        String sql;
+        ResultSet rs;
+        Statement stat = conn.createStatement();
+
+        rs = stat.executeQuery("pragma table_info('" + escape(table) + "');");
+
+        sql = "select " + "null as TABLE_CAT, " + "null as TABLE_SCHEM, " + "'" + escape(table) + "' as TABLE_NAME, "
+                + "cn as COLUMN_NAME, " + "0 as KEY_SEQ, " + "null as PK_NAME from (";
+
+        int i;
+        for (i = 0; rs.next(); i++)
+        {
+            String colName = rs.getString(2);
+
+            if (!rs.getBoolean(6))
+            {
+                i--;
+                continue;
+            }
+            if (i > 0)
+                sql += " union all ";
+
+            sql += "select '" + escape(colName) + "' as cn";
+        }
+        sql += i == 0 ? "select null as cn) limit 0;" : ");";
+        rs.close();
+
+        return stat.executeQuery(sql);
+    }
+
+    public ResultSet getExportedKeys(String c, String s, String t) throws SQLException
+    {
+        if (getExportedKeys == null)
+            getExportedKeys = conn.prepareStatement("select " + "null as PKTABLE_CAT, " + "null as PKTABLE_SCHEM, "
+                    + "null as PKTABLE_NAME, " + "null as PKCOLUMN_NAME, " + "null as FKTABLE_CAT, "
+                    + "null as FKTABLE_SCHEM, " + "null as FKTABLE_NAME, " + "null as FKCOLUMN_NAME, "
+                    + "null as KEY_SEQ, " + "null as UPDATE_RULE, " + "null as DELETE_RULE, " + "null as FK_NAME, "
+                    + "null as PK_NAME, " + "null as DEFERRABILITY limit 0;");
+        return getExportedKeys.executeQuery();
+    }
+
+    public ResultSet getImportedKeys(String c, String s, String t) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    public ResultSet getIndexInfo(String c, String s, String t, boolean u, boolean approximate) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    public ResultSet getProcedureColumns(String c, String s, String p, String colPat) throws SQLException
+    {
+        if (getProcedures == null)
+            getProcedureColumns = conn.prepareStatement("select " + "null as PROCEDURE_CAT, "
+                    + "null as PROCEDURE_SCHEM, " + "null as PROCEDURE_NAME, " + "null as COLUMN_NAME, "
+                    + "null as COLUMN_TYPE, " + "null as DATA_TYPE, " + "null as TYPE_NAME, " + "null as PRECISION, "
+                    + "null as LENGTH, " + "null as SCALE, " + "null as RADIX, " + "null as NULLABLE, "
+                    + "null as REMARKS limit 0;");
+        return getProcedureColumns.executeQuery();
+
+    }
+
+    public ResultSet getProcedures(String c, String s, String p) throws SQLException
+    {
+        if (getProcedures == null)
+            getProcedures = conn.prepareStatement("select " + "null as PROCEDURE_CAT, " + "null as PROCEDURE_SCHEM, "
+                    + "null as PROCEDURE_NAME, " + "null as UNDEF1, " + "null as UNDEF2, " + "null as UNDEF3, "
+                    + "null as REMARKS, " + "null as PROCEDURE_TYPE limit 0;");
+        return getProcedures.executeQuery();
+    }
+
+    public ResultSet getSuperTables(String c, String s, String t) throws SQLException
+    {
+        if (getSuperTables == null)
+            getSuperTables = conn.prepareStatement("select " + "null as TABLE_CAT, " + "null as TABLE_SCHEM, "
+                    + "null as TABLE_NAME, " + "null as SUPERTABLE_NAME limit 0;");
+        return getSuperTables.executeQuery();
+    }
+
+    public ResultSet getSuperTypes(String c, String s, String t) throws SQLException
+    {
+        if (getSuperTypes == null)
+            getSuperTypes = conn.prepareStatement("select " + "null as TYPE_CAT, " + "null as TYPE_SCHEM, "
+                    + "null as TYPE_NAME, " + "null as SUPERTYPE_CAT, " + "null as SUPERTYPE_SCHEM, "
+                    + "null as SUPERTYPE_NAME limit 0;");
+        return getSuperTypes.executeQuery();
+    }
+
+    public ResultSet getTablePrivileges(String c, String s, String t) throws SQLException
+    {
+        if (getTablePrivileges == null)
+            getTablePrivileges = conn.prepareStatement("select " + "null as TABLE_CAT, " + "null as TABLE_SCHEM, "
+                    + "null as TABLE_NAME, " + "null as GRANTOR, " + "null as GRANTEE, " + "null as PRIVILEGE, "
+                    + "null as IS_GRANTABLE limit 0;");
+        return getTablePrivileges.executeQuery();
+    }
+
+    public synchronized ResultSet getTables(String c, String s, String t, String[] types) throws SQLException
+    {
+        checkOpen();
+
+        t = (t == null || "".equals(t)) ? "%" : t.toUpperCase();
+
+        String sql = "select" + " null as TABLE_CAT," + " null as TABLE_SCHEM," + " upper(name) as TABLE_NAME,"
+                + " upper(type) as TABLE_TYPE," + " null as REMARKS," + " null as TYPE_CAT," + " null as TYPE_SCHEM,"
+                + " null as TYPE_NAME," + " null as SELF_REFERENCING_COL_NAME," + " null as REF_GENERATION"
+                + " from (select name, type from sqlite_master union all"
+                + "       select name, type from sqlite_temp_master)" + " where TABLE_NAME like '" + escape(t) + "'";
+
+        if (types != null)
+        {
+            sql += " and TABLE_TYPE in (";
+            for (int i = 0; i < types.length; i++)
+            {
+                if (i > 0)
+                    sql += ", ";
+                sql += "'" + types[i].toUpperCase() + "'";
+            }
+            sql += ")";
+        }
+
+        sql += ";";
+
+        return conn.createStatement().executeQuery(sql);
+    }
+
+    public ResultSet getTableTypes() throws SQLException
+    {
+        checkOpen();
+        if (getTableTypes == null)
+            getTableTypes = conn.prepareStatement("select 'TABLE' as TABLE_TYPE"
+                    + " union select 'VIEW' as TABLE_TYPE;");
+        getTableTypes.clearParameters();
+        return getTableTypes.executeQuery();
+    }
+
+    public ResultSet getTypeInfo() throws SQLException
+    {
+        if (getTypeInfo == null)
+        {
+            getTypeInfo = conn.prepareStatement("select " + "tn as TYPE_NAME, " + "dt as DATA_TYPE, "
+                    + "0 as PRECISION, " + "null as LITERAL_PREFIX, " + "null as LITERAL_SUFFIX, "
+                    + "null as CREATE_PARAMS, "
+                    + typeNullable
+                    + " as NULLABLE, "
+                    + "1 as CASE_SENSITIVE, "
+                    + typeSearchable
+                    + " as SEARCHABLE, "
+                    + "0 as UNSIGNED_ATTRIBUTE, "
+                    + "0 as FIXED_PREC_SCALE, "
+                    + "0 as AUTO_INCREMENT, "
+                    + "null as LOCAL_TYPE_NAME, "
+                    + "0 as MINIMUM_SCALE, "
+                    + "0 as MAXIMUM_SCALE, "
+                    + "0 as SQL_DATA_TYPE, "
+                    + "0 as SQL_DATETIME_SUB, "
+                    + "10 as NUM_PREC_RADIX from ("
+                    + "    select 'BLOB' as tn, "
+                    + Types.BLOB
+                    + " as dt union"
+                    + "    select 'NULL' as tn, "
+                    + Types.NULL
+                    + " as dt union"
+                    + "    select 'REAL' as tn, "
+                    + Types.REAL
+                    + " as dt union"
+                    + "    select 'TEXT' as tn, "
+                    + Types.VARCHAR
+                    + " as dt union"
+                    + "    select 'INTEGER' as tn, "
+                    + Types.INTEGER + " as dt" + ") order by TYPE_NAME;");
+        }
+
+        getTypeInfo.clearParameters();
+        return getTypeInfo.executeQuery();
+    }
+
+    public ResultSet getUDTs(String c, String s, String t, int[] types) throws SQLException
+    {
+        if (getUDTs == null)
+            getUDTs = conn.prepareStatement("select " + "null as TYPE_CAT, " + "null as TYPE_SCHEM, "
+                    + "null as TYPE_NAME, " + "null as CLASS_NAME, " + "null as DATA_TYPE, " + "null as REMARKS, "
+                    + "null as BASE_TYPE " + "limit 0;");
+
+        getUDTs.clearParameters();
+        return getUDTs.executeQuery();
+    }
+
+    public ResultSet getVersionColumns(String c, String s, String t) throws SQLException
+    {
+        if (getVersionColumns == null)
+            getVersionColumns = conn.prepareStatement("select " + "null as SCOPE, " + "null as COLUMN_NAME, "
+                    + "null as DATA_TYPE, " + "null as TYPE_NAME, " + "null as COLUMN_SIZE, "
+                    + "null as BUFFER_LENGTH, " + "null as DECIMAL_DIGITS, " + "null as PSEUDO_COLUMN limit 0;");
+        return getVersionColumns.executeQuery();
+    }
+
+    ResultSet getGeneratedKeys() throws SQLException
+    {
+        if (getGeneratedKeys == null)
+            getGeneratedKeys = conn.prepareStatement("select last_insert_rowid();");
+        return getGeneratedKeys.executeQuery();
+    }
+
+    /** Replace all instances of ' with '' */
+    private String escape(final String val)
+    {
+        // TODO: this function is ugly, pass this work off to SQLite, then we
+        // don't have to worry about Unicode 4, other characters needing
+        // escaping, etc.
+        int len = val.length();
+        StringBuffer buf = new StringBuffer(len);
+        for (int i = 0; i < len; i++)
+        {
+            if (val.charAt(i) == '\'')
+                buf.append('\'');
+            buf.append(val.charAt(i));
+        }
+        return buf.toString();
+    }
+
+    @Override
+    public boolean autoCommitFailureClosesAllResultSets() throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public ResultSet getClientInfoProperties() throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern,
+            String columnNamePattern) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public RowIdLifetime getRowIdLifetime() throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public boolean isWrapperFor(Class< ? > iface) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public <T> T unwrap(Class<T> iface) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+}
diff --git a/src/main/java/org/sqlite/NativeDB.c b/src/main/java/org/sqlite/NativeDB.c
new file mode 100644 (file)
index 0000000..c14ca45
--- /dev/null
@@ -0,0 +1,759 @@
+/* Copyright 2006 David Crawshaw, see LICENSE file for licensing [BSD]. */
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include "NativeDB.h"
+#include "sqlite3.h"
+
+static jclass dbclass = 0;
+static jclass  fclass = 0;
+static jclass  aclass = 0;
+
+static void * toref(jlong value)
+{
+    jvalue ret;
+    ret.j = value;
+    return (void *) ret.l;
+}
+
+static jlong fromref(void * value)
+{
+    jvalue ret;
+    ret.l = value;
+    return ret.j;
+}
+
+static void throwex(JNIEnv *env, jobject this)
+{
+    static jmethodID mth_throwex = 0;
+
+    if (!mth_throwex)
+        mth_throwex = (*env)->GetMethodID(env, dbclass, "throwex", "()V");
+
+    (*env)->CallVoidMethod(env, this, mth_throwex);
+}
+
+static void throwexmsg(JNIEnv *env, const char *str)
+{
+    static jmethodID mth_throwexmsg = 0;
+
+    if (!mth_throwexmsg) mth_throwexmsg = (*env)->GetStaticMethodID(
+            env, dbclass, "throwex", "(Ljava/lang/String;)V");
+
+    (*env)->CallStaticVoidMethod(env, dbclass, mth_throwexmsg,
+                                (*env)->NewStringUTF(env, str));
+}
+
+static sqlite3 * gethandle(JNIEnv *env, jobject this)
+{
+    static jfieldID pointer = 0;
+    if (!pointer) pointer = (*env)->GetFieldID(env, dbclass, "pointer", "J");
+
+    return (sqlite3 *)toref((*env)->GetLongField(env, this, pointer));
+}
+
+static void sethandle(JNIEnv *env, jobject this, sqlite3 * ref)
+{
+    static jfieldID pointer = 0;
+    if (!pointer) pointer = (*env)->GetFieldID(env, dbclass, "pointer", "J");
+
+    (*env)->SetLongField(env, this, pointer, fromref(ref));
+}
+
+/* Returns number of 16-bit blocks in UTF-16 string, not including null. */
+static jsize jstrlen(const jchar *str)
+{
+    const jchar *s;
+    for (s = str; *s; s++);
+    return (jsize)(s - str);
+}
+
+
+// User Defined Function SUPPORT ////////////////////////////////////
+
+struct UDFData {
+    JavaVM *vm;
+    jobject func;
+    struct UDFData *next;  // linked list of all UDFData instances
+};
+
+/* Returns the sqlite3_value for the given arg of the given function.
+ * If 0 is returned, an exception has been thrown to report the reason. */
+static sqlite3_value * tovalue(JNIEnv *env, jobject function, jint arg)
+{
+    jlong value_pntr = 0;
+    jint numArgs = 0;
+    static jfieldID func_value = 0,
+                    func_args = 0;
+
+    if (!func_value || !func_args) {
+        func_value = (*env)->GetFieldID(env, fclass, "value", "J");
+        func_args  = (*env)->GetFieldID(env, fclass, "args", "I");
+    }
+
+    // check we have any business being here
+    if (arg  < 0) { throwexmsg(env, "negative arg out of range"); return 0; }
+    if (!function) { throwexmsg(env, "inconstent function"); return 0; }
+
+    value_pntr = (*env)->GetLongField(env, function, func_value);
+    numArgs = (*env)->GetIntField(env, function, func_args);
+
+    if (value_pntr == 0) { throwexmsg(env, "no current value"); return 0; }
+    if (arg >= numArgs) { throwexmsg(env, "arg out of range"); return 0; }
+
+    return ((sqlite3_value**)toref(value_pntr))[arg];
+}
+
+/* called if an exception occured processing xFunc */
+static void xFunc_error(sqlite3_context *context, JNIEnv *env)
+{
+    const char *strmsg = 0;
+    jstring msg = 0;
+    jint msgsize = 0;
+
+    jclass exclass = 0;
+    static jmethodID exp_msg = 0;
+    jthrowable ex = (*env)->ExceptionOccurred(env);
+
+    (*env)->ExceptionClear(env);
+
+    if (!exp_msg) {
+        exclass = (*env)->FindClass(env, "java/lang/Throwable");
+        exp_msg = (*env)->GetMethodID(
+                env, exclass, "toString", "()Ljava/lang/String;");
+    }
+
+    msg = (jstring)(*env)->CallObjectMethod(env, ex, exp_msg);
+    if (!msg) { sqlite3_result_error(context, "unknown error", 13); return; }
+
+    msgsize = (*env)->GetStringUTFLength(env, msg);
+    strmsg = (*env)->GetStringUTFChars(env, msg, 0);
+    assert(strmsg); // out-of-memory
+
+    sqlite3_result_error(context, strmsg, msgsize);
+
+    (*env)->ReleaseStringUTFChars(env, msg, strmsg);
+}
+
+/* used to call xFunc, xStep and xFinal */
+static xCall(
+    sqlite3_context *context,
+    int args,
+    sqlite3_value** value,
+    jobject func,
+    jmethodID method)
+{
+    static jfieldID fld_context = 0,
+                     fld_value = 0,
+                     fld_args = 0;
+    JNIEnv *env = 0;
+    struct UDFData *udf = 0;
+
+    udf = (struct UDFData*)sqlite3_user_data(context);
+    assert(udf);
+    (*udf->vm)->AttachCurrentThread(udf->vm, (void **)&env, 0);
+    if (!func) func = udf->func;
+
+    if (!fld_context || !fld_value || !fld_args) {
+        fld_context = (*env)->GetFieldID(env, fclass, "context", "J");
+        fld_value   = (*env)->GetFieldID(env, fclass, "value", "J");
+        fld_args    = (*env)->GetFieldID(env, fclass, "args", "I");
+    }
+
+    (*env)->SetLongField(env, func, fld_context, fromref(context));
+    (*env)->SetLongField(env, func, fld_value, value ? fromref(value) : 0);
+    (*env)->SetIntField(env, func, fld_args, args);
+
+    (*env)->CallVoidMethod(env, func, method);
+
+    (*env)->SetLongField(env, func, fld_context, 0);
+    (*env)->SetLongField(env, func, fld_value, 0);
+    (*env)->SetIntField(env, func, fld_args, 0);
+
+    // check if xFunc threw an Exception
+    if ((*env)->ExceptionCheck(env)) xFunc_error(context, env);
+}
+
+
+void xFunc(sqlite3_context *context, int args, sqlite3_value** value)
+{
+    static jmethodID mth = 0;
+    if (!mth) {
+        JNIEnv *env;
+        struct UDFData *udf = (struct UDFData*)sqlite3_user_data(context);
+        (*udf->vm)->AttachCurrentThread(udf->vm, (void **)&env, 0);
+        mth = (*env)->GetMethodID(env, fclass, "xFunc", "()V");
+    }
+    xCall(context, args, value, 0, mth);
+}
+
+void xStep(sqlite3_context *context, int args, sqlite3_value** value)
+{
+    JNIEnv *env;
+    struct UDFData *udf;
+    jobject *func = 0;
+    static jmethodID mth = 0;
+    static jmethodID clone = 0;
+
+    if (!mth || !clone) {
+        udf = (struct UDFData*)sqlite3_user_data(context);
+        (*udf->vm)->AttachCurrentThread(udf->vm, (void **)&env, 0);
+
+        mth = (*env)->GetMethodID(env, aclass, "xStep", "()V");
+        clone = (*env)->GetMethodID(env, aclass, "clone",
+            "()Ljava/lang/Object;");
+    }
+
+    // clone the Function.Aggregate instance and store a pointer
+    // in SQLite's aggregate_context (clean up in xFinal)
+    func = sqlite3_aggregate_context(context, sizeof(jobject));
+    if (!*func) {
+        udf = (struct UDFData*)sqlite3_user_data(context);
+        (*udf->vm)->AttachCurrentThread(udf->vm, (void **)&env, 0);
+
+        *func = (*env)->CallObjectMethod(env, udf->func, clone);
+        *func = (*env)->NewGlobalRef(env, *func);
+    }
+
+    xCall(context, args, value, *func, mth);
+}
+
+void xFinal(sqlite3_context *context)
+{
+    JNIEnv *env = 0;
+    struct UDFData *udf = 0;
+    jobject *func = 0;
+    static jmethodID mth = 0;
+
+    udf = (struct UDFData*)sqlite3_user_data(context);
+    (*udf->vm)->AttachCurrentThread(udf->vm, (void **)&env, 0);
+
+    if (!mth) mth = (*env)->GetMethodID(env, aclass, "xFinal", "()V");
+
+    func = sqlite3_aggregate_context(context, sizeof(jobject));
+    assert(*func); // disaster
+
+    xCall(context, 0, 0, *func, mth);
+
+    // clean up Function.Aggregate instance
+    (*env)->DeleteGlobalRef(env, *func);
+}
+
+
+// INITIALISATION ///////////////////////////////////////////////////
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
+{
+    JNIEnv* env = 0;
+
+    if (JNI_OK != (*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_2))
+        return JNI_ERR;
+
+    dbclass = (*env)->FindClass(env, "org/sqlite/NativeDB");
+    if (!dbclass) return JNI_ERR;
+    dbclass = (*env)->NewGlobalRef(env, dbclass);
+
+    fclass = (*env)->FindClass(env, "org/sqlite/Function");
+    if (!fclass) return JNI_ERR;
+    fclass = (*env)->NewGlobalRef(env, fclass);
+
+    aclass = (*env)->FindClass(env, "org/sqlite/Function$Aggregate");
+    if (!aclass) return JNI_ERR;
+    aclass = (*env)->NewGlobalRef(env, aclass);
+
+    return JNI_VERSION_1_2;
+}
+
+
+// WRAPPERS for sqlite_* functions //////////////////////////////////
+
+JNIEXPORT void JNICALL Java_org_sqlite_NativeDB_open(
+        JNIEnv *env, jobject this, jstring file)
+{
+    int ret;
+    sqlite3 *db = gethandle(env, this);
+    const char *str;
+
+    if (db) {
+        throwexmsg(env, "DB already open");
+        sqlite3_close(db);
+        return;
+    }
+
+    str = (*env)->GetStringUTFChars(env, file, 0); 
+    if (sqlite3_open(str, &db)) {
+        throwex(env, this);
+        sqlite3_close(db);
+        return;
+    }
+    (*env)->ReleaseStringUTFChars(env, file, str);
+
+    sethandle(env, this, db);
+}
+
+JNIEXPORT void JNICALL Java_org_sqlite_NativeDB__1close(
+        JNIEnv *env, jobject this)
+{
+    if (sqlite3_close(gethandle(env, this)) != SQLITE_OK)
+        throwex(env, this);
+    sethandle(env, this, 0);
+}
+
+JNIEXPORT void JNICALL Java_org_sqlite_NativeDB_interrupt(JNIEnv *env, jobject this)
+{
+    sqlite3_interrupt(gethandle(env, this));
+}
+
+JNIEXPORT void JNICALL Java_org_sqlite_NativeDB_busy_1timeout(
+    JNIEnv *env, jobject this, jint ms)
+{
+    sqlite3_busy_timeout(gethandle(env, this), ms);
+}
+
+JNIEXPORT jlong JNICALL Java_org_sqlite_NativeDB_prepare(
+        JNIEnv *env, jobject this, jstring sql)
+{
+    sqlite3* db = gethandle(env, this);
+    sqlite3_stmt* stmt;
+
+    const char *strsql = (*env)->GetStringUTFChars(env, sql, 0);
+    int status = sqlite3_prepare(db, strsql, -1, &stmt, 0);
+    (*env)->ReleaseStringUTFChars(env, sql, strsql);
+
+    if (status != SQLITE_OK) {
+        throwex(env, this);
+        return fromref(0);
+    }
+    return fromref(stmt);
+}
+
+JNIEXPORT jstring JNICALL Java_org_sqlite_NativeDB_errmsg(JNIEnv *env, jobject this)
+{
+    return (*env)->NewStringUTF(env, sqlite3_errmsg(gethandle(env, this)));
+}
+
+JNIEXPORT jstring JNICALL Java_org_sqlite_NativeDB_libversion(
+        JNIEnv *env, jobject this)
+{
+    return (*env)->NewStringUTF(env, sqlite3_libversion());
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_changes(
+        JNIEnv *env, jobject this)
+{
+    return sqlite3_changes(gethandle(env, this));
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_finalize(
+        JNIEnv *env, jobject this, jlong stmt)
+{
+    return sqlite3_finalize(toref(stmt));
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_step(
+        JNIEnv *env, jobject this, jlong stmt)
+{
+    return sqlite3_step(toref(stmt));
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_reset(
+        JNIEnv *env, jobject this, jlong stmt)
+{
+    return sqlite3_reset(toref(stmt));
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_clear_1bindings(
+        JNIEnv *env, jobject this, jlong stmt)
+{
+    int i;
+    int count = sqlite3_bind_parameter_count(toref(stmt));
+    jint rc = SQLITE_OK;
+    for(i=1; rc==SQLITE_OK && i <= count; i++) {
+        rc = sqlite3_bind_null(toref(stmt), i);
+    }
+    return rc;
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_bind_1parameter_1count(
+        JNIEnv *env, jobject this, jlong stmt)
+{
+    return sqlite3_bind_parameter_count(toref(stmt));
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_column_1count(
+        JNIEnv *env, jobject this, jlong stmt)
+{
+    return sqlite3_column_count(toref(stmt));
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_column_1type(
+        JNIEnv *env, jobject this, jlong stmt, jint col)
+{
+    return sqlite3_column_type(toref(stmt), col);
+}
+
+JNIEXPORT jstring JNICALL Java_org_sqlite_NativeDB_column_1decltype(
+        JNIEnv *env, jobject this, jlong stmt, jint col)
+{
+    const char *str = sqlite3_column_decltype(toref(stmt), col);
+    return (*env)->NewStringUTF(env, str);
+}
+
+JNIEXPORT jstring JNICALL Java_org_sqlite_NativeDB_column_1table_1name(
+        JNIEnv *env, jobject this, jlong stmt, jint col)
+{
+    const void *str = sqlite3_column_table_name16(toref(stmt), col);
+    return str ? (*env)->NewString(env, str, jstrlen(str)) : NULL;
+}
+
+JNIEXPORT jstring JNICALL Java_org_sqlite_NativeDB_column_1name(
+        JNIEnv *env, jobject this, jlong stmt, jint col)
+{
+    const void *str = sqlite3_column_name16(toref(stmt), col);
+    return str ? (*env)->NewString(env, str, jstrlen(str)) : NULL;
+}
+
+JNIEXPORT jstring JNICALL Java_org_sqlite_NativeDB_column_1text(
+        JNIEnv *env, jobject this, jlong stmt, jint col)
+{
+    return (*env)->NewStringUTF(
+        env, (const char*)sqlite3_column_text(toref(stmt), col));
+}
+
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_NativeDB_column_1blob(
+        JNIEnv *env, jobject this, jlong stmt, jint col)
+{
+    jsize length;
+    jbyteArray jBlob;
+    jbyte *a;
+    const void *blob = sqlite3_column_blob(toref(stmt), col);
+    if (!blob) return NULL;
+
+    length = sqlite3_column_bytes(toref(stmt), col);
+    jBlob = (*env)->NewByteArray(env, length);
+    assert(jBlob); // out-of-memory
+
+    a = (*env)->GetPrimitiveArrayCritical(env, jBlob, 0);
+    memcpy(a, blob, length);
+    (*env)->ReleasePrimitiveArrayCritical(env, jBlob, a, 0);
+
+    return jBlob;
+}
+
+JNIEXPORT jdouble JNICALL Java_org_sqlite_NativeDB_column_1double(
+        JNIEnv *env, jobject this, jlong stmt, jint col)
+{
+    return sqlite3_column_double(toref(stmt), col);
+}
+
+JNIEXPORT jlong JNICALL Java_org_sqlite_NativeDB_column_1long(
+        JNIEnv *env, jobject this, jlong stmt, jint col)
+{
+    return sqlite3_column_int64(toref(stmt), col);
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_column_1int(
+        JNIEnv *env, jobject this, jlong stmt, jint col)
+{
+    return sqlite3_column_int(toref(stmt), col);
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_bind_1null(
+        JNIEnv *env, jobject this, jlong stmt, jint pos)
+{
+    return sqlite3_bind_null(toref(stmt), pos);
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_bind_1int(
+        JNIEnv *env, jobject this, jlong stmt, jint pos, jint v)
+{
+    return sqlite3_bind_int(toref(stmt), pos, v);
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_bind_1long(
+        JNIEnv *env, jobject this, jlong stmt, jint pos, jlong v)
+{
+    return sqlite3_bind_int64(toref(stmt), pos, v);
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_bind_1double(
+        JNIEnv *env, jobject this, jlong stmt, jint pos, jdouble v)
+{
+    return sqlite3_bind_double(toref(stmt), pos, v);
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_bind_1text(
+        JNIEnv *env, jobject this, jlong stmt, jint pos, jstring v)
+{
+    const char *chars = (*env)->GetStringUTFChars(env, v, 0);
+    int rc = sqlite3_bind_text(toref(stmt), pos, chars, -1, SQLITE_TRANSIENT);
+    (*env)->ReleaseStringUTFChars(env, v, chars);
+    return rc;
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_bind_1blob(
+        JNIEnv *env, jobject this, jlong stmt, jint pos, jbyteArray v)
+{
+    jint rc;
+    void *a;
+    jsize size = (*env)->GetArrayLength(env, v);
+    assert(a = (*env)->GetPrimitiveArrayCritical(env, v, 0));
+    rc = sqlite3_bind_blob(toref(stmt), pos, a, size, SQLITE_TRANSIENT);
+    (*env)->ReleasePrimitiveArrayCritical(env, v, a, JNI_ABORT);
+    return rc;
+}
+
+JNIEXPORT void JNICALL Java_org_sqlite_NativeDB_result_1null(
+        JNIEnv *env, jobject this, jlong context)
+{
+    sqlite3_result_null(toref(context));
+}
+
+JNIEXPORT void JNICALL Java_org_sqlite_NativeDB_result_1text(
+        JNIEnv *env, jobject this, jlong context, jstring value)
+{
+    const jchar *str;
+    jsize size;
+
+    if (value == NULL) { sqlite3_result_null(toref(context)); return; }
+    size = (*env)->GetStringLength(env, value) * 2;
+
+    str = (*env)->GetStringCritical(env, value, 0);
+    assert(str); // out-of-memory
+    sqlite3_result_text16(toref(context), str, size, SQLITE_TRANSIENT);
+    (*env)->ReleaseStringCritical(env, value, str);
+}
+
+JNIEXPORT void JNICALL Java_org_sqlite_NativeDB_result_1blob(
+        JNIEnv *env, jobject this, jlong context, jobject value)
+{
+    jbyte *bytes;
+    jsize size;
+
+    if (value == NULL) { sqlite3_result_null(toref(context)); return; }
+    size = (*env)->GetArrayLength(env, value);
+
+    // be careful with *Critical
+    bytes = (*env)->GetPrimitiveArrayCritical(env, value, 0);
+    assert(bytes); // out-of-memory
+    sqlite3_result_blob(toref(context), bytes, size, SQLITE_TRANSIENT);
+    (*env)->ReleasePrimitiveArrayCritical(env, value, bytes, JNI_ABORT);
+}
+
+JNIEXPORT void JNICALL Java_org_sqlite_NativeDB_result_1double(
+        JNIEnv *env, jobject this, jlong context, jdouble value)
+{
+    sqlite3_result_double(toref(context), value);
+}
+
+JNIEXPORT void JNICALL Java_org_sqlite_NativeDB_result_1long(
+        JNIEnv *env, jobject this, jlong context, jlong value)
+{
+    sqlite3_result_int64(toref(context), value);
+}
+
+JNIEXPORT void JNICALL Java_org_sqlite_NativeDB_result_1int(
+        JNIEnv *env, jobject this, jlong context, jint value)
+{
+    sqlite3_result_int(toref(context), value);
+}
+
+
+
+
+JNIEXPORT jstring JNICALL Java_org_sqlite_NativeDB_value_1text(
+        JNIEnv *env, jobject this, jobject f, jint arg)
+{
+    jint length = 0;
+    const void *str = 0;
+    sqlite3_value *value = tovalue(env, f, arg);
+    if (!value) return NULL;
+
+    length = sqlite3_value_bytes16(value) / 2; // in jchars
+    str = sqlite3_value_text16(value);
+    return str ? (*env)->NewString(env, str, length) : NULL;
+}
+
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_NativeDB_value_1blob(
+        JNIEnv *env, jobject this, jobject f, jint arg)
+{
+    jsize length;
+    jbyteArray jBlob;
+    jbyte *a;
+    const void *blob;
+    sqlite3_value *value = tovalue(env, f, arg);
+    if (!value) return NULL;
+
+    blob = sqlite3_value_blob(value);
+    if (!blob) return NULL;
+
+    length = sqlite3_value_bytes(value);
+    jBlob = (*env)->NewByteArray(env, length);
+    assert(jBlob); // out-of-memory
+
+    a = (*env)->GetPrimitiveArrayCritical(env, jBlob, 0);
+    memcpy(a, blob, length);
+    (*env)->ReleasePrimitiveArrayCritical(env, jBlob, a, 0);
+
+    return jBlob;
+}
+
+JNIEXPORT jdouble JNICALL Java_org_sqlite_NativeDB_value_1double(
+        JNIEnv *env, jobject this, jobject f, jint arg)
+{
+    sqlite3_value *value = tovalue(env, f, arg);
+    return value ? sqlite3_value_double(value) : 0;
+}
+
+JNIEXPORT jlong JNICALL Java_org_sqlite_NativeDB_value_1long(
+        JNIEnv *env, jobject this, jobject f, jint arg)
+{
+    sqlite3_value *value = tovalue(env, f, arg);
+    return value ? sqlite3_value_int64(value) : 0;
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_value_1int(
+        JNIEnv *env, jobject this, jobject f, jint arg)
+{
+    sqlite3_value *value = tovalue(env, f, arg);
+    return value ? sqlite3_value_int(value) : 0;
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_value_1type(
+        JNIEnv *env, jobject this, jobject func, jint arg)
+{
+    return sqlite3_value_type(tovalue(env, func, arg));
+}
+
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_create_1function(
+        JNIEnv *env, jobject this, jstring name, jobject func)
+{
+    jint ret = 0;
+    const char *strname = 0;
+    int isAgg = 0;
+
+    static jfieldID udfdatalist = 0;
+    struct UDFData *udf = malloc(sizeof(struct UDFData));
+
+    assert(udf); // out-of-memory
+
+    if (!udfdatalist)
+        udfdatalist = (*env)->GetFieldID(env, dbclass, "udfdatalist", "J");
+
+    isAgg = (*env)->IsInstanceOf(env, func, aclass);
+    udf->func = (*env)->NewGlobalRef(env, func);
+    (*env)->GetJavaVM(env, &udf->vm);
+
+    // add new function def to linked list
+    udf->next = toref((*env)->GetLongField(env, this, udfdatalist));
+    (*env)->SetLongField(env, this, udfdatalist, fromref(udf));
+
+    strname = (*env)->GetStringUTFChars(env, name, 0);
+    assert(strname); // out-of-memory
+
+    ret = sqlite3_create_function(
+            gethandle(env, this),
+            strname,       // function name
+            -1,            // number of args
+            SQLITE_UTF16,  // preferred chars
+            udf,
+            isAgg ? 0 :&xFunc,
+            isAgg ? &xStep : 0,
+            isAgg ? &xFinal : 0
+    );
+
+    (*env)->ReleaseStringUTFChars(env, name, strname);
+
+    return ret;
+}
+
+JNIEXPORT jint JNICALL Java_org_sqlite_NativeDB_destroy_1function(
+        JNIEnv *env, jobject this, jstring name)
+{
+    const char* strname = (*env)->GetStringUTFChars(env, name, 0);
+    sqlite3_create_function(
+        gethandle(env, this), strname, -1, SQLITE_UTF16, 0, 0, 0, 0
+    );
+    (*env)->ReleaseStringUTFChars(env, name, strname);
+}
+
+JNIEXPORT void JNICALL Java_org_sqlite_NativeDB_free_1functions(
+        JNIEnv *env, jobject this)
+{
+    // clean up all the malloc()ed UDFData instances using the
+    // linked list stored in DB.udfdatalist
+    jfieldID udfdatalist;
+    struct UDFData *udf, *udfpass;
+
+    udfdatalist = (*env)->GetFieldID(env, dbclass, "udfdatalist", "J");
+    udf = toref((*env)->GetLongField(env, this, udfdatalist));
+    (*env)->SetLongField(env, this, udfdatalist, 0);
+
+    while (udf) {
+        udfpass = udf->next;
+        (*env)->DeleteGlobalRef(env, udf->func);
+        free(udf);
+        udf = udfpass;
+    }
+}
+
+
+// COMPOUND FUNCTIONS ///////////////////////////////////////////////
+
+JNIEXPORT jobjectArray JNICALL Java_org_sqlite_NativeDB_column_1metadata(
+        JNIEnv *env, jobject this, jlong stmt)
+{
+    const char *zTableName, *zColumnName;
+    int pNotNull, pPrimaryKey, pAutoinc, i, colCount;
+    jobjectArray array;
+    jbooleanArray colData;
+    jboolean* colDataRaw;
+    sqlite3 *db;
+    sqlite3_stmt *dbstmt;
+
+    db = gethandle(env, this);
+    dbstmt = toref(stmt);
+
+    colCount = sqlite3_column_count(dbstmt);
+    array = (*env)->NewObjectArray(
+        env, colCount, (*env)->FindClass(env, "[Z"), NULL) ;
+    assert(array); // out-of-memory
+
+    colDataRaw = (jboolean*)malloc(3 * sizeof(jboolean));
+    assert(colDataRaw); // out-of-memory
+
+    for (i = 0; i < colCount; i++) {
+        // load passed column name and table name
+        zColumnName = sqlite3_column_name(dbstmt, i);
+        zTableName  = sqlite3_column_table_name(dbstmt, i);
+
+        pNotNull = 0;
+        pPrimaryKey = 0;
+        pAutoinc = 0;
+
+        // request metadata for column and load into output variables
+        if (zTableName && zColumnName) {
+            sqlite3_table_column_metadata(
+                db, 0, zTableName, zColumnName,
+                0, 0, &pNotNull, &pPrimaryKey, &pAutoinc
+            );
+        }
+
+        // load relevant metadata into 2nd dimension of return results
+        colDataRaw[0] = pNotNull;
+        colDataRaw[1] = pPrimaryKey;
+        colDataRaw[2] = pAutoinc;
+
+        colData = (*env)->NewBooleanArray(env, 3);
+        assert(colData); // out-of-memory
+
+        (*env)->SetBooleanArrayRegion(env, colData, 0, 3, colDataRaw);
+        (*env)->SetObjectArrayElement(env, array, i, colData);
+    }
+
+    free(colDataRaw);
+
+    return array;
+}
+
diff --git a/src/main/java/org/sqlite/NativeDB.java b/src/main/java/org/sqlite/NativeDB.java
new file mode 100644 (file)
index 0000000..db8f099
--- /dev/null
@@ -0,0 +1,154 @@
+/* Copyright 2006 David Crawshaw, see LICENSE file for licensing [BSD]. */
+package org.sqlite;
+
+import java.io.File;
+import java.sql.SQLException;
+
+import org.xerial.db.sql.sqlite.SQLiteJDBCLoader;
+
+/** This class provides a thin JNI layer over the SQLite3 C API. */
+final class NativeDB extends DB
+{
+    private static Boolean loaded = null;
+
+    static boolean load()
+    {
+        if (loaded != null)
+            return loaded == Boolean.TRUE;
+
+        SQLiteJDBCLoader.initialize(true);
+
+        String libpath = System.getProperty("org.sqlite.lib.path");
+        String libname = System.getProperty("org.sqlite.lib.name");
+        if (libname == null)
+            libname = System.mapLibraryName("sqlitejdbc");
+
+        try
+        {
+            if (libpath == null)
+                System.loadLibrary("sqlitejdbc");
+            else
+                System.load(new File(libpath, libname).getAbsolutePath());
+        }
+        catch (UnsatisfiedLinkError e)
+        {
+            loaded = Boolean.FALSE;
+            return false;
+        }
+
+        loaded = Boolean.TRUE;
+        return true;
+    }
+
+    /** linked list of all instanced UDFDatas */
+    private long udfdatalist = 0;
+
+    // WRAPPER FUNCTIONS ////////////////////////////////////////////
+
+    native synchronized void open(String filename) throws SQLException;
+
+    protected native synchronized void _close() throws SQLException;
+
+    native synchronized void interrupt();
+
+    native synchronized void busy_timeout(int ms);
+
+    // native synchronized void exec(String sql) throws SQLException;
+    protected native synchronized long prepare(String sql) throws SQLException;
+
+    native synchronized String errmsg();
+
+    native synchronized String libversion();
+
+    native synchronized int changes();
+
+    protected native synchronized int finalize(long stmt);
+
+    protected native synchronized int step(long stmt);
+
+    protected native synchronized int reset(long stmt);
+
+    native synchronized int clear_bindings(long stmt);
+
+    native synchronized int bind_parameter_count(long stmt);
+
+    native synchronized int column_count(long stmt);
+
+    native synchronized int column_type(long stmt, int col);
+
+    native synchronized String column_decltype(long stmt, int col);
+
+    native synchronized String column_table_name(long stmt, int col);
+
+    native synchronized String column_name(long stmt, int col);
+
+    native synchronized String column_text(long stmt, int col);
+
+    native synchronized byte[] column_blob(long stmt, int col);
+
+    native synchronized double column_double(long stmt, int col);
+
+    native synchronized long column_long(long stmt, int col);
+
+    native synchronized int column_int(long stmt, int col);
+
+    native synchronized int bind_null(long stmt, int pos);
+
+    native synchronized int bind_int(long stmt, int pos, int v);
+
+    native synchronized int bind_long(long stmt, int pos, long v);
+
+    native synchronized int bind_double(long stmt, int pos, double v);
+
+    native synchronized int bind_text(long stmt, int pos, String v);
+
+    native synchronized int bind_blob(long stmt, int pos, byte[] v);
+
+    native synchronized void result_null(long context);
+
+    native synchronized void result_text(long context, String val);
+
+    native synchronized void result_blob(long context, byte[] val);
+
+    native synchronized void result_double(long context, double val);
+
+    native synchronized void result_long(long context, long val);
+
+    native synchronized void result_int(long context, int val);
+
+    native synchronized void result_error(long context, String err);
+
+    native synchronized int value_bytes(Function f, int arg);
+
+    native synchronized String value_text(Function f, int arg);
+
+    native synchronized byte[] value_blob(Function f, int arg);
+
+    native synchronized double value_double(Function f, int arg);
+
+    native synchronized long value_long(Function f, int arg);
+
+    native synchronized int value_int(Function f, int arg);
+
+    native synchronized int value_type(Function f, int arg);
+
+    native synchronized int create_function(String name, Function func);
+
+    native synchronized int destroy_function(String name);
+
+    native synchronized void free_functions();
+
+    // COMPOUND FUNCTIONS (for optimisation) /////////////////////////
+
+    /**
+     * Provides metadata for the columns of a statement. Returns: res[col][0] =
+     * true if column constrained NOT NULL res[col][1] = true if column is part
+     * of the primary key res[col][2] = true if column is auto-increment
+     */
+    native synchronized boolean[][] column_metadata(long stmt);
+
+    static void throwex(String msg) throws SQLException
+    {
+        throw new SQLException(msg);
+    }
+}
diff --git a/src/main/java/org/sqlite/NestedDB.c b/src/main/java/org/sqlite/NestedDB.c
new file mode 100644 (file)
index 0000000..d799017
--- /dev/null
@@ -0,0 +1,62 @@
+#include <stdlib.h>
+#include "sqlite3.h"
+
+/* Provides access to metadata across NestedVM 7-argument limit on functions.*/
+struct metadata {
+  int pNotNull;
+  int pPrimaryKey;
+  int pAutoinc;
+};
+
+int column_metadata_helper(
+  sqlite3 *db,
+  sqlite3_stmt *stmt,
+  int col,
+  struct metadata *p
+){
+  const char *zTableName, *zColumnName;
+  int rc = 0;
+
+  p->pNotNull = 0;
+  p->pPrimaryKey = 0;
+  p->pAutoinc = 0;
+
+  zTableName = sqlite3_column_table_name(stmt, col);
+  zColumnName = sqlite3_column_name(stmt, col);
+
+  if (zTableName && zColumnName) {
+    rc = sqlite3_table_column_metadata(
+      db, 0, zTableName, zColumnName, 0, 0,
+      &p->pNotNull, &p->pPrimaryKey, &p->pAutoinc
+    );
+  }
+
+  return rc;
+}
+
+
+extern int _call_java(int xType, int context, int args, int value);
+
+void xFunc_helper(sqlite3_context *context, int args, sqlite3_value** value)
+{
+    _call_java(1, (int)context, args, (int)value);
+}
+
+void xStep_helper(sqlite3_context *context, int args, sqlite3_value** value)
+{
+    _call_java(2, (int)context, args, (int)value);
+}
+
+void xFinal_helper(sqlite3_context *context)
+{
+    _call_java(3, (int)context, 0, 0);
+}
+
+/* create function if pos is non-negative, aggregate if agg is true */
+int create_function_helper(sqlite3 *db, const char *name, int pos, int agg)
+{
+    return sqlite3_create_function(db, name, -1, SQLITE_ANY, (void*)pos,
+            pos>=0 && !agg ? &xFunc_helper : 0,
+            pos>=0 &&  agg ? &xStep_helper : 0,
+            pos>=0 &&  agg ? &xFinal_helper : 0);
+}
diff --git a/src/main/java/org/sqlite/PrepStmt.java b/src/main/java/org/sqlite/PrepStmt.java
new file mode 100644 (file)
index 0000000..f53669a
--- /dev/null
@@ -0,0 +1,856 @@
+/* Copyright 2006 David Crawshaw, see LICENSE file for licensing [BSD]. */
+package org.sqlite;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.sql.Date;
+import java.sql.NClob;
+import java.sql.ParameterMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.RowId;
+import java.sql.SQLException;
+import java.sql.SQLXML;
+import java.sql.Statement;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.util.Calendar;
+
+/** See comment in RS.java to explain the strange inheritance hierarchy. */
+final class PrepStmt extends RS implements PreparedStatement, ParameterMetaData, Codes
+{
+    private int columnCount;
+    private int paramCount;
+    private int batchPos;
+    private Object[] batch;
+
+    PrepStmt(Conn conn, String sql) throws SQLException
+    {
+        super(conn);
+
+        this.sql = sql;
+        db.prepare(this);
+        colsMeta = db.column_names(pointer);
+        columnCount = db.column_count(pointer);
+        paramCount = db.bind_parameter_count(pointer);
+        batch = new Object[paramCount];
+        batchPos = 0;
+    }
+
+    /** Weaker close to support object overriding (see docs in RS.java). */
+    public void close() throws SQLException
+    {
+        batch = null;
+        if (pointer == 0 || db == null)
+            clearRS();
+        else
+            clearParameters();
+    }
+
+    public void clearParameters() throws SQLException
+    {
+        checkOpen();
+        clearRS();
+        db.reset(pointer);
+        batchPos = 0;
+        if (batch != null)
+            for (int i = 0; i < batch.length; i++)
+                batch[i] = null;
+    }
+
+    protected void finalize() throws SQLException
+    {
+        db.finalize(this);
+        // TODO
+    }
+
+    public boolean execute() throws SQLException
+    {
+        checkExec();
+        clearRS();
+        db.reset(pointer); // TODO: needed?
+        resultsWaiting = db.execute(this, batch);
+        return columnCount != 0;
+    }
+
+    public ResultSet executeQuery() throws SQLException
+    {
+        checkExec();
+        if (columnCount == 0)
+            throw new SQLException("query does not return results");
+        clearRS();
+        db.reset(pointer); // TODO: needed?
+        resultsWaiting = db.execute(this, batch);
+        return getResultSet();
+    }
+
+    public int executeUpdate() throws SQLException
+    {
+        checkExec();
+        if (columnCount != 0)
+            throw new SQLException("query returns results");
+        clearRS();
+        db.reset(pointer);
+        return db.executeUpdate(this, batch);
+    }
+
+    public int[] executeBatch() throws SQLException
+    {
+        return db.executeBatch(pointer, batchPos / paramCount, batch);
+    }
+
+    public int getUpdateCount() throws SQLException
+    {
+        checkOpen();
+        if (pointer == 0 || resultsWaiting)
+            return -1;
+        return db.changes();
+    }
+
+    public void addBatch() throws SQLException
+    {
+        checkExec();
+        batchPos += paramCount;
+        if (batchPos + paramCount > batch.length)
+        {
+            Object[] nb = new Object[batch.length * 2];
+            System.arraycopy(batch, 0, nb, 0, batch.length);
+            batch = nb;
+        }
+    }
+
+    public void clearBatch() throws SQLException
+    {
+        clearParameters();
+    }
+
+    // ParameterMetaData FUNCTIONS //////////////////////////////////
+
+    public ParameterMetaData getParameterMetaData()
+    {
+        return this;
+    }
+
+    public int getParameterCount() throws SQLException
+    {
+        checkExec();
+        return paramCount;
+    }
+
+    public String getParameterClassName(int param) throws SQLException
+    {
+        checkExec();
+        return "java.lang.String";
+    }
+
+    public String getParameterTypeName(int pos)
+    {
+        return "VARCHAR";
+    }
+
+    public int getParameterType(int pos)
+    {
+        return Types.VARCHAR;
+    }
+
+    public int getParameterMode(int pos)
+    {
+        return parameterModeIn;
+    }
+
+    public int getPrecision(int pos)
+    {
+        return 0;
+    }
+
+    public int getScale(int pos)
+    {
+        return 0;
+    }
+
+    public int isNullable(int pos)
+    {
+        return parameterNullable;
+    }
+
+    public boolean isSigned(int pos)
+    {
+        return true;
+    }
+
+    public Statement getStatement()
+    {
+        return this;
+    }
+
+    // PARAMETER FUNCTIONS //////////////////////////////////////////
+
+    private void batch(int pos, Object value) throws SQLException
+    {
+        checkExec();
+        if (batch == null)
+            batch = new Object[paramCount];
+        batch[batchPos + pos - 1] = value;
+    }
+
+    public void setBoolean(int pos, boolean value) throws SQLException
+    {
+        setInt(pos, value ? 1 : 0);
+    }
+
+    public void setByte(int pos, byte value) throws SQLException
+    {
+        setInt(pos, (int) value);
+    }
+
+    public void setBytes(int pos, byte[] value) throws SQLException
+    {
+        batch(pos, value);
+    }
+
+    public void setDouble(int pos, double value) throws SQLException
+    {
+        batch(pos, new Double(value));
+    }
+
+    public void setFloat(int pos, float value) throws SQLException
+    {
+        setDouble(pos, value);
+    }
+
+    public void setInt(int pos, int value) throws SQLException
+    {
+        batch(pos, new Integer(value));
+    }
+
+    public void setLong(int pos, long value) throws SQLException
+    {
+        batch(pos, new Long(value));
+    }
+
+    public void setNull(int pos, int u1) throws SQLException
+    {
+        setNull(pos, u1, null);
+    }
+
+    public void setNull(int pos, int u1, String u2) throws SQLException
+    {
+        batch(pos, null);
+    }
+
+    public void setObject(int pos, Object value) throws SQLException
+    {
+        // TODO: catch wrapped primitives
+        batch(pos, value == null ? null : value.toString());
+    }
+
+    public void setObject(int p, Object v, int t) throws SQLException
+    {
+        setObject(p, v);
+    }
+
+    public void setObject(int p, Object v, int t, int s) throws SQLException
+    {
+        setObject(p, v);
+    }
+
+    public void setShort(int pos, short value) throws SQLException
+    {
+        setInt(pos, (int) value);
+    }
+
+    public void setString(int pos, String value) throws SQLException
+    {
+        batch(pos, value);
+    }
+
+    public void setDate(int pos, Date x) throws SQLException
+    {
+        setLong(pos, x.getTime());
+    }
+
+    public void setDate(int pos, Date x, Calendar cal) throws SQLException
+    {
+        setLong(pos, x.getTime());
+    }
+
+    public void setTime(int pos, Time x) throws SQLException
+    {
+        setLong(pos, x.getTime());
+    }
+
+    public void setTime(int pos, Time x, Calendar cal) throws SQLException
+    {
+        setLong(pos, x.getTime());
+    }
+
+    public void setTimestamp(int pos, Timestamp x) throws SQLException
+    {
+        setLong(pos, x.getTime());
+    }
+
+    public void setTimestamp(int pos, Timestamp x, Calendar cal) throws SQLException
+    {
+        setLong(pos, x.getTime());
+    }
+
+    // UNUSED ///////////////////////////////////////////////////////
+
+    public boolean execute(String sql) throws SQLException
+    {
+        throw unused();
+    }
+
+    public int executeUpdate(String sql) throws SQLException
+    {
+        throw unused();
+    }
+
+    public ResultSet executeQuery(String sql) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void addBatch(String sql) throws SQLException
+    {
+        throw unused();
+    }
+
+    private SQLException unused()
+    {
+        return new SQLException("not supported by PreparedStatment");
+    }
+
+    @Override
+    public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setClob(int parameterIndex, Reader reader) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setClob(int parameterIndex, Reader reader, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setNClob(int parameterIndex, NClob value) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setNClob(int parameterIndex, Reader reader) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setNString(int parameterIndex, String value) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setRowId(int parameterIndex, RowId x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public boolean isClosed() throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public boolean isPoolable() throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public void setPoolable(boolean poolable) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public boolean isWrapperFor(Class< ? > iface) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public <T> T unwrap(Class<T> iface) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public int getHoldability() throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public Reader getNCharacterStream(int columnIndex) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public Reader getNCharacterStream(String columnLabel) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public NClob getNClob(int columnIndex) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public NClob getNClob(String columnLabel) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public String getNString(int columnIndex) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public String getNString(String columnLabel) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public RowId getRowId(int columnIndex) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public RowId getRowId(String columnLabel) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public SQLXML getSQLXML(int columnIndex) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public SQLXML getSQLXML(String columnLabel) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateCharacterStream(int columnIndex, Reader x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateClob(int columnIndex, Reader reader) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateClob(String columnLabel, Reader reader) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateClob(int columnIndex, Reader reader, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateClob(String columnLabel, Reader reader, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNClob(int columnIndex, NClob clob) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNClob(String columnLabel, NClob clob) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNClob(int columnIndex, Reader reader) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNClob(String columnLabel, Reader reader) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNString(int columnIndex, String string) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNString(String columnLabel, String string) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateRowId(int columnIndex, RowId x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateRowId(String columnLabel, RowId x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+}
diff --git a/src/main/java/org/sqlite/RS.java b/src/main/java/org/sqlite/RS.java
new file mode 100644 (file)
index 0000000..ac8eb21
--- /dev/null
@@ -0,0 +1,406 @@
+/* Copyright 2006 David Crawshaw, see LICENSE file for licensing [BSD]. */
+package org.sqlite;
+
+import java.sql.*;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.util.Calendar;
+import java.util.Map;
+
+/** Implements a JDBC ResultSet.
+ *
+ * As only one ResultSet can exist per statement, this implementation
+ * takes the odd step of making the ResultSet and Statement the same
+ * object. This means:
+ *     ResultSet rs = statement.executeQuery("SELECT ...");
+ *
+ * Generates no temporary ResultSet object, it just returns itself.
+ * When a great many ResultSets are used (e.g. in a loop), this can
+ * help reduce the load on the Garbage collector.
+ *
+ * As a result of this odd arrangement, Stmt and PrepStmt must
+ * extend RS:
+ *     Object -- Unused -- RS -- Stmt
+ *                          | -- PrepStmt
+ *
+ * Such inheritance requires careful checking of the object state,
+ * for which the check...() functions and isRS() function handle.
+ */
+abstract class RS extends Unused implements ResultSet, ResultSetMetaData, Codes
+{
+    Conn conn;
+    DB db;
+
+    String sql = null;
+    long pointer = 0;
+    boolean isAfterLast = false;
+    boolean resultsWaiting = false;
+
+    int maxRows;              // max. number of rows as set by a Statement
+    String[] cols = null;     // if null, the RS is closed()
+    String[] colsMeta = null; // same as cols, but used by Meta interface
+    boolean[][] meta = null;
+
+    private int limitRows; // 0 means no limit, must check against maxRows
+    private int row = 1;   // number of current row, starts at 1
+    private int lastCol;   // last column accessed, for wasNull(). -1 if none
+
+    RS(Conn conn) {
+        this.conn = conn;
+        this.db = conn.db();
+    }
+
+
+    // INTERNAL FUNCTIONS ///////////////////////////////////////////
+
+    protected final void checkOpen() throws SQLException {
+        if (db == null) throw new SQLException("statement is closed");
+    }
+    protected final void checkExec() throws SQLException {
+        if (pointer == 0) throw new SQLException("statement is not executing");
+    }
+    protected final void checkRS() throws SQLException {
+        if (db == null || !isRS()) throw new SQLException("ResultSet closed");
+    }
+    /** Returns true if this Statement is an currently an active ResultSet. */
+    protected final boolean isRS() { return cols != null; }
+
+    // takes col in [1,x] form, returns in [0,x-1] form
+    private int checkCol(int col) throws SQLException {
+        checkOpen();
+        if (colsMeta == null) throw new IllegalStateException(
+            "SQLite JDBC: inconsistent internal state");
+        if (col < 1 || col > colsMeta.length) throw new SQLException(
+            "column " + col + " out of bounds [1," + colsMeta.length + "]");
+        return --col;
+    }
+
+    // takes col in [1,x] form, marks it as last accessed and returns [0,x-1]
+    private int markCol(int col) throws SQLException {
+        checkRS(); checkCol(col); lastCol = col; return --col;
+    }
+
+    private void checkMeta() throws SQLException {
+        checkCol(1);
+        if (meta == null) meta = db.column_metadata(pointer);
+    }
+
+
+    // ResultSet Functions //////////////////////////////////////////
+
+    // returns col in [1,x] form
+    public int findColumn(String col) throws SQLException {
+        checkRS();
+        for (int i=0; i < cols.length; i++)
+            if (col.equalsIgnoreCase(cols[i])) return i+1;
+        throw new SQLException("no such column: '"+col+"'");
+    }
+
+    public boolean next() throws SQLException {
+        if (isAfterLast) return false;  // finished ResultSet
+        lastCol = -1;
+
+        // first row is loaded by execute(), so do not step() again
+        if (row == 1) { row++; return true; }
+
+        // check if we are row limited by the statement or the ResultSet
+        if (maxRows != 0 && row > maxRows) return false;
+        if (limitRows != 0 && row >= limitRows) return false;
+
+        // do the real work
+        int rc = db.step(pointer);
+        if (rc == SQLITE_ERROR)
+            db.reset(pointer);
+
+        switch (rc) {
+            case SQLITE_BUSY:
+                throw new SQLException("database locked");
+            case SQLITE_DONE:
+                isAfterLast = true;
+                close();      // agressive closing to avoid writer starvation
+                return false;
+            case SQLITE_ROW: row++; return true;
+            case SQLITE_MISUSE:
+                 throw new SQLException("JDBC internal consistency error");
+            case SQLITE_ERROR:
+            default:
+                 db.throwex(); return false;
+        }
+    }
+
+    public int getType() throws SQLException { return TYPE_FORWARD_ONLY; }
+
+    public int getFetchSize() throws SQLException { return limitRows; }
+    public void setFetchSize(int rows) throws SQLException {
+        if (0 > rows || (maxRows != 0 && rows > maxRows))
+            throw new SQLException("fetch size " + rows
+                                   + " out of bounds " + maxRows);
+        limitRows = rows; 
+    }
+
+    public int getFetchDirection() throws SQLException {
+        checkOpen(); return ResultSet.FETCH_FORWARD; }
+    public void setFetchDirection(int d) throws SQLException {
+        checkOpen();
+        if (d != ResultSet.FETCH_FORWARD)
+            throw new SQLException("only FETCH_FORWARD direction supported");
+    }
+
+    public boolean isAfterLast() throws SQLException { return isAfterLast; }
+    public boolean isBeforeFirst() throws SQLException {
+        return !isAfterLast && row == 1; }
+    public boolean isFirst() throws SQLException { return row == 2; }
+    public boolean isLast() throws SQLException { // FIXME
+        throw new SQLException("function not yet implemented for SQLite"); }
+
+    /** Resets the RS in a way safe for both Stmt and PrepStmt.
+     *  Full reset happens in Stmt.close(). */
+    void clearRS() throws SQLException {
+        cols = null;
+        isAfterLast = true;
+        limitRows = 0;
+        row = 1;
+        lastCol = -1;
+    }
+    protected void finalize() throws SQLException { clearRS(); }
+
+    public int getRow() throws SQLException { return row; }
+
+    public boolean wasNull() throws SQLException {
+        return db.column_type(pointer, markCol(lastCol)) == SQLITE_NULL;
+    }
+
+
+    // DATA ACCESS FUNCTIONS ////////////////////////////////////////
+
+    public boolean getBoolean(int col) throws SQLException {
+        return getInt(col) == 0 ? false : true; }
+    public boolean getBoolean(String col) throws SQLException {
+        return getBoolean(findColumn(col)); }
+
+    public byte getByte(int col) throws SQLException {
+        return (byte)getInt(col); }
+    public byte getByte(String col) throws SQLException {
+        return getByte(findColumn(col)); }
+
+    public byte[] getBytes(int col) throws SQLException {
+        return db.column_blob(pointer, markCol(col)); }
+    public byte[] getBytes(String col) throws SQLException {
+        return getBytes(findColumn(col)); }
+
+    public Date getDate(int col) throws SQLException {
+        return new Date(db.column_long(pointer, markCol(col))); }
+    public Date getDate(int col, Calendar cal) throws SQLException {
+        if (cal == null) return getDate(col);
+        cal.setTimeInMillis(db.column_long(pointer, markCol(col)));
+        return new Date(cal.getTime().getTime());
+    }
+    public Date getDate(String col) throws SQLException {
+        return getDate(findColumn(col), Calendar.getInstance()); }
+    public Date getDate(String col, Calendar cal) throws SQLException {
+        return getDate(findColumn(col), cal); }
+
+    public double getDouble(int col) throws SQLException {
+        return db.column_double(pointer, markCol(col)); }
+    public double getDouble(String col) throws SQLException {
+        return getDouble(findColumn(col)); }
+
+    public float getFloat(int col) throws SQLException {
+        return (float)db.column_double(pointer, markCol(col)); }
+    public float getFloat(String col) throws SQLException {
+        return getFloat(findColumn(col)); }
+
+    public int getInt(int col) throws SQLException {
+        return db.column_int(pointer, markCol(col)); }
+    public int getInt(String col) throws SQLException {
+        return getInt(findColumn(col)); }
+
+    public long getLong(int col) throws SQLException {
+        return db.column_long(pointer, markCol(col)); }
+    public long getLong(String col) throws SQLException {
+        return getLong(findColumn(col)); }
+
+    public short getShort(int col) throws SQLException {
+        return (short)getInt(col); }
+    public short getShort(String col) throws SQLException {
+        return getShort(findColumn(col)); }
+
+    public String getString(int col) throws SQLException {
+        return db.column_text(pointer, markCol(col)); }
+    public String getString(String col) throws SQLException {
+        return getString(findColumn(col)); }
+
+    public Time getTime(int col) throws SQLException {
+        return new Time(db.column_long(pointer, markCol(col))); }
+    public Time getTime(int col, Calendar cal) throws SQLException {
+        if (cal == null) return getTime(col);
+        cal.setTimeInMillis(db.column_long(pointer, markCol(col)));
+        return new Time(cal.getTime().getTime());
+    }
+    public Time getTime(String col) throws SQLException {
+        return getTime(findColumn(col)); }
+    public Time getTime(String col, Calendar cal) throws SQLException {
+        return getTime(findColumn(col), cal); }
+
+    public Timestamp getTimestamp(int col) throws SQLException {
+        return new Timestamp(db.column_long(pointer, markCol(col))); }
+    public Timestamp getTimestamp(int col, Calendar cal) throws SQLException {
+        if (cal == null) return getTimestamp(col);
+        cal.setTimeInMillis(db.column_long(pointer, markCol(col)));
+        return new Timestamp(cal.getTime().getTime());
+    }
+    public Timestamp getTimestamp(String col) throws SQLException {
+        return getTimestamp(findColumn(col)); }
+    public Timestamp getTimestamp(String c, Calendar ca) throws SQLException {
+        return getTimestamp(findColumn(c), ca); }
+
+    public Object getObject(int col) throws SQLException {
+        switch (db.column_type(pointer, checkCol(col))) {
+            case SQLITE_INTEGER: return new Integer(getInt(col));
+            case SQLITE_FLOAT:   return new Double(getDouble(col));
+            case SQLITE_BLOB:    return getBytes(col);
+            case SQLITE_NULL:    return null;
+            case SQLITE_TEXT:
+            default:
+                return getString(col);
+        }
+    }
+    public Object getObject(String col) throws SQLException {
+        return getObject(findColumn(col)); }
+
+
+    // ResultSetMetaData Functions //////////////////////////////////
+
+    // we do not need to check the RS is open, only that colsMeta
+    // is not null, done with checkCol(int).
+
+    public String getCatalogName(int col) throws SQLException {
+        return db.column_table_name(pointer, checkCol(col)); }
+    public String getColumnClassName(int col) throws SQLException {
+        checkCol(col); return "java.lang.Object"; }
+    public int getColumnCount() throws SQLException {
+        checkCol(1); return colsMeta.length;
+    }
+    public int getColumnDisplaySize(int col) throws SQLException {
+        return Integer.MAX_VALUE; }
+    public String getColumnLabel(int col) throws SQLException {
+        return getColumnName(col); }
+    public String getColumnName(int col) throws SQLException {
+        return db.column_name(pointer, checkCol(col)); }
+    public int getColumnType(int col) throws SQLException {
+        switch (db.column_type(pointer, checkCol(col))) {
+            case SQLITE_INTEGER: return Types.INTEGER;
+            case SQLITE_FLOAT:   return Types.FLOAT;
+            case SQLITE_BLOB:    return Types.BLOB;
+            case SQLITE_NULL:    return Types.NULL;
+            case SQLITE_TEXT:
+            default:
+                return Types.VARCHAR;
+        }
+    }
+    public String getColumnTypeName(int col) throws SQLException {
+        return db.column_decltype(pointer, checkCol(col));
+    }
+    public int getPrecision(int col) throws SQLException { return 0; } // FIXME
+    public int getScale(int col) throws SQLException { return 0; }
+    public String getSchemaName(int col) throws SQLException { return ""; }
+    public String getTableName(int col) throws SQLException {
+        return db.column_table_name(pointer, checkCol(col)); }
+    public int isNullable(int col) throws SQLException {
+        checkMeta();
+        return meta[checkCol(col)][1] ? columnNoNulls: columnNullable;
+    }
+    public boolean isAutoIncrement(int col) throws SQLException {
+        checkMeta(); return meta[checkCol(col)][2]; }
+    public boolean isCaseSensitive(int col) throws SQLException { return true; }
+    public boolean isCurrency(int col) throws SQLException { return false; }
+    public boolean isDefinitelyWritable(int col) throws SQLException {
+        return true; } // FIXME: check db file constraints?
+    public boolean isReadOnly(int col) throws SQLException { return false; }
+    public boolean isSearchable(int col) throws SQLException { return true; }
+    public boolean isSigned(int col) throws SQLException { return false; }
+    public boolean isWritable(int col) throws SQLException { return true; }
+
+    public int getConcurrency() throws SQLException { return CONCUR_READ_ONLY; }
+
+    public boolean rowDeleted()  throws SQLException { return false; }
+    public boolean rowInserted() throws SQLException { return false; }
+    public boolean rowUpdated()  throws SQLException { return false; }
+
+    public int getResultSetConcurrency() throws SQLException {
+        checkOpen(); return ResultSet.CONCUR_READ_ONLY; }
+    public int getResultSetHoldability() throws SQLException {
+        checkOpen(); return ResultSet.CLOSE_CURSORS_AT_COMMIT; }
+    public int getResultSetType() throws SQLException {
+        checkOpen(); return ResultSet.TYPE_FORWARD_ONLY; }
+
+
+    // SHARED BY Stmt, PrepStmt /////////////////////////////////////
+
+    public String getCursorName() throws SQLException { return null; }
+    public void setCursorName(String name) {}
+
+    public SQLWarning getWarnings() throws SQLException { return null; }
+    public void clearWarnings() throws SQLException {}
+
+    public Connection getConnection() throws SQLException {
+        checkOpen(); return conn; }
+    public ResultSetMetaData getMetaData() throws SQLException {
+        checkOpen(); return this; }
+
+    public void cancel() throws SQLException { checkExec(); db.interrupt(); }
+    public int getQueryTimeout() throws SQLException {
+        checkOpen(); return conn.getTimeout(); }
+    public void setQueryTimeout(int seconds) throws SQLException {
+        checkOpen();
+        if (seconds < 0) throw new SQLException("query timeout must be >= 0");
+        conn.setTimeout(1000 * seconds);
+    }
+
+    // TODO: write test
+    public int getMaxRows() throws SQLException { checkOpen(); return maxRows; }
+    public void setMaxRows(int max) throws SQLException {
+        checkOpen();
+        if (max < 0) throw new SQLException("max row count must be >= 0");
+        maxRows = max;
+    }
+
+    public int getMaxFieldSize() throws SQLException { return 0; }
+    public void setMaxFieldSize(int max) throws SQLException {
+        if (max < 0) throw new SQLException(
+            "max field size "+max+" cannot be negative");
+    }
+
+    public ResultSet getResultSet() throws SQLException {
+        checkExec();
+        if (isRS()) throw new SQLException("ResultSet already requested");
+        if (db.column_count(pointer) == 0) throw new SQLException(
+            "no ResultSet available");
+        if (colsMeta == null) colsMeta = db.column_names(pointer);
+        cols = colsMeta;
+
+        isAfterLast = !resultsWaiting;
+        if (resultsWaiting) resultsWaiting = false;
+        return this;
+    }
+
+    /** As SQLite's last_insert_rowid() function is DB-specific not
+     *  statement specific, this function introduces a race condition
+     *  if the same connection is used by two threads and both insert. */
+    public ResultSet getGeneratedKeys() throws SQLException {
+        return ((MetaData)conn.getMetaData()).getGeneratedKeys();
+    }
+
+    /** SQLite does not support multiple results from execute(). */
+    public boolean getMoreResults() throws SQLException {
+        return getMoreResults(0);
+    }
+    public boolean getMoreResults(int c) throws SQLException {
+        checkOpen();
+        close(); // as we never have another result, clean up pointer
+        return false;
+    }
+}
diff --git a/src/main/java/org/sqlite/Stmt.java b/src/main/java/org/sqlite/Stmt.java
new file mode 100644 (file)
index 0000000..5fe4b57
--- /dev/null
@@ -0,0 +1,580 @@
+/* Copyright 2006 David Crawshaw, see LICENSE file for licensing [BSD]. */
+package org.sqlite;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.sql.BatchUpdateException;
+import java.sql.NClob;
+import java.sql.ResultSet;
+import java.sql.RowId;
+import java.sql.SQLException;
+import java.sql.SQLXML;
+import java.sql.Statement;
+import java.util.ArrayList;
+
+/** See comment in RS.java to explain the strange inheritance hierarchy. */
+class Stmt extends RS implements Statement, Codes
+{
+    private ArrayList batch = null;
+
+    Stmt(Conn conn)
+    {
+        super(conn);
+    }
+
+    /** Calls sqlite3_step() and sets up results. Expects a clean stmt. */
+    protected boolean exec() throws SQLException
+    {
+        if (pointer == 0)
+            throw new SQLException("SQLite JDBC internal error: pointer == 0 on exec.");
+        if (isRS())
+            throw new SQLException("SQLite JDBC internal error: isRS() on exec.");
+
+        boolean rc = false;
+        try
+        {
+            rc = db.execute(this, null);
+        }
+        finally
+        {
+            resultsWaiting = rc;
+        }
+
+        return db.column_count(pointer) != 0;
+    }
+
+    // PUBLIC INTERFACE /////////////////////////////////////////////
+
+    public Statement getStatement()
+    {
+        return this;
+    }
+
+    /**
+     * More lax than JDBC spec, a Statement can be reused after close(). This is
+     * to support Stmt and RS sharing a heap object.
+     */
+    public void close() throws SQLException
+    {
+        if (pointer == 0)
+            return;
+        clearRS();
+        colsMeta = null;
+        meta = null;
+        batch = null;
+        int resp = db.finalize(this);
+        if (resp != SQLITE_OK && resp != SQLITE_MISUSE)
+            db.throwex();
+    }
+
+    /**
+     * The JVM does not ensure finalize() is called, so a Map in the DB class
+     * keeps track of statements for finalization.
+     */
+    protected void finalize() throws SQLException
+    {
+        close();
+    }
+
+    public int getUpdateCount() throws SQLException
+    {
+        checkOpen();
+        if (pointer == 0 || resultsWaiting)
+            return -1;
+        return db.changes();
+    }
+
+    public boolean execute(String sql) throws SQLException
+    {
+        checkOpen();
+        close();
+        this.sql = sql;
+        db.prepare(this);
+        return exec();
+    }
+
+    public ResultSet executeQuery(String sql) throws SQLException
+    {
+        checkOpen();
+        close();
+        this.sql = sql;
+        db.prepare(this);
+        if (!exec())
+        {
+            close();
+            throw new SQLException("query does not return ResultSet");
+        }
+        return getResultSet();
+    }
+
+    public int executeUpdate(String sql) throws SQLException
+    {
+        checkOpen();
+        close();
+        this.sql = sql;
+        int changes = 0;
+        try
+        {
+            db.prepare(this);
+            changes = db.executeUpdate(this, null);
+        }
+        finally
+        {
+            close();
+        }
+        return changes;
+    }
+
+    public void addBatch(String sql) throws SQLException
+    {
+        checkOpen();
+        if (batch == null)
+            batch = new ArrayList();
+        batch.add(sql);
+    }
+
+    public void clearBatch() throws SQLException
+    {
+        checkOpen();
+        if (batch != null)
+            batch.clear();
+    }
+
+    public int[] executeBatch() throws SQLException
+    {
+        // TODO: optimise
+        checkOpen();
+        close();
+        if (batch == null)
+            return new int[] {};
+
+        int[] changes = new int[batch.size()];
+
+        synchronized (db)
+        {
+            try
+            {
+                for (int i = 0; i < changes.length; i++)
+                {
+                    try
+                    {
+                        sql = (String) batch.get(i);
+                        db.prepare(this);
+                        changes[i] = db.executeUpdate(this, null);
+                    }
+                    catch (SQLException e)
+                    {
+                        throw new BatchUpdateException("batch entry " + i + ": " + e.getMessage(), changes);
+                    }
+                    finally
+                    {
+                        db.finalize(this);
+                    }
+                }
+            }
+            finally
+            {
+                batch.clear();
+            }
+        }
+
+        return changes;
+    }
+
+    @Override
+    public boolean isClosed() throws SQLException
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isPoolable() throws SQLException
+    {
+        return false;
+    }
+
+    @Override
+    public void setPoolable(boolean poolable) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public boolean isWrapperFor(Class< ? > iface) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+    }
+
+    @Override
+    public <T> T unwrap(Class<T> iface) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public int getHoldability() throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public Reader getNCharacterStream(int columnIndex) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public Reader getNCharacterStream(String columnLabel) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public NClob getNClob(int columnIndex) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public NClob getNClob(String columnLabel) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public String getNString(int columnIndex) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public String getNString(String columnLabel) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public RowId getRowId(int columnIndex) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public RowId getRowId(String columnLabel) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public SQLXML getSQLXML(int columnIndex) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public SQLXML getSQLXML(String columnLabel) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateCharacterStream(int columnIndex, Reader x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateClob(int columnIndex, Reader reader) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateClob(String columnLabel, Reader reader) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateClob(int columnIndex, Reader reader, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateClob(String columnLabel, Reader reader, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException
+    {
+        throw new SQLException("not yet implemented");
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public void updateNClob(int columnIndex, NClob clob) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNClob(String columnLabel, NClob clob) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNClob(int columnIndex, Reader reader) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNClob(String columnLabel, Reader reader) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNString(int columnIndex, String string) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateNString(String columnLabel, String string) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateRowId(int columnIndex, RowId x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateRowId(String columnLabel, RowId x) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+
+    @Override
+    public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException
+    {
+        // TODO Auto-generated method stub
+        throw new SQLException("not yet implemented");
+
+    }
+}
diff --git a/src/main/java/org/sqlite/Unused.java b/src/main/java/org/sqlite/Unused.java
new file mode 100644 (file)
index 0000000..2cd2d02
--- /dev/null
@@ -0,0 +1,536 @@
+/* Copyright 2006 David Crawshaw, see LICENSE file for licensing [BSD]. */
+package org.sqlite;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Date;
+import java.sql.Ref;
+import java.sql.SQLException;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Map;
+
+/** Unused JDBC functions from Statement, PreparedStatement and ResultSet. */
+abstract class Unused
+{
+    private SQLException unused()
+    {
+        return new SQLException("not implemented by SQLite JDBC driver");
+    }
+
+    // Statement ////////////////////////////////////////////////////
+
+    public void setEscapeProcessing(boolean enable) throws SQLException
+    {
+        throw unused();
+    }
+
+    public boolean execute(String sql, int[] colinds) throws SQLException
+    {
+        throw unused();
+    }
+
+    public boolean execute(String sql, String[] colnames) throws SQLException
+    {
+        throw unused();
+    }
+
+    public int executeUpdate(String sql, int autoKeys) throws SQLException
+    {
+        throw unused();
+    }
+
+    public int executeUpdate(String sql, int[] colinds) throws SQLException
+    {
+        throw unused();
+    }
+
+    public int executeUpdate(String sql, String[] cols) throws SQLException
+    {
+        throw unused();
+    }
+
+    public boolean execute(String sql, int autokeys) throws SQLException
+    {
+        throw unused();
+    }
+
+    // PreparedStatement ////////////////////////////////////////////
+
+    public void setArray(int i, Array x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void setBlob(int i, Blob x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void setCharacterStream(int pos, Reader reader, int length) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void setClob(int i, Clob x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void setRef(int i, Ref x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void setUnicodeStream(int pos, InputStream x, int length) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void setURL(int pos, URL x) throws SQLException
+    {
+        throw unused();
+    }
+
+    // ResultSet ////////////////////////////////////////////////////
+
+    public Array getArray(int i) throws SQLException
+    {
+        throw unused();
+    }
+
+    public Array getArray(String col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public InputStream getAsciiStream(int col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public InputStream getAsciiStream(String col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public BigDecimal getBigDecimal(int col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public BigDecimal getBigDecimal(int col, int s) throws SQLException
+    {
+        throw unused();
+    }
+
+    public BigDecimal getBigDecimal(String col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public BigDecimal getBigDecimal(String col, int s) throws SQLException
+    {
+        throw unused();
+    }
+
+    public InputStream getBinaryStream(int col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public InputStream getBinaryStream(String col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public Blob getBlob(int col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public Blob getBlob(String col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public Reader getCharacterStream(int col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public Reader getCharacterStream(String col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public Clob getClob(int col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public Clob getClob(String col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public Object getObject(int col, Map<String, Class< ? >> map) throws SQLException
+    {
+        throw unused();
+    }
+
+    public Object getObject(String col, Map<String, Class< ? >> map) throws SQLException
+    {
+        throw unused();
+    }
+
+    public Ref getRef(int i) throws SQLException
+    {
+        throw unused();
+    }
+
+    public Ref getRef(String col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public InputStream getUnicodeStream(int col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public InputStream getUnicodeStream(String col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public URL getURL(int col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public URL getURL(String col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void insertRow() throws SQLException
+    {
+        throw new SQLException("ResultSet is TYPE_FORWARD_ONLY");
+    }
+
+    public void moveToCurrentRow() throws SQLException
+    {
+        throw new SQLException("ResultSet is TYPE_FORWARD_ONLY");
+    }
+
+    public void moveToInsertRow() throws SQLException
+    {
+        throw new SQLException("ResultSet is TYPE_FORWARD_ONLY");
+    }
+
+    public boolean last() throws SQLException
+    {
+        throw new SQLException("ResultSet is TYPE_FORWARD_ONLY");
+    }
+
+    public boolean previous() throws SQLException
+    {
+        throw new SQLException("ResultSet is TYPE_FORWARD_ONLY");
+    }
+
+    public boolean relative(int rows) throws SQLException
+    {
+        throw new SQLException("ResultSet is TYPE_FORWARD_ONLY");
+    }
+
+    public boolean absolute(int row) throws SQLException
+    {
+        throw new SQLException("ResultSet is TYPE_FORWARD_ONLY");
+    }
+
+    public void afterLast() throws SQLException
+    {
+        throw new SQLException("ResultSet is TYPE_FORWARD_ONLY");
+    }
+
+    public void beforeFirst() throws SQLException
+    {
+        throw new SQLException("ResultSet is TYPE_FORWARD_ONLY");
+    }
+
+    public boolean first() throws SQLException
+    {
+        throw new SQLException("ResultSet is TYPE_FORWARD_ONLY");
+    }
+
+    public void cancelRowUpdates() throws SQLException
+    {
+        throw unused();
+    }
+
+    public void deleteRow() throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateArray(int col, Array x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateArray(String col, Array x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateAsciiStream(int col, InputStream x, int l) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateAsciiStream(String col, InputStream x, int l) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateBigDecimal(int col, BigDecimal x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateBigDecimal(String col, BigDecimal x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateBinaryStream(int c, InputStream x, int l) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateBinaryStream(String c, InputStream x, int l) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateBlob(int col, Blob x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateBlob(String col, Blob x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateBoolean(int col, boolean x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateBoolean(String col, boolean x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateByte(int col, byte x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateByte(String col, byte x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateBytes(int col, byte[] x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateBytes(String col, byte[] x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateCharacterStream(int c, Reader x, int l) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateCharacterStream(String c, Reader r, int l) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateClob(int col, Clob x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateClob(String col, Clob x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateDate(int col, Date x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateDate(String col, Date x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateDouble(int col, double x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateDouble(String col, double x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateFloat(int col, float x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateFloat(String col, float x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateInt(int col, int x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateInt(String col, int x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateLong(int col, long x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateLong(String col, long x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateNull(int col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateNull(String col) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateObject(int c, Object x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateObject(int c, Object x, int s) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateObject(String col, Object x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateObject(String c, Object x, int s) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateRef(int col, Ref x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateRef(String c, Ref x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateRow() throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateShort(int c, short x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateShort(String c, short x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateString(int c, String x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateString(String c, String x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateTime(int c, Time x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateTime(String c, Time x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateTimestamp(int c, Timestamp x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void updateTimestamp(String c, Timestamp x) throws SQLException
+    {
+        throw unused();
+    }
+
+    public void refreshRow() throws SQLException
+    {
+        throw unused();
+    }
+}
index 4188b2c..82ad23f 100644 (file)
@@ -52,6 +52,13 @@ public class SQLiteJDBCLoader
         setSQLiteNativeLibraryPath();\r
     }\r
 \r
+    public static void initialize(boolean forceReload)\r
+    {\r
+        if (forceReload)\r
+            extracted = false;\r
+        setSQLiteNativeLibraryPath();\r
+    }\r
+\r
     private static boolean extractLibraryFile(String libraryResourcePath, String libraryFolder, String libraryFileName)\r
     {\r
         File libFile = new File(libraryFolder, libraryFileName);\r
index 8b385f5..3ff18ca 100644 (file)
@@ -52,7 +52,7 @@ public class SQLiteJDBCLoaderTest
     @Test
     public void query() throws ClassNotFoundException
     {
-        SQLiteJDBCLoader.initialize();
+        // SQLiteJDBCLoader.initialize();
 
         // load the sqlite-JDBC driver into the current class loader
         Class.forName("org.sqlite.JDBC");