diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:05:43 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:05:43 -0800 |
commit | f013e1afd1e68af5e3b868c26a653bbfb39538f8 (patch) | |
tree | 7ad6c8fd9c7b55f4b4017171dec1cb760bbd26bf /core/java/android/database | |
parent | e70cfafe580c6f2994c4827cd8a534aabf3eb05c (diff) | |
download | frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.zip frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.tar.gz frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.tar.bz2 |
Code drop from //branches/cupcake/...@124589
Diffstat (limited to 'core/java/android/database')
-rw-r--r-- | core/java/android/database/AbstractCursor.java | 21 | ||||
-rw-r--r-- | core/java/android/database/AbstractWindowedCursor.java | 2 | ||||
-rw-r--r-- | core/java/android/database/CursorWindow.java | 9 | ||||
-rw-r--r-- | core/java/android/database/DatabaseUtils.java | 15 | ||||
-rw-r--r-- | core/java/android/database/sqlite/SQLiteCursor.java | 174 | ||||
-rw-r--r-- | core/java/android/database/sqlite/SQLiteDatabase.java | 168 | ||||
-rw-r--r-- | core/java/android/database/sqlite/SQLiteOpenHelper.java | 2 | ||||
-rw-r--r-- | core/java/android/database/sqlite/SQLiteProgram.java | 4 | ||||
-rw-r--r-- | core/java/android/database/sqlite/SQLiteQuery.java | 56 |
9 files changed, 399 insertions, 52 deletions
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java index e81f7f8..76f0860 100644 --- a/core/java/android/database/AbstractCursor.java +++ b/core/java/android/database/AbstractCursor.java @@ -21,6 +21,10 @@ import android.net.Uri; import android.util.Config; import android.util.Log; import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; import java.lang.ref.WeakReference; import java.lang.UnsupportedOperationException; @@ -457,9 +461,24 @@ public abstract class AbstractCursor implements CrossProcessCursor { mContentObservable.unregisterObserver(observer); } } - + + /** + * @hide pending API council approval + */ + protected void notifyDataSetChange() { + mDataSetObservable.notifyChanged(); + } + + /** + * @hide pending API council approval + */ + protected DataSetObservable getDataSetObservable() { + return mDataSetObservable; + + } public void registerDataSetObserver(DataSetObserver observer) { mDataSetObservable.registerObserver(observer); + } public void unregisterDataSetObserver(DataSetObserver observer) { diff --git a/core/java/android/database/AbstractWindowedCursor.java b/core/java/android/database/AbstractWindowedCursor.java index 1ec4312..4ac0aef 100644 --- a/core/java/android/database/AbstractWindowedCursor.java +++ b/core/java/android/database/AbstractWindowedCursor.java @@ -172,7 +172,7 @@ public abstract class AbstractWindowedCursor extends AbstractCursor super.checkPosition(); if (mWindow == null) { - throw new StaleDataException("This cursor has changed, you must call requery()"); + throw new StaleDataException("Access closed cursor"); } } diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java index 72dc3a9..8e26730 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -409,8 +409,13 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * change across a call to clear(). */ public void clear() { - mStartPos = 0; - native_clear(); + acquireReference(); + try { + mStartPos = 0; + native_clear(); + } finally { + releaseReference(); + } } /** Clears out the native side of things */ diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index ab0dc3f..2ff7294 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -241,6 +241,21 @@ public class DatabaseUtils { } /** + * Concatenates two SQL WHERE clauses, handling empty or null values. + * @hide + */ + public static String concatenateWhere(String a, String b) { + if (TextUtils.isEmpty(a)) { + return b; + } + if (TextUtils.isEmpty(b)) { + return a; + } + + return "(" + a + ") AND (" + b + ")"; + } + + /** * return the collation key * @param name * @return the collation key diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java index ae2fc95..70b9b83 100644 --- a/core/java/android/database/sqlite/SQLiteCursor.java +++ b/core/java/android/database/sqlite/SQLiteCursor.java @@ -18,7 +18,12 @@ package android.database.sqlite; import android.database.AbstractWindowedCursor; import android.database.CursorWindow; +import android.database.DataSetObserver; import android.database.SQLException; + +import android.os.Handler; +import android.os.Message; +import android.os.Process; import android.text.TextUtils; import android.util.Config; import android.util.Log; @@ -26,6 +31,7 @@ import android.util.Log; import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; /** * A Cursor implementation that exposes results from a query on a @@ -59,7 +65,131 @@ public class SQLiteCursor extends AbstractWindowedCursor { /** Used to find out where a cursor was allocated in case it never got * released. */ private StackTraceElement[] mStackTraceElements; - + + /** + * mMaxRead is the max items that each cursor window reads + * default to a very high value + */ + private int mMaxRead = Integer.MAX_VALUE; + private int mInitialRead = Integer.MAX_VALUE; + private int mCursorState = 0; + private ReentrantLock mLock = null; + private boolean mPendingData = false; + + /** + * support for a cursor variant that doesn't always read all results + * initialRead is the initial number of items that cursor window reads + * if query contains more than this number of items, a thread will be + * created and handle the left over items so that caller can show + * results as soon as possible + * @param initialRead initial number of items that cursor read + * @param maxRead leftover items read at maxRead items per time + * @hide + */ + public void setLoadStyle(int initialRead, int maxRead) { + mMaxRead = maxRead; + mInitialRead = initialRead; + mLock = new ReentrantLock(true); + } + + private void queryThreadLock() { + if (mLock != null) { + mLock.lock(); + } + } + + private void queryThreadUnlock() { + if (mLock != null) { + mLock.unlock(); + } + } + + + /** + * @hide + */ + final private class QueryThread implements Runnable { + private final int mThreadState; + QueryThread(int version) { + mThreadState = version; + } + private void sendMessage() { + if (mNotificationHandler != null) { + mNotificationHandler.sendEmptyMessage(1); + mPendingData = false; + } else { + mPendingData = true; + } + + } + public void run() { + // use cached mWindow, to avoid get null mWindow + CursorWindow cw = mWindow; + Process.setThreadPriority(Process.myTid(), Process.THREAD_PRIORITY_BACKGROUND); + // the cursor's state doesn't change + while (true) { + mLock.lock(); + if (mCursorState != mThreadState) { + mLock.unlock(); + break; + } + try { + int count = mQuery.fillWindow(cw, mMaxRead, mCount); + // return -1 means not finished + if (count != 0) { + if (count == NO_COUNT){ + mCount += mMaxRead; + sendMessage(); + } else { + mCount = count; + sendMessage(); + break; + } + } else { + break; + } + } catch (Exception e) { + // end the tread when the cursor is close + break; + } finally { + mLock.unlock(); + } + } + } + } + + /** + * @hide + */ + protected class MainThreadNotificationHandler extends Handler { + public void handleMessage(Message msg) { + notifyDataSetChange(); + } + } + + /** + * @hide + */ + protected MainThreadNotificationHandler mNotificationHandler; + + public void registerDataSetObserver(DataSetObserver observer) { + super.registerDataSetObserver(observer); + if ((Integer.MAX_VALUE != mMaxRead || Integer.MAX_VALUE != mInitialRead) && + mNotificationHandler == null) { + queryThreadLock(); + try { + mNotificationHandler = new MainThreadNotificationHandler(); + if (mPendingData) { + notifyDataSetChange(); + mPendingData = false; + } + } finally { + queryThreadUnlock(); + } + } + + } + /** * Execute a query and provide access to its result set through a Cursor * interface. For a query such as: {@code SELECT name, birth, phone FROM @@ -146,11 +276,22 @@ public class SQLiteCursor extends AbstractWindowedCursor { // If there isn't a window set already it will only be accessed locally mWindow = new CursorWindow(true /* the window is local only */); } else { - mWindow.clear(); + mCursorState++; + queryThreadLock(); + try { + mWindow.clear(); + } finally { + queryThreadUnlock(); + } } - - // mWindow must be cleared - mCount = mQuery.fillWindow(mWindow, startPos); + mWindow.setStartPosition(startPos); + mCount = mQuery.fillWindow(mWindow, mInitialRead, 0); + // return -1 means not finished + if (mCount == NO_COUNT){ + mCount = startPos + mInitialRead; + Thread t = new Thread(new QueryThread(mCursorState), "query thread"); + t.start(); + } } @Override @@ -344,6 +485,7 @@ public class SQLiteCursor extends AbstractWindowedCursor { private void deactivateCommon() { if (Config.LOGV) Log.v(TAG, "<<< Releasing cursor " + this); + mCursorState = 0; if (mWindow != null) { mWindow.close(); mWindow = null; @@ -368,6 +510,9 @@ public class SQLiteCursor extends AbstractWindowedCursor { @Override public boolean requery() { + if (isClosed()) { + return false; + } long timeStart = 0; if (Config.LOGV) { timeStart = System.currentTimeMillis(); @@ -385,8 +530,13 @@ public class SQLiteCursor extends AbstractWindowedCursor { // This one will recreate the temp table, and get its count mDriver.cursorRequeried(this); mCount = NO_COUNT; - // Requery the program that runs over the temp table - mQuery.requery(); + mCursorState++; + queryThreadLock(); + try { + mQuery.requery(); + } finally { + queryThreadUnlock(); + } } finally { mDatabase.unlock(); } @@ -405,9 +555,15 @@ public class SQLiteCursor extends AbstractWindowedCursor { } @Override - public void setWindow(CursorWindow window) { + public void setWindow(CursorWindow window) { if (mWindow != null) { - mWindow.close(); + mCursorState++; + queryThreadLock(); + try { + mWindow.close(); + } finally { + queryThreadUnlock(); + } mCount = NO_COUNT; } mWindow = window; diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index e497190..fa062c8 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -54,6 +54,69 @@ public class SQLiteDatabase extends SQLiteClosable { private final static String TAG = "Database"; /** + * Algorithms used in ON CONFLICT clause + * http://www.sqlite.org/lang_conflict.html + * @hide + */ + public enum ConflictAlgorithm { + /** + * When a constraint violation occurs, an immediate ROLLBACK occurs, + * thus ending the current transaction, and the command aborts with a + * return code of SQLITE_CONSTRAINT. If no transaction is active + * (other than the implied transaction that is created on every command) + * then this algorithm works the same as ABORT. + */ + ROLLBACK("ROLLBACK"), + + /** + * When a constraint violation occurs,no ROLLBACK is executed + * so changes from prior commands within the same transaction + * are preserved. This is the default behavior. + */ + ABORT("ABORT"), + + /** + * When a constraint violation occurs, the command aborts with a return + * code SQLITE_CONSTRAINT. But any changes to the database that + * the command made prior to encountering the constraint violation + * are preserved and are not backed out. + */ + FAIL("FAIL"), + + /** + * When a constraint violation occurs, the one row that contains + * the constraint violation is not inserted or changed. + * But the command continues executing normally. Other rows before and + * after the row that contained the constraint violation continue to be + * inserted or updated normally. No error is returned. + */ + IGNORE("IGNORE"), + + /** + * When a UNIQUE constraint violation occurs, the pre-existing rows that + * are causing the constraint violation are removed prior to inserting + * or updating the current row. Thus the insert or update always occurs. + * The command continues executing normally. No error is returned. + * If a NOT NULL constraint violation occurs, the NULL value is replaced + * by the default value for that column. If the column has no default + * value, then the ABORT algorithm is used. If a CHECK constraint + * violation occurs then the IGNORE algorithm is used. When this conflict + * resolution strategy deletes rows in order to satisfy a constraint, + * it does not invoke delete triggers on those rows. + * This behavior might change in a future release. + */ + REPLACE("REPLACE"); + + private final String mValue; + ConflictAlgorithm(String value) { + mValue = value; + } + public String value() { + return mValue; + } + } + + /** * Maximum Length Of A LIKE Or GLOB Pattern * The pattern matching algorithm used in the default LIKE and GLOB implementation * of SQLite can exhibit O(N^2) performance (where N is the number of characters in @@ -437,8 +500,26 @@ public class SQLiteDatabase extends SQLiteClosable { * successful so far. Do not call setTransactionSuccessful before calling this. When this * returns a new transaction will have been created but not marked as successful. * @return true if the transaction was yielded + * @deprecated if the db is locked more than once (becuase of nested transactions) then the lock + * will not be yielded. Use yieldIfContendedSafely instead. */ public boolean yieldIfContended() { + return yieldIfContendedHelper(false /* do not check yielding */); + } + + /** + * Temporarily end the transaction to let other threads run. The transaction is assumed to be + * successful so far. Do not call setTransactionSuccessful before calling this. When this + * returns a new transaction will have been created but not marked as successful. This assumes + * that there are no nested transactions (beginTransaction has only been called once) and will + * through an exception if that is not the case. + * @return true if the transaction was yielded + */ + public boolean yieldIfContendedSafely() { + return yieldIfContendedHelper(true /* check yielding */); + } + + private boolean yieldIfContendedHelper(boolean checkFullyYielded) { if (mLock.getQueueLength() == 0) { // Reset the lock acquire time since we know that the thread was willing to yield // the lock at this time. @@ -448,6 +529,12 @@ public class SQLiteDatabase extends SQLiteClosable { } setTransactionSuccessful(); endTransaction(); + if (checkFullyYielded) { + if (this.isDbLockedByCurrentThread()) { + throw new IllegalStateException( + "Db locked more than once. yielfIfContended cannot yield"); + } + } beginTransaction(); return true; } @@ -1031,6 +1118,28 @@ public class SQLiteDatabase extends SQLiteClosable { } /** + * Runs the provided SQL and returns a cursor over the result set. + * The cursor will read an initial set of rows and the return to the caller. + * It will continue to read in batches and send data changed notifications + * when the later batches are ready. + * @param sql the SQL query. The SQL string must not be ; terminated + * @param selectionArgs You may include ?s in where clause in the query, + * which will be replaced by the values from selectionArgs. The + * values will be bound as Strings. + * @param initialRead set the initial count of items to read from the cursor + * @param maxRead set the count of items to read on each iteration after the first + * @return A {@link Cursor} object, which is positioned before the first entry + * @hide pending API council approval + */ + public Cursor rawQuery(String sql, String[] selectionArgs, + int initialRead, int maxRead) { + SQLiteCursor c = (SQLiteCursor)rawQueryWithFactory( + null, sql, selectionArgs, null); + c.setLoadStyle(initialRead, maxRead); + return c; + } + + /** * Convenience method for inserting a row into the database. * * @param table the table to insert the row into @@ -1044,7 +1153,7 @@ public class SQLiteDatabase extends SQLiteClosable { */ public long insert(String table, String nullColumnHack, ContentValues values) { try { - return insertOrReplace(table, nullColumnHack, values, false); + return insertWithOnConflict(table, nullColumnHack, values, null); } catch (SQLException e) { Log.e(TAG, "Error inserting " + values, e); return -1; @@ -1066,7 +1175,7 @@ public class SQLiteDatabase extends SQLiteClosable { */ public long insertOrThrow(String table, String nullColumnHack, ContentValues values) throws SQLException { - return insertOrReplace(table, nullColumnHack, values, false) ; + return insertWithOnConflict(table, nullColumnHack, values, null); } /** @@ -1082,7 +1191,8 @@ public class SQLiteDatabase extends SQLiteClosable { */ public long replace(String table, String nullColumnHack, ContentValues initialValues) { try { - return insertOrReplace(table, nullColumnHack, initialValues, true); + return insertWithOnConflict(table, nullColumnHack, initialValues, + ConflictAlgorithm.REPLACE); } catch (SQLException e) { Log.e(TAG, "Error inserting " + initialValues, e); return -1; @@ -1103,22 +1213,38 @@ public class SQLiteDatabase extends SQLiteClosable { */ public long replaceOrThrow(String table, String nullColumnHack, ContentValues initialValues) throws SQLException { - return insertOrReplace(table, nullColumnHack, initialValues, true); + return insertWithOnConflict(table, nullColumnHack, initialValues, + ConflictAlgorithm.REPLACE); } - private long insertOrReplace(String table, String nullColumnHack, - ContentValues initialValues, boolean allowReplace) { + /** + * General method for inserting a row into the database. + * + * @param table the table to insert the row into + * @param nullColumnHack SQL doesn't allow inserting a completely empty row, + * so if initialValues is empty this column will explicitly be + * assigned a NULL value + * @param initialValues this map contains the initial column values for the + * row. The keys should be the column names and the values the + * column values + * @param algorithm {@link ConflictAlgorithm} for insert conflict resolver + * @return the row ID of the newly inserted row, or -1 if an error occurred + * @hide + */ + public long insertWithOnConflict(String table, String nullColumnHack, + ContentValues initialValues, ConflictAlgorithm algorithm) { if (!isOpen()) { throw new IllegalStateException("database not open"); } // Measurements show most sql lengths <= 152 StringBuilder sql = new StringBuilder(152); - sql.append("INSERT "); - if (allowReplace) { - sql.append("OR REPLACE "); + sql.append("INSERT"); + if (algorithm != null) { + sql.append(" OR "); + sql.append(algorithm.value()); } - sql.append("INTO "); + sql.append(" INTO "); sql.append(table); // Measurements show most values lengths < 40 StringBuilder values = new StringBuilder(40); @@ -1241,6 +1367,23 @@ public class SQLiteDatabase extends SQLiteClosable { * @return the number of rows affected */ public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { + return updateWithOnConflict(table, values, whereClause, whereArgs, null); + } + + /** + * Convenience method for updating rows in the database. + * + * @param table the table to update in + * @param values a map from column names to new column values. null is a + * valid value that will be translated to NULL. + * @param whereClause the optional WHERE clause to apply when updating. + * Passing null will update all rows. + * @param algorithm {@link ConflictAlgorithm} for update conflict resolver + * @return the number of rows affected + * @hide + */ + public int updateWithOnConflict(String table, ContentValues values, + String whereClause, String[] whereArgs, ConflictAlgorithm algorithm) { if (!isOpen()) { throw new IllegalStateException("database not open"); } @@ -1251,6 +1394,11 @@ public class SQLiteDatabase extends SQLiteClosable { StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); + if (algorithm != null) { + sql.append(" OR "); + sql.append(algorithm.value()); + } + sql.append(table); sql.append(" SET "); diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java index f6872ac..35bf645 100644 --- a/core/java/android/database/sqlite/SQLiteOpenHelper.java +++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java @@ -26,8 +26,6 @@ import android.util.Log; * optionally {@link #onOpen}, and this class takes care of opening the database * if it exists, creating it if it does not, and upgrading it as necessary. * Transactions are used to make sure the database is always in a sensible state. - * - * @see com.google.provider.NotePad.NotePadProvider */ public abstract class SQLiteOpenHelper { private static final String TAG = SQLiteOpenHelper.class.getSimpleName(); diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index e0341a2..f89c87d 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -239,7 +239,9 @@ public abstract class SQLiteProgram extends SQLiteClosable { Log.d(TAG, " " + ste); } } - onAllReferencesReleased(); + // when in finalize() it is already removed from weakhashmap + // so it is safe to not removed itself from db + onAllReferencesReleasedFromContainer(); } } diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java index 40855b6..22c53ab 100644 --- a/core/java/android/database/sqlite/SQLiteQuery.java +++ b/core/java/android/database/sqlite/SQLiteQuery.java @@ -40,9 +40,8 @@ public class SQLiteQuery extends SQLiteProgram { * Create a persistent query object. * * @param db The database that this query object is associated with - * @param query The SQL string for this query. It must include "INDEX -1 - * OFFSET ?" at the end - * @param offsetIndex The 1-based index to the OFFSET parameter + * @param query The SQL string for this query. + * @param offsetIndex The 1-based index to the OFFSET parameter, */ /* package */ SQLiteQuery(SQLiteDatabase db, String query, int offsetIndex, String[] bindArgs) { super(db, query); @@ -59,24 +58,28 @@ public class SQLiteQuery extends SQLiteProgram { * @param startPos The position to start reading rows from * @return number of total rows in the query */ - /* package */ int fillWindow(CursorWindow window, int startPos) { - if (startPos < 0) { - throw new IllegalArgumentException("startPos should > 0"); - } - window.setStartPosition(startPos); + /* package */ int fillWindow(CursorWindow window, + int maxRead, int lastPos) { mDatabase.lock(); try { acquireReference(); - window.acquireReference(); - return native_fill_window(window, startPos, mOffsetIndex); - } catch (IllegalStateException e){ - // simply ignore it - return 0; - } catch (SQLiteDatabaseCorruptException e) { - mDatabase.onCorruption(); - throw e; + try { + window.acquireReference(); + // if the start pos is not equal to 0, then most likely window is + // too small for the data set, loading by another thread + // is not safe in this situation. the native code will ignore maxRead + return native_fill_window(window, window.getStartPosition(), mOffsetIndex, + maxRead, lastPos); + } catch (IllegalStateException e){ + // simply ignore it + return 0; + } catch (SQLiteDatabaseCorruptException e) { + mDatabase.onCorruption(); + throw e; + } finally { + window.releaseReference(); + } } finally { - window.releaseReference(); releaseReference(); mDatabase.unlock(); } @@ -113,7 +116,13 @@ public class SQLiteQuery extends SQLiteProgram { releaseReference(); } } - + + /** {@hide pending API Council approval} */ + @Override + public String toString() { + return "SQLiteQuery: " + mQuery; + } + @Override public void close() { super.close(); @@ -124,11 +133,6 @@ public class SQLiteQuery extends SQLiteProgram { * Called by SQLiteCursor when it is requeried. */ /* package */ void requery() { - boolean oldMClosed = mClosed; - if (mClosed) { - mClosed = false; - compile(mQuery, false); - } if (mBindArgs != null) { int len = mBindArgs.length; try { @@ -136,8 +140,7 @@ public class SQLiteQuery extends SQLiteProgram { super.bindString(i + 1, mBindArgs[i]); } } catch (SQLiteMisuseException e) { - StringBuilder errMsg = new StringBuilder - ("old mClosed " + oldMClosed + " mQuery " + mQuery); + StringBuilder errMsg = new StringBuilder("mQuery " + mQuery); for (int i = 0; i < len; i++) { errMsg.append(" "); errMsg.append(mBindArgs[i]); @@ -174,7 +177,8 @@ public class SQLiteQuery extends SQLiteProgram { if (!mClosed) super.bindString(index, value); } - private final native int native_fill_window(CursorWindow window, int startPos, int offsetParam); + private final native int native_fill_window(CursorWindow window, + int startPos, int offsetParam, int maxRead, int lastPos); private final native int native_column_count(); |