From e3f77e306182c0c3f65d85c507814b1938560d73 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 3 Jul 2008 02:03:12 +0000 Subject: [PATCH] add prefix to the extracted library file git-svn-id: http://www.xerial.org/svn/project/XerialJ/trunk/sqlite-jdbc@2254 ae02f08e-27ec-0310-ae8c-8ba02fe2eafd --- lib/sqlitejdbc-v038-native.jar | Bin 29917 -> 0 bytes pom.xml | 6 + src/main/java/org/sqlite/Codes.java | 106 +++ src/main/java/org/sqlite/Conn.java | 253 +++++++ src/main/java/org/sqlite/DB.java | 344 +++++++++ src/main/java/org/sqlite/Function.java | 182 +++++ src/main/java/org/sqlite/JDBC.java | 69 ++ src/main/java/org/sqlite/MetaData.java | 718 +++++++++++++++++++ src/main/java/org/sqlite/NativeDB.c | 780 +++++++++++++++++++++ src/main/java/org/sqlite/NativeDB.java | 204 ++++++ src/main/java/org/sqlite/PrepStmt.java | 217 ++++++ src/main/java/org/sqlite/RS.java | 369 ++++++++++ src/main/java/org/sqlite/Stmt.java | 246 +++++++ src/main/java/org/sqlite/Unused.java | 250 +++++++ .../org/xerial/db/sql/sqlite/SQLiteJDBCLoader.java | 15 +- src/test/java/org/sqlite/DBMetaDataTest.java | 28 +- src/test/java/org/sqlite/PrepStmtTest.java | 44 +- src/test/java/org/sqlite/StatementTest.java | 43 +- src/test/java/org/sqlite/TransactionTest.java | 23 +- 19 files changed, 3852 insertions(+), 45 deletions(-) delete mode 100644 lib/sqlitejdbc-v038-native.jar create mode 100644 src/main/java/org/sqlite/Codes.java create mode 100644 src/main/java/org/sqlite/Conn.java create mode 100644 src/main/java/org/sqlite/DB.java create mode 100644 src/main/java/org/sqlite/Function.java create mode 100644 src/main/java/org/sqlite/JDBC.java create mode 100644 src/main/java/org/sqlite/MetaData.java create mode 100644 src/main/java/org/sqlite/NativeDB.c create mode 100644 src/main/java/org/sqlite/NativeDB.java create mode 100644 src/main/java/org/sqlite/PrepStmt.java create mode 100644 src/main/java/org/sqlite/RS.java create mode 100644 src/main/java/org/sqlite/Stmt.java create mode 100644 src/main/java/org/sqlite/Unused.java diff --git a/lib/sqlitejdbc-v038-native.jar b/lib/sqlitejdbc-v038-native.jar deleted file mode 100644 index fe748785e0ba4e04999ef8bbb8c0008f5db2945d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29917 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 diff --git a/pom.xml b/pom.xml index 40a04b6..059839a 100644 --- a/pom.xml +++ b/pom.xml @@ -51,8 +51,14 @@ org.xerial.thirdparty + sqlitejdbc-nested + 3.5.9-SNAPSHOT + + + org.xerial.thirdparty jdbc-api 1.4 + provided diff --git a/src/main/java/org/sqlite/Codes.java b/src/main/java/org/sqlite/Codes.java new file mode 100644 index 0000000..ab78411 --- /dev/null +++ b/src/main/java/org/sqlite/Codes.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2007 David Crawshaw + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +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 index 0000000..9c3857e --- /dev/null +++ b/src/main/java/org/sqlite/Conn.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2007 David Crawshaw + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +package org.sqlite; + +import java.sql.*; +import java.util.*; +import java.io.File; +import java.lang.ref.WeakReference; + +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, boolean sharedCache) + throws SQLException { + this(url, filename); + db.shared_cache(sharedCache); + } + 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 { + // The extra check to exists() is necessary as createNewFile() + // does not follow the JavaDoc when used on read-only shares. + if (!file.exists() && 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(this, 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 map) throws SQLException + { throw new SQLException("not yet implemented");} + + public boolean isReadOnly() throws SQLException { return false; } // FIXME + public void setReadOnly(boolean ro) throws SQLException {} + + 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; } + + 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;"); + } + + public void commit() throws SQLException { + checkOpen(); + if (autoCommit) throw new SQLException("database in auto-commit mode"); + db.exec("commit;"); + db.exec("begin;"); + } + + public void rollback() throws SQLException { + checkOpen(); + if (autoCommit) throw new SQLException("database in auto-commit mode"); + db.exec("rollback;"); + db.exec("begin;"); + } + + 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); + } + + /** Used to supply DatabaseMetaData.getDriverVersion(). */ + String getDriverVersion() { + if (db != null) { + String dbname = db.getClass().getName(); + if (dbname.indexOf("NestedDB") >= 0) + return "pure"; + if (dbname.indexOf("NativeDB") >= 0) + return "native"; + } + return "unloaded"; + } + + + // 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"); } + + public Struct createStruct(String t, Object[] attr) throws SQLException { + throw new SQLException("unsupported by SQLite"); } +} diff --git a/src/main/java/org/sqlite/DB.java b/src/main/java/org/sqlite/DB.java new file mode 100644 index 0000000..277b233 --- /dev/null +++ b/src/main/java/org/sqlite/DB.java @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2007 David Crawshaw + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +package org.sqlite; + +import java.lang.ref.*; +import java.io.File; +import java.sql.*; +import java.util.*; + +/* + * This class is the interface to SQLite. It provides some helper functions + * used by other parts of the driver. The goal of the helper functions here + * are not only to provide functionality, but to handle contractual + * differences between the JDBC specification and the SQLite C API. + * + * The process of moving SQLite weirdness into this class is incomplete. + * You'll still find lots of code in Stmt and PrepStmt that are doing + * implicit contract conversions. Sorry. + * + * The two subclasses, NativeDB and NestedDB, provide the actual access to + * SQLite functions. + */ +abstract class DB implements Codes +{ + /** The JDBC Connection that 'owns' this database instance. */ + Conn conn = null; + + /** The "begin;"and "commit;" statement handles. */ + long begin = 0; + long commit = 0; + + /** Tracer for statements to avoid unfinalized statements on db close. */ + private Map stmts = new Hashtable(); + + // WRAPPER FUNCTIONS //////////////////////////////////////////// + + 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; + abstract int shared_cache(boolean enable) throws SQLException; + + final synchronized void exec(String sql) throws SQLException { + long pointer = 0; + try { + pointer = prepare(sql); + switch (step(pointer)) { + case SQLITE_DONE: + ensureAutoCommit(); + return; + case SQLITE_ROW: + return; + default: + throwex(); + } + } finally { + finalize(pointer); + } + } + + final synchronized void open(Conn conn, String file) throws SQLException { + this.conn = conn; + _open(file); + } + + 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(); + Stmt stmt = (Stmt)entry.getValue(); + finalize(((Long)entry.getKey()).longValue()); + if (stmt != null) { + stmt.pointer = 0; + } + i.remove(); + } + } + + // remove memory used by user-defined functions + free_functions(); + + // clean up commit object + if (begin != 0) { + finalize(begin); + begin = 0; + } + if (commit != 0) { + finalize(commit); + commit = 0; + } + + _close(); + } + + final synchronized void prepare(Stmt stmt) throws SQLException { + if (stmt.pointer != 0) + finalize(stmt); + stmt.pointer = prepare(stmt.sql); + stmts.put(new Long(stmt.pointer), stmt); + } + + final synchronized int finalize(Stmt 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; + } + + protected abstract void _open(String filename) throws SQLException; + 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]; + + try { + 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); + if (rc != SQLITE_DONE) { + reset(stmt); + if (rc == SQLITE_ROW) throw new BatchUpdateException( + "batch entry "+i+": query returns results", changes); + throwex(); + } + + changes[i] = changes(); + } + } finally { + ensureAutoCommit(); + } + + reset(stmt); + return changes; + } + + final synchronized boolean execute(Stmt 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.pointer)) { + case SQLITE_DONE: + reset(stmt.pointer); + ensureAutoCommit(); + return false; + case SQLITE_ROW: + return true; + case SQLITE_BUSY: + case SQLITE_LOCKED: + throw new SQLException("database locked"); + case SQLITE_MISUSE: + throw new SQLException(errmsg()); + default: + finalize(stmt); + throw new SQLException(errmsg()); + } + } + + final synchronized int executeUpdate(Stmt stmt, Object[] vals) + throws SQLException { + if (execute(stmt, vals)) + throw new SQLException("query returns results"); + reset(stmt.pointer); + return changes(); + } + + final void throwex() throws SQLException { + throw new SQLException(errmsg()); + } + + /* + * SQLite and the JDBC API have very different ideas about the meaning + * of auto-commit. Under JDBC, when executeUpdate() returns in + * auto-commit mode (the default), the programmer assumes the data has + * been written to disk. In SQLite however, a call to sqlite3_step() + * with an INSERT statement can return SQLITE_OK, and yet the data is + * still in limbo. + * + * This limbo appears when another statement on the database is active, + * e.g. a SELECT. SQLite auto-commit waits until the final read + * statement finishes, and then writes whatever updates have already + * been OKed. So if a program crashes before the reads are complete, + * data is lost. E.g: + * + * select begins + * insert + * select continues + * select finishes + * + * Works as expected, however + * + * select beings + * insert + * select continues + * crash + * + * Results in the data never being written to disk. + * + * As a solution, we call "commit" after every statement in auto-commit + * mode. + */ + final void ensureAutoCommit() throws SQLException { + if (!conn.getAutoCommit()) + return; + + if (begin == 0) + begin = prepare("begin;"); + if (commit == 0) + commit = prepare("commit;"); + + try { + if (step(begin) != SQLITE_DONE) + return; // assume we are in a transaction + if (step(commit) != SQLITE_DONE) { + reset(commit); + throwex(); + } + //throw new SQLException("unable to auto-commit"); + } finally { + reset(begin); + reset(commit); + } + } +} diff --git a/src/main/java/org/sqlite/Function.java b/src/main/java/org/sqlite/Function.java new file mode 100644 index 0000000..8faa757 --- /dev/null +++ b/src/main/java/org/sqlite/Function.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2007 David Crawshaw + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +package org.sqlite; + +import java.sql.*; + +/** Provides an interface for creating SQLite user-defined functions. + * + *

A subclass of org.sqlite.Function can be registered with + * Function.create() and called by the name it was given. All + * functions must implement xFunc(), which is called when SQLite + * runs the custom function.

+ * + * Eg. + * + *
+ *      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();");
+ *  
+ * + *

Arguments passed to a custom function can be accessed using the + * protected functions provided. args() returns + * the number of arguments passed, while + * value_<type>(int) returns the value of the specific + * argument. Similarly a function can return a value using the + * result(<type>) function.

+ * + *

Aggregate functions are not yet supported, but coming soon.

+ * + */ +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 value_*(int), return results with + * result(*) and throw errors with error(String). */ + protected abstract void xFunc() throws SQLException; + + + /** Returns the number of arguments passed to the function. + * Can only be called from xFunc(). */ + protected synchronized final int args() + throws SQLException { checkContext(); return args; } + + /** Called by xFunc to return a value. */ + protected synchronized final void result(byte[] value) + throws SQLException { checkContext(); db.result_blob(context, value); } + + /** Called by xFunc to return a value. */ + protected synchronized final void result(double value) + throws SQLException { checkContext(); db.result_double(context,value);} + + /** Called by xFunc to return a value. */ + protected synchronized final void result(int value) + throws SQLException { checkContext(); db.result_int(context, value); } + + /** Called by xFunc to return a value. */ + protected synchronized final void result(long value) + throws SQLException { checkContext(); db.result_long(context, value); } + + /** Called by xFunc to return a value. */ + protected synchronized final void result() + throws SQLException { checkContext(); db.result_null(context); } + + /** Called by xFunc to return a value. */ + protected synchronized final void result(String value) + throws SQLException { checkContext(); db.result_text(context, value); } + + /** Called by xFunc to throw an error. */ + protected synchronized final void error(String err) + throws SQLException { checkContext(); db.result_error(context, err); } + + /** Called by xFunc 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 xFunc 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 xFunc 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 xFunc 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 xFunc 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 xFunc 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 xFunc 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 index 0000000..5db5362 --- /dev/null +++ b/src/main/java/org/sqlite/JDBC.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2007 David Crawshaw + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +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 { + DriverPropertyInfo sharedCache = new DriverPropertyInfo( + "shared_cache", "false"); + sharedCache.choices = new String[] { "true", "false" }; + sharedCache.description = + "Enable SQLite Shared-Cache mode, native driver only."; + sharedCache.required = false; + + return new DriverPropertyInfo[] { sharedCache }; + } + + 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 + String file = PREFIX.equalsIgnoreCase(url) ? + ":memory:" : url.substring(PREFIX.length()); + + if (info.getProperty("shared_cache") == null) + return new Conn(url, file); + else + return new Conn(url, file, + Boolean.parseBoolean(info.getProperty("shared_cache"))); + } +} diff --git a/src/main/java/org/sqlite/MetaData.java b/src/main/java/org/sqlite/MetaData.java new file mode 100644 index 0000000..a6a10e4 --- /dev/null +++ b/src/main/java/org/sqlite/MetaData.java @@ -0,0 +1,718 @@ +/* + * Copyright (c) 2007 David Crawshaw + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package org.sqlite; + +import java.sql.*; + +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 conn.getDriverVersion(); } + 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 rs; + 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, " + + "ct 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 (colNotNull != null) colNullable = colNotNull.equals("0") ? 1:0; + if (colFound) sql += " union all "; + colFound = true; + + colType = colType == null ? "TEXT" : colType.toUpperCase(); + int colJavaType = -1; + if (colType == "INT" || colType == "INTEGER") + colJavaType = Types.INTEGER; + else if (colType == "TEXT") + colJavaType = Types.VARCHAR; + else if (colType == "FLOAT") + colJavaType = Types.FLOAT; + else + colJavaType = Types.VARCHAR; + + sql += "select " + + i + " as ordpos, " + + colNullable + " as colnullable, '" + + colJavaType + "' as ct, '" + + 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(); + } + + public Struct createStruct(String t, Object[] attr) throws SQLException { + throw new SQLException("Not yet implemented by SQLite JDBC driver"); } + public ResultSet getFunctionColumns(String a, String b, String c, + String d) throws SQLException { + throw new SQLException("Not yet implemented by SQLite JDBC driver"); } +} diff --git a/src/main/java/org/sqlite/NativeDB.c b/src/main/java/org/sqlite/NativeDB.c new file mode 100644 index 0000000..4edc2aa --- /dev/null +++ b/src/main/java/org/sqlite/NativeDB.c @@ -0,0 +1,780 @@ +/* + * Copyright (c) 2007 David Crawshaw + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#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 jint JNICALL Java_org_sqlite_NativeDB_shared_1cache( + JNIEnv *env, jobject this, jboolean enable) +{ + return sqlite3_enable_shared_cache(enable ? 1 : 0); +} + +JNIEXPORT void JNICALL Java_org_sqlite_NativeDB__1open( + 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_v2(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 index 0000000..d629fec --- /dev/null +++ b/src/main/java/org/sqlite/NativeDB.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2007 David Crawshaw + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package org.sqlite; + +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 +{ + /** SQLite connection handle. */ + long pointer = 0; + + private static Boolean loaded = null; + + static boolean load() + { + if (loaded != null) + return loaded == Boolean.TRUE; + + return loaded = SQLiteJDBCLoader.initialize(); + + // String libpath = System.getProperty("org.sqlite.lib.path"); + // String libname = System.getProperty("org.sqlite.lib.name"); + // if (libname == null) libname = System.mapLibraryName("sqlitejdbc"); + // + // // look for a pre-installed library + // try { + // if (libpath == null) System.loadLibrary("sqlitejdbc"); + // else System.load(new File(libpath, libname).getAbsolutePath()); + // + // loaded = Boolean.TRUE; + // return true; + // } catch (UnsatisfiedLinkError e) { } // fall through + // + // // guess what a bundled library would be called + // String osname = System.getProperty("os.name").toLowerCase(); + // String osarch = System.getProperty("os.arch"); + // if (osname.startsWith("mac os")) { + // osname = "mac"; + // osarch = "universal"; + // } + // if (osname.startsWith("windows")) + // osname = "win"; + // if (osname.startsWith("sunos")) + // osname = "solaris"; + // if (osarch.startsWith("i") && osarch.endsWith("86")) + // osarch = "x86"; + // libname = osname + '-' + osarch + ".lib"; + // + // // try a bundled library + // try { + // ClassLoader cl = NativeDB.class.getClassLoader(); + // InputStream in = cl.getResourceAsStream(libname); + // if (in == null) + // throw new Exception("libname: "+libname+" not found"); + // File tmplib = File.createTempFile("libsqlitejdbc-", ".lib"); + // tmplib.deleteOnExit(); + // OutputStream out = new FileOutputStream(tmplib); + // byte[] buf = new byte[1024]; + // for (int len; (len = in.read(buf)) != -1;) + // out.write(buf, 0, len); + // in.close(); + // out.close(); + // + // System.load(tmplib.getAbsolutePath()); + // + // loaded = Boolean.TRUE; + // return true; + // } catch (Exception e) { } + // + // loaded = Boolean.FALSE; + // return false; + } + + /** linked list of all instanced UDFDatas */ + private long udfdatalist = 0; + + // WRAPPER FUNCTIONS //////////////////////////////////////////// + + protected native synchronized void _open(String file) throws SQLException; + + protected native synchronized void _close() throws SQLException; + + native synchronized int shared_cache(boolean enable); + + 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/PrepStmt.java b/src/main/java/org/sqlite/PrepStmt.java new file mode 100644 index 0000000..77bbef5 --- /dev/null +++ b/src/main/java/org/sqlite/PrepStmt.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2007 David Crawshaw + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package org.sqlite; + +import java.io.Reader; +import java.io.InputStream; +import java.net.URL; +import java.sql.*; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Calendar; + +final class PrepStmt extends Stmt + implements PreparedStatement, ParameterMetaData, Codes +{ + private int columnCount; + private int paramCount; + + PrepStmt(Conn conn, String sql) throws SQLException { + super(conn); + + this.sql = sql; + db.prepare(this); + rs.colsMeta = db.column_names(pointer); + columnCount = db.column_count(pointer); + paramCount = db.bind_parameter_count(pointer); + batch = new Object[paramCount]; + batchPos = 0; + } + + public void clearParameters() throws SQLException { + checkOpen(); + db.reset(pointer); + clearBatch(); + } + + protected void finalize() throws SQLException { close(); } + + public boolean execute() throws SQLException { + checkOpen(); + rs.close(); + db.reset(pointer); + resultsWaiting = db.execute(this, batch); + return columnCount != 0; + } + + public ResultSet executeQuery() throws SQLException { + checkOpen(); + if (columnCount == 0) + throw new SQLException("query does not return results"); + rs.close(); + db.reset(pointer); + resultsWaiting = db.execute(this, batch); + return getResultSet(); + } + + public int executeUpdate() throws SQLException { + checkOpen(); + if (columnCount != 0) + throw new SQLException("query returns results"); + rs.close(); + db.reset(pointer); + return db.executeUpdate(this, batch); + } + + public int[] executeBatch() throws SQLException { + if (batchPos == 0) return new int[] {}; + 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 { + checkOpen(); + batchPos += paramCount; + if (batchPos + paramCount > batch.length) { + Object[] nb = new Object[batch.length * 2]; + System.arraycopy(batch, 0, nb, 0, batch.length); + batch = nb; + } + System.arraycopy(batch, batchPos - paramCount, + batch, batchPos, paramCount); + } + + + // ParameterMetaData FUNCTIONS ////////////////////////////////// + + public ParameterMetaData getParameterMetaData() { return this; } + + public int getParameterCount() throws SQLException { + checkOpen(); return paramCount; } + public String getParameterClassName(int param) throws SQLException { + checkOpen(); 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 { + checkOpen(); + 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 { + if (value == null) + batch(pos, null); + else if (value instanceof java.util.Date) + batch(pos, new Long(((java.util.Date)value).getTime())); + else if (value instanceof Date) + batch(pos, new Long(((Date)value).getTime())); + else if (value instanceof Time) + batch(pos, new Long(((Time)value).getTime())); + else if (value instanceof Timestamp) + batch(pos, new Long(((Timestamp)value).getTime())); + else if (value instanceof Long) batch(pos, value); + else if (value instanceof Integer) batch(pos, value); + else if (value instanceof Float) batch(pos, value); + else if (value instanceof Double) batch(pos, value); + else + batch(pos, 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 { + setObject(pos, x); } + public void setDate(int pos, Date x, Calendar cal) throws SQLException { + setObject(pos, x); } + public void setTime(int pos, Time x) throws SQLException { + setObject(pos, x); } + public void setTime(int pos, Time x, Calendar cal) throws SQLException { + setObject(pos, x); } + public void setTimestamp(int pos, Timestamp x) throws SQLException { + setObject(pos, x); } + public void setTimestamp(int pos, Timestamp x, Calendar cal) + throws SQLException { + setObject(pos, x); + } + + public ResultSetMetaData getMetaData() throws SQLException { + checkOpen(); return rs; } + + + // 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"); + } +} diff --git a/src/main/java/org/sqlite/RS.java b/src/main/java/org/sqlite/RS.java new file mode 100644 index 0000000..3518d54 --- /dev/null +++ b/src/main/java/org/sqlite/RS.java @@ -0,0 +1,369 @@ +/* + * Copyright (c) 2007 David Crawshaw + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +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. + */ +final class RS extends Unused implements ResultSet, ResultSetMetaData, Codes +{ + private final Stmt stmt; + private final DB db; + + boolean open = false ; // true means have results and can iterate them + 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(Stmt stmt) { + this.stmt = stmt; + this.db = stmt.db; + } + + + // INTERNAL FUNCTIONS /////////////////////////////////////////// + + boolean isOpen() { return open; } + + /* Throws SQLException if ResultSet is not open. */ + void checkOpen() throws SQLException { + if (!open) throw new SQLException("ResultSet closed"); + } + + // takes col in [1,x] form, returns in [0,x-1] form + private int checkCol(int col) throws SQLException { + 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 { + checkOpen(); checkCol(col); lastCol = col; return --col; + } + + private void checkMeta() throws SQLException { + checkCol(1); + if (meta == null) meta = db.column_metadata(stmt.pointer); + } + + + // ResultSet Functions ////////////////////////////////////////// + + public void close() throws SQLException { + cols = null; + colsMeta = null; + meta = null; + open = false; + limitRows = 0; + row = 1; + lastCol = -1; + + if (stmt == null) + return; + if (stmt != null && stmt.pointer != 0) + db.reset(stmt.pointer); + } + + // returns col in [1,x] form + public int findColumn(String col) throws SQLException { + checkOpen(); + int c = -1; + for (int i=0; i < cols.length; i++) { + if (col.equalsIgnoreCase(cols[i]) + || (cols[i].toUpperCase().endsWith(col.toUpperCase()) && + cols[i].charAt(cols[i].length() - col.length()) == '.')) { + if (c == -1) + c = i; + else + throw new SQLException("ambiguous column: '"+col+"'"); + } + } + if (c == -1) + throw new SQLException("no such column: '"+col+"'"); + else + return c + 1; + } + + public boolean next() throws SQLException { + if (!open) 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 + switch (db.step(stmt.pointer)) { + case SQLITE_DONE: + close(); // agressive closing to avoid writer starvation + return false; + case SQLITE_ROW: row++; return true; + case SQLITE_BUSY: + throw new SQLException("database locked"); + 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 !open; } + public boolean isBeforeFirst() throws SQLException { + return open && 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"); } + + protected void finalize() throws SQLException { close(); } + + public int getRow() throws SQLException { return row; } + + public boolean wasNull() throws SQLException { + return db.column_type(stmt.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(stmt.pointer, markCol(col)); } + public byte[] getBytes(String col) throws SQLException { + return getBytes(findColumn(col)); } + + public Date getDate(int col) throws SQLException { + if (db.column_type(stmt.pointer, markCol(col)) == SQLITE_NULL) + return null; + return new Date(db.column_long(stmt.pointer, markCol(col))); + } + public Date getDate(int col, Calendar cal) throws SQLException { + if (db.column_type(stmt.pointer, markCol(col)) == SQLITE_NULL) + return null; + if (cal == null) return getDate(col); + cal.setTimeInMillis(db.column_long(stmt.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 { + if (db.column_type(stmt.pointer, markCol(col)) == SQLITE_NULL) + return 0; + return db.column_double(stmt.pointer, markCol(col)); + } + public double getDouble(String col) throws SQLException { + return getDouble(findColumn(col)); } + + public float getFloat(int col) throws SQLException { + if (db.column_type(stmt.pointer, markCol(col)) == SQLITE_NULL) + return 0; + return (float)db.column_double(stmt.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(stmt.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(stmt.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(stmt.pointer, markCol(col)); } + public String getString(String col) throws SQLException { + return getString(findColumn(col)); } + + public Time getTime(int col) throws SQLException { + if (db.column_type(stmt.pointer, markCol(col)) == SQLITE_NULL) + return null; + return new Time(db.column_long(stmt.pointer, markCol(col))); } + public Time getTime(int col, Calendar cal) throws SQLException { + if (cal == null) return getTime(col); + if (db.column_type(stmt.pointer, markCol(col)) == SQLITE_NULL) + return null; + cal.setTimeInMillis(db.column_long(stmt.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 { + if (db.column_type(stmt.pointer, markCol(col)) == SQLITE_NULL) + return null; + return new Timestamp(db.column_long(stmt.pointer, markCol(col))); } + public Timestamp getTimestamp(int col, Calendar cal) throws SQLException { + if (cal == null) return getTimestamp(col); + if (db.column_type(stmt.pointer, markCol(col)) == SQLITE_NULL) + return null; + cal.setTimeInMillis(db.column_long(stmt.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(stmt.pointer, checkCol(col))) { + case SQLITE_INTEGER: + long val = getLong(col); + if (val > (long)Integer.MAX_VALUE + || val < (long)Integer.MIN_VALUE) + return new Long(val); + else + return new Integer((int)val); + 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)); } + + public Statement getStatement() { return stmt; } + public String getCursorName() throws SQLException { return null; } + public SQLWarning getWarnings() throws SQLException { return null; } + public void clearWarnings() throws SQLException {} + + // ResultSetMetaData Functions ////////////////////////////////// + + // we do not need to check the RS is open, only that colsMeta + // is not null, done with checkCol(int). + + public ResultSetMetaData getMetaData() throws SQLException { + return this; } + + public String getCatalogName(int col) throws SQLException { + return db.column_table_name(stmt.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(stmt.pointer, checkCol(col)); } + public int getColumnType(int col) throws SQLException { + switch (db.column_type(stmt.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 { + switch (db.column_type(stmt.pointer, checkCol(col))) { + case SQLITE_INTEGER: return "integer"; + case SQLITE_FLOAT: return "float"; + case SQLITE_BLOB: return "blob"; + case SQLITE_NULL: return "null"; + case SQLITE_TEXT: + default: return "text"; + } + } + 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(stmt.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; } +} diff --git a/src/main/java/org/sqlite/Stmt.java b/src/main/java/org/sqlite/Stmt.java new file mode 100644 index 0000000..9b9b660 --- /dev/null +++ b/src/main/java/org/sqlite/Stmt.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2007 David Crawshaw + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +package org.sqlite; + +import java.sql.*; +import java.util.ArrayList; + +class Stmt extends Unused implements Statement, Codes +{ + final Conn conn; + final DB db; + final RS rs; + + long pointer; + String sql = null; + + int batchPos; + Object[] batch = null; + boolean resultsWaiting = false; + + Stmt(Conn c) { + conn = c; + db = conn.db(); + rs = new RS(this); + } + + protected final void checkOpen() throws SQLException { + if (pointer == 0) throw new SQLException("statement is not executing"); + } + boolean isOpen() throws SQLException { + return (pointer != 0); + } + + /** Calls sqlite3_step() and sets up results. Expects a clean stmt. */ + protected boolean exec() throws SQLException { + if (sql == null) throw new SQLException( + "SQLiteJDBC internal error: sql==null"); + if (rs.isOpen()) throw new SQLException( + "SQLite JDBC internal error: rs.isOpen() on exec."); + + boolean rc = false; + try { + rc = db.execute(this, null); + } finally { + resultsWaiting = rc; + } + + return db.column_count(pointer) != 0; + } + + + // PUBLIC INTERFACE ///////////////////////////////////////////// + + public void close() throws SQLException { + if (pointer == 0) return; + rs.close(); + batch = null; + batchPos = 0; + int resp = db.finalize(this); + if (resp != SQLITE_OK && resp != SQLITE_MISUSE) + db.throwex(); + } + + protected void finalize() throws SQLException { close(); } + + public boolean execute(String sql) throws SQLException { + close(); + this.sql = sql; + db.prepare(this); + return exec(); + } + + public ResultSet executeQuery(String sql) throws SQLException { + 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 { + close(); + this.sql = sql; + int changes = 0; + try { + db.prepare(this); + changes = db.executeUpdate(this, null); + } finally { close(); } + return changes; + } + + public ResultSet getResultSet() throws SQLException { + checkOpen(); + if (rs.isOpen()) throw new SQLException("ResultSet already requested"); + if (db.column_count(pointer) == 0) throw new SQLException( + "no ResultSet available"); + if (rs.colsMeta == null) + rs.colsMeta = db.column_names(pointer); + rs.cols = rs.colsMeta; + + rs.open = resultsWaiting; + resultsWaiting = false; + return rs; + } + + /* + * This function has a complex behaviour best understood by carefully + * reading the JavaDoc for getMoreResults() and considering the test + * StatementTest.execute(). + */ + public int getUpdateCount() throws SQLException { + if (pointer != 0 + && !rs.isOpen() + && !resultsWaiting + && db.column_count(pointer) == 0) + return db.changes(); + return -1; + } + + public void addBatch(String sql) throws SQLException { + close(); + if (batch == null || batchPos + 1 >= batch.length) { + Object[] nb = new Object[Math.max(10, batchPos * 2)]; + if (batch != null) + System.arraycopy(batch, 0, nb, 0, batch.length); + batch = nb; + } + batch[batchPos++] = sql; + } + + public void clearBatch() throws SQLException { + batchPos = 0; + if (batch != null) + for (int i=0; i < batch.length; i++) + batch[i] = null; + } + + public int[] executeBatch() throws SQLException { + // TODO: optimise + close(); + if (batch == null || batchPos == 0) return new int[] {}; + + int[] changes = new int[batchPos]; + + synchronized (db) { try { + for (int i=0; i < changes.length; i++) { + try { + this.sql = (String)batch[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 { + clearBatch(); + } } + + return changes; + } + + public void setCursorName(String name) {} + + public SQLWarning getWarnings() throws SQLException { return null; } + public void clearWarnings() throws SQLException {} + + public Connection getConnection() throws SQLException { + return conn; } + + public void cancel() throws SQLException { rs.checkOpen(); db.interrupt(); } + public int getQueryTimeout() throws SQLException { + return conn.getTimeout(); } + public void setQueryTimeout(int seconds) throws SQLException { + 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 rs.maxRows; + } + public void setMaxRows(int max) throws SQLException { + checkOpen(); + if (max < 0) throw new SQLException("max row count must be >= 0"); + rs.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 int getFetchSize() throws SQLException { return rs.getFetchSize(); } + public void setFetchSize(int r) throws SQLException { rs.setFetchSize(r); } + public int getFetchDirection() throws SQLException { + return rs.getFetchDirection(); + } + public void setFetchDirection(int d) throws SQLException { + rs.setFetchDirection(d); + } + + /** 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; + } + + public int getResultSetConcurrency() throws SQLException { + return ResultSet.CONCUR_READ_ONLY; } + public int getResultSetHoldability() throws SQLException { + return ResultSet.CLOSE_CURSORS_AT_COMMIT; } + public int getResultSetType() throws SQLException { + return ResultSet.TYPE_FORWARD_ONLY; } +} diff --git a/src/main/java/org/sqlite/Unused.java b/src/main/java/org/sqlite/Unused.java new file mode 100644 index 0000000..1957699 --- /dev/null +++ b/src/main/java/org/sqlite/Unused.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2007 David Crawshaw + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package org.sqlite; + +import java.io.*; +import java.math.*; +import java.net.*; +import java.sql.*; +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 map) + throws SQLException { throw unused(); } + public Object getObject(String col, Map 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(); } +} diff --git a/src/main/java/org/xerial/db/sql/sqlite/SQLiteJDBCLoader.java b/src/main/java/org/xerial/db/sql/sqlite/SQLiteJDBCLoader.java index 7ed05f0..ee43101 100644 --- a/src/main/java/org/xerial/db/sql/sqlite/SQLiteJDBCLoader.java +++ b/src/main/java/org/xerial/db/sql/sqlite/SQLiteJDBCLoader.java @@ -97,11 +97,13 @@ public class SQLiteJDBCLoader * @param targetFolder * @return */ - private static boolean extractLibraryFile(String libFolderForCurrentOS, String libraryFileName, String targetFolder) + private static boolean extractAndLoadLibraryFile(String libFolderForCurrentOS, String libraryFileName, + String targetFolder) { String nativeLibraryFilePath = libFolderForCurrentOS + "/" + libraryFileName; + final String prefix = "sqlite-3.5.9-"; - File extractedLibFile = new File(targetFolder, libraryFileName); + File extractedLibFile = new File(targetFolder, prefix + libraryFileName); try { @@ -216,15 +218,14 @@ public class SQLiteJDBCLoader if (SQLiteJDBCLoader.class.getResource(sqliteNativeLibraryPath + "/" + sqliteNativeLibraryName) == null) { - throw new UnsupportedOperationException(String.format( - "SQLite for current OS %s(%s) is not supported in this sqlite-jdbc driver", OSInfo.getOSName(), - OSInfo.getArchName())); + // use nested VM version + return; } // temporary library folder String tempFolder = new File(System.getProperty("java.io.tmpdir")).getAbsolutePath(); - /* Try extracting thelibrary from jar */ - if (extractLibraryFile(sqliteNativeLibraryPath, sqliteNativeLibraryName, tempFolder)) + /* Try extracting the library from jar */ + if (extractAndLoadLibraryFile(sqliteNativeLibraryPath, sqliteNativeLibraryName, tempFolder)) { extracted = true; return; diff --git a/src/test/java/org/sqlite/DBMetaDataTest.java b/src/test/java/org/sqlite/DBMetaDataTest.java index e7b8692..512999c 100644 --- a/src/test/java/org/sqlite/DBMetaDataTest.java +++ b/src/test/java/org/sqlite/DBMetaDataTest.java @@ -170,6 +170,9 @@ public class DBMetaDataTest assertTrue(rs.next()); assertEquals(rs.getString("COLUMN_NAME"), "sn"); assertFalse(rs.next()); + + rs = meta.getColumns(null, null, "doesnotexist", "%"); + assertFalse(rs.next()); } @Test @@ -432,16 +435,15 @@ public class DBMetaDataTest rs.close(); } - /* - * TODO @Test public void columnOrderOfgetImportedKeys() throws SQLException { - * @Test public void columnOrderOfgetExportedKeys() throws SQLException { - * @Test public void columnOrderOfgetCrossReference() throws SQLException { - * @Test public void columnOrderOfgetTypeInfo() throws SQLException { @Test - * public void columnOrderOfgetIndexInfo() throws SQLException { @Test - * public void columnOrderOfgetSuperTypes() throws SQLException { @Test - * public void columnOrderOfgetSuperTables() throws SQLException { @Test - * public void columnOrderOfgetAttributes() throws SQLException { - */ + /* TODO + @Test public void columnOrderOfgetImportedKeys() throws SQLException { + @Test public void columnOrderOfgetExportedKeys() throws SQLException { + @Test public void columnOrderOfgetCrossReference() throws SQLException { + @Test public void columnOrderOfgetTypeInfo() throws SQLException { + @Test public void columnOrderOfgetIndexInfo() throws SQLException { + @Test public void columnOrderOfgetSuperTypes() throws SQLException { + @Test public void columnOrderOfgetSuperTables() throws SQLException { + @Test public void columnOrderOfgetAttributes() throws SQLException {*/ @Test public void columnOrderOfgetUDTs() throws SQLException @@ -458,4 +460,10 @@ public class DBMetaDataTest assertEquals(rsmeta.getColumnName(6), "REMARKS"); assertEquals(rsmeta.getColumnName(7), "BASE_TYPE"); } + + @Test + public void version() throws SQLException + { + assertNotNull(meta.getDatabaseProductVersion()); + } } diff --git a/src/test/java/org/sqlite/PrepStmtTest.java b/src/test/java/org/sqlite/PrepStmtTest.java index 066e54e..cacdb61 100644 --- a/src/test/java/org/sqlite/PrepStmtTest.java +++ b/src/test/java/org/sqlite/PrepStmtTest.java @@ -295,11 +295,9 @@ public class PrepStmtTest @Test public void tokens() throws SQLException { - /* - * checks for a bug where a substring is read by the driver as the full - * original string, caused by my idiocyin assuming the pascal-style - * string was null terminated. Thanks Oliver Randschau. - */ + /* checks for a bug where a substring is read by the driver as the + * full original string, caused by my idiocyin assuming the + * pascal-style string was null terminated. Thanks Oliver Randschau. */ StringTokenizer st = new StringTokenizer("one two three"); st.nextToken(); String substr = st.nextToken(); @@ -433,11 +431,9 @@ public class PrepStmtTest assertEquals(meta.getColumnName(1), "col1"); assertEquals(meta.getColumnName(2), "col2"); assertEquals(meta.getColumnName(3), "delta"); - /* - * assertEquals(meta.getColumnType(1), Types.INTEGER); - * assertEquals(meta.getColumnType(2), Types.INTEGER); - * assertEquals(meta.getColumnType(3), Types.INTEGER); - */ + /*assertEquals(meta.getColumnType(1), Types.INTEGER); + assertEquals(meta.getColumnType(2), Types.INTEGER); + assertEquals(meta.getColumnType(3), Types.INTEGER);*/ meta = prep.executeQuery().getMetaData(); assertEquals(meta.getColumnCount(), 3); @@ -487,6 +483,34 @@ public class PrepStmtTest prep.executeUpdate(); } + @Test + public void reusingSetValues() throws SQLException + { + PreparedStatement prep = conn.prepareStatement("select ?,?;"); + prep.setInt(1, 9); + + for (int i = 0; i < 10; i++) + { + prep.setInt(2, i); + ResultSet rs = prep.executeQuery(); + assertTrue(rs.next()); + assertEquals(rs.getInt(1), 9); + assertEquals(rs.getInt(2), i); + } + + for (int i = 0; i < 10; i++) + { + prep.setInt(2, i); + ResultSet rs = prep.executeQuery(); + assertTrue(rs.next()); + assertEquals(rs.getInt(1), 9); + assertEquals(rs.getInt(2), i); + rs.close(); + } + + prep.close(); + } + @Test(expected = SQLException.class) public void noSuchTable() throws SQLException { diff --git a/src/test/java/org/sqlite/StatementTest.java b/src/test/java/org/sqlite/StatementTest.java index c62d10e..5a9c440 100644 --- a/src/test/java/org/sqlite/StatementTest.java +++ b/src/test/java/org/sqlite/StatementTest.java @@ -16,6 +16,7 @@ import java.sql.Statement; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; /** These tests are designed to stress Statements on memory databases. */ @@ -204,10 +205,11 @@ public class StatementTest { stat.addBatch("create table batch (c1);"); stat.addBatch("insert into batch values (1);"); + stat.addBatch("insert into batch values (1);"); stat.addBatch("insert into batch values (2);"); stat.addBatch("insert into batch values (3);"); stat.addBatch("insert into batch values (4);"); - assertArrayEq(new int[] { 0, 1, 1, 1, 1 }, stat.executeBatch()); + assertArrayEq(new int[] { 0, 1, 1, 1, 1, 1 }, stat.executeBatch()); assertArrayEq(new int[] {}, stat.executeBatch()); stat.clearBatch(); stat.addBatch("insert into batch values (9);"); @@ -221,7 +223,7 @@ public class StatementTest ResultSet rs = stat.executeQuery("select count(*) from batch;"); assertTrue(rs.next()); - assertEquals(7, rs.getInt(1)); + assertEquals(8, rs.getInt(1)); rs.close(); } @@ -267,6 +269,43 @@ public class StatementTest rs.close(); } + @Test + public void columnNaming() throws SQLException + { + stat.executeUpdate("create table t1 (c1 integer);"); + stat.executeUpdate("create table t2 (c1 integer);"); + stat.executeUpdate("insert into t1 values (1);"); + stat.executeUpdate("insert into t2 values (1);"); + ResultSet rs = stat.executeQuery("select a.c1 AS c1 from t1 a, t2 where a.c1=t2.c1;"); + assertTrue(rs.next()); + assertEquals(rs.getInt("c1"), 1); + rs.close(); + } + + @Test + public void nullDate() throws SQLException + { + ResultSet rs = stat.executeQuery("select null;"); + assertTrue(rs.next()); + assertEquals(rs.getDate(1), null); + assertEquals(rs.getTime(1), null); + rs.close(); + } + + @Ignore + @Test(expected = SQLException.class) + public void ambiguousColumnNaming() throws SQLException + { + stat.executeUpdate("create table t1 (c1 int);"); + stat.executeUpdate("create table t2 (c1 int, c2 int);"); + stat.executeUpdate("insert into t1 values (1);"); + stat.executeUpdate("insert into t2 values (2, 1);"); + ResultSet rs = stat.executeQuery("select a.c1, b.c1 from t1 a, t2 b where a.c1=b.c2;"); + assertTrue(rs.next()); + assertEquals(rs.getInt("c1"), 1); + rs.close(); + } + @Test(expected = SQLException.class) public void failToDropWhenRSOpen() throws SQLException { diff --git a/src/test/java/org/sqlite/TransactionTest.java b/src/test/java/org/sqlite/TransactionTest.java index 2618305..8cff7bd 100644 --- a/src/test/java/org/sqlite/TransactionTest.java +++ b/src/test/java/org/sqlite/TransactionTest.java @@ -219,35 +219,26 @@ public class TransactionTest synchronized (lock) { lock.done = true; + lock.notify(); } } }.start(); + Thread.sleep(100); rs.close(); - for (int i = 0; i < 40; i++) + synchronized (lock) { - boolean isDone = false; - synchronized (lock) - { - isDone = lock.done; - } - if (isDone) - return; - try - { - Thread.sleep(100); - } - catch (Exception e) - {} + lock.wait(5000); + if (!lock.done) + throw new Exception("should be done"); } - - throw new Exception("should have caught done from second thread"); } @Test(expected = SQLException.class) public void secondConnMustTimeout() throws SQLException { + stat1.setQueryTimeout(1); stat1.executeUpdate("create table t (c1);"); stat1.executeUpdate("insert into t values (1);"); stat1.executeUpdate("insert into t values (2);"); -- 2.11.0