From ae756f43e7d01ae804c297b01b85cfbfd931af97 Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 6 Sep 2015 11:58:25 +0200 Subject: [PATCH] Now saves shops to a SQLite Database --- libs/org/sqlite/Codes.java | 106 ++ libs/org/sqlite/Conn.java | 440 ++++++ libs/org/sqlite/DB.java | 444 ++++++ libs/org/sqlite/ExtendedCommand.java | 116 ++ libs/org/sqlite/Function.java | 182 +++ libs/org/sqlite/JDBC.java | 79 ++ libs/org/sqlite/MetaData.java | 1214 +++++++++++++++++ libs/org/sqlite/NativeDB.java | 212 +++ libs/org/sqlite/NestedDB.java | 662 +++++++++ libs/org/sqlite/OSInfo.java | 80 ++ libs/org/sqlite/PrepStmt.java | 330 +++++ libs/org/sqlite/RS.java | 540 ++++++++ libs/org/sqlite/ResourceFinder.java | 102 ++ libs/org/sqlite/SQLiteConfig.java | 449 ++++++ libs/org/sqlite/SQLiteDataSource.java | 228 ++++ libs/org/sqlite/SQLiteErrorCode.java | 92 ++ libs/org/sqlite/SQLiteJDBCLoader.java | 271 ++++ libs/org/sqlite/SQLiteOpenMode.java | 59 + libs/org/sqlite/Stmt.java | 337 +++++ libs/org/sqlite/Unused.java | 244 ++++ plugin.yml | 2 +- src/de/epiceric/shopchest/Commands.java | 79 +- src/de/epiceric/shopchest/ShopChest.java | 77 +- src/de/epiceric/shopchest/config/Config.java | 2 +- .../shopchest/event/InteractShop.java | 29 +- .../event/RegenerateShopItemAfterRemove.java | 2 +- .../epiceric/shopchest/interfaces/Utils.java | 30 +- .../shopchest/interfaces/utils/Utils_R1.java | 33 +- .../shopchest/interfaces/utils/Utils_R2.java | 36 +- .../shopchest/interfaces/utils/Utils_R3.java | 33 +- src/de/epiceric/shopchest/shop/Shop.java | 25 +- src/de/epiceric/shopchest/sql/Database.java | 487 +++++++ src/de/epiceric/shopchest/sql/Error.java | 14 + src/de/epiceric/shopchest/sql/Errors.java | 16 + src/de/epiceric/shopchest/sql/SQLite.java | 73 + .../epiceric/shopchest/utils/ShopUtils.java | 1 + 36 files changed, 6983 insertions(+), 143 deletions(-) create mode 100644 libs/org/sqlite/Codes.java create mode 100644 libs/org/sqlite/Conn.java create mode 100644 libs/org/sqlite/DB.java create mode 100644 libs/org/sqlite/ExtendedCommand.java create mode 100644 libs/org/sqlite/Function.java create mode 100644 libs/org/sqlite/JDBC.java create mode 100644 libs/org/sqlite/MetaData.java create mode 100644 libs/org/sqlite/NativeDB.java create mode 100644 libs/org/sqlite/NestedDB.java create mode 100644 libs/org/sqlite/OSInfo.java create mode 100644 libs/org/sqlite/PrepStmt.java create mode 100644 libs/org/sqlite/RS.java create mode 100644 libs/org/sqlite/ResourceFinder.java create mode 100644 libs/org/sqlite/SQLiteConfig.java create mode 100644 libs/org/sqlite/SQLiteDataSource.java create mode 100644 libs/org/sqlite/SQLiteErrorCode.java create mode 100644 libs/org/sqlite/SQLiteJDBCLoader.java create mode 100644 libs/org/sqlite/SQLiteOpenMode.java create mode 100644 libs/org/sqlite/Stmt.java create mode 100644 libs/org/sqlite/Unused.java create mode 100644 src/de/epiceric/shopchest/sql/Database.java create mode 100644 src/de/epiceric/shopchest/sql/Error.java create mode 100644 src/de/epiceric/shopchest/sql/Errors.java create mode 100644 src/de/epiceric/shopchest/sql/SQLite.java diff --git a/libs/org/sqlite/Codes.java b/libs/org/sqlite/Codes.java new file mode 100644 index 0000000..ab78411 --- /dev/null +++ b/libs/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/libs/org/sqlite/Conn.java b/libs/org/sqlite/Conn.java new file mode 100644 index 0000000..176b779 --- /dev/null +++ b/libs/org/sqlite/Conn.java @@ -0,0 +1,440 @@ +/* + * 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.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Struct; +import java.util.Map; +import java.util.Properties; + +class Conn implements Connection +{ + private final String url; + private String fileName; + private DB db = null; + private MetaData meta = null; + private boolean autoCommit = true; + private int transactionIsolation = TRANSACTION_SERIALIZABLE; + private int timeout = 0; + + public Conn(String url, String fileName) throws SQLException { + this(url, fileName, new Properties()); + } + + public Conn(String url, String fileName, Properties prop) throws SQLException { + this.url = url; + this.fileName = fileName; + + SQLiteConfig config = new SQLiteConfig(prop); + open(config.getOpenModeFlags()); + + boolean enableSharedCache = config.isEnabledSharedCache(); + boolean enableLoadExtension = config.isEnabledLoadExtension(); + db.shared_cache(enableSharedCache); + db.enable_load_extension(enableLoadExtension); + + // set pragmas + config.apply(this); + } + + private static final String RESOURCE_NAME_PREFIX = ":resource:"; + + private void open(int openModeFlags) throws SQLException { + // check the path to the file exists + if (!":memory:".equals(fileName)) { + if (fileName.startsWith(RESOURCE_NAME_PREFIX)) { + String resourceName = fileName.substring(RESOURCE_NAME_PREFIX.length()); + + // search the class path + ClassLoader contextCL = Thread.currentThread().getContextClassLoader(); + URL resourceAddr = contextCL.getResource(resourceName); + if (resourceAddr == null) { + try { + resourceAddr = new URL(resourceName); + } + catch (MalformedURLException e) { + throw new SQLException(String.format("resource %s not found: %s", resourceName, e)); + } + } + + try { + fileName = extractResource(resourceAddr).getAbsolutePath(); + } + catch (IOException e) { + throw new SQLException(String.format("failed to load %s: %s", resourceName, e)); + } + } + else { + 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(); + } + } + + // tries to load native library first + try { + Class< ? > nativedb = Class.forName("org.sqlite.NativeDB"); + if (((Boolean) nativedb.getDeclaredMethod("load", (Class< ? >[]) null).invoke((Object) null, + (Object[]) null)).booleanValue()) + db = (DB) nativedb.newInstance(); + + } + catch (Exception e) {} // fall through to nested library + + // load nested library (pure-java SQLite) + if (db == null) { + try { + db = (DB) Class.forName("org.sqlite.NestedDB").newInstance(); + } + catch (Exception e) { + throw new SQLException("no SQLite library found"); + } + } + + db.open(this, fileName, openModeFlags); + setTimeout(3000); + } + + /** + * @param resourceAddr + * @return extracted file name + * @throws IOException + */ + private File extractResource(URL resourceAddr) throws IOException { + if (resourceAddr.getProtocol().equals("file")) { + try { + return new File(resourceAddr.toURI()); + } + catch (URISyntaxException e) { + throw new IOException(e.getMessage()); + } + } + + String tempFolder = new File(System.getProperty("java.io.tmpdir")).getAbsolutePath(); + String dbFileName = String.format("sqlite-jdbc-tmp-%d.db", resourceAddr.hashCode()); + File dbFile = new File(tempFolder, dbFileName); + + if (dbFile.exists()) { + long resourceLastModified = resourceAddr.openConnection().getLastModified(); + long tmpFileLastModified = dbFile.lastModified(); + if (resourceLastModified < tmpFileLastModified) { + return dbFile; + } + else { + // remove the old DB file + boolean deletionSucceeded = dbFile.delete(); + if (!deletionSucceeded) { + throw new IOException("failed to remove existing DB file: " + dbFile.getAbsolutePath()); + } + } + + // String md5sum1 = SQLiteJDBCLoader.md5sum(resourceAddr.openStream()); + // String md5sum2 = SQLiteJDBCLoader.md5sum(new FileInputStream(dbFile)); + // + // if (md5sum1.equals(md5sum2)) + // return dbFile; // no need to extract the DB file + // else + // { + // } + } + + byte[] buffer = new byte[8192]; // 8K buffer + FileOutputStream writer = new FileOutputStream(dbFile); + InputStream reader = resourceAddr.openStream(); + try { + int bytesRead = 0; + while ((bytesRead = reader.read(buffer)) != -1) { + writer.write(buffer, 0, bytesRead); + } + return dbFile; + } + finally { + writer.close(); + reader.close(); + } + + } + + 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"); + } + + @Override + 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 transactionIsolation; + } + + public void setTransactionIsolation(int level) throws SQLException { + switch (level) { + case TRANSACTION_SERIALIZABLE: + db.exec("PRAGMA read_uncommitted = false;"); + break; + case TRANSACTION_READ_UNCOMMITTED: + db.exec("PRAGMA read_uncommitted = true;"); + break; + default: + throw new SQLException("SQLite supports only TRANSACTION_SERIALIZABLE and TRANSACTION_READ_UNCOMMITTED."); + } + transactionIsolation = level; + } + + 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 { + return prepareStatement(sql); + } + + public PreparedStatement prepareStatement(String sql, int[] colInds) throws SQLException { + return prepareStatement(sql); + } + + public PreparedStatement prepareStatement(String sql, String[] colNames) throws SQLException { + return prepareStatement(sql); + } + + 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/libs/org/sqlite/DB.java b/libs/org/sqlite/DB.java new file mode 100644 index 0000000..fbdb054 --- /dev/null +++ b/libs/org/sqlite/DB.java @@ -0,0 +1,444 @@ +/* + * 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.BatchUpdateException; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/* + * 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 final Map stmts = new HashMap(); + + // 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; + + abstract int enable_load_extension(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, int openFlags) throws SQLException { + this.conn = conn; + _open(file, openFlags); + } + + 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, int openFlags) throws SQLException; + + protected abstract void _close() throws SQLException; + + protected abstract int _exec(String sql) 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; + + abstract int backup(String dbName, String destFileName, ProgressObserver observer) throws SQLException; + + abstract int restore(String dbName, String sourceFileName, ProgressObserver observer) throws SQLException; + + public static interface ProgressObserver + { + public void progress(int remaining, int pageCount); + } + + /** + * 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 Short) { + return bind_int(stmt, pos, ((Short) v).intValue()); + } + else if (v instanceof Long) { + return bind_long(stmt, pos, ((Long) v).longValue()); + } + else if (v instanceof Float) { + return bind_double(stmt, pos, ((Float) v).doubleValue()); + } + 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(); + } + + int statusCode = step(stmt.pointer); + switch (statusCode) { + case SQLITE_DONE: + reset(stmt.pointer); + ensureAutoCommit(); + return false; + case SQLITE_ROW: + return true; + case SQLITE_BUSY: + case SQLITE_LOCKED: + case SQLITE_MISUSE: + throw newSQLException(statusCode); + default: + finalize(stmt); + throw newSQLException(statusCode); + } + + } + + final synchronized boolean execute(String sql) throws SQLException { + int statusCode = _exec(sql); + switch (statusCode) { + case SQLITE_OK: + return false; + case SQLITE_DONE: + ensureAutoCommit(); + return false; + case SQLITE_ROW: + return true; + default: + throw newSQLException(statusCode); + } + } + + 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()); + } + + final void throwex(int errorCode) throws SQLException { + throw newSQLException(errorCode); + } + + final void throwex(int errorCode, String errorMessage) throws SQLException { + throw newSQLException(errorCode, errorMessage); + } + + static SQLException newSQLException(int errorCode, String errorMessage) throws SQLException { + SQLiteErrorCode code = SQLiteErrorCode.getErrorCode(errorCode); + return new SQLException(String.format("%s (%s)", code, errorMessage)); + } + + private SQLException newSQLException(int errorCode) throws SQLException { + return newSQLException(errorCode, 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/libs/org/sqlite/ExtendedCommand.java b/libs/org/sqlite/ExtendedCommand.java new file mode 100644 index 0000000..ac4e120 --- /dev/null +++ b/libs/org/sqlite/ExtendedCommand.java @@ -0,0 +1,116 @@ +//-------------------------------------- +// sqlite-jdbc Project +// +// ExtendedCommand.java +// Since: Mar 12, 2010 +// +// $URL$ +// $Author$ +//-------------------------------------- +package org.sqlite; + +import java.sql.SQLException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * parsing SQLite specific extension of SQL command + * + * @author leo + * + */ +public class ExtendedCommand +{ + public static interface SQLExtension + { + public void execute(DB db) throws SQLException; + } + + public static SQLExtension parse(String sql) throws SQLException { + if (sql == null) + return null; + + if (sql.startsWith("backup")) + return BackupCommand.parse(sql); + else if (sql.startsWith("restore")) + return RestoreCommand.parse(sql); + + return null; + } + + public static String removeQuotation(String s) { + if (s == null) + return s; + + if ((s.startsWith("\"") && s.endsWith("\"")) || (s.startsWith("'") && s.endsWith("'"))) + return s.substring(1, s.length() - 1); + else + return s; + } + + public static class BackupCommand implements SQLExtension + { + public final String srcDB; + public final String destFile; + + public BackupCommand(String srcDB, String destFile) { + this.srcDB = srcDB; + this.destFile = destFile; + } + + private static Pattern backupCmd = Pattern + .compile("backup(\\s+(\"[^\"]*\"|'[^\']*\'|\\S+))?\\s+to\\s+(\"[^\"]*\"|'[^\']*\'|\\S+)"); + + public static BackupCommand parse(String sql) throws SQLException { + if (sql != null) { + Matcher m = backupCmd.matcher(sql); + if (m.matches()) { + String dbName = removeQuotation(m.group(2)); + String dest = removeQuotation(m.group(3)); + if (dbName == null || dbName.length() == 0) + dbName = "main"; + + return new BackupCommand(dbName, dest); + } + } + throw new SQLException("syntax error: " + sql); + } + + public void execute(DB db) throws SQLException { + db.backup(srcDB, destFile, null); + } + + } + + public static class RestoreCommand implements SQLExtension + { + public final String targetDB; + public final String srcFile; + private static Pattern restoreCmd = Pattern + .compile("restore(\\s+(\"[^\"]*\"|'[^\']*\'|\\S+))?\\s+from\\s+(\"[^\"]*\"|'[^\']*\'|\\S+)"); + + public RestoreCommand(String targetDB, String srcFile) { + this.targetDB = targetDB; + this.srcFile = srcFile; + } + + public static RestoreCommand parse(String sql) throws SQLException { + if (sql != null) { + Matcher m = restoreCmd.matcher(sql); + if (m.matches()) { + String dbName = removeQuotation(m.group(2)); + String dest = removeQuotation(m.group(3)); + if (dbName == null || dbName.length() == 0) + dbName = "main"; + return new RestoreCommand(dbName, dest); + } + } + throw new SQLException("syntax error: " + sql); + } + + public void execute(DB db) throws SQLException { + db.restore(targetDB, srcFile, null); + } + } + +} diff --git a/libs/org/sqlite/Function.java b/libs/org/sqlite/Function.java new file mode 100644 index 0000000..8faa757 --- /dev/null +++ b/libs/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/libs/org/sqlite/JDBC.java b/libs/org/sqlite/JDBC.java new file mode 100644 index 0000000..c05c3c7 --- /dev/null +++ b/libs/org/sqlite/JDBC.java @@ -0,0 +1,79 @@ +/* + * 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.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; +import java.util.Properties; + +public class JDBC implements Driver +{ + public static final String PREFIX = "jdbc:sqlite:"; + + static { + try { + DriverManager.registerDriver(new JDBC()); + } + catch (SQLException e) { + e.printStackTrace(); + } + } + + public int getMajorVersion() { + return SQLiteJDBCLoader.getMajorVersion(); + } + + public int getMinorVersion() { + return SQLiteJDBCLoader.getMinorVersion(); + } + + public boolean jdbcCompliant() { + return false; + } + + public boolean acceptsURL(String url) { + return isValidURL(url); + } + + public static boolean isValidURL(String url) { + return url != null && url.toLowerCase().startsWith(PREFIX); + } + + public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { + return SQLiteConfig.getDriverPropertyInfo(); + } + + public Connection connect(String url, Properties info) throws SQLException { + return createConnection(url, info); + } + + static String extractAddress(String url) { + // if no file name is given use a memory database + return PREFIX.equalsIgnoreCase(url) ? ":memory:" : url.substring(PREFIX.length()); + } + + public static Connection createConnection(String url, Properties prop) throws SQLException { + if (!isValidURL(url)) + throw new SQLException("invalid database address: " + url); + + url = url.trim(); + return new Conn(url, extractAddress(url), prop); + } +} diff --git a/libs/org/sqlite/MetaData.java b/libs/org/sqlite/MetaData.java new file mode 100644 index 0000000..b34fa10 --- /dev/null +++ b/libs/org/sqlite/MetaData.java @@ -0,0 +1,1214 @@ +/* + * 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.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Struct; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Iterator; + +class MetaData implements DatabaseMetaData +{ + private Conn conn; + private PreparedStatement getTables = null, getTableTypes = null, getTypeInfo = null, getCatalogs = null, + getSchemas = null, getUDTs = null, getColumnsTblName = null, getSuperTypes = null, getSuperTables = null, + getTablePrivileges = null, getIndexInfo = 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 (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 (getIndexInfo != null) + getIndexInfo.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; + getCatalogs = null; + getSchemas = null; + getUDTs = null; + getColumnsTblName = null; + getSuperTypes = null; + getSuperTables = null; + getTablePrivileges = null; + getIndexInfo = 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.equals("INT") || colType.equals("INTEGER")) + colJavaType = Types.INTEGER; + else if (colType.equals("TEXT")) + colJavaType = Types.VARCHAR; + else if (colType.equals("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 (pt == null) + return getExportedKeys(fc, fs, ft); + if (ft == null) + return getImportedKeys(pc, ps, pt); + + StringBuilder query = new StringBuilder(); + query.append(String.format("select %s as PKTABLE_CAT, %s as PKTABLE_SCHEM, %s as PKTABLE_NAME, ", quote(pc), + quote(ps), quote(pt)) + + "'' as PKCOLUMN_NAME, " + + String.format("%s as FKTABLE_CAT, %s as FKTABLE_SCHEM, %s as FKTABLE_NAME, ", quote(fc), quote(fs), + quote(ft)) + + "'' as FKCOLUMN_NAME, -1 as KEY_SEQ, 3 as UPDATE_RULE, " + + "3 as DELETE_RULE, '' as FK_NAME, '' as PK_NAME, " + + Integer.toString(importedKeyInitiallyDeferred) + + " as DEFERRABILITY limit 0;"); + return conn.createStatement().executeQuery(query.toString()); + } + + 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); + } + + private static String quote(String tableName) { + if (tableName == null) + return "null"; + else + return String.format("'%s'", tableName); + } + + public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException { + + StringBuilder exportedKeysQuery = new StringBuilder(); + exportedKeysQuery.append(String.format("select %s as PKTABLE_CAT, %s as PKTABLE_SCHEM, %s as PKTABLE_NAME, ", + quote(catalog), quote(schema), quote(table)) + + String.format("pcn as PKCOLUMN_NAME, %s as FKTABLE_CAT, %s as FKTABLE_SCHEM, ", quote(catalog), + quote(schema)) + + "fkn as FKTABLE_NAME, fcn as FKCOLUMN_NAME, " + + "ks as KEY_SEQ, " + + "ur as UPDATE_RULE, " + + "dr as DELETE_RULE, " + + "'' as FK_NAME, " + + "'' as PK_NAME, " + + Integer.toString(importedKeyInitiallyDeferred) + " as DEFERRABILITY from ("); + + // retrieve table list + String tableListQuery = String.format("select name from sqlite_master where type = 'table'"); + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery(tableListQuery); + ArrayList tableList = new ArrayList(); + while (rs.next()) + tableList.add(rs.getString(1)); + rs.close(); + + // find imported keys for each table + int count = 0; + for (String targetTable : tableList) { + String foreignKeyQuery = String.format("pragma foreign_key_list('%s');", escape(targetTable)); + + try { + ResultSet fk = stat.executeQuery(foreignKeyQuery); + for (; fk.next();) { + int keySeq = fk.getInt(2) + 1; + String PKTabName = fk.getString(3); + String FKColName = fk.getString(4); + String PKColName = fk.getString(5); + String updateRule = fk.getString(6); + String deleteRule = fk.getString(7); + + if (PKTabName == null || !PKTabName.equals(table)) + continue; + + if (count > 0) + exportedKeysQuery.append(" union all "); + + exportedKeysQuery.append("select " + Integer.toString(keySeq) + " as ks," + "'" + + escape(targetTable) + "' as fkn," + "'" + escape(FKColName) + "' as fcn," + "'" + + escape(PKColName) + "' as pcn," + String.format("case '%s' ", escape(updateRule)) + + String.format("when 'NO ACTION' then %d ", importedKeyNoAction) + + String.format("when 'CASCADE' then %d ", importedKeyCascade) + + String.format("when 'RESTRICT' then %d ", importedKeyRestrict) + + String.format("when 'SET NULL' then %d ", importedKeySetNull) + + String.format("when 'SET DEFAULT' then %d ", importedKeySetDefault) + "end as ur," + + String.format("case '%s' ", escape(deleteRule)) + + String.format("when 'NO ACTION' then %d ", importedKeyNoAction) + + String.format("when 'CASCADE' then %d ", importedKeyCascade) + + String.format("when 'RESTRICT' then %d ", importedKeyRestrict) + + String.format("when 'SET NULL' then %d ", importedKeySetNull) + + String.format("when 'SET DEFAULT' then %d ", importedKeySetDefault) + "end as dr"); + + count++; + } + + exportedKeysQuery.append(");"); + fk.close(); + } + catch (SQLException e) { + // continue + } + } + + String sql = (count > 0) ? exportedKeysQuery.toString() : (String.format( + "select %s as PKTABLE_CAT, %s as PKTABLE_SCHEM, %s as PKTABLE_NAME, ", quote(catalog), quote(schema), + quote(table)) + + "'' as PKCOLUMN_NAME, " + + String.format("%s as FKTABLE_CAT, %s as FKTABLE_SCHEM, ", quote(catalog), quote(schema)) + + "'' as FKTABLE_NAME, " + + "'' as FKCOLUMN_NAME, " + + "-1 as KEY_SEQ, " + + "3 as UPDATE_RULE, " + + "3 as DELETE_RULE, " + "'' as FK_NAME, " + "'' as PK_NAME, " + "5 as DEFERRABILITY limit 0;"); + return stat.executeQuery(sql); + } + + public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException { + String sql; + ResultSet rs = null; + Statement stat = conn.createStatement(); + + sql = String.format("select %s as PKTABLE_CAT, %s as PKTABLE_SCHEM, ", quote(catalog), quote(schema)) + + String + .format( + "ptn as PKTABLE_NAME, pcn as PKCOLUMN_NAME, %s as FKTABLE_CAT, %s as FKTABLE_SCHEM, %s as FKTABLE_NAME, ", + quote(catalog), quote(schema), quote(table)) + "fcn as FKCOLUMN_NAME, " + + "ks as KEY_SEQ, " + "ur as UPDATE_RULE, " + "dr as DELETE_RULE, " + "'' as FK_NAME, " + + "'' as PK_NAME, " + Integer.toString(importedKeyInitiallyDeferred) + " as DEFERRABILITY from ("; + + // Use a try catch block to avoid "query does not return ResultSet" error + try { + rs = stat.executeQuery("pragma foreign_key_list('" + escape(table) + "');"); + int i; + for (i = 0; rs.next(); i++) { + int keySeq = rs.getInt(2) + 1; + String PKTabName = rs.getString(3); + String FKColName = rs.getString(4); + String PKColName = rs.getString(5); + String updateRule = rs.getString(6); + String deleteRule = rs.getString(7); + + if (i > 0) + sql += " union all "; + + sql += String.format("select %d as ks,", keySeq) + + String.format("'%s' as ptn, '%s' as fcn, '%s' as pcn,", escape(PKTabName), escape(FKColName), + escape(PKColName)) + String.format("case '%s' ", escape(updateRule)) + + String.format("when 'NO ACTION' then %d ", importedKeyNoAction) + + String.format("when 'CASCADE' then %d ", importedKeyCascade) + + String.format("when 'RESTRICT' then %d ", importedKeyRestrict) + + String.format("when 'SET NULL' then %d ", importedKeySetNull) + + String.format("when 'SET DEFAULT' then %d ", importedKeySetDefault) + "end as ur," + + String.format("case '%s' ", escape(deleteRule)) + + String.format("when 'NO ACTION' then %d ", importedKeyNoAction) + + String.format("when 'CASCADE' then %d ", importedKeyCascade) + + String.format("when 'RESTRICT' then %d ", importedKeyRestrict) + + String.format("when 'SET NULL' then %d ", importedKeySetNull) + + String.format("when 'SET DEFAULT' then %d ", importedKeySetDefault) + "end as dr"; + } + sql += ");"; + rs.close(); + } + catch (SQLException e) { + sql += "select -1 as ks, '' as ptn, '' as fcn, '' as pcn, " + importedKeyNoAction + " as ur, " + + importedKeyNoAction + " as dr) limit 0;"; + } + + return stat.executeQuery(sql); + } + + public ResultSet getIndexInfo(String c, String s, String t, boolean u, boolean approximate) throws SQLException { + String sql; + ResultSet rs = null; + Statement stat = conn.createStatement(); + + sql = "select " + "null as TABLE_CAT, " + "null as TABLE_SCHEM, " + "'" + escape(t) + "' as TABLE_NAME, " + + "un as NON_UNIQUE, " + "null as INDEX_QUALIFIER, " + "n as INDEX_NAME, " + + Integer.toString(tableIndexOther) + " as TYPE, " + "op as ORDINAL_POSITION, " + "cn as COLUMN_NAME, " + + "null as ASC_OR_DESC, " + "0 as CARDINALITY, " + "0 as PAGES, " + "null as FILTER_CONDITION from ("; + + // Use a try catch block to avoid "query does not return ResultSet" error + try { + ArrayList indexList = new ArrayList(); + + rs = stat.executeQuery("pragma index_list('" + escape(t) + "');"); + while (rs.next()) { + indexList.add(new ArrayList()); + indexList.get(indexList.size() - 1).add(rs.getString(2)); + indexList.get(indexList.size() - 1).add(rs.getInt(3)); + } + rs.close(); + + int i = 0; + Iterator indexIterator = indexList.iterator(); + ArrayList currentIndex; + while (indexIterator.hasNext()) { + currentIndex = (ArrayList) indexIterator.next(); + String indexName = currentIndex.get(0).toString(); + int unique = (Integer) currentIndex.get(1); + + rs = stat.executeQuery("pragma index_info('" + escape(indexName) + "');"); + for (; rs.next(); i++) { + + int ordinalPosition = rs.getInt(1) + 1; + String colName = rs.getString(3); + + if (i > 0) + sql += " union all "; + + sql += "select " + Integer.toString(1 - unique) + " as un," + "'" + escape(indexName) + "' as n," + + Integer.toString(ordinalPosition) + " as op," + "'" + escape(colName) + "' as cn"; + i++; + } + rs.close(); + } + sql += ");"; + } + catch (SQLException e) { + sql += "select null as un, null as n, null as op, null as cn) limit 0;"; + } + + return stat.executeQuery(sql); + } + + 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," + " 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/libs/org/sqlite/NativeDB.java b/libs/org/sqlite/NativeDB.java new file mode 100644 index 0000000..72780cc --- /dev/null +++ b/libs/org/sqlite/NativeDB.java @@ -0,0 +1,212 @@ +/* + * 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; + +/** 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 isLoaded = false; + private static boolean loadSucceeded = false; + + static boolean load() { + if (isLoaded) + return loadSucceeded == true; + + loadSucceeded = SQLiteJDBCLoader.initialize(); + isLoaded = true; + return loadSucceeded; + } + + /** linked list of all instanced UDFDatas */ + private final long udfdatalist = 0; + + // WRAPPER FUNCTIONS //////////////////////////////////////////// + + @Override + protected native synchronized void _open(String file, int openFlags) throws SQLException; + + @Override + protected native synchronized void _close() throws SQLException; + + @Override + protected native synchronized int _exec(String sql) throws SQLException; + + @Override + native synchronized int shared_cache(boolean enable); + + @Override + native synchronized int enable_load_extension(boolean enable); + + @Override + native synchronized void interrupt(); + + @Override + native synchronized void busy_timeout(int ms); + + //native synchronized void exec(String sql) throws SQLException; + @Override + protected native synchronized long prepare(String sql) throws SQLException; + + @Override + native synchronized String errmsg(); + + @Override + native synchronized String libversion(); + + @Override + native synchronized int changes(); + + @Override + protected native synchronized int finalize(long stmt); + + @Override + protected native synchronized int step(long stmt); + + @Override + protected native synchronized int reset(long stmt); + + @Override + native synchronized int clear_bindings(long stmt); + + @Override + native synchronized int bind_parameter_count(long stmt); + + @Override + native synchronized int column_count(long stmt); + + @Override + native synchronized int column_type(long stmt, int col); + + @Override + native synchronized String column_decltype(long stmt, int col); + + @Override + native synchronized String column_table_name(long stmt, int col); + + @Override + native synchronized String column_name(long stmt, int col); + + @Override + native synchronized String column_text(long stmt, int col); + + @Override + native synchronized byte[] column_blob(long stmt, int col); + + @Override + native synchronized double column_double(long stmt, int col); + + @Override + native synchronized long column_long(long stmt, int col); + + @Override + native synchronized int column_int(long stmt, int col); + + @Override + native synchronized int bind_null(long stmt, int pos); + + @Override + native synchronized int bind_int(long stmt, int pos, int v); + + @Override + native synchronized int bind_long(long stmt, int pos, long v); + + @Override + native synchronized int bind_double(long stmt, int pos, double v); + + @Override + native synchronized int bind_text(long stmt, int pos, String v); + + @Override + native synchronized int bind_blob(long stmt, int pos, byte[] v); + + @Override + native synchronized void result_null(long context); + + @Override + native synchronized void result_text(long context, String val); + + @Override + native synchronized void result_blob(long context, byte[] val); + + @Override + native synchronized void result_double(long context, double val); + + @Override + native synchronized void result_long(long context, long val); + + @Override + native synchronized void result_int(long context, int val); + + @Override + native synchronized void result_error(long context, String err); + + @Override + native synchronized int value_bytes(Function f, int arg); + + @Override + native synchronized String value_text(Function f, int arg); + + @Override + native synchronized byte[] value_blob(Function f, int arg); + + @Override + native synchronized double value_double(Function f, int arg); + + @Override + native synchronized long value_long(Function f, int arg); + + @Override + native synchronized int value_int(Function f, int arg); + + @Override + native synchronized int value_type(Function f, int arg); + + @Override + native synchronized int create_function(String name, Function func); + + @Override + native synchronized int destroy_function(String name); + + @Override + native synchronized void free_functions(); + + @Override + native synchronized int backup(String dbName, String destFileName, ProgressObserver observer) throws SQLException; + + @Override + native synchronized int restore(String dbName, String sourceFileName, ProgressObserver observer) + throws SQLException; + + // 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 + */ + @Override + native synchronized boolean[][] column_metadata(long stmt); + + static void throwex(String msg) throws SQLException { + throw new SQLException(msg); + } +} diff --git a/libs/org/sqlite/NestedDB.java b/libs/org/sqlite/NestedDB.java new file mode 100644 index 0000000..af00f20 --- /dev/null +++ b/libs/org/sqlite/NestedDB.java @@ -0,0 +1,662 @@ +/* + * 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.PrintWriter; +import java.sql.SQLException; + +import org.ibex.nestedvm.Runtime; + +// FEATURE: strdup is wasteful, SQLite interface will take unterminated char* + +/** Communicates with the Java version of SQLite provided by NestedVM. */ +final class NestedDB extends DB implements Runtime.CallJavaCB +{ + /** database pointer */ + int handle = 0; + + /** sqlite binary embedded in nestedvm */ + private Runtime rt = null; + + /** user defined functions referenced by position (stored in used data) */ + private Function[] functions = null; + private String[] funcNames = null; + + // WRAPPER FUNCTIONS //////////////////////////////////////////// + + @Override + protected synchronized void _open(String filename, int openFlags) throws SQLException { + if (handle != 0) + throw new SQLException("DB already open"); + + // handle silly windows drive letter mapping + if (filename.length() > 2) { + char drive = Character.toLowerCase(filename.charAt(0)); + if (filename.charAt(1) == ':' && drive >= 'a' && drive <= 'z') { + + // convert to nestedvm's "/c:/file" format + filename = filename.substring(2); + filename = filename.replace('\\', '/'); + filename = "/" + drive + ":" + filename; + } + } + + // start the nestedvm runtime + try { + rt = (Runtime) Class.forName("org.sqlite.SQLite").newInstance(); + rt.start(); + } + catch (Exception e) { + throw new CausedSQLException(e); + } + + // callback for user defined functions + rt.setCallJavaCB(this); + + // open the db and retrieve sqlite3_db* pointer + int passback = rt.xmalloc(4); + int str = rt.strdup(filename); + // if (call("sqlite3_open_v2", str, openFlags, 0, passback) != SQLITE_OK) + if (call("sqlite3_open_v2", str, passback, openFlags, 0) != SQLITE_OK) + throwex(); + handle = deref(passback); + rt.free(str); + rt.free(passback); + } + + /* callback for Runtime.CallJavaCB above */ + public int call(int xType, int context, int args, int value) { + xUDF(xType, context, args, value); + return 0; + } + + @Override + protected synchronized void _close() throws SQLException { + if (handle == 0) + return; + try { + if (call("sqlite3_close", handle) != SQLITE_OK) + throwex(); + } + finally { + handle = 0; + rt.stop(); + rt = null; + } + } + + @Override + int shared_cache(boolean enable) throws SQLException { + // The shared cache is per-process, so it is useless as + // each nested connection is its own process. + return -1; + } + + @Override + int enable_load_extension(boolean enable) throws SQLException { + // TODO enable_load_extension is not supported in pure-java mode + //return call("sqlite3_enable_load_extension", handle, enable ? 1 : 0); + return 1; + } + + @Override + synchronized void interrupt() throws SQLException { + call("sqlite3_interrupt", handle); + } + + @Override + synchronized void busy_timeout(int ms) throws SQLException { + call("sqlite3_busy_timeout", handle, ms); + } + + @Override + protected synchronized long prepare(String sql) throws SQLException { + + int passback = rt.xmalloc(4); + int str = rt.strdup(sql); + int ret = call("sqlite3_prepare_v2", handle, str, -1, passback, 0); + rt.free(str); + if (ret != SQLITE_OK) { + rt.free(passback); + throwex(ret); + } + int pointer = deref(passback); + rt.free(passback); + return pointer; + } + + @Override + synchronized String errmsg() throws SQLException { + return cstring(call("sqlite3_errmsg", handle)); + } + + @Override + synchronized String libversion() throws SQLException { + return cstring(call("sqlite3_libversion", handle)); + } + + @Override + synchronized int changes() throws SQLException { + return call("sqlite3_changes", handle); + } + + @Override + protected synchronized int _exec(String sql) throws SQLException { + if (rt == null) + throw DB.newSQLException(SQLiteErrorCode.SQLITE_MISUSE.code, "attempt to use the closed conection"); + int passback = rt.xmalloc(4); + int str = rt.strdup(sql); + int status = call("sqlite3_exec", handle, str, 0, 0, passback); + if (status != SQLITE_OK) { + String errorMessage = cstring(passback); + call("sqlite3_free", deref(passback)); + rt.free(passback); + throwex(status, errorMessage); + } + rt.free(passback); + return status; + } + + @Override + protected synchronized int finalize(long stmt) throws SQLException { + return call("sqlite3_finalize", (int) stmt); + } + + @Override + protected synchronized int step(long stmt) throws SQLException { + return call("sqlite3_step", (int) stmt); + } + + @Override + protected synchronized int reset(long stmt) throws SQLException { + return call("sqlite3_reset", (int) stmt); + } + + @Override + synchronized int clear_bindings(long stmt) throws SQLException { + return call("sqlite3_clear_bindings", (int) stmt); + } + + @Override + synchronized int bind_parameter_count(long stmt) throws SQLException { + return call("sqlite3_bind_parameter_count", (int) stmt); + } + + @Override + synchronized int column_count(long stmt) throws SQLException { + return call("sqlite3_column_count", (int) stmt); + } + + @Override + synchronized int column_type(long stmt, int col) throws SQLException { + return call("sqlite3_column_type", (int) stmt, col); + } + + @Override + synchronized String column_name(long stmt, int col) throws SQLException { + return utfstring(call("sqlite3_column_name", (int) stmt, col)); + } + + @Override + synchronized String column_text(long stmt, int col) throws SQLException { + return utfstring(call("sqlite3_column_text", (int) stmt, col)); + } + + @Override + synchronized byte[] column_blob(long stmt, int col) throws SQLException { + int addr = call("sqlite3_column_blob", (int) stmt, col); + if (addr == 0) + return null; + byte[] blob = new byte[call("sqlite3_column_bytes", (int) stmt, col)]; + copyin(addr, blob, blob.length); + return blob; + } + + @Override + synchronized double column_double(long stmt, int col) throws SQLException { + try { + return Double.parseDouble(column_text(stmt, col)); + } + catch (NumberFormatException e) { + return Double.NaN; + } // TODO + } + + @Override + synchronized long column_long(long stmt, int col) throws SQLException { + try { + return Long.parseLong(column_text(stmt, col)); + } + catch (NumberFormatException e) { + return 0; + } // TODO + } + + @Override + synchronized int column_int(long stmt, int col) throws SQLException { + return call("sqlite3_column_int", (int) stmt, col); + } + + @Override + synchronized String column_decltype(long stmt, int col) throws SQLException { + return utfstring(call("sqlite3_column_decltype", (int) stmt, col)); + } + + @Override + synchronized String column_table_name(long stmt, int col) throws SQLException { + return utfstring(call("sqlite3_column_table_name", (int) stmt, col)); + } + + @Override + synchronized int bind_null(long stmt, int pos) throws SQLException { + return call("sqlite3_bind_null", (int) stmt, pos); + } + + @Override + synchronized int bind_int(long stmt, int pos, int v) throws SQLException { + return call("sqlite3_bind_int", (int) stmt, pos, v); + } + + @Override + synchronized int bind_long(long stmt, int pos, long v) throws SQLException { + return bind_text(stmt, pos, Long.toString(v)); // TODO + } + + @Override + synchronized int bind_double(long stmt, int pos, double v) throws SQLException { + return bind_text(stmt, pos, Double.toString(v)); // TODO + } + + @Override + synchronized int bind_text(long stmt, int pos, String v) throws SQLException { + if (v == null) + return bind_null(stmt, pos); + return call("sqlite3_bind_text", (int) stmt, pos, rt.strdup(v), -1, rt.lookupSymbol("free")); + } + + @Override + synchronized int bind_blob(long stmt, int pos, byte[] buf) throws SQLException { + if (buf == null || buf.length < 1) + return bind_null(stmt, pos); + int len = buf.length; + int blob = rt.xmalloc(len); // free()ed by sqlite3_bind_blob + copyout(buf, blob, len); + return call("sqlite3_bind_blob", (int) stmt, pos, blob, len, rt.lookupSymbol("free")); + } + + @Override + synchronized void result_null(long cxt) throws SQLException { + call("sqlite3_result_null", (int) cxt); + } + + @Override + synchronized void result_text(long cxt, String val) throws SQLException { + call("sqlite3_result_text", (int) cxt, rt.strdup(val), -1, rt.lookupSymbol("free")); + } + + @Override + synchronized void result_blob(long cxt, byte[] val) throws SQLException { + if (val == null || val.length == 0) { + result_null(cxt); + return; + } + int blob = rt.xmalloc(val.length); + copyout(val, blob, val.length); + call("sqlite3_result_blob", (int) cxt, blob, val.length, rt.lookupSymbol("free")); + } + + @Override + synchronized void result_double(long cxt, double val) throws SQLException { + result_text(cxt, Double.toString(val)); + } // TODO + + @Override + synchronized void result_long(long cxt, long val) throws SQLException { + result_text(cxt, Long.toString(val)); + } // TODO + + @Override + synchronized void result_int(long cxt, int val) throws SQLException { + call("sqlite3_result_int", (int) cxt, val); + } + + @Override + synchronized void result_error(long cxt, String err) throws SQLException { + int str = rt.strdup(err); + call("sqlite3_result_error", (int) cxt, str, -1); + rt.free(str); + } + + @Override + synchronized int value_bytes(Function f, int arg) throws SQLException { + return call("sqlite3_value_bytes", value(f, arg)); + } + + @Override + synchronized String value_text(Function f, int arg) throws SQLException { + return utfstring(call("sqlite3_value_text", value(f, arg))); + } + + @Override + synchronized byte[] value_blob(Function f, int arg) throws SQLException { + int addr = call("sqlite3_value_blob", value(f, arg)); + if (addr == 0) + return null; + byte[] blob = new byte[value_bytes(f, arg)]; + copyin(addr, blob, blob.length); + return blob; + } + + @Override + synchronized double value_double(Function f, int arg) throws SQLException { + return Double.parseDouble(value_text(f, arg)); // TODO + } + + @Override + synchronized long value_long(Function f, int arg) throws SQLException { + return Long.parseLong(value_text(f, arg)); // TODO + } + + @Override + synchronized int value_int(Function f, int arg) throws SQLException { + return call("sqlite3_value_int", value(f, arg)); + } + + @Override + synchronized int value_type(Function f, int arg) throws SQLException { + return call("sqlite3_value_type", value(f, arg)); + } + + private int value(Function f, int arg) throws SQLException { + return deref((int) f.value + (arg * 4)); + } + + @Override + synchronized int create_function(String name, Function func) throws SQLException { + if (functions == null) { + functions = new Function[10]; + funcNames = new String[10]; + } + + // find a position + int pos; + for (pos = 0; pos < functions.length; pos++) + if (functions[pos] == null) + break; + + if (pos == functions.length) { // expand function arrays + Function[] fnew = new Function[functions.length * 2]; + String[] nnew = new String[funcNames.length * 2]; + System.arraycopy(functions, 0, fnew, 0, functions.length); + System.arraycopy(funcNames, 0, nnew, 0, funcNames.length); + functions = fnew; + funcNames = nnew; + } + + // register function + functions[pos] = func; + funcNames[pos] = name; + int rc; + int str = rt.strdup(name); + rc = call("create_function_helper", handle, str, pos, func instanceof Function.Aggregate ? 1 : 0); + rt.free(str); + return rc; + } + + @Override + synchronized int destroy_function(String name) throws SQLException { + if (name == null) + return 0; + + // find function position number + int pos; + for (pos = 0; pos < funcNames.length; pos++) + if (name.equals(funcNames[pos])) + break; + if (pos == funcNames.length) + return 0; + + functions[pos] = null; + funcNames[pos] = null; + + // deregister function + int rc; + int str = rt.strdup(name); + rc = call("create_function_helper", handle, str, -1, 0); + rt.free(str); + return rc; + } + + /* unused as we use the user_data pointer to store a single word */ + @Override + synchronized void free_functions() {} + + /** Callback used by xFunc (1), xStep (2) and xFinal (3). */ + synchronized void xUDF(int xType, int context, int args, int value) { + Function func = null; + + try { + int pos = call("sqlite3_user_data", context); + func = functions[pos]; + if (func == null) + throw new SQLException("function state inconsistent"); + + func.context = context; + func.value = value; + func.args = args; + + switch (xType) { + case 1: + func.xFunc(); + break; + case 2: + ((Function.Aggregate) func).xStep(); + break; + case 3: + ((Function.Aggregate) func).xFinal(); + break; + } + } + catch (SQLException e) { + try { + String err = e.toString(); + if (err == null) + err = "unknown error"; + int str = rt.strdup(err); + call("sqlite3_result_error", context, str, -1); + rt.free(str); + } + catch (SQLException exp) { + exp.printStackTrace();//TODO + } + } + finally { + if (func != null) { + func.context = 0; + func.value = 0; + func.args = 0; + } + } + } + + /** Calls support function found in upstream/sqlite-metadata.patch */ + @Override + synchronized boolean[][] column_metadata(long stmt) throws SQLException { + int colCount = call("sqlite3_column_count", (int) stmt); + boolean[][] meta = new boolean[colCount][3]; + int pass; + + pass = rt.xmalloc(12); // struct metadata + + for (int i = 0; i < colCount; i++) { + call("column_metadata_helper", handle, (int) stmt, i, pass); + meta[i][0] = deref(pass) == 1; + meta[i][1] = deref(pass + 4) == 1; + meta[i][2] = deref(pass + 8) == 1; + } + + rt.free(pass); + return meta; + } + + @Override + int backup(String dbName, String destFileName, ProgressObserver observer) throws SQLException { + throw new SQLException("backup command is not supported in pure-java mode"); + } + + @Override + int restore(String dbName, String sourceFileName, ProgressObserver observer) throws SQLException { + throw new SQLException("restore command is not supported in pure-java mode"); + } + + // HELPER FUNCTIONS ///////////////////////////////////////////// + + /** safe to reuse parameter arrays as all functions are syncrhonized */ + private final int[] p0 = new int[] {}, p1 = new int[] { 0 }, p2 = new int[] { 0, 0 }, p3 = new int[] { 0, 0, 0 }, + p4 = new int[] { 0, 0, 0, 0 }, p5 = new int[] { 0, 0, 0, 0, 0 }; + + private int call(String addr, int a0) throws SQLException { + p1[0] = a0; + return call(addr, p1); + } + + private int call(String addr, int a0, int a1) throws SQLException { + p2[0] = a0; + p2[1] = a1; + return call(addr, p2); + } + + private int call(String addr, int a0, int a1, int a2) throws SQLException { + p3[0] = a0; + p3[1] = a1; + p3[2] = a2; + return call(addr, p3); + } + + private int call(String addr, int a0, int a1, int a2, int a3) throws SQLException { + p4[0] = a0; + p4[1] = a1; + p4[2] = a2; + p4[3] = a3; + return call(addr, p4); + } + + private int call(String addr, int a0, int a1, int a2, int a3, int a4) throws SQLException { + p5[0] = a0; + p5[1] = a1; + p5[2] = a2; + p5[3] = a3; + p5[4] = a4; + return call(addr, p5); + } + + private int call(String func, int[] args) throws SQLException { + try { + return rt.call(func, args); + } + catch (Runtime.CallException e) { + throw new CausedSQLException(e); + } + } + + /** Dereferences a pointer, returning the word it points to. */ + private int deref(int pointer) throws SQLException { + try { + return rt.memRead(pointer); + } + catch (Runtime.ReadFaultException e) { + throw new CausedSQLException(e); + } + } + + private String utfstring(int str) throws SQLException { + try { + return rt.utfstring(str); + } + catch (Runtime.ReadFaultException e) { + throw new CausedSQLException(e); + } + } + + private String cstring(int str) throws SQLException { + try { + return rt.cstring(str); + } + catch (Runtime.ReadFaultException e) { + throw new CausedSQLException(e); + } + } + + private void copyin(int addr, byte[] buf, int count) throws SQLException { + try { + rt.copyin(addr, buf, count); + } + catch (Runtime.ReadFaultException e) { + throw new CausedSQLException(e); + } + } + + private void copyout(byte[] buf, int addr, int count) throws SQLException { + try { + rt.copyout(buf, addr, count); + } + catch (Runtime.FaultException e) { + throw new CausedSQLException(e); + } + } + + /** Maps any exception onto an SQLException. */ + private static final class CausedSQLException extends SQLException + { + private final Exception cause; + + CausedSQLException(Exception e) { + if (e == null) + throw new RuntimeException("null exception cause"); + cause = e; + } + + @Override + public Throwable getCause() { + return cause; + } + + @Override + public void printStackTrace() { + cause.printStackTrace(); + } + + @Override + public void printStackTrace(PrintWriter s) { + cause.printStackTrace(s); + } + + @Override + public Throwable fillInStackTrace() { + return cause.fillInStackTrace(); + } + + @Override + public StackTraceElement[] getStackTrace() { + return cause.getStackTrace(); + } + + @Override + public String getMessage() { + return cause.getMessage(); + } + } +} diff --git a/libs/org/sqlite/OSInfo.java b/libs/org/sqlite/OSInfo.java new file mode 100644 index 0000000..e71c294 --- /dev/null +++ b/libs/org/sqlite/OSInfo.java @@ -0,0 +1,80 @@ +/*-------------------------------------------------------------------------- + * Copyright 2008 Taro L. Saito + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *--------------------------------------------------------------------------*/ +//-------------------------------------- +// sqlite-jdbc Project +// +// OSInfo.java +// Since: May 20, 2008 +// +// $URL$ +// $Author$ +//-------------------------------------- +package org.sqlite; + +/** + * Provides OS name and architecture name. + * + * @author leo + * + */ +public class OSInfo +{ + public static void main(String[] args) { + if (args.length >= 1) { + if ("--os".equals(args[0])) { + System.out.print(getOSName()); + return; + } + else if ("--arch".equals(args[0])) { + System.out.print(getArchName()); + return; + } + } + + System.out.print(getNativeLibFolderPathForCurrentOS()); + } + + public static String getNativeLibFolderPathForCurrentOS() { + return getOSName() + "/" + getArchName(); + } + + public static String getOSName() { + return translateOSNameToFolderName(System.getProperty("os.name")); + } + + public static String getArchName() { + return translateArchNameToFolderName(System.getProperty("os.arch")); + } + + public static String translateOSNameToFolderName(String osName) { + if (osName.contains("Windows")) { + return "Windows"; + } + else if (osName.contains("Mac")) { + return "Mac"; + } + else if (osName.contains("Linux")) { + return "Linux"; + } + else { + return osName.replaceAll("\\W", ""); + } + } + + public static String translateArchNameToFolderName(String archName) { + return archName.replaceAll("\\W", ""); + } +} diff --git a/libs/org/sqlite/PrepStmt.java b/libs/org/sqlite/PrepStmt.java new file mode 100644 index 0000000..f9adec5 --- /dev/null +++ b/libs/org/sqlite/PrepStmt.java @@ -0,0 +1,330 @@ +/* + * 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.IOException; +import java.io.Reader; +import java.sql.Date; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +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(); + } + + @Override + 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); + } + + @Override + public int[] executeBatch() throws SQLException { + if (batchPos == 0) + return new int[] {}; + try { + return db.executeBatch(pointer, batchPos / paramCount, batch); + } + finally { + clearBatch(); + } + } + + @Override + 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, 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 { + batch(pos, new Float(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 Short) + batch(pos, new Integer(((Short) value).intValue())); + else if (value instanceof Float) + batch(pos, value); + else if (value instanceof Double) + batch(pos, value); + else if (value instanceof Boolean) + setBoolean(pos, ((Boolean) value).booleanValue()); + else if (value instanceof byte[]) + 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, value); + } + + public void setString(int pos, String value) throws SQLException { + batch(pos, value); + } + + public void setCharacterStream(int pos, Reader reader, int length) throws SQLException { + try { + // copy chars from reader to StringBuffer + StringBuffer sb = new StringBuffer(); + char[] cbuf = new char[8192]; + int cnt; + + while ((cnt = reader.read(cbuf)) > 0) { + sb.append(cbuf, 0, cnt); + } + + // set as string + setString(pos, sb.toString()); + } + catch (IOException e) { + throw new SQLException("Cannot read from character stream, exception message: " + e.getMessage()); + } + } + + 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 /////////////////////////////////////////////////////// + + @Override + public boolean execute(String sql) throws SQLException { + throw unused(); + } + + @Override + public int executeUpdate(String sql) throws SQLException { + throw unused(); + } + + @Override + public ResultSet executeQuery(String sql) throws SQLException { + throw unused(); + } + + @Override + public void addBatch(String sql) throws SQLException { + throw unused(); + } + + private SQLException unused() { + return new SQLException("not supported by PreparedStatment"); + } +} diff --git a/libs/org/sqlite/RS.java b/libs/org/sqlite/RS.java new file mode 100644 index 0000000..9700949 --- /dev/null +++ b/libs/org/sqlite/RS.java @@ -0,0 +1,540 @@ +/* + * 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.StringReader; +import java.sql.Date; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.Calendar; + +/** + * 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 = 0; // number of current row, starts at 1 (0 is for before loading data) + 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 = 0; + 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 == 0) { + row++; + return true; + } + + // check if we are row limited by the statement or the ResultSet + if (maxRows != 0 && row > maxRows) + return false; + + // do the real work + int statusCode = db.step(stmt.pointer); + switch (statusCode) { + case SQLITE_DONE: + close(); // agressive closing to avoid writer starvation + return false; + case SQLITE_ROW: + row++; + return true; + case SQLITE_BUSY: + default: + db.throwex(statusCode); + 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 == 0; + } + + public boolean isFirst() throws SQLException { + return row == 1; + } + + public boolean isLast() throws SQLException { // FIXME + throw new SQLException("function not yet implemented for SQLite"); + } + + @Override + 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 Reader getCharacterStream(int col) throws SQLException { + return new StringReader(getString(col)); + } + + public Reader getCharacterStream(String col) throws SQLException { + return getCharacterStream(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 > Integer.MAX_VALUE || val < 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/libs/org/sqlite/ResourceFinder.java b/libs/org/sqlite/ResourceFinder.java new file mode 100644 index 0000000..0ee3c7e --- /dev/null +++ b/libs/org/sqlite/ResourceFinder.java @@ -0,0 +1,102 @@ +/*-------------------------------------------------------------------------- + * Copyright 2009 Taro L. Saito + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *--------------------------------------------------------------------------*/ +//-------------------------------------- +// sqlite-jdbc Project +// +// ResourceFinder.java +// Since: Apr 28, 2009 +// +// $URL$ +// $Author$ +//-------------------------------------- +package org.sqlite; + +import java.net.URL; + +/** + * Resource address finder for files inside the jar file + * + * @author leo + * + */ +public class ResourceFinder +{ + /** + * Gets the {@link URL} of the file resource + * + * @param referenceClass + * the base class for finding resources files. This method will + * search the package containing the given referenceClass. + * @param resourceFileName + * the resource file name relative to the package of the + * referenceClass + * @return the URL of the file resource + */ + public static URL find(Class< ? > referenceClass, String resourceFileName) + { + return find(referenceClass.getClassLoader(), referenceClass.getPackage(), resourceFileName); + } + + /** + * Finds the {@link URL} of the resource + * + * @param basePackage + * the base package to find the resource + * @param resourceFileName + * the resource file name relative to the package folder + * @return the URL of the specified resource + */ + public static URL find(ClassLoader classLoader, Package basePackage, String resourceFileName) + { + return find(classLoader, basePackage.getName(), resourceFileName); + } + + /** + * Finds the {@link URL} of the resource + * + * @param packageName + * the base package name to find the resource + * @param resourceFileName + * the resource file name relative to the package folder + * @return the URL of the specified resource + */ + public static URL find(ClassLoader classLoader, String packageName, String resourceFileName) + { + String packagePath = packagePath(packageName); + String resourcePath = packagePath + resourceFileName; + if (!resourcePath.startsWith("/")) + resourcePath = "/" + resourcePath; + + return classLoader.getResource(resourcePath); + } + + private static String packagePath(Class< ? > referenceClass) + { + return packagePath(referenceClass.getPackage()); + } + + private static String packagePath(Package basePackage) + { + return packagePath(basePackage.getName()); + } + + private static String packagePath(String packageName) + { + String packageAsPath = packageName.replaceAll("\\.", "/"); + return packageAsPath.endsWith("/") ? packageAsPath : packageAsPath + "/"; + } + +} diff --git a/libs/org/sqlite/SQLiteConfig.java b/libs/org/sqlite/SQLiteConfig.java new file mode 100644 index 0000000..64d717a --- /dev/null +++ b/libs/org/sqlite/SQLiteConfig.java @@ -0,0 +1,449 @@ +/*-------------------------------------------------------------------------- + * Copyright 2009 Taro L. Saito + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *--------------------------------------------------------------------------*/ +//-------------------------------------- +// sqlite-jdbc Project +// +// SQLiteConfig.java +// Since: Dec 8, 2009 +// +// $URL$ +// $Author$ +//-------------------------------------- +package org.sqlite; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashSet; +import java.util.Properties; + +/** + * SQLite Configuration + * + * See also http://www.sqlite.org/pragma.html + * + * @author leo + * + */ +public class SQLiteConfig +{ + private final Properties pragmaTable; + private int openModeFlag = 0x00; + + public SQLiteConfig() { + this(new Properties()); + } + + public SQLiteConfig(Properties prop) { + this.pragmaTable = prop; + String openMode = pragmaTable.getProperty(Pragma.OPEN_MODE.pragmaName); + if (openMode != null) { + openModeFlag = Integer.parseInt(openMode); + } + else { + // set the default open mode of SQLite3 + setOpenMode(SQLiteOpenMode.READWRITE); + setOpenMode(SQLiteOpenMode.CREATE); + } + } + + /** + * Create a new JDBC connection using the current configuration + * + * @return + * @throws SQLException + */ + public Connection createConnection(String url) throws SQLException { + return JDBC.createConnection(url, toProperties()); + } + + /** + * Apply the current configuration to connection + * + * @param conn + * @throws SQLException + */ + public void apply(Connection conn) throws SQLException { + + HashSet pragmaParams = new HashSet(); + for (Pragma each : Pragma.values()) { + pragmaParams.add(each.pragmaName); + } + + pragmaParams.remove(Pragma.OPEN_MODE.pragmaName); + pragmaParams.remove(Pragma.SHARED_CACHE.pragmaName); + pragmaParams.remove(Pragma.LOAD_EXTENSION.pragmaName); + + Statement stat = conn.createStatement(); + try { + int count = 0; + for (Object each : pragmaTable.keySet()) { + String key = each.toString(); + if (!pragmaParams.contains(key)) + continue; + + String value = pragmaTable.getProperty(key); + if (value != null) { + String sql = String.format("pragma %s=%s", key, value); + stat.addBatch(sql); + count++; + } + } + if (count > 0) + stat.executeBatch(); + } + finally { + if (stat != null) + stat.close(); + } + + } + + private void set(Pragma pragma, boolean flag) { + setPragma(pragma, Boolean.toString(flag)); + } + + private void set(Pragma pragma, int num) { + setPragma(pragma, Integer.toString(num)); + } + + private boolean getBoolean(Pragma pragma, String defaultValue) { + return Boolean.parseBoolean(pragmaTable.getProperty(pragma.pragmaName, defaultValue)); + } + + public boolean isEnabledSharedCache() { + return getBoolean(Pragma.SHARED_CACHE, "false"); + } + + public boolean isEnabledLoadExtension() { + return getBoolean(Pragma.LOAD_EXTENSION, "false"); + } + + public int getOpenModeFlags() { + return openModeFlag; + } + + /** + * Set a pragma value. To take effect the pragma settings, + * + * @param pragma + * @param value + */ + public void setPragma(Pragma pragma, String value) { + pragmaTable.put(pragma.pragmaName, value); + } + + /** + * Convert this SQLiteConfig settings into a Properties object, that can be + * passed to the {@link DriverManager#getConnection(String, Properties)}. + * + * @return properties representation of this configuration + */ + public Properties toProperties() { + pragmaTable.setProperty(Pragma.OPEN_MODE.pragmaName, Integer.toString(openModeFlag)); + + return pragmaTable; + } + + static DriverPropertyInfo[] getDriverPropertyInfo() { + Pragma[] pragma = Pragma.values(); + DriverPropertyInfo[] result = new DriverPropertyInfo[pragma.length]; + int index = 0; + for (Pragma p : Pragma.values()) { + DriverPropertyInfo di = new DriverPropertyInfo(p.pragmaName, null); + di.choices = p.choices; + di.description = p.description; + di.required = false; + result[index++] = di; + } + + return result; + } + + private static final String[] OnOff = new String[] { "true", "false" }; + + private static enum Pragma { + + // Parameters requiring SQLite3 API invocation + OPEN_MODE("open_mode", "Database open-mode flag", null), + SHARED_CACHE("shared_cache", "Enablse SQLite Shared-Cache mode, native driver only", OnOff), + LOAD_EXTENSION("enable_load_extension", "Enable SQLite load_extention() function, native driver only", OnOff), + + // Pragmas that can be set after opening the database + CACHE_SIZE("cache_size"), + CASE_SENSITIVE_LIKE("case_sensitive_like", OnOff), + COUNT_CHANGES("count_changes", OnOff), + DEFAULT_CACHE_SIZE("default_cache_size"), + EMPTY_RESULT_CALLBACKS("empty_result_callback", OnOff), + ENCODING("encoding", toStringArray(Encoding.values())), + FOREIGN_KEYS("foreign_keys", OnOff), + FULL_COLUMN_NAMES("full_column_names", OnOff), + FULL_SYNC("fullsync", OnOff), + INCREMENTAL_VACUUM("incremental_vacuum"), + JOURNAL_MODE("journal_mode", toStringArray(JournalMode.values())), + JOURNAL_SIZE_LIMIT("journal_size_limit"), + LEGACY_FILE_FORMAT("legacy_file_format", OnOff), + LOCKING_MODE("locking_mode", toStringArray(LockingMode.values())), + PAGE_SIZE("page_size"), + MAX_PAGE_COUNT("max_page_count"), + READ_UNCOMMITED("read_uncommited", OnOff), + RECURSIVE_TRIGGERS("recursive_triggers", OnOff), + REVERSE_UNORDERED_SELECTS("reverse_unordered_selects", OnOff), + SHORT_COLUMN_NAMES("short_column_names", OnOff), + SYNCHRONOUS("synchronous", toStringArray(SynchronousMode.values())), + TEMP_STORE("temp_store", toStringArray(TempStore.values())), + TEMP_STORE_DIRECTORY("temp_store_directory"), + USER_VERSION("user_version"); + + public final String pragmaName; + public final String[] choices; + public final String description; + + private Pragma(String pragmaName) { + this(pragmaName, null); + } + + private Pragma(String pragmaName, String[] choices) { + this(pragmaName, null, null); + } + + private Pragma(String pragmaName, String description, String[] choices) { + this.pragmaName = pragmaName; + this.description = description; + this.choices = choices; + } + } + + /** + * Set the database open mode + * + * @param mode + */ + public void setOpenMode(SQLiteOpenMode mode) { + openModeFlag |= mode.flag; + } + + /** + * Reset the specified database open mode flag + * + * @param mode + */ + public void resetOpenMode(SQLiteOpenMode mode) { + openModeFlag &= ~mode.flag; + } + + public void setSharedCache(boolean enable) { + set(Pragma.SHARED_CACHE, enable); + } + + public void enableLoadExtension(boolean enable) { + set(Pragma.LOAD_EXTENSION, enable); + } + + public void setReadOnly(boolean readOnly) { + if (readOnly) { + setOpenMode(SQLiteOpenMode.READONLY); + resetOpenMode(SQLiteOpenMode.READWRITE); + } + else { + setOpenMode(SQLiteOpenMode.READWRITE); + resetOpenMode(SQLiteOpenMode.READONLY); + } + } + + public void setCacheSize(int numberOfPages) { + set(Pragma.CACHE_SIZE, numberOfPages); + } + + public void enableCaseSensitiveLike(boolean enable) { + set(Pragma.CASE_SENSITIVE_LIKE, enable); + } + + public void enableCountChanges(boolean enable) { + set(Pragma.COUNT_CHANGES, enable); + } + + /** + * Set the cache size persistently across database connections + * + * @param numberOfPages + */ + public void setDefaultCacheSize(int numberOfPages) { + set(Pragma.DEFAULT_CACHE_SIZE, numberOfPages); + } + + public void enableEmptyResultCallBacks(boolean enable) { + set(Pragma.EMPTY_RESULT_CALLBACKS, enable); + } + + /** + * The common interface for retrieving the available pragma parameter + * values. + * + * @author leo + * + */ + private static interface PragmaValue + { + public String getValue(); + } + + /** + * Convert the given Enum values to a string array + * + * @param list + * @return + */ + private static String[] toStringArray(PragmaValue[] list) { + String[] result = new String[list.length]; + for (int i = 0; i < list.length; i++) { + result[i] = list[i].getValue(); + } + return result; + } + + public static enum Encoding implements PragmaValue { + UTF8("UTF-8"), UTF16("UTF-16"), UTF16_LITTLE_ENDIAN("UTF-16le"), UTF16_BIG_ENDIAN("UTF-16be"); + public final String typeName; + + private Encoding(String typeName) { + this.typeName = typeName; + } + + public String getValue() { + return typeName; + } + } + + public static enum JournalMode implements PragmaValue { + DELETE, TRUNCATE, PERSIST, MEMORY, OFF; + + public String getValue() { + return name(); + } + } + + public void setEncoding(Encoding encoding) { + setPragma(Pragma.ENCODING, encoding.typeName); + } + + public void enforceForeignKeys(boolean enforce) { + set(Pragma.FOREIGN_KEYS, enforce); + } + + public void enableFullColumnNames(boolean enable) { + set(Pragma.FULL_COLUMN_NAMES, enable); + } + + public void enableFullSync(boolean enable) { + set(Pragma.FULL_SYNC, enable); + } + + public void incrementalVacuum(int numberOfPagesToBeRemoved) { + set(Pragma.INCREMENTAL_VACUUM, numberOfPagesToBeRemoved); + } + + public void setJournalMode(JournalMode mode) { + setPragma(Pragma.JOURNAL_MODE, mode.name()); + } + + // public void setJournalMode(String databaseName, JournalMode mode) { + // setPragma(databaseName, Pragma.JOURNAL_MODE, mode.name()); + // } + + public void setJounalSizeLimit(int limit) { + set(Pragma.JOURNAL_SIZE_LIMIT, limit); + } + + public void useLegacyFileFormat(boolean use) { + set(Pragma.LEGACY_FILE_FORMAT, use); + } + + public static enum LockingMode implements PragmaValue { + NORMAL, EXCLUSIVE; + public String getValue() { + return name(); + } + } + + public void setLockingMode(LockingMode mode) { + setPragma(Pragma.LOCKING_MODE, mode.name()); + } + + // public void setLockingMode(String databaseName, LockingMode mode) { + // setPragma(databaseName, Pragma.LOCKING_MODE, mode.name()); + // } + + public void setPageSize(int numBytes) { + set(Pragma.PAGE_SIZE, numBytes); + } + + public void setMaxPageCount(int numPages) { + set(Pragma.MAX_PAGE_COUNT, numPages); + } + + public void setReadUncommited(boolean useReadUncommitedIsolationMode) { + set(Pragma.READ_UNCOMMITED, useReadUncommitedIsolationMode); + } + + public void enableRecursiveTriggers(boolean enable) { + set(Pragma.RECURSIVE_TRIGGERS, enable); + } + + public void enableReverseUnorderedSelects(boolean enable) { + set(Pragma.REVERSE_UNORDERED_SELECTS, enable); + } + + public void enableShortColumnNames(boolean enable) { + set(Pragma.SHORT_COLUMN_NAMES, enable); + } + + public static enum SynchronousMode implements PragmaValue { + OFF, NORMAL, FULL; + + public String getValue() { + return name(); + } + } + + public void setSynchronous(SynchronousMode mode) { + setPragma(Pragma.SYNCHRONOUS, mode.name()); + } + + public static enum TempStore implements PragmaValue { + DEFAULT, FILE, MEMORY; + + public String getValue() { + return name(); + } + + } + + public void setTempStore(TempStore storeType) { + setPragma(Pragma.TEMP_STORE, storeType.name()); + } + + public void setTempStoreDirectory(String directoryName) { + setPragma(Pragma.TEMP_STORE_DIRECTORY, String.format("'%s'", directoryName)); + } + + public void setUserVersion(int version) { + set(Pragma.USER_VERSION, version); + } + +} diff --git a/libs/org/sqlite/SQLiteDataSource.java b/libs/org/sqlite/SQLiteDataSource.java new file mode 100644 index 0000000..fb06a1b --- /dev/null +++ b/libs/org/sqlite/SQLiteDataSource.java @@ -0,0 +1,228 @@ +/*-------------------------------------------------------------------------- + * Copyright 2010 Taro L. Saito + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *--------------------------------------------------------------------------*/ +//-------------------------------------- +// sqlite-jdbc Project +// +// SQLiteDataSource.java +// Since: Mar 11, 2010 +// +// $URL$ +// $Author$ +//-------------------------------------- +package org.sqlite; + +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +import javax.sql.DataSource; + +import org.sqlite.SQLiteConfig.Encoding; +import org.sqlite.SQLiteConfig.JournalMode; +import org.sqlite.SQLiteConfig.LockingMode; +import org.sqlite.SQLiteConfig.SynchronousMode; +import org.sqlite.SQLiteConfig.TempStore; + +/** + * Provides {@link DataSource} API for configuring SQLite database connection + * + * @author leo + * + */ +public class SQLiteDataSource implements DataSource +{ + private SQLiteConfig config; + private transient PrintWriter logger; + private int loginTimeout = 1; + + private String url = JDBC.PREFIX; // use memory database in default + + public SQLiteDataSource() { + this.config = new SQLiteConfig(); // default configuration + } + + public SQLiteDataSource(SQLiteConfig config) { + this.config = config; + } + + /** + * Set the configuration parameters via {@link SQLiteConfig} object + * + * @param config + */ + public void setConfig(SQLiteConfig config) { + this.config = config; + } + + public SQLiteConfig getConfig() { + return config; + } + + // configuration parameters + public void setUrl(String url) { + this.url = url; + } + + public String getUrl() { + return url; + } + + public void setSharedCache(boolean enable) { + config.setSharedCache(enable); + } + + public void setLoadExtension(boolean enable) { + config.enableLoadExtension(enable); + } + + public void setReadOnly(boolean readOnly) { + config.setReadOnly(readOnly); + } + + public void setCacheSize(int numberOfPages) { + config.setCacheSize(numberOfPages); + } + + public void setCaseSensitiveLike(boolean enable) { + config.enableCaseSensitiveLike(enable); + } + + public void setCouncChanges(boolean enable) { + config.enableCountChanges(enable); + } + + public void setDefaultCacheSize(int numberOfPages) { + config.setDefaultCacheSize(numberOfPages); + } + + public void setEncoding(String encoding) { + config.setEncoding(Encoding.valueOf(encoding)); + } + + public void setEnforceForeinKeys(boolean enforce) { + config.enforceForeignKeys(enforce); + } + + public void setFullColumnNames(boolean enable) { + config.enableFullColumnNames(enable); + } + + public void setFullSync(boolean enable) { + config.enableFullSync(enable); + } + + public void setIncrementalVacuum(int numberOfPagesToBeRemoved) { + config.incrementalVacuum(numberOfPagesToBeRemoved); + } + + public void setJournalMode(String mode) { + config.setJournalMode(JournalMode.valueOf(mode)); + } + + public void setJournalSizeLimit(int limit) { + config.setJounalSizeLimit(limit); + } + + public void setLegacyFileFormat(boolean use) { + config.useLegacyFileFormat(use); + } + + public void setLockingMode(String mode) { + config.setLockingMode(LockingMode.valueOf(mode)); + } + + public void setPageSize(int numBytes) { + config.setPageSize(numBytes); + } + + public void setMaxPageCount(int numPages) { + config.setMaxPageCount(numPages); + } + + public void setReadUncommited(boolean useReadUncommitedIsolationMode) { + config.setReadUncommited(useReadUncommitedIsolationMode); + } + + public void setRecursiveTriggers(boolean enable) { + config.enableRecursiveTriggers(enable); + } + + public void setReverseUnorderedSelects(boolean enable) { + config.enableReverseUnorderedSelects(enable); + } + + public void setShortColumnNames(boolean enable) { + config.enableShortColumnNames(enable); + } + + public void setSynchronous(String mode) { + config.setSynchronous(SynchronousMode.valueOf(mode)); + } + + public void setTempStore(String storeType) { + config.setTempStore(TempStore.valueOf(storeType)); + } + + public void setTempStoreDirectory(String directoryName) { + config.setTempStoreDirectory(directoryName); + } + + public void setUserVersion(int version) { + config.setUserVersion(version); + } + + // codes for the DataSource interface + + public Connection getConnection() throws SQLException { + return getConnection(null, null); + } + + public Connection getConnection(String username, String password) throws SQLException { + Properties p = config.toProperties(); + if (username != null) + p.put("user", username); + if (password != null) + p.put("pass", password); + return JDBC.createConnection(url, p); + } + + public PrintWriter getLogWriter() throws SQLException { + return logger; + } + + public int getLoginTimeout() throws SQLException { + return loginTimeout; + } + + public void setLogWriter(PrintWriter out) throws SQLException { + this.logger = out; + } + + public void setLoginTimeout(int seconds) throws SQLException { + loginTimeout = seconds; + } + + public boolean isWrapperFor(Class< ? > iface) throws SQLException { + return iface.isInstance(this); + } + + @SuppressWarnings("unchecked") + public T unwrap(Class iface) throws SQLException { + return (T) this; + } + +} diff --git a/libs/org/sqlite/SQLiteErrorCode.java b/libs/org/sqlite/SQLiteErrorCode.java new file mode 100644 index 0000000..2028bbe --- /dev/null +++ b/libs/org/sqlite/SQLiteErrorCode.java @@ -0,0 +1,92 @@ +/*-------------------------------------------------------------------------- + * Copyright 2009 Taro L. Saito + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *--------------------------------------------------------------------------*/ +//-------------------------------------- +// sqlite-jdbc Project +// +// SQLiteErrorCode.java +// Since: Apr 21, 2009 +// +// $URL$ +// $Author$ +//-------------------------------------- +package org.sqlite; + +/** + * SQLite3 error code + * + * @author leo + * @see http://www.sqlite.org/c3ref/c_abort.html + * + */ +public enum SQLiteErrorCode { + + UNKNOWN_ERROR(-1, "unknown error"), + SQLITE_OK(0, "Successful result"), + /* beginning-of-error-codes */ + SQLITE_ERROR(1, "SQL error or missing database"), + SQLITE_INTERNAL(2, "Internal logic error in SQLite"), + SQLITE_PERM(3, " Access permission denied"), + SQLITE_ABORT(4, " Callback routine requested an abort"), + SQLITE_BUSY(5, " The database file is locked"), + SQLITE_LOCKED(6, " A table in the database is locked"), + SQLITE_NOMEM(7, " A malloc() failed"), + SQLITE_READONLY(8, " Attempt to write a readonly database"), + SQLITE_INTERRUPT(9, " Operation terminated by sqlite3_interrupt()"), + SQLITE_IOERR(10, " Some kind of disk I/O error occurred"), + SQLITE_CORRUPT(11, " The database disk image is malformed"), + SQLITE_NOTFOUND(12, " NOT USED. Table or record not found"), + SQLITE_FULL(13, " Insertion failed because database is full"), + SQLITE_CANTOPEN(14, " Unable to open the database file"), + SQLITE_PROTOCOL(15, " NOT USED. Database lock protocol error"), + SQLITE_EMPTY(16, " Database is empty"), + SQLITE_SCHEMA(17, " The database schema changed"), + SQLITE_TOOBIG(18, " String or BLOB exceeds size limit"), + SQLITE_CONSTRAINT(19, " Abort due to constraint violation"), + SQLITE_MISMATCH(20, " Data type mismatch"), + SQLITE_MISUSE(21, " Library used incorrectly"), + SQLITE_NOLFS(22, " Uses OS features not supported on host"), + SQLITE_AUTH(23, " Authorization denied"), + SQLITE_FORMAT(24, " Auxiliary database format error"), + SQLITE_RANGE(25, " 2nd parameter to sqlite3_bind out of range"), + SQLITE_NOTADB(26, " File opened that is not a database file"), + SQLITE_ROW(100, " sqlite3_step() has another row ready"), + SQLITE_DONE(101, " sqlite3_step() has finished executing"); + + public final int code; + public final String message; + + private SQLiteErrorCode(int code, String message) + { + this.code = code; + this.message = message; + } + + public static SQLiteErrorCode getErrorCode(int errorCode) + { + for (SQLiteErrorCode each : SQLiteErrorCode.values()) + { + if (errorCode == each.code) + return each; + } + return UNKNOWN_ERROR; + } + + @Override + public String toString() + { + return String.format("[%s] %s", this.name(), message); + } +} diff --git a/libs/org/sqlite/SQLiteJDBCLoader.java b/libs/org/sqlite/SQLiteJDBCLoader.java new file mode 100644 index 0000000..4ca0a2f --- /dev/null +++ b/libs/org/sqlite/SQLiteJDBCLoader.java @@ -0,0 +1,271 @@ +/*-------------------------------------------------------------------------- + * Copyright 2007 Taro L. Saito + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *--------------------------------------------------------------------------*/ +//-------------------------------------- +// SQLite JDBC Project +// +// SQLite.java +// Since: 2007/05/10 +// +// $URL$ +// $Author$ +//-------------------------------------- +package org.sqlite; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Properties; + +/** + * Set the system properties, org.sqlite.lib.path, org.sqlite.lib.name, + * appropriately so that the SQLite JDBC driver can find *.dll, *.jnilib and + * *.so files, according to the current OS (win, linux, mac). + * + * The library files are automatically extracted from this project's package + * (JAR). + * + * usage: call {@link #initialize()} before using SQLite JDBC driver. + * + * @author leo + * + */ +public class SQLiteJDBCLoader +{ + + private static boolean extracted = false; + + public static boolean initialize() { + loadSQLiteNativeLibrary(); + return extracted; + } + + static boolean getPureJavaFlag() { + return Boolean.parseBoolean(System.getProperty("sqlite.purejava", "false")); + } + + public static boolean isPureJavaMode() { + return !isNativeMode(); + } + + public static boolean isNativeMode() { + if (getPureJavaFlag()) + return false; + + // load the driver + initialize(); + return extracted; + } + + /** + * Computes the MD5 value of the input stream + * + * @param input + * @return + * @throws IOException + * @throws NoSuchAlgorithmException + */ + static String md5sum(InputStream input) throws IOException { + BufferedInputStream in = new BufferedInputStream(input); + + try { + MessageDigest digest = java.security.MessageDigest.getInstance("MD5"); + DigestInputStream digestInputStream = new DigestInputStream(in, digest); + for (; digestInputStream.read() >= 0;) { + + } + ByteArrayOutputStream md5out = new ByteArrayOutputStream(); + md5out.write(digest.digest()); + return md5out.toString(); + } + catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("MD5 algorithm is not available: " + e); + } + finally { + in.close(); + } + } + + /** + * Extract the specified library file to the target folder + * + * @param libFolderForCurrentOS + * @param libraryFileName + * @param targetFolder + * @return + */ + private static boolean extractAndLoadLibraryFile(String libFolderForCurrentOS, String libraryFileName, + String targetFolder) { + String nativeLibraryFilePath = libFolderForCurrentOS + "/" + libraryFileName; + final String prefix = "sqlite-" + getVersion() + "-"; + + String extractedLibFileName = prefix + libraryFileName; + File extractedLibFile = new File(targetFolder, extractedLibFileName); + + try { + if (extractedLibFile.exists()) { + // test md5sum value + String md5sum1 = md5sum(SQLiteJDBCLoader.class.getResourceAsStream(nativeLibraryFilePath)); + String md5sum2 = md5sum(new FileInputStream(extractedLibFile)); + + if (md5sum1.equals(md5sum2)) { + return loadNativeLibrary(targetFolder, extractedLibFileName); + } + else { + // remove old native library file + boolean deletionSucceeded = extractedLibFile.delete(); + if (!deletionSucceeded) { + throw new IOException("failed to remove existing native library file: " + + extractedLibFile.getAbsolutePath()); + } + } + } + + // extract file into the current directory + InputStream reader = SQLiteJDBCLoader.class.getResourceAsStream(nativeLibraryFilePath); + FileOutputStream writer = new FileOutputStream(extractedLibFile); + byte[] buffer = new byte[1024]; + int bytesRead = 0; + while ((bytesRead = reader.read(buffer)) != -1) { + writer.write(buffer, 0, bytesRead); + } + + writer.close(); + reader.close(); + + if (!System.getProperty("os.name").contains("Windows")) { + try { + Runtime.getRuntime().exec(new String[] { "chmod", "755", extractedLibFile.getAbsolutePath() }) + .waitFor(); + } + catch (Throwable e) {} + } + + return loadNativeLibrary(targetFolder, extractedLibFileName); + } + catch (IOException e) { + System.err.println(e.getMessage()); + return false; + } + + } + + private static synchronized boolean loadNativeLibrary(String path, String name) { + File libPath = new File(path, name); + if (libPath.exists()) { + + try { + System.load(new File(path, name).getAbsolutePath()); + return true; + } + catch (UnsatisfiedLinkError e) { + System.err.println(e); + return false; + } + + } + else + return false; + } + + private static void loadSQLiteNativeLibrary() { + if (extracted) + return; + + boolean runInPureJavaMode = getPureJavaFlag(); + if (runInPureJavaMode) { + extracted = false; + return; + } + + // Try loading library from org.sqlite.lib.path library path */ + String sqliteNativeLibraryPath = System.getProperty("org.sqlite.lib.path"); + String sqliteNativeLibraryName = System.getProperty("org.sqlite.lib.name"); + if (sqliteNativeLibraryName == null) + sqliteNativeLibraryName = System.mapLibraryName("sqlitejdbc"); + + if (sqliteNativeLibraryPath != null) { + if (loadNativeLibrary(sqliteNativeLibraryPath, sqliteNativeLibraryName)) { + extracted = true; + return; + } + } + + // Load the os-dependent library from a jar file + sqliteNativeLibraryPath = "/native/" + OSInfo.getNativeLibFolderPathForCurrentOS(); + + if (SQLiteJDBCLoader.class.getResource(sqliteNativeLibraryPath + "/" + sqliteNativeLibraryName) == null) { + // use nested VM version + return; + } + + // temporary library folder + String tempFolder = new File(System.getProperty("java.io.tmpdir")).getAbsolutePath(); + // Try extracting the library from jar + if (extractAndLoadLibraryFile(sqliteNativeLibraryPath, sqliteNativeLibraryName, tempFolder)) { + extracted = true; + return; + } + + extracted = false; + return; + } + + private static void getNativeLibraryFolderForTheCurrentOS() { + String osName = OSInfo.getOSName(); + String archName = OSInfo.getArchName(); + + } + + public static int getMajorVersion() { + String[] c = getVersion().split("\\."); + return (c.length > 0) ? Integer.parseInt(c[0]) : 1; + } + + public static int getMinorVersion() { + String[] c = getVersion().split("\\."); + return (c.length > 1) ? Integer.parseInt(c[1]) : 0; + } + + public static String getVersion() { + + URL versionFile = SQLiteJDBCLoader.class.getResource("/META-INF/maven/org.xerial/sqlite-jdbc/pom.properties"); + if (versionFile == null) + versionFile = SQLiteJDBCLoader.class.getResource("/META-INF/maven/org.xerial/sqlite-jdbc/VERSION"); + + String version = "unknown"; + try { + if (versionFile != null) { + Properties versionData = new Properties(); + versionData.load(versionFile.openStream()); + version = versionData.getProperty("version", version); + version = version.trim().replaceAll("[^0-9\\.]", ""); + } + } + catch (IOException e) { + System.err.println(e); + } + return version; + } + +} diff --git a/libs/org/sqlite/SQLiteOpenMode.java b/libs/org/sqlite/SQLiteOpenMode.java new file mode 100644 index 0000000..89f63fb --- /dev/null +++ b/libs/org/sqlite/SQLiteOpenMode.java @@ -0,0 +1,59 @@ +/*-------------------------------------------------------------------------- + * Copyright 2009 Taro L. Saito + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *--------------------------------------------------------------------------*/ +//-------------------------------------- +// sqlite-jdbc Project +// +// SQLiteOpenMode.java +// Since: Dec 8, 2009 +// +// $URL$ +// $Author$ +//-------------------------------------- +package org.sqlite; + +/** + * Database file open modes of SQLite. + * + * See also http://sqlite.org/c3ref/open.html + * + * @author leo + * + */ +public enum SQLiteOpenMode { + READONLY(0x00000001), /* Ok for int SQLITE3_open_v2() */ + READWRITE(0x00000002), /* Ok for int SQLITE3_open_v2() */ + CREATE(0x00000004), /* Ok for int SQLITE3_open_v2() */ + DELETEONCLOSE(0x00000008), /* VFS only */ + EXCLUSIVE(0x00000010), /* VFS only */ + MAIN_DB(0x00000100), /* VFS only */ + TEMP_DB(0x00000200), /* VFS only */ + TRANSIENT_DB(0x00000400), /* VFS only */ + MAIN_JOURNAL(0x00000800), /* VFS only */ + TEMP_JOURNAL(0x00001000), /* VFS only */ + SUBJOURNAL(0x00002000), /* VFS only */ + MASTER_JOURNAL(0x00004000), /* VFS only */ + NOMUTEX(0x00008000), /* Ok for int SQLITE3_open_v2() */ + FULLMUTEX(0x00010000), /* Ok for int SQLITE3_open_v2() */ + SHAREDCACHE(0x00020000), /* Ok for int SQLITE3_open_v2() */ + PRIVATECACHE(0x00040000) /* Ok for sqlite3_open_v2() */ + ; + + public final int flag; + + private SQLiteOpenMode(int flag) { + this.flag = flag; + } +} diff --git a/libs/org/sqlite/Stmt.java b/libs/org/sqlite/Stmt.java new file mode 100644 index 0000000..f32b67b --- /dev/null +++ b/libs/org/sqlite/Stmt.java @@ -0,0 +1,337 @@ +/* + * 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.BatchUpdateException; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; + +import org.sqlite.DB.ProgressObserver; +import org.sqlite.ExtendedCommand.SQLExtension; + +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; + } + + protected boolean exec(String sql) 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(sql); + } + 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(); + } + + @Override + 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(); + } + + static class BackupObserver implements ProgressObserver + { + public void progress(int remaining, int pageCount) { + System.out.println(String.format("remaining:%d, page count:%d", remaining, pageCount)); + } + } + + public int executeUpdate(String sql) throws SQLException { + close(); + this.sql = sql; + + int changes = 0; + SQLExtension ext = ExtendedCommand.parse(sql); + if (ext != null) { + // execute extended command + ext.execute(db); + } + else { + try { + //db.prepare(this); + //changes = db.executeUpdate(this, null); + + // directly invokes the exec API to support multiple SQL statements + int statusCode = db._exec(sql); + if (statusCode != SQLITE_OK) + throw DB.newSQLException(statusCode, ""); + + changes = db.changes(); + } + 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: optimize + 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/libs/org/sqlite/Unused.java b/libs/org/sqlite/Unused.java new file mode 100644 index 0000000..e10b791 --- /dev/null +++ b/libs/org/sqlite/Unused.java @@ -0,0 +1,244 @@ +/* + * 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 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 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/plugin.yml b/plugin.yml index b558d0a..e2afa21 100644 --- a/plugin.yml +++ b/plugin.yml @@ -2,7 +2,7 @@ name: ShopChest main: de.epiceric.shopchest.ShopChest -version: 1.5.0 +version: 1.5.1 author: EpicEric website: https://www.spigotmc.org/resources/shopchest.11431/ depend: [Vault] diff --git a/src/de/epiceric/shopchest/Commands.java b/src/de/epiceric/shopchest/Commands.java index 86c4048..218c7e5 100644 --- a/src/de/epiceric/shopchest/Commands.java +++ b/src/de/epiceric/shopchest/Commands.java @@ -1,16 +1,22 @@ package de.epiceric.shopchest; +import java.io.File; +import java.io.IOException; import java.lang.reflect.Method; -import java.util.ArrayList; +import java.nio.file.Files; import java.util.List; -import java.util.UUID; +import java.util.Set; import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.OfflinePlayer; +import org.bukkit.World; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.defaults.BukkitCommand; +import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -28,7 +34,6 @@ import de.epiceric.shopchest.utils.ClickType; import de.epiceric.shopchest.utils.ClickType.EnumClickType; import de.epiceric.shopchest.utils.ShopUtils; import de.epiceric.shopchest.utils.UpdateChecker; -import net.md_5.bungee.api.ChatColor; import net.milkbowl.vault.permission.Permission; public class Commands extends BukkitCommand { @@ -156,6 +161,12 @@ public class Commands extends BukkitCommand { p.sendMessage(Config.noPermission_limits()); } + } else if (args[0].equalsIgnoreCase("compile")){ + + if (perm.has(p, "shopchest.compile")) { + compile(p); + } + } else { sendBasicHelpMessage(p); return true; @@ -175,6 +186,65 @@ public class Commands extends BukkitCommand { } + private void compile(Player player) { + + player.sendMessage(" "); + player.sendMessage(ChatColor.RED + "" + ChatColor.BOLD + "Starting Compilation..."); + player.sendMessage(ChatColor.GOLD + "This command will be removed in the next update. Be sure to compile the shops in this version!"); + player.sendMessage(ChatColor.GOLD + "Please execute this command only once or the whole plugin will get messed up and all shops must be deleted!"); + + File shopChestDataFolder = new File(plugin.getDataFolder(), "data"); + if (!shopChestDataFolder.exists()) { + player.sendMessage(ChatColor.RED + "Cannot find shops.yml file."); + player.sendMessage(ChatColor.RED + "Be sure it's located in \"/plugins/ShopChest/data/shops.yml\""); + player.sendMessage(" "); + return; + } + + File shopChestsFile = new File(shopChestDataFolder, "shops.yml"); + if (!shopChestsFile.exists()) { + player.sendMessage(ChatColor.RED + "Cannot find shops.yml file."); + player.sendMessage(ChatColor.RED + "Be sure it's located in \"/plugins/ShopChest/data/shops.yml\""); + player.sendMessage(" "); + return; + } + + YamlConfiguration shopChests = YamlConfiguration.loadConfiguration(shopChestsFile); + + Set keys = shopChests.getKeys(false); + + for (String key : keys) { + + OfflinePlayer vendor = shopChests.getOfflinePlayer(key + ".vendor"); + int locationX = shopChests.getInt(key + ".location.x"); + int locationY = shopChests.getInt(key + ".location.y"); + int locationZ = shopChests.getInt(key + ".location.z"); + World locationWorld = plugin.getServer().getWorld(shopChests.getString(key + ".location.world")); + Location location = new Location(locationWorld, locationX, locationY, locationZ); + ItemStack product = shopChests.getItemStack(key + ".product"); + double buyPrice = shopChests.getDouble(key + ".price.buy"); + double sellPrice = shopChests.getDouble(key + ".price.sell"); + boolean infinite = shopChests.getBoolean(key + ".infinite"); + + ShopChest.sqlite.addShop(new Shop(plugin, vendor, product, location, buyPrice, sellPrice, infinite)); + + } + + + player.sendMessage(ChatColor.GREEN + "Successfully compiled " + String.valueOf(keys.size()) + " Shops."); + + try { + Files.delete(shopChestsFile.toPath()); + Files.delete(shopChestDataFolder.toPath()); + player.sendMessage(ChatColor.GREEN + "Successfully deleted data folder."); + } catch (IOException e) { + player.sendMessage(ChatColor.RED + "Could not delete data folder. You may delete it yourself."); + } + + player.sendMessage(" "); + + } + private void checkUpdates(Player player) { player.sendMessage(Config.checking_update()); @@ -205,8 +275,7 @@ public class Commands extends BukkitCommand { private void reload(Player player) { - ShopChest.utils.reload(); - player.sendMessage(Config.reloaded_shops(ShopChest.getInstance().shopChests.getKeys(false).size())); + ShopChest.utils.reload(player); } diff --git a/src/de/epiceric/shopchest/ShopChest.java b/src/de/epiceric/shopchest/ShopChest.java index a59c486..991367b 100644 --- a/src/de/epiceric/shopchest/ShopChest.java +++ b/src/de/epiceric/shopchest/ShopChest.java @@ -5,22 +5,16 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.sql.Statement; import java.util.ArrayList; import java.util.logging.Logger; import org.bukkit.Bukkit; import org.bukkit.ChatColor; -import org.bukkit.Location; -import org.bukkit.OfflinePlayer; -import org.bukkit.World; -import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; -import org.yi.acru.bukkit.Lockette.Lockette; - import com.griefcraft.lwc.LWC; import com.griefcraft.lwc.LWCPlugin; @@ -36,6 +30,7 @@ import de.epiceric.shopchest.interfaces.utils.Utils_R1; import de.epiceric.shopchest.interfaces.utils.Utils_R2; import de.epiceric.shopchest.interfaces.utils.Utils_R3; import de.epiceric.shopchest.shop.Shop; +import de.epiceric.shopchest.sql.SQLite; import de.epiceric.shopchest.interfaces.JsonBuilder; import de.epiceric.shopchest.interfaces.JsonBuilder.ClickAction; import de.epiceric.shopchest.interfaces.JsonBuilder.HoverAction; @@ -52,14 +47,13 @@ public class ShopChest extends JavaPlugin{ private static ShopChest instance; - public File shopChestsFile; - public YamlConfiguration shopChests; - - public static Logger logger = Logger.getLogger("Minecraft"); + public static Statement statement; + public static Logger logger; public static Economy econ = null; public static Permission perm = null; public static LWC lwc = null; public static boolean lockette = false; + public static SQLite sqlite; public static boolean isUpdateNeeded = false; public static String latestVersion = ""; @@ -92,14 +86,16 @@ public class ShopChest extends JavaPlugin{ @Override public void onEnable() { + logger = getLogger(); + if (getServer().getPluginManager().getPlugin("Vault") == null) { - logger.severe("[ShopChest] Could not find plugin 'Vault'!"); + logger.severe("Could not find plugin 'Vault'!"); getServer().getPluginManager().disablePlugin(this); return; } if (!setupEconomy() ) { - logger.severe("[ShopChest] Could not find any Vault dependency!"); + logger.severe("Could not find any Vault dependency!"); getServer().getPluginManager().disablePlugin(this); return; } @@ -108,16 +104,19 @@ public class ShopChest extends JavaPlugin{ Metrics metrics = new Metrics(this); metrics.start(); } catch (IOException e) { - logger.severe("[ShopChest] [PluginMetrics] Could not submit stats."); + logger.severe("Could not submit stats."); } - + + sqlite = new SQLite(this); + sqlite.load(); + switch (Utils.getVersion(getServer())) { case "v1_8_R1": utils = new Utils_R1(); break; case "v1_8_R2": utils = new Utils_R2(); break; case "v1_8_R3": utils = new Utils_R3(); break; default: - logger.severe("[ShopChest] Incompatible Server Version!"); + logger.severe("Incompatible Server Version!"); getServer().getPluginManager().disablePlugin(this); return; } @@ -142,14 +141,14 @@ public class ShopChest extends JavaPlugin{ saveDefaultConfig(); UpdateChecker uc = new UpdateChecker(this, getDescription().getWebsite()); - logger.info("[ShopChest] Checking for Updates"); + logger.info("Checking for Updates"); if(uc.updateNeeded()) { latestVersion = uc.getVersion(); downloadLink = uc.getLink(); isUpdateNeeded = true; Bukkit.getConsoleSender().sendMessage("[ShopChest] " + ChatColor.GOLD + "New version available: " + ChatColor.RED + latestVersion); } else { - logger.info("[ShopChest] No new version available"); + logger.info("No new version available"); isUpdateNeeded = false; } @@ -167,15 +166,7 @@ public class ShopChest extends JavaPlugin{ } } - } - - - File shopChestDataFolder = new File(getDataFolder(), "data"); - shopChestDataFolder.mkdirs(); - - shopChestsFile = new File(getDataFolder(), "/data/shops.yml"); - if (!shopChestsFile.exists()) - try {shopChestsFile.createNewFile();} catch (IOException e) {e.printStackTrace();} + } File itemNamesFile = new File(getDataFolder(), "item_names.txt"); @@ -183,9 +174,7 @@ public class ShopChest extends JavaPlugin{ try {itemNamesFile.createNewFile();} catch (IOException e) {e.printStackTrace();} copy(getResource("item_names.txt"), itemNamesFile); - - shopChests = YamlConfiguration.loadConfiguration(shopChestsFile); - + try { Commands.registerCommand(new Commands(this, Config.main_command_name(), "Manage Shops.", "", new ArrayList()), this); @@ -214,26 +203,26 @@ public class ShopChest extends JavaPlugin{ } private void initializeShops() { + + int count = 0; - Bukkit.getConsoleSender().sendMessage("[ShopChest] Found " + String.valueOf(shopChests.getKeys(false).size()) + " Shops"); - - for (String key : shopChests.getKeys(false)) { + for (int id = 1; id < sqlite.getCount() + 1; id++) { - OfflinePlayer vendor = shopChests.getOfflinePlayer(key + ".vendor"); - int locationX = shopChests.getInt(key + ".location.x"); - int locationY = shopChests.getInt(key + ".location.y"); - int locationZ = shopChests.getInt(key + ".location.z"); - World locationWorld = getServer().getWorld(shopChests.getString(key + ".location.world")); - Location location = new Location(locationWorld, locationX, locationY, locationZ); - ItemStack product = shopChests.getItemStack(key + ".product"); - double buyPrice = shopChests.getDouble(key + ".price.buy"); - double sellPrice = shopChests.getDouble(key + ".price.sell"); - boolean infinite = shopChests.getBoolean(key + ".infinite"); + try { + Shop shop = sqlite.getShop(id); + shop.createHologram(); + shop.createItem(); + ShopUtils.addShop(shop); + } catch (NullPointerException e) { + continue; + } - ShopUtils.addShop(new Shop(this, vendor, product, location, buyPrice, sellPrice, infinite)); + count++; } + logger.info("Initialized " + String.valueOf(count) + " Shops"); + } public static void copy(InputStream in, File file) { diff --git a/src/de/epiceric/shopchest/config/Config.java b/src/de/epiceric/shopchest/config/Config.java index 7b41cee..9ae7b2b 100644 --- a/src/de/epiceric/shopchest/config/Config.java +++ b/src/de/epiceric/shopchest/config/Config.java @@ -15,7 +15,7 @@ public class Config { public static boolean buy_greater_or_equal_sell() {return plugin.getConfig().getBoolean("buy-greater-or-equal-sell");} public static double maximal_distance() {return plugin.getConfig().getDouble("maximal-distance");} - public static int default_limit() {return plugin.getConfig().getInt("shop-limits.default");} + public static int default_limit() {return plugin.getConfig().getInt("shop-limits.default");} public static String main_command_name() { return plugin.getConfig().getString("main-command-name");} public static String currency_symbol() { return plugin.getConfig().getString("currency-symbol").replaceAll("(&([a-f0-9k-or]))", "\u00A7$2");} diff --git a/src/de/epiceric/shopchest/event/InteractShop.java b/src/de/epiceric/shopchest/event/InteractShop.java index b410ddf..034a840 100644 --- a/src/de/epiceric/shopchest/event/InteractShop.java +++ b/src/de/epiceric/shopchest/event/InteractShop.java @@ -1,6 +1,5 @@ package de.epiceric.shopchest.event; -import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -9,11 +8,9 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.Chest; -import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; @@ -22,18 +19,17 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.yi.acru.bukkit.Lockette.Lockette; -import com.griefcraft.lwc.LWC; import com.griefcraft.model.Protection; import de.epiceric.shopchest.ShopChest; import de.epiceric.shopchest.config.Config; import de.epiceric.shopchest.shop.Shop; +import de.epiceric.shopchest.sql.SQLite; import de.epiceric.shopchest.utils.ClickType; import de.epiceric.shopchest.utils.EnchantmentNames; import de.epiceric.shopchest.utils.ItemNames; import de.epiceric.shopchest.utils.ShopUtils; import de.epiceric.shopchest.interfaces.Utils; -import net.md_5.bungee.api.ChatColor; import net.milkbowl.vault.economy.Economy; import net.milkbowl.vault.economy.EconomyResponse; import net.milkbowl.vault.permission.Permission; @@ -43,11 +39,10 @@ public class InteractShop implements Listener{ private ShopChest plugin; private Permission perm = ShopChest.perm; private Economy econ = ShopChest.econ; - private YamlConfiguration shopChests; + private SQLite sqlite = ShopChest.sqlite; public InteractShop(ShopChest plugin) { this.plugin = plugin; - shopChests = plugin.shopChests; } @EventHandler @@ -231,18 +226,10 @@ public class InteractShop implements Listener{ private void create(Player executor, Location location, ItemStack product, double buyPrice, double sellPrice, boolean infinite) { Shop shop = new Shop(plugin, executor, product, location, buyPrice, sellPrice, infinite); + shop.createHologram(); + shop.createItem(); - shopChests.set(ShopUtils.getConfigTitle(location) + ".vendor", executor); - shopChests.set(ShopUtils.getConfigTitle(location) + ".location.world", location.getWorld().getName()); - shopChests.set(ShopUtils.getConfigTitle(location) + ".location.x", location.getBlockX()); - shopChests.set(ShopUtils.getConfigTitle(location) + ".location.y", location.getBlockY()); - shopChests.set(ShopUtils.getConfigTitle(location) + ".location.z", location.getBlockZ()); - shopChests.set(ShopUtils.getConfigTitle(location) + ".product", product); - shopChests.set(ShopUtils.getConfigTitle(location) + ".price.buy", buyPrice); - shopChests.set(ShopUtils.getConfigTitle(location) + ".price.sell", sellPrice); - shopChests.set(ShopUtils.getConfigTitle(location) + ".infinite", infinite); - - try {shopChests.save(plugin.shopChestsFile);} catch (IOException ex) {ex.printStackTrace();} + sqlite.addShop(shop); ShopUtils.addShop(shop); executor.sendMessage(Config.shop_created()); @@ -255,12 +242,10 @@ public class InteractShop implements Listener{ private void remove(Player executor, Shop shop) { - shop.getItem().remove(); ShopUtils.removeShop(shop); - shopChests.set(ShopUtils.getConfigTitle(shop.getLocation()), null); - try {shopChests.save(plugin.shopChestsFile);} catch (IOException ex) {ex.printStackTrace();} - + sqlite.removeShop(shop); + for (Player player : plugin.getServer().getOnlinePlayers()) { shop.getHologram().hidePlayer(player); } diff --git a/src/de/epiceric/shopchest/event/RegenerateShopItemAfterRemove.java b/src/de/epiceric/shopchest/event/RegenerateShopItemAfterRemove.java index a6e291b..1b3d018 100644 --- a/src/de/epiceric/shopchest/event/RegenerateShopItemAfterRemove.java +++ b/src/de/epiceric/shopchest/event/RegenerateShopItemAfterRemove.java @@ -19,7 +19,7 @@ public class RegenerateShopItemAfterRemove implements Listener { } } - if (containsShopItem) ShopChest.utils.reload(); + if (containsShopItem) ShopChest.utils.reload(null); } diff --git a/src/de/epiceric/shopchest/interfaces/Utils.java b/src/de/epiceric/shopchest/interfaces/Utils.java index cebeb5c..82c5bb6 100644 --- a/src/de/epiceric/shopchest/interfaces/Utils.java +++ b/src/de/epiceric/shopchest/interfaces/Utils.java @@ -1,14 +1,17 @@ package de.epiceric.shopchest.interfaces; +import org.apache.commons.codec.binary.Base64; import org.bukkit.Material; import org.bukkit.Server; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; public abstract class Utils { - public abstract void reload(); + public abstract void reload(Player p); public abstract void removeShops(); @@ -33,4 +36,29 @@ public abstract class Utils { return string.matches("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[34][0-9a-fA-F]{3}-[89ab][0-9a-fA-F]{3}-[0-9a-fA-F]{12}"); } + public static String encode(ItemStack itemStack) { + YamlConfiguration config = new YamlConfiguration(); + config.set("i", itemStack); + return new String(Base64.encodeBase64(config.saveToString().getBytes())); + } + + public static String toString(ItemStack itemStack) { + YamlConfiguration config = new YamlConfiguration(); + config.set("i", itemStack); + return config.saveToString(); + } + + public static ItemStack decode(String string) { + YamlConfiguration config = new YamlConfiguration(); + try { + config.loadFromString(new String(Base64.decodeBase64(string.getBytes()))); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + return config.getItemStack("i", null); + } + + + } diff --git a/src/de/epiceric/shopchest/interfaces/utils/Utils_R1.java b/src/de/epiceric/shopchest/interfaces/utils/Utils_R1.java index 9edb92d..fb2ab2f 100644 --- a/src/de/epiceric/shopchest/interfaces/utils/Utils_R1.java +++ b/src/de/epiceric/shopchest/interfaces/utils/Utils_R1.java @@ -1,13 +1,10 @@ package de.epiceric.shopchest.interfaces.utils; import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.OfflinePlayer; -import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.inventory.ItemStack; import de.epiceric.shopchest.ShopChest; +import de.epiceric.shopchest.config.Config; import de.epiceric.shopchest.interfaces.Utils; import de.epiceric.shopchest.interfaces.Hologram; import de.epiceric.shopchest.shop.Shop; @@ -17,7 +14,7 @@ import net.minecraft.server.v1_8_R1.EntityArmorStand; public class Utils_R1 extends Utils { @Override - public void reload() { + public void reload(Player player) { for (Shop shop : ShopUtils.getShops()) { Hologram hologram = shop.getHologram(); @@ -37,23 +34,25 @@ public class Utils_R1 extends Utils { } - for (String key : ShopChest.getInstance().shopChests.getKeys(false)) { + int count = 0; + + for (int id = 1; id < ShopChest.sqlite.getCount() + 1; id++) { - OfflinePlayer vendor = ShopChest.getInstance().shopChests.getOfflinePlayer(key + ".vendor"); - int locationX = ShopChest.getInstance().shopChests.getInt(key + ".location.x"); - int locationY = ShopChest.getInstance().shopChests.getInt(key + ".location.y"); - int locationZ = ShopChest.getInstance().shopChests.getInt(key + ".location.z"); - World locationWorld = ShopChest.getInstance().getServer().getWorld(ShopChest.getInstance().shopChests.getString(key + ".location.world")); - Location location = new Location(locationWorld, locationX, locationY, locationZ); - ItemStack product = ShopChest.getInstance().shopChests.getItemStack(key + ".product"); - double buyPrice = ShopChest.getInstance().shopChests.getDouble(key + ".price.buy"); - double sellPrice = ShopChest.getInstance().shopChests.getDouble(key + ".price.sell"); - boolean infinite = ShopChest.getInstance().shopChests.getBoolean(key + ".infinite"); + try { + Shop shop = ShopChest.sqlite.getShop(id); + shop.createHologram(); + shop.createItem(); + ShopUtils.addShop(shop); + } catch (NullPointerException e) { + continue; + } - ShopUtils.addShop(new Shop(ShopChest.getInstance(), vendor, product, location, buyPrice, sellPrice, infinite)); + count++; } + if (player != null) player.sendMessage(Config.reloaded_shops(count)); + for (Player p : Bukkit.getOnlinePlayers()) { Bukkit.getPluginManager().callEvent(new PlayerMoveEvent(p, p.getLocation(), p.getLocation())); } diff --git a/src/de/epiceric/shopchest/interfaces/utils/Utils_R2.java b/src/de/epiceric/shopchest/interfaces/utils/Utils_R2.java index 432ea94..f52c34d 100644 --- a/src/de/epiceric/shopchest/interfaces/utils/Utils_R2.java +++ b/src/de/epiceric/shopchest/interfaces/utils/Utils_R2.java @@ -1,15 +1,13 @@ package de.epiceric.shopchest.interfaces.utils; import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.OfflinePlayer; -import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.inventory.ItemStack; + import de.epiceric.shopchest.ShopChest; -import de.epiceric.shopchest.interfaces.Utils; +import de.epiceric.shopchest.config.Config; import de.epiceric.shopchest.interfaces.Hologram; +import de.epiceric.shopchest.interfaces.Utils; import de.epiceric.shopchest.shop.Shop; import de.epiceric.shopchest.utils.ShopUtils; import net.minecraft.server.v1_8_R2.EntityArmorStand; @@ -17,7 +15,7 @@ import net.minecraft.server.v1_8_R2.EntityArmorStand; public class Utils_R2 extends Utils { @Override - public void reload() { + public void reload(Player player) { for (Shop shop : ShopUtils.getShops()) { Hologram hologram = shop.getHologram(); @@ -37,23 +35,25 @@ public class Utils_R2 extends Utils { } - for (String key : ShopChest.getInstance().shopChests.getKeys(false)) { + int count = 0; + + for (int id = 1; id < ShopChest.sqlite.getCount() + 1; id++) { - OfflinePlayer vendor = ShopChest.getInstance().shopChests.getOfflinePlayer(key + ".vendor"); - int locationX = ShopChest.getInstance().shopChests.getInt(key + ".location.x"); - int locationY = ShopChest.getInstance().shopChests.getInt(key + ".location.y"); - int locationZ = ShopChest.getInstance().shopChests.getInt(key + ".location.z"); - World locationWorld = ShopChest.getInstance().getServer().getWorld(ShopChest.getInstance().shopChests.getString(key + ".location.world")); - Location location = new Location(locationWorld, locationX, locationY, locationZ); - ItemStack product = ShopChest.getInstance().shopChests.getItemStack(key + ".product"); - double buyPrice = ShopChest.getInstance().shopChests.getDouble(key + ".price.buy"); - double sellPrice = ShopChest.getInstance().shopChests.getDouble(key + ".price.sell"); - boolean infinite = ShopChest.getInstance().shopChests.getBoolean(key + ".infinite"); + try { + Shop shop = ShopChest.sqlite.getShop(id); + shop.createHologram(); + shop.createItem(); + ShopUtils.addShop(shop); + } catch (NullPointerException e) { + continue; + } - ShopUtils.addShop(new Shop(ShopChest.getInstance(), vendor, product, location, buyPrice, sellPrice, infinite)); + count++; } + if (player != null) player.sendMessage(Config.reloaded_shops(count)); + for (Player p : Bukkit.getOnlinePlayers()) { Bukkit.getPluginManager().callEvent(new PlayerMoveEvent(p, p.getLocation(), p.getLocation())); } diff --git a/src/de/epiceric/shopchest/interfaces/utils/Utils_R3.java b/src/de/epiceric/shopchest/interfaces/utils/Utils_R3.java index 86e77e0..2d4dd56 100644 --- a/src/de/epiceric/shopchest/interfaces/utils/Utils_R3.java +++ b/src/de/epiceric/shopchest/interfaces/utils/Utils_R3.java @@ -1,13 +1,10 @@ package de.epiceric.shopchest.interfaces.utils; import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.OfflinePlayer; -import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.inventory.ItemStack; import de.epiceric.shopchest.ShopChest; +import de.epiceric.shopchest.config.Config; import de.epiceric.shopchest.interfaces.Utils; import de.epiceric.shopchest.interfaces.Hologram; import de.epiceric.shopchest.shop.Shop; @@ -17,7 +14,7 @@ import net.minecraft.server.v1_8_R3.EntityArmorStand; public class Utils_R3 extends Utils { @Override - public void reload() { + public void reload(Player player) { for (Shop shop : ShopUtils.getShops()) { Hologram hologram = shop.getHologram(); @@ -37,23 +34,25 @@ public class Utils_R3 extends Utils { } - for (String key : ShopChest.getInstance().shopChests.getKeys(false)) { + int count = 0; + + for (int id = 1; id < ShopChest.sqlite.getCount() + 1; id++) { - OfflinePlayer vendor = ShopChest.getInstance().shopChests.getOfflinePlayer(key + ".vendor"); - int locationX = ShopChest.getInstance().shopChests.getInt(key + ".location.x"); - int locationY = ShopChest.getInstance().shopChests.getInt(key + ".location.y"); - int locationZ = ShopChest.getInstance().shopChests.getInt(key + ".location.z"); - World locationWorld = ShopChest.getInstance().getServer().getWorld(ShopChest.getInstance().shopChests.getString(key + ".location.world")); - Location location = new Location(locationWorld, locationX, locationY, locationZ); - ItemStack product = ShopChest.getInstance().shopChests.getItemStack(key + ".product"); - double buyPrice = ShopChest.getInstance().shopChests.getDouble(key + ".price.buy"); - double sellPrice = ShopChest.getInstance().shopChests.getDouble(key + ".price.sell"); - boolean infinite = ShopChest.getInstance().shopChests.getBoolean(key + ".infinite"); + try { + Shop shop = ShopChest.sqlite.getShop(id); + shop.createHologram(); + shop.createItem(); + ShopUtils.addShop(shop); + } catch (NullPointerException e) { + continue; + } - ShopUtils.addShop(new Shop(ShopChest.getInstance(), vendor, product, location, buyPrice, sellPrice, infinite)); + count++; } + if (player != null) player.sendMessage(Config.reloaded_shops(count)); + for (Player p : Bukkit.getOnlinePlayers()) { Bukkit.getPluginManager().callEvent(new PlayerMoveEvent(p, p.getLocation(), p.getLocation())); } diff --git a/src/de/epiceric/shopchest/shop/Shop.java b/src/de/epiceric/shopchest/shop/Shop.java index c0a1bf2..d181c70 100644 --- a/src/de/epiceric/shopchest/shop/Shop.java +++ b/src/de/epiceric/shopchest/shop/Shop.java @@ -45,11 +45,9 @@ public class Shop { this.buyPrice = buyPrice; this.sellPrice = sellPrice; this.infinite = infinite; - this.hologram = createHologram(product, location, buyPrice, sellPrice); - this.item = createItem(product, location); } - public Item createItem(ItemStack product, Location location) { + public void createItem() { Item item; Location itemLocation; @@ -70,19 +68,16 @@ public class Shop { item.setVelocity(new Vector(0, 0, 0)); item.setMetadata("shopItem", new FixedMetadataValue(plugin, true)); - return item; - + this.item = item; } - public Hologram createHologram(ItemStack product, Location shopLocation, double buyPrice, double sellPrice) { - + public void createHologram() { + boolean doubleChest; - - Hologram hologram; - + Chest[] chests = new Chest[2]; - Block b = shopLocation.getBlock(); + Block b = location.getBlock(); if (b.getType().equals(Material.CHEST) || b.getType().equals(Material.TRAPPED_CHEST)) { @@ -106,7 +101,7 @@ public class Shop { } } else { - return null; + return; } Location holoLocation; @@ -145,11 +140,9 @@ public class Shop { case "v1_8_R1": hologram = new Hologram_R1(holoText, holoLocation); break; case "v1_8_R2": hologram = new Hologram_R2(holoText, holoLocation); break; case "v1_8_R3": hologram = new Hologram_R3(holoText, holoLocation); break; - default: return null; + default: return; } - - return hologram; - + } public OfflinePlayer getVendor() { diff --git a/src/de/epiceric/shopchest/sql/Database.java b/src/de/epiceric/shopchest/sql/Database.java new file mode 100644 index 0000000..308bf42 --- /dev/null +++ b/src/de/epiceric/shopchest/sql/Database.java @@ -0,0 +1,487 @@ +package de.epiceric.shopchest.sql; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.UUID; +import java.util.logging.Level; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.bukkit.inventory.ItemStack; + +import de.epiceric.shopchest.ShopChest; +import de.epiceric.shopchest.interfaces.Utils; +import de.epiceric.shopchest.shop.Shop; +import de.epiceric.shopchest.utils.ShopUtils; + +public abstract class Database { + ShopChest plugin; + Connection connection; + // The name of the table we created back in SQLite class. + public String table = "shop_list"; + + public String world = ""; + public String vendor = ""; + public ItemStack product = null; + public Location location = null; + public double buyPrice = 0; + public double sellPrice = 0; + public boolean infinite = false; + + public Database(ShopChest instance){ + plugin = instance; + } + + public abstract Connection getSQLConnection(); + + public abstract void load(); + + public void initialize(){ + connection = getSQLConnection(); + try{ + PreparedStatement ps = connection.prepareStatement("SELECT * FROM " + table + " WHERE id = ?"); + ResultSet rs = ps.executeQuery(); + close(ps,rs); + + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Unable to retreive connection", ex); + } + } + + public int getNextFreeID() { + for (int i = 1; i <= getCount(); i++) { + if (getProduct(i) == null) { + return i; + } else { + if (i == getCount()) { + return i + 1; + } + } + } + + return 0; + } + + public int getCount() { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + + int highestID = 0; + try { + conn = getSQLConnection(); + ps = conn.prepareStatement("SELECT * FROM " + table + ";"); + + rs = ps.executeQuery(); + while(rs.next()){ + if(rs.getInt("id") > highestID) { + highestID = rs.getInt("id"); + } + } + return highestID; + + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionExecute(), ex); + } finally { + try { + if (ps != null) + ps.close(); + if (conn != null) + conn.close(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionClose(), ex); + } + } + return -1; + } + + public int getShopID(Shop shop) { + + + for (int i = 1; i < getCount() + 1; i++) { + + try { + Shop s = getShop(i); + if (s.getLocation().equals(shop.getLocation())) { + return i; + } + } catch (NullPointerException ex) { + continue; + } + + } + + return 0; + } + + public void removeShop(Shop shop) { + + int id = getShopID(shop); + if (id == 0) return; + + shop.getItem().remove(); + + Connection conn = null; + PreparedStatement ps = null; + try { + conn = getSQLConnection(); + ps = conn.prepareStatement("DELETE FROM " + table + " where id = " + id + ";"); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionExecute(), ex); + } finally { + try { + if (ps != null) + ps.close(); + if (conn != null) + conn.close(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionClose(), ex); + } + } + + } + + public void removeShop(int id) { + + if (id == 0) return; + removeShop(getShop(id)); + + } + + private World getWorld(int id) { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + try { + conn = getSQLConnection(); + ps = conn.prepareStatement("SELECT * FROM " + table + " WHERE id = " + id + ";"); + + rs = ps.executeQuery(); + while(rs.next()){ + if(rs.getInt("id") == id) { + return Bukkit.getWorld(rs.getString("world")); + } + } + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionExecute(), ex); + } finally { + try { + if (ps != null) + ps.close(); + if (conn != null) + conn.close(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionClose(), ex); + } + } + return null; + } + + @SuppressWarnings("deprecation") + public OfflinePlayer getVendor(int id) { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + try { + conn = getSQLConnection(); + ps = conn.prepareStatement("SELECT * FROM " + table + " WHERE id = " + id + ";"); + + rs = ps.executeQuery(); + while(rs.next()){ + if(rs.getInt("id") == id){ + return (Utils.isUUID(rs.getString("vendor"))) ? Bukkit.getOfflinePlayer(UUID.fromString(rs.getString("vendor"))) : Bukkit.getOfflinePlayer(rs.getString("vendor")); + } + } + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionExecute(), ex); + } finally { + try { + if (ps != null) + ps.close(); + if (conn != null) + conn.close(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionClose(), ex); + } + } + return null; + } + + public ItemStack getProduct(int id) { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + try { + conn = getSQLConnection(); + ps = conn.prepareStatement("SELECT * FROM " + table + " WHERE id = " + id + ";"); + + rs = ps.executeQuery(); + while(rs.next()){ + if(rs.getInt("id") == id){ + return Utils.decode(rs.getString("product")); + } + } + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionExecute(), ex); + } finally { + try { + if (ps != null) + ps.close(); + if (conn != null) + conn.close(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionClose(), ex); + } + } + return null; + } + + private int getX(int id) { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + try { + conn = getSQLConnection(); + ps = conn.prepareStatement("SELECT * FROM " + table + " WHERE id = " + id + ";"); + + rs = ps.executeQuery(); + while(rs.next()){ + if(rs.getInt("id") == id){ + return rs.getInt("x"); + } + } + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionExecute(), ex); + } finally { + try { + if (ps != null) + ps.close(); + if (conn != null) + conn.close(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionClose(), ex); + } + } + return 0; + } + + private int getY(int id) { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + try { + conn = getSQLConnection(); + ps = conn.prepareStatement("SELECT * FROM " + table + " WHERE id = " + id + ";"); + + rs = ps.executeQuery(); + while(rs.next()){ + if(rs.getInt("id") == id){ + return rs.getInt("y"); + } + } + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionExecute(), ex); + } finally { + try { + if (ps != null) + ps.close(); + if (conn != null) + conn.close(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionClose(), ex); + } + } + return 0; + } + + private int getZ(int id) { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + try { + conn = getSQLConnection(); + ps = conn.prepareStatement("SELECT * FROM " + table + " WHERE id = " + id + ";"); + + rs = ps.executeQuery(); + while(rs.next()){ + if(rs.getInt("id") == id){ + return rs.getInt("z"); + } + } + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionExecute(), ex); + } finally { + try { + if (ps != null) + ps.close(); + if (conn != null) + conn.close(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionClose(), ex); + } + } + return 0; + } + + public Location getLocation(int id) { + return new Location(getWorld(id), getX(id), getY(id), getZ(id)); + } + + public double getBuyPrice(int id) { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + try { + conn = getSQLConnection(); + ps = conn.prepareStatement("SELECT * FROM " + table + " WHERE id = " + id + ";"); + + rs = ps.executeQuery(); + while(rs.next()){ + if(rs.getInt("id") == id){ + return rs.getDouble("buyprice"); + } + } + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionExecute(), ex); + } finally { + try { + if (ps != null) + ps.close(); + if (conn != null) + conn.close(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionClose(), ex); + } + } + return 0; + } + + public double getSellPrice(int id) { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + try { + conn = getSQLConnection(); + ps = conn.prepareStatement("SELECT * FROM " + table + " WHERE id = " + id + ";"); + + rs = ps.executeQuery(); + while(rs.next()){ + if(rs.getInt("id") == id){ + return rs.getDouble("sellprice"); + } + } + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionExecute(), ex); + } finally { + try { + if (ps != null) + ps.close(); + if (conn != null) + conn.close(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionClose(), ex); + } + } + return 0; + } + + public boolean isInfinite(int id) { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + try { + conn = getSQLConnection(); + ps = conn.prepareStatement("SELECT * FROM " + table + " WHERE id = " + id + ";"); + + rs = ps.executeQuery(); + while(rs.next()){ + if(rs.getInt("id") == id){ + return rs.getBoolean("infinite"); + } + } + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionExecute(), ex); + } finally { + try { + if (ps != null) + ps.close(); + if (conn != null) + conn.close(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionClose(), ex); + } + } + + return false; + } + + public Shop getShop(int id) { + OfflinePlayer vendor = getVendor(id); + Location location = getLocation(id); + ItemStack product = getProduct(id); + double buyPrice = getBuyPrice(id); + double sellPrice = getSellPrice(id); + boolean infinite = isInfinite(id); + + if (ShopUtils.isShop(location)) return ShopUtils.getShop(location); + else return new Shop(plugin, vendor, product, location, buyPrice, sellPrice, infinite); + } + + + public void setShop(int id, Shop shop) { + Connection conn = null; + PreparedStatement ps = null; + try { + conn = getSQLConnection(); + ps = conn.prepareStatement("REPLACE INTO " + table + " (id,vendor,product,world,x,y,z,buyprice,sellprice,infinite) VALUES(?,?,?,?,?,?,?,?,?,?)"); + + ps.setInt(1, id); + ps.setString(2, shop.getVendor().getUniqueId().toString()); + ps.setString(3, Utils.encode(shop.getProduct())); + ps.setString(4, shop.getLocation().getWorld().getName()); + ps.setInt(5, shop.getLocation().getBlockX()); + ps.setInt(6, shop.getLocation().getBlockY()); + ps.setInt(7, shop.getLocation().getBlockZ()); + ps.setDouble(8, shop.getBuyPrice()); + ps.setDouble(9, shop.getSellPrice()); + ps.setBoolean(10, shop.isInfinite()); + + ps.executeUpdate(); + return; + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionExecute(), ex); + } finally { + try { + if (ps != null) + ps.close(); + if (conn != null) + conn.close(); + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, Errors.sqlConnectionClose(), ex); + } + } + return; + } + + public void addShop(Shop shop) { + int id = getNextFreeID(); + setShop(id, shop); + } + + + public void close(PreparedStatement ps,ResultSet rs){ + try { + if (ps != null) + ps.close(); + if (rs != null) + rs.close(); + } catch (SQLException ex) { + Error.close(plugin, ex); + } + } +} \ No newline at end of file diff --git a/src/de/epiceric/shopchest/sql/Error.java b/src/de/epiceric/shopchest/sql/Error.java new file mode 100644 index 0000000..7328ee2 --- /dev/null +++ b/src/de/epiceric/shopchest/sql/Error.java @@ -0,0 +1,14 @@ +package de.epiceric.shopchest.sql; + +import java.util.logging.Level; + +import de.epiceric.shopchest.ShopChest; + +public class Error { + public static void execute(ShopChest plugin, Exception ex){ + plugin.getLogger().log(Level.SEVERE, "Couldn't execute MySQL statement: ", ex); + } + public static void close(ShopChest plugin, Exception ex){ + plugin.getLogger().log(Level.SEVERE, "Failed to close MySQL connection: ", ex); + } +} diff --git a/src/de/epiceric/shopchest/sql/Errors.java b/src/de/epiceric/shopchest/sql/Errors.java new file mode 100644 index 0000000..927b7e8 --- /dev/null +++ b/src/de/epiceric/shopchest/sql/Errors.java @@ -0,0 +1,16 @@ +package de.epiceric.shopchest.sql; + +public class Errors { + public static String sqlConnectionExecute(){ + return "Couldn't execute MySQL statement: "; + } + public static String sqlConnectionClose(){ + return "Failed to close MySQL connection: "; + } + public static String noSQLConnection(){ + return "Unable to retreive MYSQL connection: "; + } + public static String noTableFound(){ + return "Database Error: No Table Found"; + } +} diff --git a/src/de/epiceric/shopchest/sql/SQLite.java b/src/de/epiceric/shopchest/sql/SQLite.java new file mode 100644 index 0000000..75389a5 --- /dev/null +++ b/src/de/epiceric/shopchest/sql/SQLite.java @@ -0,0 +1,73 @@ +package de.epiceric.shopchest.sql; + +import java.io.File; +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.logging.Level; + +import de.epiceric.shopchest.ShopChest; + +public class SQLite extends Database { + + String dbname; + + public SQLite(ShopChest instance){ + super(instance); + dbname = "shops"; + } + + public String SQLiteCreateTokensTable = "CREATE TABLE IF NOT EXISTS shop_list (" + + "`id` int(11) NOT NULL," + + "`vendor` varchar(32) NOT NULL," + + "`product` varchar(32) NOT NULL," + + "`world` varchar(32) NOT NULL," + + "`x` int(11) NOT NULL," + + "`y` int(11) NOT NULL," + + "`z` int(11) NOT NULL," + + "`buyprice` float(32) NOT NULL," + + "`sellprice` float(32) NOT NULL," + + "`infinite` boolean NOT NULL," + + "PRIMARY KEY (`id`)" + + ");"; + + + // SQL creation stuff, You can leave the below stuff untouched. + public Connection getSQLConnection() { + File dataFolder = new File(plugin.getDataFolder(), dbname+".db"); + if (!dataFolder.exists()){ + try { + dataFolder.createNewFile(); + } catch (IOException e) { + plugin.getLogger().log(Level.SEVERE, "File write error: "+dbname+".db"); + } + } + try { + if(connection!=null&&!connection.isClosed()){ + return connection; + } + Class.forName("org.sqlite.JDBC"); + connection = DriverManager.getConnection("jdbc:sqlite:" + dataFolder); + return connection; + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE,"SQLite exception on initialize", ex); + } catch (ClassNotFoundException ex) { + plugin.getLogger().log(Level.SEVERE, "You need the SQLite JBDC library. Google it. Put it in /lib folder."); + } + return null; + } + + public void load() { + connection = getSQLConnection(); + try { + Statement s = connection.createStatement(); + s.executeUpdate(SQLiteCreateTokensTable); + s.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + initialize(); + } +} diff --git a/src/de/epiceric/shopchest/utils/ShopUtils.java b/src/de/epiceric/shopchest/utils/ShopUtils.java index c7b0813..e0b8406 100644 --- a/src/de/epiceric/shopchest/utils/ShopUtils.java +++ b/src/de/epiceric/shopchest/utils/ShopUtils.java @@ -91,6 +91,7 @@ public class ShopUtils { Chest r = (Chest) dc.getRightSide(); Chest l = (Chest) dc.getLeftSide(); + shop.getItem().remove(); shopLocation.remove(r.getLocation()); shopLocation.remove(l.getLocation()); return;