diff options
Diffstat (limited to 'core/java/android/database')
9 files changed, 560 insertions, 130 deletions
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index 72f62fd..710bd53 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -19,6 +19,8 @@ package android.database.sqlite; import dalvik.system.BlockGuard; import dalvik.system.CloseGuard; +import android.content.CancelationSignal; +import android.content.OperationCanceledException; import android.database.Cursor; import android.database.CursorWindow; import android.database.DatabaseUtils; @@ -82,7 +84,7 @@ import java.util.regex.Pattern; * * @hide */ -public final class SQLiteConnection { +public final class SQLiteConnection implements CancelationSignal.OnCancelListener { private static final String TAG = "SQLiteConnection"; private static final boolean DEBUG = false; @@ -108,6 +110,12 @@ public final class SQLiteConnection { private boolean mOnlyAllowReadOnlyOperations; + // The number of times attachCancelationSignal has been called. + // Because SQLite statement execution can be re-entrant, we keep track of how many + // times we have attempted to attach a cancelation signal to the connection so that + // we can ensure that we detach the signal at the right time. + private int mCancelationSignalAttachCount; + private static native int nativeOpen(String path, int openFlags, String label, boolean enableTrace, boolean enableProfile); private static native void nativeClose(int connectionPtr); @@ -145,6 +153,8 @@ public final class SQLiteConnection { int connectionPtr, int statementPtr, int windowPtr, int startPos, int requiredPos, boolean countAllRows); private static native int nativeGetDbLookaside(int connectionPtr); + private static native void nativeCancel(int connectionPtr); + private static native void nativeResetCancel(int connectionPtr, boolean cancelable); private SQLiteConnection(SQLiteConnectionPool pool, SQLiteDatabaseConfiguration configuration, @@ -345,11 +355,14 @@ public final class SQLiteConnection { * * @param sql The SQL statement to execute. * @param bindArgs The arguments to bind, or null if none. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. + * @throws OperationCanceledException if the operation was canceled. */ - public void execute(String sql, Object[] bindArgs) { + public void execute(String sql, Object[] bindArgs, + CancelationSignal cancelationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -361,7 +374,12 @@ public final class SQLiteConnection { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); applyBlockGuardPolicy(statement); - nativeExecute(mConnectionPtr, statement.mStatementPtr); + attachCancelationSignal(cancelationSignal); + try { + nativeExecute(mConnectionPtr, statement.mStatementPtr); + } finally { + detachCancelationSignal(cancelationSignal); + } } finally { releasePreparedStatement(statement); } @@ -378,13 +396,16 @@ public final class SQLiteConnection { * * @param sql The SQL statement to execute. * @param bindArgs The arguments to bind, or null if none. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * @return The value of the first column in the first row of the result set * as a <code>long</code>, or zero if none. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. + * @throws OperationCanceledException if the operation was canceled. */ - public long executeForLong(String sql, Object[] bindArgs) { + public long executeForLong(String sql, Object[] bindArgs, + CancelationSignal cancelationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -396,7 +417,12 @@ public final class SQLiteConnection { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); applyBlockGuardPolicy(statement); - return nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr); + attachCancelationSignal(cancelationSignal); + try { + return nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr); + } finally { + detachCancelationSignal(cancelationSignal); + } } finally { releasePreparedStatement(statement); } @@ -413,13 +439,16 @@ public final class SQLiteConnection { * * @param sql The SQL statement to execute. * @param bindArgs The arguments to bind, or null if none. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * @return The value of the first column in the first row of the result set * as a <code>String</code>, or null if none. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. + * @throws OperationCanceledException if the operation was canceled. */ - public String executeForString(String sql, Object[] bindArgs) { + public String executeForString(String sql, Object[] bindArgs, + CancelationSignal cancelationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -431,7 +460,12 @@ public final class SQLiteConnection { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); applyBlockGuardPolicy(statement); - return nativeExecuteForString(mConnectionPtr, statement.mStatementPtr); + attachCancelationSignal(cancelationSignal); + try { + return nativeExecuteForString(mConnectionPtr, statement.mStatementPtr); + } finally { + detachCancelationSignal(cancelationSignal); + } } finally { releasePreparedStatement(statement); } @@ -449,14 +483,17 @@ public final class SQLiteConnection { * * @param sql The SQL statement to execute. * @param bindArgs The arguments to bind, or null if none. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * @return The file descriptor for a shared memory region that contains * the value of the first column in the first row of the result set as a BLOB, * or null if none. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. + * @throws OperationCanceledException if the operation was canceled. */ - public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs) { + public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs, + CancelationSignal cancelationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -469,9 +506,14 @@ public final class SQLiteConnection { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); applyBlockGuardPolicy(statement); - int fd = nativeExecuteForBlobFileDescriptor( - mConnectionPtr, statement.mStatementPtr); - return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null; + attachCancelationSignal(cancelationSignal); + try { + int fd = nativeExecuteForBlobFileDescriptor( + mConnectionPtr, statement.mStatementPtr); + return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null; + } finally { + detachCancelationSignal(cancelationSignal); + } } finally { releasePreparedStatement(statement); } @@ -489,12 +531,15 @@ public final class SQLiteConnection { * * @param sql The SQL statement to execute. * @param bindArgs The arguments to bind, or null if none. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * @return The number of rows that were changed. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. + * @throws OperationCanceledException if the operation was canceled. */ - public int executeForChangedRowCount(String sql, Object[] bindArgs) { + public int executeForChangedRowCount(String sql, Object[] bindArgs, + CancelationSignal cancelationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -507,8 +552,13 @@ public final class SQLiteConnection { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); applyBlockGuardPolicy(statement); - return nativeExecuteForChangedRowCount( - mConnectionPtr, statement.mStatementPtr); + attachCancelationSignal(cancelationSignal); + try { + return nativeExecuteForChangedRowCount( + mConnectionPtr, statement.mStatementPtr); + } finally { + detachCancelationSignal(cancelationSignal); + } } finally { releasePreparedStatement(statement); } @@ -526,12 +576,15 @@ public final class SQLiteConnection { * * @param sql The SQL statement to execute. * @param bindArgs The arguments to bind, or null if none. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * @return The row id of the last row that was inserted, or 0 if none. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. + * @throws OperationCanceledException if the operation was canceled. */ - public long executeForLastInsertedRowId(String sql, Object[] bindArgs) { + public long executeForLastInsertedRowId(String sql, Object[] bindArgs, + CancelationSignal cancelationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -544,8 +597,13 @@ public final class SQLiteConnection { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); applyBlockGuardPolicy(statement); - return nativeExecuteForLastInsertedRowId( - mConnectionPtr, statement.mStatementPtr); + attachCancelationSignal(cancelationSignal); + try { + return nativeExecuteForLastInsertedRowId( + mConnectionPtr, statement.mStatementPtr); + } finally { + detachCancelationSignal(cancelationSignal); + } } finally { releasePreparedStatement(statement); } @@ -571,14 +629,17 @@ public final class SQLiteConnection { * so that it does. Must be greater than or equal to <code>startPos</code>. * @param countAllRows True to count all rows that the query would return * regagless of whether they fit in the window. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * @return The number of rows that were counted during query execution. Might * not be all rows in the result set unless <code>countAllRows</code> is true. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. + * @throws OperationCanceledException if the operation was canceled. */ public int executeForCursorWindow(String sql, Object[] bindArgs, - CursorWindow window, int startPos, int requiredPos, boolean countAllRows) { + CursorWindow window, int startPos, int requiredPos, boolean countAllRows, + CancelationSignal cancelationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -597,14 +658,19 @@ public final class SQLiteConnection { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); applyBlockGuardPolicy(statement); - final long result = nativeExecuteForCursorWindow( - mConnectionPtr, statement.mStatementPtr, window.mWindowPtr, - startPos, requiredPos, countAllRows); - actualPos = (int)(result >> 32); - countedRows = (int)result; - filledRows = window.getNumRows(); - window.setStartPosition(actualPos); - return countedRows; + attachCancelationSignal(cancelationSignal); + try { + final long result = nativeExecuteForCursorWindow( + mConnectionPtr, statement.mStatementPtr, window.mWindowPtr, + startPos, requiredPos, countAllRows); + actualPos = (int)(result >> 32); + countedRows = (int)result; + filledRows = window.getNumRows(); + window.setStartPosition(actualPos); + return countedRows; + } finally { + detachCancelationSignal(cancelationSignal); + } } finally { releasePreparedStatement(statement); } @@ -685,6 +751,46 @@ public final class SQLiteConnection { recyclePreparedStatement(statement); } + private void attachCancelationSignal(CancelationSignal cancelationSignal) { + if (cancelationSignal != null) { + cancelationSignal.throwIfCanceled(); + + mCancelationSignalAttachCount += 1; + if (mCancelationSignalAttachCount == 1) { + // Reset cancelation flag before executing the statement. + nativeResetCancel(mConnectionPtr, true /*cancelable*/); + + // After this point, onCancel() may be called concurrently. + cancelationSignal.setOnCancelListener(this); + } + } + } + + private void detachCancelationSignal(CancelationSignal cancelationSignal) { + if (cancelationSignal != null) { + assert mCancelationSignalAttachCount > 0; + + mCancelationSignalAttachCount -= 1; + if (mCancelationSignalAttachCount == 0) { + // After this point, onCancel() cannot be called concurrently. + cancelationSignal.setOnCancelListener(null); + + // Reset cancelation flag after executing the statement. + nativeResetCancel(mConnectionPtr, false /*cancelable*/); + } + } + } + + // CancelationSignal.OnCancelationListener callback. + // This method may be called on a different thread than the executing statement. + // However, it will only be called between calls to attachCancelationSignal and + // detachCancelationSignal, while a statement is executing. We can safely assume + // that the SQLite connection is still alive. + @Override + public void onCancel() { + nativeCancel(mConnectionPtr); + } + private void bindArguments(PreparedStatement statement, Object[] bindArgs) { final int count = bindArgs != null ? bindArgs.length : 0; if (count != statement.mNumParameters) { @@ -822,8 +928,8 @@ public final class SQLiteConnection { long pageCount = 0; long pageSize = 0; try { - pageCount = executeForLong("PRAGMA page_count;", null); - pageSize = executeForLong("PRAGMA page_size;", null); + pageCount = executeForLong("PRAGMA page_count;", null, null); + pageSize = executeForLong("PRAGMA page_size;", null, null); } catch (SQLiteException ex) { // Ignore. } @@ -834,15 +940,15 @@ public final class SQLiteConnection { // the main database which we have already described. CursorWindow window = new CursorWindow("collectDbStats"); try { - executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false); + executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null); for (int i = 1; i < window.getNumRows(); i++) { String name = window.getString(i, 1); String path = window.getString(i, 2); pageCount = 0; pageSize = 0; try { - pageCount = executeForLong("PRAGMA " + name + ".page_count;", null); - pageSize = executeForLong("PRAGMA " + name + ".page_size;", null); + pageCount = executeForLong("PRAGMA " + name + ".page_count;", null, null); + pageSize = executeForLong("PRAGMA " + name + ".page_size;", null, null); } catch (SQLiteException ex) { // Ignore. } diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java index 5469213..d335738 100644 --- a/core/java/android/database/sqlite/SQLiteConnectionPool.java +++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java @@ -18,6 +18,8 @@ package android.database.sqlite; import dalvik.system.CloseGuard; +import android.content.CancelationSignal; +import android.content.OperationCanceledException; import android.database.sqlite.SQLiteDebug.DbStats; import android.os.SystemClock; import android.util.Log; @@ -282,13 +284,16 @@ public final class SQLiteConnectionPool implements Closeable { * @param sql If not null, try to find a connection that already has * the specified SQL statement in its prepared statement cache. * @param connectionFlags The connection request flags. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * @return The connection that was acquired, never null. * * @throws IllegalStateException if the pool has been closed. * @throws SQLiteException if a database error occurs. + * @throws OperationCanceledException if the operation was canceled. */ - public SQLiteConnection acquireConnection(String sql, int connectionFlags) { - return waitForConnection(sql, connectionFlags); + public SQLiteConnection acquireConnection(String sql, int connectionFlags, + CancelationSignal cancelationSignal) { + return waitForConnection(sql, connectionFlags, cancelationSignal); } /** @@ -497,7 +502,8 @@ public final class SQLiteConnectionPool implements Closeable { } // Might throw. - private SQLiteConnection waitForConnection(String sql, int connectionFlags) { + private SQLiteConnection waitForConnection(String sql, int connectionFlags, + CancelationSignal cancelationSignal) { final boolean wantPrimaryConnection = (connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0; @@ -505,6 +511,11 @@ public final class SQLiteConnectionPool implements Closeable { synchronized (mLock) { throwIfClosedLocked(); + // Abort if canceled. + if (cancelationSignal != null) { + cancelationSignal.throwIfCanceled(); + } + // Try to acquire a connection. SQLiteConnection connection = null; if (!wantPrimaryConnection) { @@ -538,6 +549,18 @@ public final class SQLiteConnectionPool implements Closeable { } else { mConnectionWaiterQueue = waiter; } + + if (cancelationSignal != null) { + final int nonce = waiter.mNonce; + cancelationSignal.setOnCancelListener(new CancelationSignal.OnCancelListener() { + @Override + public void onCancel() { + synchronized (mLock) { + cancelConnectionWaiterLocked(waiter, nonce); + } + } + }); + } } // Park the thread until a connection is assigned or the pool is closed. @@ -547,7 +570,9 @@ public final class SQLiteConnectionPool implements Closeable { for (;;) { // Detect and recover from connection leaks. if (mConnectionLeaked.compareAndSet(true, false)) { - wakeConnectionWaitersLocked(); + synchronized (mLock) { + wakeConnectionWaitersLocked(); + } } // Wait to be unparked (may already have happened), a timeout, or interruption. @@ -560,15 +585,16 @@ public final class SQLiteConnectionPool implements Closeable { synchronized (mLock) { throwIfClosedLocked(); - SQLiteConnection connection = waiter.mAssignedConnection; - if (connection != null) { - recycleConnectionWaiterLocked(waiter); - return connection; - } - - RuntimeException ex = waiter.mException; - if (ex != null) { + final SQLiteConnection connection = waiter.mAssignedConnection; + final RuntimeException ex = waiter.mException; + if (connection != null || ex != null) { + if (cancelationSignal != null) { + cancelationSignal.setOnCancelListener(null); + } recycleConnectionWaiterLocked(waiter); + if (connection != null) { + return connection; + } throw ex; // rethrow! } @@ -585,6 +611,40 @@ public final class SQLiteConnectionPool implements Closeable { } // Can't throw. + private void cancelConnectionWaiterLocked(ConnectionWaiter waiter, int nonce) { + if (waiter.mNonce != nonce) { + // Waiter already removed and recycled. + return; + } + + if (waiter.mAssignedConnection != null || waiter.mException != null) { + // Waiter is done waiting but has not woken up yet. + return; + } + + // Waiter must still be waiting. Dequeue it. + ConnectionWaiter predecessor = null; + ConnectionWaiter current = mConnectionWaiterQueue; + while (current != waiter) { + assert current != null; + predecessor = current; + current = current.mNext; + } + if (predecessor != null) { + predecessor.mNext = waiter.mNext; + } else { + mConnectionWaiterQueue = waiter.mNext; + } + + // Send the waiter an exception and unpark it. + waiter.mException = new OperationCanceledException(); + LockSupport.unpark(waiter.mThread); + + // Check whether removing this waiter will enable other waiters to make progress. + wakeConnectionWaitersLocked(); + } + + // Can't throw. private void logConnectionPoolBusyLocked(long waitMillis, int connectionFlags) { final Thread thread = Thread.currentThread(); StringBuilder msg = new StringBuilder(); @@ -826,6 +886,7 @@ public final class SQLiteConnectionPool implements Closeable { waiter.mSql = null; waiter.mAssignedConnection = null; waiter.mException = null; + waiter.mNonce += 1; mConnectionWaiterPool = waiter; } @@ -904,5 +965,6 @@ public final class SQLiteConnectionPool implements Closeable { public int mConnectionFlags; public SQLiteConnection mAssignedConnection; public RuntimeException mException; + public int mNonce; } } diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 9cb6480..7db7bfb 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -16,7 +16,9 @@ package android.database.sqlite; +import android.content.CancelationSignal; import android.content.ContentValues; +import android.content.OperationCanceledException; import android.content.res.Resources; import android.database.Cursor; import android.database.DatabaseErrorHandler; @@ -492,7 +494,7 @@ public class SQLiteDatabase extends SQLiteClosable { boolean exclusive) { getThreadSession().beginTransaction(exclusive ? SQLiteSession.TRANSACTION_MODE_EXCLUSIVE : SQLiteSession.TRANSACTION_MODE_IMMEDIATE, transactionListener, - getThreadDefaultConnectionFlags(false /*readOnly*/)); + getThreadDefaultConnectionFlags(false /*readOnly*/), null); } /** @@ -500,7 +502,7 @@ public class SQLiteDatabase extends SQLiteClosable { * are committed and rolled back. */ public void endTransaction() { - getThreadSession().endTransaction(); + getThreadSession().endTransaction(null); } /** @@ -597,7 +599,7 @@ public class SQLiteDatabase extends SQLiteClosable { } private boolean yieldIfContendedHelper(boolean throwIfUnsafe, long sleepAfterYieldDelay) { - return getThreadSession().yieldTransaction(sleepAfterYieldDelay, throwIfUnsafe); + return getThreadSession().yieldTransaction(sleepAfterYieldDelay, throwIfUnsafe, null); } /** @@ -935,7 +937,48 @@ public class SQLiteDatabase extends SQLiteClosable { String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) { return queryWithFactory(null, distinct, table, columns, selection, selectionArgs, - groupBy, having, orderBy, limit); + groupBy, having, orderBy, limit, null); + } + + /** + * Query the given URL, returning a {@link Cursor} over the result set. + * + * @param distinct true if you want each row to be unique, false otherwise. + * @param table The table name to compile the query against. + * @param columns A list of which columns to return. Passing null will + * return all columns, which is discouraged to prevent reading + * data from storage that isn't going to be used. + * @param selection A filter declaring which rows to return, formatted as an + * SQL WHERE clause (excluding the WHERE itself). Passing null + * will return all rows for the given table. + * @param selectionArgs You may include ?s in selection, which will be + * replaced by the values from selectionArgs, in order that they + * appear in the selection. The values will be bound as Strings. + * @param groupBy A filter declaring how to group rows, formatted as an SQL + * GROUP BY clause (excluding the GROUP BY itself). Passing null + * will cause the rows to not be grouped. + * @param having A filter declare which row groups to include in the cursor, + * if row grouping is being used, formatted as an SQL HAVING + * clause (excluding the HAVING itself). Passing null will cause + * all row groups to be included, and is required when row + * grouping is not being used. + * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause + * (excluding the ORDER BY itself). Passing null will use the + * default sort order, which may be unordered. + * @param limit Limits the number of rows returned by the query, + * formatted as LIMIT clause. Passing null denotes no LIMIT clause. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * If the operation is canceled, then {@link OperationCanceledException} will be thrown + * when the query is executed. + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. + * @see Cursor + */ + public Cursor query(boolean distinct, String table, String[] columns, + String selection, String[] selectionArgs, String groupBy, + String having, String orderBy, String limit, CancelationSignal cancelationSignal) { + return queryWithFactory(null, distinct, table, columns, selection, selectionArgs, + groupBy, having, orderBy, limit, cancelationSignal); } /** @@ -974,12 +1017,55 @@ public class SQLiteDatabase extends SQLiteClosable { boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) { + return queryWithFactory(cursorFactory, distinct, table, columns, selection, + selectionArgs, groupBy, having, orderBy, limit, null); + } + + /** + * Query the given URL, returning a {@link Cursor} over the result set. + * + * @param cursorFactory the cursor factory to use, or null for the default factory + * @param distinct true if you want each row to be unique, false otherwise. + * @param table The table name to compile the query against. + * @param columns A list of which columns to return. Passing null will + * return all columns, which is discouraged to prevent reading + * data from storage that isn't going to be used. + * @param selection A filter declaring which rows to return, formatted as an + * SQL WHERE clause (excluding the WHERE itself). Passing null + * will return all rows for the given table. + * @param selectionArgs You may include ?s in selection, which will be + * replaced by the values from selectionArgs, in order that they + * appear in the selection. The values will be bound as Strings. + * @param groupBy A filter declaring how to group rows, formatted as an SQL + * GROUP BY clause (excluding the GROUP BY itself). Passing null + * will cause the rows to not be grouped. + * @param having A filter declare which row groups to include in the cursor, + * if row grouping is being used, formatted as an SQL HAVING + * clause (excluding the HAVING itself). Passing null will cause + * all row groups to be included, and is required when row + * grouping is not being used. + * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause + * (excluding the ORDER BY itself). Passing null will use the + * default sort order, which may be unordered. + * @param limit Limits the number of rows returned by the query, + * formatted as LIMIT clause. Passing null denotes no LIMIT clause. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * If the operation is canceled, then {@link OperationCanceledException} will be thrown + * when the query is executed. + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. + * @see Cursor + */ + public Cursor queryWithFactory(CursorFactory cursorFactory, + boolean distinct, String table, String[] columns, + String selection, String[] selectionArgs, String groupBy, + String having, String orderBy, String limit, CancelationSignal cancelationSignal) { throwIfNotOpen(); // fail fast String sql = SQLiteQueryBuilder.buildQueryString( distinct, table, columns, selection, groupBy, having, orderBy, limit); - return rawQueryWithFactory( - cursorFactory, sql, selectionArgs, findEditTable(table)); + return rawQueryWithFactory(cursorFactory, sql, selectionArgs, + findEditTable(table), cancelationSignal); } /** @@ -1067,7 +1153,25 @@ public class SQLiteDatabase extends SQLiteClosable { * {@link Cursor}s are not synchronized, see the documentation for more details. */ public Cursor rawQuery(String sql, String[] selectionArgs) { - return rawQueryWithFactory(null, sql, selectionArgs, null); + return rawQueryWithFactory(null, sql, selectionArgs, null, null); + } + + /** + * Runs the provided SQL and returns a {@link Cursor} over the result set. + * + * @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 cancelationSignal A signal to cancel the operation in progress, or null if none. + * If the operation is canceled, then {@link OperationCanceledException} will be thrown + * when the query is executed. + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. + */ + public Cursor rawQuery(String sql, String[] selectionArgs, + CancelationSignal cancelationSignal) { + return rawQueryWithFactory(null, sql, selectionArgs, null, cancelationSignal); } /** @@ -1085,9 +1189,31 @@ public class SQLiteDatabase extends SQLiteClosable { public Cursor rawQueryWithFactory( CursorFactory cursorFactory, String sql, String[] selectionArgs, String editTable) { + return rawQueryWithFactory(cursorFactory, sql, selectionArgs, editTable, null); + } + + /** + * Runs the provided SQL and returns a cursor over the result set. + * + * @param cursorFactory the cursor factory to use, or null for the default factory + * @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 editTable the name of the first table, which is editable + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * If the operation is canceled, then {@link OperationCanceledException} will be thrown + * when the query is executed. + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. + */ + public Cursor rawQueryWithFactory( + CursorFactory cursorFactory, String sql, String[] selectionArgs, + String editTable, CancelationSignal cancelationSignal) { throwIfNotOpen(); // fail fast - SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable); + SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable, + cancelationSignal); return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory, selectionArgs); } @@ -1786,7 +1912,7 @@ public class SQLiteDatabase extends SQLiteClosable { */ void lockPrimaryConnection() { getThreadSession().beginTransaction(SQLiteSession.TRANSACTION_MODE_DEFERRED, - null, SQLiteConnectionPool.CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY); + null, SQLiteConnectionPool.CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY, null); } /** @@ -1795,7 +1921,7 @@ public class SQLiteDatabase extends SQLiteClosable { * @see #lockPrimaryConnection() */ void unlockPrimaryConnection() { - getThreadSession().endTransaction(); + getThreadSession().endTransaction(null); } @Override diff --git a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java index 52fd1d2..c490dc6 100644 --- a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java +++ b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java @@ -16,6 +16,7 @@ package android.database.sqlite; +import android.content.CancelationSignal; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase.CursorFactory; @@ -28,16 +29,19 @@ public class SQLiteDirectCursorDriver implements SQLiteCursorDriver { private final SQLiteDatabase mDatabase; private final String mEditTable; private final String mSql; + private final CancelationSignal mCancelationSignal; private SQLiteQuery mQuery; - public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable) { + public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable, + CancelationSignal cancelationSignal) { mDatabase = db; mEditTable = editTable; mSql = sql; + mCancelationSignal = cancelationSignal; } public Cursor query(CursorFactory factory, String[] selectionArgs) { - final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql); + final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancelationSignal); final Cursor cursor; try { query.bindAllArgsAsStrings(selectionArgs); diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index 8194458..f3da2a6 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -16,6 +16,7 @@ package android.database.sqlite; +import android.content.CancelationSignal; import android.database.DatabaseUtils; import java.util.Arrays; @@ -36,7 +37,8 @@ public abstract class SQLiteProgram extends SQLiteClosable { private final int mNumParameters; private final Object[] mBindArgs; - SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs) { + SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs, + CancelationSignal cancelationSignalForPrepare) { mDatabase = db; mSql = sql.trim(); @@ -54,7 +56,8 @@ public abstract class SQLiteProgram extends SQLiteClosable { boolean assumeReadOnly = (n == DatabaseUtils.STATEMENT_SELECT); SQLiteStatementInfo info = new SQLiteStatementInfo(); db.getThreadSession().prepare(mSql, - db.getThreadDefaultConnectionFlags(assumeReadOnly), info); + db.getThreadDefaultConnectionFlags(assumeReadOnly), + cancelationSignalForPrepare, info); mReadOnly = info.readOnly; mColumnNames = info.columnNames; mNumParameters = info.numParameters; diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java index 17aa886..df2e260 100644 --- a/core/java/android/database/sqlite/SQLiteQuery.java +++ b/core/java/android/database/sqlite/SQLiteQuery.java @@ -16,6 +16,8 @@ package android.database.sqlite; +import android.content.CancelationSignal; +import android.content.OperationCanceledException; import android.database.CursorWindow; import android.util.Log; @@ -29,8 +31,12 @@ import android.util.Log; public final class SQLiteQuery extends SQLiteProgram { private static final String TAG = "SQLiteQuery"; - SQLiteQuery(SQLiteDatabase db, String query) { - super(db, query, null); + private final CancelationSignal mCancelationSignal; + + SQLiteQuery(SQLiteDatabase db, String query, CancelationSignal cancelationSignal) { + super(db, query, null, cancelationSignal); + + mCancelationSignal = cancelationSignal; } /** @@ -44,6 +50,9 @@ public final class SQLiteQuery extends SQLiteProgram { * return regardless of whether they fit in the window. * @return Number of rows that were enumerated. Might not be all rows * unless countAllRows is true. + * + * @throws SQLiteException if an error occurs. + * @throws OperationCanceledException if the operation was canceled. */ int fillWindow(CursorWindow window, int startPos, int requiredPos, boolean countAllRows) { acquireReference(); @@ -51,7 +60,8 @@ public final class SQLiteQuery extends SQLiteProgram { window.acquireReference(); try { int numRows = getSession().executeForCursorWindow(getSql(), getBindArgs(), - window, startPos, requiredPos, countAllRows, getConnectionFlags()); + window, startPos, requiredPos, countAllRows, getConnectionFlags(), + mCancelationSignal); return numRows; } catch (SQLiteDatabaseCorruptException ex) { onCorruption(); diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java index 1b7b398..89469cb 100644 --- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java +++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -16,6 +16,8 @@ package android.database.sqlite; +import android.content.CancelationSignal; +import android.content.OperationCanceledException; import android.database.Cursor; import android.database.DatabaseUtils; import android.provider.BaseColumns; @@ -137,8 +139,9 @@ public class SQLiteQueryBuilder /** * Sets the cursor factory to be used for the query. You can use * one factory for all queries on a database but it is normally - * easier to specify the factory when doing this query. @param - * factory the factor to use + * easier to specify the factory when doing this query. + * + * @param factory the factory to use. */ public void setCursorFactory(SQLiteDatabase.CursorFactory factory) { mFactory = factory; @@ -289,7 +292,7 @@ public class SQLiteQueryBuilder String selection, String[] selectionArgs, String groupBy, String having, String sortOrder) { return query(db, projectionIn, selection, selectionArgs, groupBy, having, sortOrder, - null /* limit */); + null /* limit */, null /* cancelationSignal */); } /** @@ -327,6 +330,48 @@ public class SQLiteQueryBuilder public Cursor query(SQLiteDatabase db, String[] projectionIn, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder, String limit) { + return query(db, projectionIn, selection, selectionArgs, + groupBy, having, sortOrder, limit, null); + } + + /** + * Perform a query by combining all current settings and the + * information passed into this method. + * + * @param db the database to query on + * @param projectionIn A list of which columns to return. Passing + * null will return all columns, which is discouraged to prevent + * reading data from storage that isn't going to be used. + * @param selection A filter declaring which rows to return, + * formatted as an SQL WHERE clause (excluding the WHERE + * itself). Passing null will return all rows for the given URL. + * @param selectionArgs You may include ?s in selection, which + * will be replaced by the values from selectionArgs, in order + * that they appear in the selection. The values will be bound + * as Strings. + * @param groupBy A filter declaring how to group rows, formatted + * as an SQL GROUP BY clause (excluding the GROUP BY + * itself). Passing null will cause the rows to not be grouped. + * @param having A filter declare which row groups to include in + * the cursor, if row grouping is being used, formatted as an + * SQL HAVING clause (excluding the HAVING itself). Passing + * null will cause all row groups to be included, and is + * required when row grouping is not being used. + * @param sortOrder How to order the rows, formatted as an SQL + * ORDER BY clause (excluding the ORDER BY itself). Passing null + * will use the default sort order, which may be unordered. + * @param limit Limits the number of rows returned by the query, + * formatted as LIMIT clause. Passing null denotes no LIMIT clause. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * If the operation is canceled, then {@link OperationCanceledException} will be thrown + * when the query is executed. + * @return a cursor over the result set + * @see android.content.ContentResolver#query(android.net.Uri, String[], + * String, String[], String) + */ + public Cursor query(SQLiteDatabase db, String[] projectionIn, + String selection, String[] selectionArgs, String groupBy, + String having, String sortOrder, String limit, CancelationSignal cancelationSignal) { if (mTables == null) { return null; } @@ -341,7 +386,8 @@ public class SQLiteQueryBuilder // in both the wrapped and original forms. String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy, having, sortOrder, limit); - validateQuerySql(db, sqlForValidation); // will throw if query is invalid + validateQuerySql(db, sqlForValidation, + cancelationSignal); // will throw if query is invalid } String sql = buildQuery( @@ -353,16 +399,18 @@ public class SQLiteQueryBuilder } return db.rawQueryWithFactory( mFactory, sql, selectionArgs, - SQLiteDatabase.findEditTable(mTables)); // will throw if query is invalid + SQLiteDatabase.findEditTable(mTables), + cancelationSignal); // will throw if query is invalid } /** * Verifies that a SQL SELECT statement is valid by compiling it. * If the SQL statement is not valid, this method will throw a {@link SQLiteException}. */ - private void validateQuerySql(SQLiteDatabase db, String sql) { + private void validateQuerySql(SQLiteDatabase db, String sql, + CancelationSignal cancelationSignal) { db.getThreadSession().prepare(sql, - db.getThreadDefaultConnectionFlags(true /*readOnly*/), null); + db.getThreadDefaultConnectionFlags(true /*readOnly*/), cancelationSignal, null); } /** diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java index a933051..b5a3e31 100644 --- a/core/java/android/database/sqlite/SQLiteSession.java +++ b/core/java/android/database/sqlite/SQLiteSession.java @@ -16,6 +16,8 @@ package android.database.sqlite; +import android.content.CancelationSignal; +import android.content.OperationCanceledException; import android.database.CursorWindow; import android.database.DatabaseUtils; import android.os.ParcelFileDescriptor; @@ -156,8 +158,6 @@ import android.os.ParcelFileDescriptor; * triggers may call custom SQLite functions that perform additional queries. * </p> * - * TODO: Support timeouts on all possibly blocking operations. - * * @hide */ public final class SQLiteSession { @@ -280,24 +280,34 @@ public final class SQLiteSession { * @param transactionListener The transaction listener, or null if none. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * * @throws IllegalStateException if {@link #setTransactionSuccessful} has already been * called for the current transaction. + * @throws SQLiteException if an error occurs. + * @throws OperationCanceledException if the operation was canceled. * * @see #setTransactionSuccessful * @see #yieldTransaction * @see #endTransaction */ public void beginTransaction(int transactionMode, - SQLiteTransactionListener transactionListener, int connectionFlags) { + SQLiteTransactionListener transactionListener, int connectionFlags, + CancelationSignal cancelationSignal) { throwIfTransactionMarkedSuccessful(); - beginTransactionUnchecked(transactionMode, transactionListener, connectionFlags); + beginTransactionUnchecked(transactionMode, transactionListener, connectionFlags, + cancelationSignal); } private void beginTransactionUnchecked(int transactionMode, - SQLiteTransactionListener transactionListener, int connectionFlags) { + SQLiteTransactionListener transactionListener, int connectionFlags, + CancelationSignal cancelationSignal) { + if (cancelationSignal != null) { + cancelationSignal.throwIfCanceled(); + } + if (mTransactionStack == null) { - acquireConnection(null, connectionFlags); // might throw + acquireConnection(null, connectionFlags, cancelationSignal); // might throw } try { // Set up the transaction such that we can back out safely @@ -306,13 +316,15 @@ public final class SQLiteSession { // Execute SQL might throw a runtime exception. switch (transactionMode) { case TRANSACTION_MODE_IMMEDIATE: - mConnection.execute("BEGIN IMMEDIATE;", null); // might throw + mConnection.execute("BEGIN IMMEDIATE;", null, + cancelationSignal); // might throw break; case TRANSACTION_MODE_EXCLUSIVE: - mConnection.execute("BEGIN EXCLUSIVE;", null); // might throw + mConnection.execute("BEGIN EXCLUSIVE;", null, + cancelationSignal); // might throw break; default: - mConnection.execute("BEGIN;", null); // might throw + mConnection.execute("BEGIN;", null, cancelationSignal); // might throw break; } } @@ -323,7 +335,7 @@ public final class SQLiteSession { transactionListener.onBegin(); // might throw } catch (RuntimeException ex) { if (mTransactionStack == null) { - mConnection.execute("ROLLBACK;", null); // might throw + mConnection.execute("ROLLBACK;", null, cancelationSignal); // might throw } throw ex; } @@ -372,20 +384,28 @@ public final class SQLiteSession { * This method must be called exactly once for each call to {@link #beginTransaction}. * </p> * + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * * @throws IllegalStateException if there is no current transaction. + * @throws SQLiteException if an error occurs. + * @throws OperationCanceledException if the operation was canceled. * * @see #beginTransaction * @see #setTransactionSuccessful * @see #yieldTransaction */ - public void endTransaction() { + public void endTransaction(CancelationSignal cancelationSignal) { throwIfNoTransaction(); assert mConnection != null; - endTransactionUnchecked(); + endTransactionUnchecked(cancelationSignal); } - private void endTransactionUnchecked() { + private void endTransactionUnchecked(CancelationSignal cancelationSignal) { + if (cancelationSignal != null) { + cancelationSignal.throwIfCanceled(); + } + final Transaction top = mTransactionStack; boolean successful = top.mMarkedSuccessful && !top.mChildFailed; @@ -414,9 +434,9 @@ public final class SQLiteSession { } else { try { if (successful) { - mConnection.execute("COMMIT;", null); // might throw + mConnection.execute("COMMIT;", null, cancelationSignal); // might throw } else { - mConnection.execute("ROLLBACK;", null); // might throw + mConnection.execute("ROLLBACK;", null, cancelationSignal); // might throw } } finally { releaseConnection(); // might throw @@ -467,16 +487,20 @@ public final class SQLiteSession { * @param throwIfUnsafe If true, then instead of returning false when no * transaction is in progress, a nested transaction is in progress, or when * the transaction has already been marked successful, throws {@link IllegalStateException}. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * @return True if the transaction was actually yielded. * * @throws IllegalStateException if <code>throwIfNested</code> is true and * there is no current transaction, there is a nested transaction in progress or * if {@link #setTransactionSuccessful} has already been called for the current transaction. + * @throws SQLiteException if an error occurs. + * @throws OperationCanceledException if the operation was canceled. * * @see #beginTransaction * @see #endTransaction */ - public boolean yieldTransaction(long sleepAfterYieldDelayMillis, boolean throwIfUnsafe) { + public boolean yieldTransaction(long sleepAfterYieldDelayMillis, boolean throwIfUnsafe, + CancelationSignal cancelationSignal) { if (throwIfUnsafe) { throwIfNoTransaction(); throwIfTransactionMarkedSuccessful(); @@ -493,10 +517,16 @@ public final class SQLiteSession { return false; } - return yieldTransactionUnchecked(sleepAfterYieldDelayMillis); // might throw + return yieldTransactionUnchecked(sleepAfterYieldDelayMillis, + cancelationSignal); // might throw } - private boolean yieldTransactionUnchecked(long sleepAfterYieldDelayMillis) { + private boolean yieldTransactionUnchecked(long sleepAfterYieldDelayMillis, + CancelationSignal cancelationSignal) { + if (cancelationSignal != null) { + cancelationSignal.throwIfCanceled(); + } + if (!mConnectionPool.shouldYieldConnection(mConnection, mConnectionFlags)) { return false; } @@ -504,7 +534,7 @@ public final class SQLiteSession { final int transactionMode = mTransactionStack.mMode; final SQLiteTransactionListener listener = mTransactionStack.mListener; final int connectionFlags = mConnectionFlags; - endTransactionUnchecked(); // might throw + endTransactionUnchecked(cancelationSignal); // might throw if (sleepAfterYieldDelayMillis > 0) { try { @@ -514,7 +544,8 @@ public final class SQLiteSession { } } - beginTransactionUnchecked(transactionMode, listener, connectionFlags); // might throw + beginTransactionUnchecked(transactionMode, listener, connectionFlags, + cancelationSignal); // might throw return true; } @@ -535,17 +566,24 @@ public final class SQLiteSession { * @param sql The SQL statement to prepare. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate * with information about the statement, or null if none. * * @throws SQLiteException if an error occurs, such as a syntax error. + * @throws OperationCanceledException if the operation was canceled. */ - public void prepare(String sql, int connectionFlags, SQLiteStatementInfo outStatementInfo) { + public void prepare(String sql, int connectionFlags, CancelationSignal cancelationSignal, + SQLiteStatementInfo outStatementInfo) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - acquireConnection(sql, connectionFlags); // might throw + if (cancelationSignal != null) { + cancelationSignal.throwIfCanceled(); + } + + acquireConnection(sql, connectionFlags, cancelationSignal); // might throw try { mConnection.prepare(sql, outStatementInfo); // might throw } finally { @@ -560,22 +598,25 @@ public final class SQLiteSession { * @param bindArgs The arguments to bind, or null if none. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. + * @throws OperationCanceledException if the operation was canceled. */ - public void execute(String sql, Object[] bindArgs, int connectionFlags) { + public void execute(String sql, Object[] bindArgs, int connectionFlags, + CancelationSignal cancelationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags)) { + if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) { return; } - acquireConnection(sql, connectionFlags); // might throw + acquireConnection(sql, connectionFlags, cancelationSignal); // might throw try { - mConnection.execute(sql, bindArgs); // might throw + mConnection.execute(sql, bindArgs, cancelationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -588,24 +629,27 @@ public final class SQLiteSession { * @param bindArgs The arguments to bind, or null if none. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * @return The value of the first column in the first row of the result set * as a <code>long</code>, or zero if none. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. + * @throws OperationCanceledException if the operation was canceled. */ - public long executeForLong(String sql, Object[] bindArgs, int connectionFlags) { + public long executeForLong(String sql, Object[] bindArgs, int connectionFlags, + CancelationSignal cancelationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags)) { + if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) { return 0; } - acquireConnection(sql, connectionFlags); // might throw + acquireConnection(sql, connectionFlags, cancelationSignal); // might throw try { - return mConnection.executeForLong(sql, bindArgs); // might throw + return mConnection.executeForLong(sql, bindArgs, cancelationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -618,24 +662,27 @@ public final class SQLiteSession { * @param bindArgs The arguments to bind, or null if none. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * @return The value of the first column in the first row of the result set * as a <code>String</code>, or null if none. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. + * @throws OperationCanceledException if the operation was canceled. */ - public String executeForString(String sql, Object[] bindArgs, int connectionFlags) { + public String executeForString(String sql, Object[] bindArgs, int connectionFlags, + CancelationSignal cancelationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags)) { + if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) { return null; } - acquireConnection(sql, connectionFlags); // might throw + acquireConnection(sql, connectionFlags, cancelationSignal); // might throw try { - return mConnection.executeForString(sql, bindArgs); // might throw + return mConnection.executeForString(sql, bindArgs, cancelationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -649,26 +696,29 @@ public final class SQLiteSession { * @param bindArgs The arguments to bind, or null if none. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * @return The file descriptor for a shared memory region that contains * the value of the first column in the first row of the result set as a BLOB, * or null if none. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. + * @throws OperationCanceledException if the operation was canceled. */ public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs, - int connectionFlags) { + int connectionFlags, CancelationSignal cancelationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags)) { + if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) { return null; } - acquireConnection(sql, connectionFlags); // might throw + acquireConnection(sql, connectionFlags, cancelationSignal); // might throw try { - return mConnection.executeForBlobFileDescriptor(sql, bindArgs); // might throw + return mConnection.executeForBlobFileDescriptor(sql, bindArgs, + cancelationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -682,23 +732,27 @@ public final class SQLiteSession { * @param bindArgs The arguments to bind, or null if none. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * @return The number of rows that were changed. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. + * @throws OperationCanceledException if the operation was canceled. */ - public int executeForChangedRowCount(String sql, Object[] bindArgs, int connectionFlags) { + public int executeForChangedRowCount(String sql, Object[] bindArgs, int connectionFlags, + CancelationSignal cancelationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags)) { + if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) { return 0; } - acquireConnection(sql, connectionFlags); // might throw + acquireConnection(sql, connectionFlags, cancelationSignal); // might throw try { - return mConnection.executeForChangedRowCount(sql, bindArgs); // might throw + return mConnection.executeForChangedRowCount(sql, bindArgs, + cancelationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -712,23 +766,27 @@ public final class SQLiteSession { * @param bindArgs The arguments to bind, or null if none. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * @return The row id of the last row that was inserted, or 0 if none. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. + * @throws OperationCanceledException if the operation was canceled. */ - public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags) { + public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags, + CancelationSignal cancelationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags)) { + if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) { return 0; } - acquireConnection(sql, connectionFlags); // might throw + acquireConnection(sql, connectionFlags, cancelationSignal); // might throw try { - return mConnection.executeForLastInsertedRowId(sql, bindArgs); // might throw + return mConnection.executeForLastInsertedRowId(sql, bindArgs, + cancelationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -750,15 +808,17 @@ public final class SQLiteSession { * regagless of whether they fit in the window. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * @return The number of rows that were counted during query execution. Might * not be all rows in the result set unless <code>countAllRows</code> is true. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. + * @throws OperationCanceledException if the operation was canceled. */ public int executeForCursorWindow(String sql, Object[] bindArgs, CursorWindow window, int startPos, int requiredPos, boolean countAllRows, - int connectionFlags) { + int connectionFlags, CancelationSignal cancelationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -766,15 +826,16 @@ public final class SQLiteSession { throw new IllegalArgumentException("window must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags)) { + if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) { window.clear(); return 0; } - acquireConnection(sql, connectionFlags); // might throw + acquireConnection(sql, connectionFlags, cancelationSignal); // might throw try { return mConnection.executeForCursorWindow(sql, bindArgs, - window, startPos, requiredPos, countAllRows); // might throw + window, startPos, requiredPos, countAllRows, + cancelationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -793,35 +854,45 @@ public final class SQLiteSession { * @param bindArgs The arguments to bind, or null if none. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. + * @param cancelationSignal A signal to cancel the operation in progress, or null if none. * @return True if the statement was of a special form that was handled here, * false otherwise. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. + * @throws OperationCanceledException if the operation was canceled. */ - private boolean executeSpecial(String sql, Object[] bindArgs, int connectionFlags) { + private boolean executeSpecial(String sql, Object[] bindArgs, int connectionFlags, + CancelationSignal cancelationSignal) { + if (cancelationSignal != null) { + cancelationSignal.throwIfCanceled(); + } + final int type = DatabaseUtils.getSqlStatementType(sql); switch (type) { case DatabaseUtils.STATEMENT_BEGIN: - beginTransaction(TRANSACTION_MODE_EXCLUSIVE, null, connectionFlags); + beginTransaction(TRANSACTION_MODE_EXCLUSIVE, null, connectionFlags, + cancelationSignal); return true; case DatabaseUtils.STATEMENT_COMMIT: setTransactionSuccessful(); - endTransaction(); + endTransaction(cancelationSignal); return true; case DatabaseUtils.STATEMENT_ABORT: - endTransaction(); + endTransaction(cancelationSignal); return true; } return false; } - private void acquireConnection(String sql, int connectionFlags) { + private void acquireConnection(String sql, int connectionFlags, + CancelationSignal cancelationSignal) { if (mConnection == null) { assert mConnectionUseCount == 0; - mConnection = mConnectionPool.acquireConnection(sql, connectionFlags); // might throw + mConnection = mConnectionPool.acquireConnection(sql, connectionFlags, + cancelationSignal); // might throw mConnectionFlags = connectionFlags; } mConnectionUseCount += 1; diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java index 4e20da0..b1092d7 100644 --- a/core/java/android/database/sqlite/SQLiteStatement.java +++ b/core/java/android/database/sqlite/SQLiteStatement.java @@ -28,7 +28,7 @@ import android.os.ParcelFileDescriptor; */ public final class SQLiteStatement extends SQLiteProgram { SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) { - super(db, sql, bindArgs); + super(db, sql, bindArgs, null); } /** @@ -41,7 +41,7 @@ public final class SQLiteStatement extends SQLiteProgram { public void execute() { acquireReference(); try { - getSession().execute(getSql(), getBindArgs(), getConnectionFlags()); + getSession().execute(getSql(), getBindArgs(), getConnectionFlags(), null); } catch (SQLiteDatabaseCorruptException ex) { onCorruption(); throw ex; @@ -62,7 +62,7 @@ public final class SQLiteStatement extends SQLiteProgram { acquireReference(); try { return getSession().executeForChangedRowCount( - getSql(), getBindArgs(), getConnectionFlags()); + getSql(), getBindArgs(), getConnectionFlags(), null); } catch (SQLiteDatabaseCorruptException ex) { onCorruption(); throw ex; @@ -84,7 +84,7 @@ public final class SQLiteStatement extends SQLiteProgram { acquireReference(); try { return getSession().executeForLastInsertedRowId( - getSql(), getBindArgs(), getConnectionFlags()); + getSql(), getBindArgs(), getConnectionFlags(), null); } catch (SQLiteDatabaseCorruptException ex) { onCorruption(); throw ex; @@ -105,7 +105,7 @@ public final class SQLiteStatement extends SQLiteProgram { acquireReference(); try { return getSession().executeForLong( - getSql(), getBindArgs(), getConnectionFlags()); + getSql(), getBindArgs(), getConnectionFlags(), null); } catch (SQLiteDatabaseCorruptException ex) { onCorruption(); throw ex; @@ -126,7 +126,7 @@ public final class SQLiteStatement extends SQLiteProgram { acquireReference(); try { return getSession().executeForString( - getSql(), getBindArgs(), getConnectionFlags()); + getSql(), getBindArgs(), getConnectionFlags(), null); } catch (SQLiteDatabaseCorruptException ex) { onCorruption(); throw ex; @@ -147,7 +147,7 @@ public final class SQLiteStatement extends SQLiteProgram { acquireReference(); try { return getSession().executeForBlobFileDescriptor( - getSql(), getBindArgs(), getConnectionFlags()); + getSql(), getBindArgs(), getConnectionFlags(), null); } catch (SQLiteDatabaseCorruptException ex) { onCorruption(); throw ex; |