package android.database.sqlite; import com.almworks.sqlite4java.SQLite; import com.almworks.sqlite4java.SQLiteConnection; import com.almworks.sqlite4java.SQLiteConstants; import com.almworks.sqlite4java.SQLiteStatement; import android.content.ContentResolver; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.util.Log; import java.io.File; import java.sql.SQLException; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import static com.applang.Util.*; import static com.applang.Util2.*; /** * Exposes methods to manage a SQLite database. * @hide */ public class SQLiteDatabase { private static final String TAG = SQLiteDatabase.class.getSimpleName(); public SQLiteDatabase() { Logger.getLogger("com.almworks.sqlite4java").setLevel(Level.SEVERE); } public static String getSQLiteVersion() { try { return SQLite.getSQLiteVersion(); } catch (Exception e) { Log.e(TAG, "native lib loading failed", e); return ""; } } public static final int CREATE_IF_NECESSARY = 0x10000000; SQLiteConnection connection = null; /** * Used to allow returning sub-classes of {@link Cursor} when calling query. */ public interface CursorFactory { /** * See * {@link SQLiteCursor#SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)}. */ public Cursor newCursor(SQLiteConnection con, final SQLiteStatement stm); } private CursorFactory mFactory = new CursorFactory() { public Cursor newCursor(SQLiteConnection con, final SQLiteStatement stm) { return new Cursor() { int cnt = 0, position = -1; { if (stm != null) try { if (stm.hasStepped()) stm.reset(false); while (stm.step()) { cnt++; } } catch (com.almworks.sqlite4java.SQLiteException e) {} } public int getCount() { return cnt; } public int getPosition() { return position; } public boolean moveToFirst() { if (stm == null) return false; try { position = -1; if (stm.hasStepped()) stm.reset(false); return moveToNext(); } catch (com.almworks.sqlite4java.SQLiteException e) { return false; } } public boolean moveToNext() { if (stm == null) return false; try { position++; return stm.step(); } catch (com.almworks.sqlite4java.SQLiteException e) { position = -1; return false; } } public boolean moveToPosition(int position) { boolean retval = stm != null && position > -1 && position < cnt; if (position < this.position) { this.position = -1; } while (retval && position > this.position) { retval = this.position < 0 ? moveToFirst() : moveToNext(); } return retval; } public boolean moveToPrevious() { return moveToPosition(position - 1); } public boolean moveToLast() { return moveToPosition(cnt - 1); } public boolean move(int offset) { return moveToPosition(position + offset); } public boolean isClosed() { if (stm != null) return stm.isDisposed(); return true; } public void close() { if (stm != null) { stm.dispose(); } } public boolean isNull(int columnIndex) { try { if (stm != null && stm.hasRow()) return stm.columnNull(columnIndex); } catch (com.almworks.sqlite4java.SQLiteException e) {} return true; } public boolean isLast() { throw new UnsupportedOperationException("Not implemented"); } public boolean isFirst() { throw new UnsupportedOperationException("Not implemented"); } public boolean isBeforeFirst() { throw new UnsupportedOperationException("Not implemented"); } public boolean isAfterLast() { throw new UnsupportedOperationException("Not implemented"); } public int getType(int columnIndex) { try { if (stm != null && stm.hasRow()) switch (stm.columnType(columnIndex)) { case SQLiteConstants.SQLITE_FLOAT: return FIELD_TYPE_FLOAT; case SQLiteConstants.SQLITE_INTEGER: return FIELD_TYPE_INTEGER; case SQLiteConstants.SQLITE_TEXT: return FIELD_TYPE_STRING; case SQLiteConstants.SQLITE_BLOB: return FIELD_TYPE_BLOB; case SQLiteConstants.SQLITE_NULL: return FIELD_TYPE_NULL; } } catch (com.almworks.sqlite4java.SQLiteException e) {} return -1; } public String getString(int columnIndex) { try { if (stm != null && stm.hasRow()) return stm.columnString(columnIndex); } catch (com.almworks.sqlite4java.SQLiteException e) {} return null; } public int getInt(int columnIndex) { try { if (stm != null && stm.hasRow()) return stm.columnInt(columnIndex); } catch (com.almworks.sqlite4java.SQLiteException e) {} return Integer.MIN_VALUE; } public short getShort(int columnIndex) { try { if (stm != null && stm.hasRow()) return Short.parseShort(stm.columnString(columnIndex)); } catch (com.almworks.sqlite4java.SQLiteException e) {} return Short.MIN_VALUE; } public long getLong(int columnIndex) { try { if (stm != null && stm.hasRow()) return stm.columnLong(columnIndex); } catch (com.almworks.sqlite4java.SQLiteException e) {} return Long.MIN_VALUE; } public float getFloat(int columnIndex) { try { if (stm != null && stm.hasRow()) return Float.parseFloat(stm.columnString(columnIndex)); } catch (com.almworks.sqlite4java.SQLiteException e) {} return Float.NaN; } public double getDouble(int columnIndex) { try { if (stm != null && stm.hasRow()) return stm.columnDouble(columnIndex); } catch (com.almworks.sqlite4java.SQLiteException e) {} return Double.NaN; } public byte[] getBlob(int columnIndex) { try { if (stm != null && stm.hasRow()) return stm.columnBlob(columnIndex); } catch (com.almworks.sqlite4java.SQLiteException e) {} return null; } public int getColumnCount() { try { if (stm != null) return stm.columnCount(); } catch (com.almworks.sqlite4java.SQLiteException e) {} return 0; } public String getColumnName(int columnIndex) { try { if (stm != null) return stm.getColumnName(columnIndex); } catch (com.almworks.sqlite4java.SQLiteException e) {} return null; } public String[] getColumnNames() { String[] names = new String[getColumnCount()]; for (int i = 0; i < names.length; i++) names[i] = getColumnName(i); return names; } public int getColumnIndex(String columnName) { return asList(getColumnNames()).indexOf(columnName); } public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException { throw new UnsupportedOperationException("Not implemented"); } public boolean requery() { throw new UnsupportedOperationException("Not implemented"); } public void deactivate() { throw new UnsupportedOperationException("Not implemented"); } public void setNotificationUri(ContentResolver cr, Uri uri) { // TODO Auto-generated method stub } }; } }; public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags) { SQLiteDatabase db = new SQLiteDatabase(); db.mFlags = flags; try { db.connection = new SQLiteConnection(path == null ? null : new File(path)); db.connection.open((flags & CREATE_IF_NECESSARY) > 0); String libPath = System.getProperty("sqlite4java.library.path"); String os = getOs(); String arch = getArch(os); String libName = String.format("pcre-%s-%s.so", os, arch); File extensionFile = new File(libPath, libName); if (fileExists(extensionFile)) { db.connection.setExtensionLoadingEnabled(true); db.connection.loadExtension(extensionFile, "sqlite3_regexp_init"); } } catch (com.almworks.sqlite4java.SQLiteException e) { diag_println(DIAG_OFF, "openDatabase", path); Log.e(TAG, "openDatabase", e); return null; } if (factory != null) db.mFactory = factory; return db; } private static String getArch(String os) { String arch = System.getProperty("os.arch"); if (arch == null) { Log.w(TAG, "os.arch is null"); arch = "x86"; } else { arch = arch.toLowerCase(Locale.US); if ("win32".equals(os) && "amd64".equals(arch)) { arch = "x64"; } } Log.d(TAG, "os.arch=" + arch); return arch; } private static String getOs() { String osname = System.getProperty("os.name"); String os; if (osname == null) { Log.w(TAG, "os.name is null"); os = "linux"; } else { osname = osname.toLowerCase(Locale.US); if (osname.startsWith("mac") || osname.startsWith("darwin") || osname.startsWith("os x")) { os = "osx"; } else if (osname.startsWith("windows")) { os = "win32"; } else { String runtimeName = System.getProperty("java.runtime.name"); if (runtimeName != null && runtimeName.toLowerCase(Locale.US).contains("android")) { os = "android"; } else { os = "linux"; } } } Log.d(TAG, "os.name=" + osname + "; os=" + os); return os; } public static SQLiteDatabase openOrCreateDatabase(File file, CursorFactory factory) { return openOrCreateDatabase(file.getPath(), factory); } public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory) { return openDatabase(path, factory, CREATE_IF_NECESSARY); } public static SQLiteDatabase create(CursorFactory factory) { // This is a magic string with special meaning for SQLite. return openDatabase(":memory:", factory, CREATE_IF_NECESSARY); } public boolean isOpen() { return connection.isOpen(); } public void close() { connection.dispose(); } public void execSQL(String sql) { try { connection.exec(sql); } catch (com.almworks.sqlite4java.SQLiteException e) { Log.e(TAG, "execSQL", e); } } SQLiteStatement doBind(SQLiteStatement stm, Object[] bindArgs) throws com.almworks.sqlite4java.SQLiteException { if (bindArgs != null) { clearBindings(stm); int size = bindArgs.length; for (int i = 0; i < size; i++) { this.addToBindArgs(i + 1, bindArgs[i]); } if (size > 0) for (int index : mBindArgs.keySet()) { Object value = mBindArgs.get(index); if (value == null) { stm.bindNull(index); } else if (value instanceof Double || value instanceof Float) { stm.bind(index, ((Number) value).doubleValue()); } else if (value instanceof Number) { stm.bind(index, ((Number) value).longValue()); } else if (value instanceof Boolean) { Boolean bool = (Boolean)value; stm.bind(index, (bool) ? 1 : 0); } else if (value instanceof byte[]){ stm.bind(index, (byte[]) value); } else { stm.bind(index, value.toString()); } } } return stm; } HashMap<Integer, Object> mBindArgs = null; void clearBindings(SQLiteStatement stm) throws com.almworks.sqlite4java.SQLiteException { if (stm != null && mBindArgs != null) stm.clearBindings(); mBindArgs = null; } void addToBindArgs(int index, Object value) { if (mBindArgs == null) { mBindArgs = new HashMap<Integer, Object>(); } mBindArgs.put(index, value); } public boolean execSQL(String sql, Object[] bindArgs) { SQLiteStatement stm = null; try { stm = doBind(connection.prepare(sql), bindArgs); stm.step(); return true; } catch (com.almworks.sqlite4java.SQLiteException e) { Log.d(TAG, String.format("%s : %s", stm, e.getMessage())); Log.e(TAG, "execSQL", e); } finally { if (stm != null) stm.dispose(); } return false; } public Cursor rawQuery(String sql, String[] selectionArgs) throws SQLiteException { return rawQueryWithFactory(null, sql, selectionArgs, null); } public Cursor rawQueryWithFactory( CursorFactory cursorFactory, String sql, String[] selectionArgs, String editTable) throws SQLiteException { SQLiteStatement stm = null; try { stm = doBind(connection.prepare(sql), selectionArgs); return (cursorFactory != null ? cursorFactory : mFactory) .newCursor(connection, stm); } catch (com.almworks.sqlite4java.SQLiteException e) { Log.d(TAG, String.format("%s : %s", stm, e.getMessage())); throw new SQLiteException(2, TAG, e); } } public long insert(String table, Object nullColumnHack, ContentValues values) { StringBuilder sql = new StringBuilder(); sql.append("INSERT"); // sql.append(CONFLICT_VALUES[conflictAlgorithm]); sql.append(" INTO "); sql.append(table); sql.append('('); Object[] bindArgs = null; int size = (values != null && values.size() > 0) ? values.size() : 0; if (size > 0) { bindArgs = new Object[size]; int i = 0; for (String colName : values.keySet()) { sql.append((i > 0) ? "," : ""); sql.append(colName); bindArgs[i++] = values.get(colName); } sql.append(')'); sql.append(" VALUES ("); for (i = 0; i < size; i++) { sql.append((i > 0) ? ",?" : "?"); } } else { sql.append(nullColumnHack + ") VALUES (NULL"); } sql.append(')'); if (execSQL(sql.toString(), bindArgs)) try { return connection.getLastInsertId(); } catch (com.almworks.sqlite4java.SQLiteException e) {} return -1; } public long insertOrThrow(String table, String nullColumnHack, ContentValues values) throws SQLException { return insert(table, nullColumnHack, values); } public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { if (values == null || values.size() == 0) { throw new IllegalArgumentException("Empty values"); } StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); // sql.append(CONFLICT_VALUES[conflictAlgorithm]); sql.append(table); sql.append(" SET "); // move all bind args to one array int setValuesSize = values.size(); int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length); Object[] bindArgs = new Object[bindArgsSize]; int i = 0; for (String colName : values.keySet()) { sql.append((i > 0) ? "," : ""); sql.append(colName); bindArgs[i++] = values.get(colName); sql.append("=?"); } if (whereArgs != null) { for (i = setValuesSize; i < bindArgsSize; i++) { bindArgs[i] = whereArgs[i - setValuesSize]; } } if (notNullOrEmpty(whereClause)) { sql.append(" WHERE "); sql.append(whereClause); } if (execSQL(sql.toString(), bindArgs)) try { return connection.getChanges(); } catch (com.almworks.sqlite4java.SQLiteException e) {} return -1; } public int delete(String table, String whereClause, String[] whereArgs) { String sql = "DELETE FROM " + table + (notNullOrEmpty(whereClause) ? " WHERE " + whereClause : ""); if (execSQL(sql, whereArgs)) try { return connection.getChanges(); } catch (com.almworks.sqlite4java.SQLiteException e) {} return -1; } public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) { return query(false, table, columns, selection, selectionArgs, groupBy, having, orderBy, null /* limit */); } public Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) throws SQLiteException { try { String sql = SQLiteQueryBuilder.buildQueryString( distinct, table, columns, selection, groupBy, having, orderBy, limit); return rawQuery(sql, selectionArgs); } catch (SQLiteException e) { Log.e(TAG, "query", e); return null; } } /** * Sets the database version. * * @param version the new database version */ public void setVersion(int version) { execSQL("PRAGMA user_version = " + version); } /** * Gets the database version. * * @return the database version */ public int getVersion() { Cursor cursor = null; lock(); try { cursor = rawQuery("PRAGMA user_version;", null); int version = 0; if (cursor != null && cursor.moveToFirst()) { version = cursor.getInt(0); } return version; } catch (SQLiteException e) { Log.e(TAG, "getVersion", e); return 0; } finally { if (cursor != null) cursor.close(); unlock(); } } /** * Control whether or not the SQLiteDatabase is made thread-safe by using locks * around critical sections. This is pretty expensive, so if you know that your * DB will only be used by a single thread then you should set this to false. * The default is true. * @param lockingEnabled set to true to enable locks, false otherwise */ public void setLockingEnabled(boolean lockingEnabled) { mLockingEnabled = lockingEnabled; } /** * If set then the SQLiteDatabase is made thread-safe by using locks * around critical sections */ private boolean mLockingEnabled = false; void lock() { if (mLockingEnabled) { } } void unlock() { if (mLockingEnabled) { } } /** * Sets the locale for this database. Does nothing if this database has * the NO_LOCALIZED_COLLATORS flag set or was opened read only. * @throws SQLException if the locale could not be set. The most common reason * for this is that there is no collator available for the locale you requested. * In this case the database remains unchanged. */ public void setLocale(Locale locale) { lock(); try { // native_setLocale(locale.toString(), mFlags); } finally { unlock(); } } public void beginTransaction() { mTransactionIsSuccessful = false; execSQL("BEGIN EXCLUSIVE;"); } private boolean mTransactionIsSuccessful = false; public void setTransactionSuccessful() { mTransactionIsSuccessful = true; } public void endTransaction() { if (mTransactionIsSuccessful) { execSQL("COMMIT;"); } else { execSQL("ROLLBACK;"); } } /** * Finds the name of the first table, which is editable. * * @param tables a list of tables * @return the first table listed */ public static String findEditTable(String tables) { if (notNullOrEmpty(tables)) { // find the first word terminated by either a space or a comma int spacepos = tables.indexOf(' '); int commapos = tables.indexOf(','); if (spacepos > 0 && (spacepos < commapos || commapos < 0)) { return tables.substring(0, spacepos); } else if (commapos > 0 && (commapos < spacepos || spacepos < 0) ) { return tables.substring(0, commapos); } return tables; } else { throw new IllegalStateException("Invalid tables"); } } public static final int OPEN_READWRITE = 0x00000000; public static final int OPEN_READONLY = 0x00000001; private static final int OPEN_READ_MASK = 0x00000001; private int mFlags; public boolean isReadOnly() { return (mFlags & OPEN_READ_MASK) == OPEN_READONLY; } }