/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.database.sqlite; import android.database.CursorWindow; import android.database.DatabaseUtils; import android.os.CancellationSignal; import android.os.OperationCanceledException; import android.os.ParcelFileDescriptor; /** * Provides a single client the ability to use a database. * *

About database sessions

*

* Database access is always performed using a session. The session * manages the lifecycle of transactions and database connections. *

* Sessions can be used to perform both read-only and read-write operations. * There is some advantage to knowing when a session is being used for * read-only purposes because the connection pool can optimize the use * of the available connections to permit multiple read-only operations * to execute in parallel whereas read-write operations may need to be serialized. *

* When Write Ahead Logging (WAL) is enabled, the database can * execute simultaneous read-only and read-write transactions, provided that * at most one read-write transaction is performed at a time. When WAL is not * enabled, read-only transactions can execute in parallel but read-write * transactions are mutually exclusive. *

* *

Ownership and concurrency guarantees

*

* Session objects are not thread-safe. In fact, session objects are thread-bound. * The {@link SQLiteDatabase} uses a thread-local variable to associate a session * with each thread for the use of that thread alone. Consequently, each thread * has its own session object and therefore its own transaction state independent * of other threads. *

* A thread has at most one session per database. This constraint ensures that * a thread can never use more than one database connection at a time for a * given database. As the number of available database connections is limited, * if a single thread tried to acquire multiple connections for the same database * at the same time, it might deadlock. Therefore we allow there to be only * one session (so, at most one connection) per thread per database. *

* *

Transactions

*

* There are two kinds of transaction: implicit transactions and explicit * transactions. *

* An implicit transaction is created whenever a database operation is requested * and there is no explicit transaction currently in progress. An implicit transaction * only lasts for the duration of the database operation in question and then it * is ended. If the database operation was successful, then its changes are committed. *

* An explicit transaction is started by calling {@link #beginTransaction} and * specifying the desired transaction mode. Once an explicit transaction has begun, * all subsequent database operations will be performed as part of that transaction. * To end an explicit transaction, first call {@link #setTransactionSuccessful} if the * transaction was successful, then call {@link #end}. If the transaction was * marked successful, its changes will be committed, otherwise they will be rolled back. *

* Explicit transactions can also be nested. A nested explicit transaction is * started with {@link #beginTransaction}, marked successful with * {@link #setTransactionSuccessful}and ended with {@link #endTransaction}. * If any nested transaction is not marked successful, then the entire transaction * including all of its nested transactions will be rolled back * when the outermost transaction is ended. *

* To improve concurrency, an explicit transaction can be yielded by calling * {@link #yieldTransaction}. If there is contention for use of the database, * then yielding ends the current transaction, commits its changes, releases the * database connection for use by another session for a little while, and starts a * new transaction with the same properties as the original one. * Changes committed by {@link #yieldTransaction} cannot be rolled back. *

* When a transaction is started, the client can provide a {@link SQLiteTransactionListener} * to listen for notifications of transaction-related events. *

* Recommended usage: *

 * // First, begin the transaction.
 * session.beginTransaction(SQLiteSession.TRANSACTION_MODE_DEFERRED, 0);
 * try {
 *     // Then do stuff...
 *     session.execute("INSERT INTO ...", null, 0);
 *
 *     // As the very last step before ending the transaction, mark it successful.
 *     session.setTransactionSuccessful();
 * } finally {
 *     // Finally, end the transaction.
 *     // This statement will commit the transaction if it was marked successful or
 *     // roll it back otherwise.
 *     session.endTransaction();
 * }
 * 
*

* *

Database connections

*

* A {@link SQLiteDatabase} can have multiple active sessions at the same * time. Each session acquires and releases connections to the database * as needed to perform each requested database transaction. If all connections * are in use, then database transactions on some sessions will block until a * connection becomes available. *

* The session acquires a single database connection only for the duration * of a single (implicit or explicit) database transaction, then releases it. * This characteristic allows a small pool of database connections to be shared * efficiently by multiple sessions as long as they are not all trying to perform * database transactions at the same time. *

* *

Responsiveness

*

* Because there are a limited number of database connections and the session holds * a database connection for the entire duration of a database transaction, * it is important to keep transactions short. This is especially important * for read-write transactions since they may block other transactions * from executing. Consider calling {@link #yieldTransaction} periodically * during long-running transactions. *

* Another important consideration is that transactions that take too long to * run may cause the application UI to become unresponsive. Even if the transaction * is executed in a background thread, the user will get bored and * frustrated if the application shows no data for several seconds while * a transaction runs. *

* Guidelines: *

* *

Reentrance

*

* This class must tolerate reentrant execution of SQLite operations because * triggers may call custom SQLite functions that perform additional queries. *

* * @hide */ public final class SQLiteSession { private final SQLiteConnectionPool mConnectionPool; private SQLiteConnection mConnection; private int mConnectionFlags; private int mConnectionUseCount; private Transaction mTransactionPool; private Transaction mTransactionStack; /** * Transaction mode: Deferred. *

* In a deferred transaction, no locks are acquired on the database * until the first operation is performed. If the first operation is * read-only, then a SHARED lock is acquired, otherwise * a RESERVED lock is acquired. *

* While holding a SHARED lock, this session is only allowed to * read but other sessions are allowed to read or write. * While holding a RESERVED lock, this session is allowed to read * or write but other sessions are only allowed to read. *

* Because the lock is only acquired when needed in a deferred transaction, * it is possible for another session to write to the database first before * this session has a chance to do anything. *

* Corresponds to the SQLite BEGIN DEFERRED transaction mode. *

*/ public static final int TRANSACTION_MODE_DEFERRED = 0; /** * Transaction mode: Immediate. *

* When an immediate transaction begins, the session acquires a * RESERVED lock. *

* While holding a RESERVED lock, this session is allowed to read * or write but other sessions are only allowed to read. *

* Corresponds to the SQLite BEGIN IMMEDIATE transaction mode. *

*/ public static final int TRANSACTION_MODE_IMMEDIATE = 1; /** * Transaction mode: Exclusive. *

* When an exclusive transaction begins, the session acquires an * EXCLUSIVE lock. *

* While holding an EXCLUSIVE lock, this session is allowed to read * or write but no other sessions are allowed to access the database. *

* Corresponds to the SQLite BEGIN EXCLUSIVE transaction mode. *

*/ public static final int TRANSACTION_MODE_EXCLUSIVE = 2; /** * Creates a session bound to the specified connection pool. * * @param connectionPool The connection pool. */ public SQLiteSession(SQLiteConnectionPool connectionPool) { if (connectionPool == null) { throw new IllegalArgumentException("connectionPool must not be null"); } mConnectionPool = connectionPool; } /** * Returns true if the session has a transaction in progress. * * @return True if the session has a transaction in progress. */ public boolean hasTransaction() { return mTransactionStack != null; } /** * Returns true if the session has a nested transaction in progress. * * @return True if the session has a nested transaction in progress. */ public boolean hasNestedTransaction() { return mTransactionStack != null && mTransactionStack.mParent != null; } /** * Returns true if the session has an active database connection. * * @return True if the session has an active database connection. */ public boolean hasConnection() { return mConnection != null; } /** * Begins a transaction. *

* Transactions may nest. If the transaction is not in progress, * then a database connection is obtained and a new transaction is started. * Otherwise, a nested transaction is started. *

* Each call to {@link #beginTransaction} must be matched exactly by a call * to {@link #endTransaction}. To mark a transaction as successful, * call {@link #setTransactionSuccessful} before calling {@link #endTransaction}. * If the transaction is not successful, or if any of its nested * transactions were not successful, then the entire transaction will * be rolled back when the outermost transaction is ended. *

* * @param transactionMode The transaction mode. One of: {@link #TRANSACTION_MODE_DEFERRED}, * {@link #TRANSACTION_MODE_IMMEDIATE}, or {@link #TRANSACTION_MODE_EXCLUSIVE}. * Ignored when creating a nested transaction. * @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 cancellationSignal 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, CancellationSignal cancellationSignal) { throwIfTransactionMarkedSuccessful(); beginTransactionUnchecked(transactionMode, transactionListener, connectionFlags, cancellationSignal); } private void beginTransactionUnchecked(int transactionMode, SQLiteTransactionListener transactionListener, int connectionFlags, CancellationSignal cancellationSignal) { if (cancellationSignal != null) { cancellationSignal.throwIfCanceled(); } if (mTransactionStack == null) { acquireConnection(null, connectionFlags, cancellationSignal); // might throw } try { // Set up the transaction such that we can back out safely // in case we fail part way. if (mTransactionStack == null) { // Execute SQL might throw a runtime exception. switch (transactionMode) { case TRANSACTION_MODE_IMMEDIATE: mConnection.execute("BEGIN IMMEDIATE;", null, cancellationSignal); // might throw break; case TRANSACTION_MODE_EXCLUSIVE: mConnection.execute("BEGIN EXCLUSIVE;", null, cancellationSignal); // might throw break; default: mConnection.execute("BEGIN;", null, cancellationSignal); // might throw break; } } // Listener might throw a runtime exception. if (transactionListener != null) { try { transactionListener.onBegin(); // might throw } catch (RuntimeException ex) { if (mTransactionStack == null) { mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw } throw ex; } } // Bookkeeping can't throw, except an OOM, which is just too bad... Transaction transaction = obtainTransaction(transactionMode, transactionListener); transaction.mParent = mTransactionStack; mTransactionStack = transaction; } finally { if (mTransactionStack == null) { releaseConnection(); // might throw } } } /** * Marks the current transaction as having completed successfully. *

* This method can be called at most once between {@link #beginTransaction} and * {@link #endTransaction} to indicate that the changes made by the transaction should be * committed. If this method is not called, the changes will be rolled back * when the transaction is ended. *

* * @throws IllegalStateException if there is no current transaction, or if * {@link #setTransactionSuccessful} has already been called for the current transaction. * * @see #beginTransaction * @see #endTransaction */ public void setTransactionSuccessful() { throwIfNoTransaction(); throwIfTransactionMarkedSuccessful(); mTransactionStack.mMarkedSuccessful = true; } /** * Ends the current transaction and commits or rolls back changes. *

* If this is the outermost transaction (not nested within any other * transaction), then the changes are committed if {@link #setTransactionSuccessful} * was called or rolled back otherwise. *

* This method must be called exactly once for each call to {@link #beginTransaction}. *

* * @param cancellationSignal 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(CancellationSignal cancellationSignal) { throwIfNoTransaction(); assert mConnection != null; endTransactionUnchecked(cancellationSignal, false); } private void endTransactionUnchecked(CancellationSignal cancellationSignal, boolean yielding) { if (cancellationSignal != null) { cancellationSignal.throwIfCanceled(); } final Transaction top = mTransactionStack; boolean successful = (top.mMarkedSuccessful || yielding) && !top.mChildFailed; RuntimeException listenerException = null; final SQLiteTransactionListener listener = top.mListener; if (listener != null) { try { if (successful) { listener.onCommit(); // might throw } else { listener.onRollback(); // might throw } } catch (RuntimeException ex) { listenerException = ex; successful = false; } } mTransactionStack = top.mParent; recycleTransaction(top); if (mTransactionStack != null) { if (!successful) { mTransactionStack.mChildFailed = true; } } else { try { if (successful) { mConnection.execute("COMMIT;", null, cancellationSignal); // might throw } else { mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw } } finally { releaseConnection(); // might throw } } if (listenerException != null) { throw listenerException; } } /** * Temporarily ends a transaction to let other threads have use of * the database. Begins a new transaction after a specified delay. *

* If there are other threads waiting to acquire connections, * then the current transaction is committed and the database * connection is released. After a short delay, a new transaction * is started. *

* The transaction is assumed to be successful so far. Do not call * {@link #setTransactionSuccessful()} before calling this method. * This method will fail if the transaction has already been marked * successful. *

* The changes that were committed by a yield cannot be rolled back later. *

* Before this method was called, there must already have been * a transaction in progress. When this method returns, there will * still be a transaction in progress, either the same one as before * or a new one if the transaction was actually yielded. *

* This method should not be called when there is a nested transaction * in progress because it is not possible to yield a nested transaction. * If throwIfNested is true, then attempting to yield * a nested transaction will throw {@link IllegalStateException}, otherwise * the method will return false in that case. *

* If there is no nested transaction in progress but a previous nested * transaction failed, then the transaction is not yielded (because it * must be rolled back) and this method returns false. *

* * @param sleepAfterYieldDelayMillis A delay time to wait after yielding * the database connection to allow other threads some time to run. * If the value is less than or equal to zero, there will be no additional * delay beyond the time it will take to begin a new transaction. * @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 cancellationSignal A signal to cancel the operation in progress, or null if none. * @return True if the transaction was actually yielded. * * @throws IllegalStateException if throwIfNested 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, CancellationSignal cancellationSignal) { if (throwIfUnsafe) { throwIfNoTransaction(); throwIfTransactionMarkedSuccessful(); throwIfNestedTransaction(); } else { if (mTransactionStack == null || mTransactionStack.mMarkedSuccessful || mTransactionStack.mParent != null) { return false; } } assert mConnection != null; if (mTransactionStack.mChildFailed) { return false; } return yieldTransactionUnchecked(sleepAfterYieldDelayMillis, cancellationSignal); // might throw } private boolean yieldTransactionUnchecked(long sleepAfterYieldDelayMillis, CancellationSignal cancellationSignal) { if (cancellationSignal != null) { cancellationSignal.throwIfCanceled(); } if (!mConnectionPool.shouldYieldConnection(mConnection, mConnectionFlags)) { return false; } final int transactionMode = mTransactionStack.mMode; final SQLiteTransactionListener listener = mTransactionStack.mListener; final int connectionFlags = mConnectionFlags; endTransactionUnchecked(cancellationSignal, true); // might throw if (sleepAfterYieldDelayMillis > 0) { try { Thread.sleep(sleepAfterYieldDelayMillis); } catch (InterruptedException ex) { // we have been interrupted, that's all we need to do } } beginTransactionUnchecked(transactionMode, listener, connectionFlags, cancellationSignal); // might throw return true; } /** * Prepares a statement for execution but does not bind its parameters or execute it. *

* This method can be used to check for syntax errors during compilation * prior to execution of the statement. If the {@code outStatementInfo} argument * is not null, the provided {@link SQLiteStatementInfo} object is populated * with information about the statement. *

* A prepared statement makes no reference to the arguments that may eventually * be bound to it, consequently it it possible to cache certain prepared statements * such as SELECT or INSERT/UPDATE statements. If the statement is cacheable, * then it will be stored in the cache for later and reused if possible. *

* * @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 cancellationSignal 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, CancellationSignal cancellationSignal, SQLiteStatementInfo outStatementInfo) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } if (cancellationSignal != null) { cancellationSignal.throwIfCanceled(); } acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { mConnection.prepare(sql, outStatementInfo); // might throw } finally { releaseConnection(); // might throw } } /** * Executes a statement that does not return a result. * * @param sql The SQL statement to execute. * @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 cancellationSignal 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, CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { return; } acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { mConnection.execute(sql, bindArgs, cancellationSignal); // might throw } finally { releaseConnection(); // might throw } } /** * Executes a statement that returns a single long result. * * @param sql The SQL statement to execute. * @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 cancellationSignal 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 long, 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, CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { return 0; } acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { return mConnection.executeForLong(sql, bindArgs, cancellationSignal); // might throw } finally { releaseConnection(); // might throw } } /** * Executes a statement that returns a single {@link String} result. * * @param sql The SQL statement to execute. * @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 cancellationSignal 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 String, 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, CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { return null; } acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { return mConnection.executeForString(sql, bindArgs, cancellationSignal); // might throw } finally { releaseConnection(); // might throw } } /** * Executes a statement that returns a single BLOB result as a * file descriptor to a shared memory region. * * @param sql The SQL statement to execute. * @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 cancellationSignal 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, CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { return null; } acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { return mConnection.executeForBlobFileDescriptor(sql, bindArgs, cancellationSignal); // might throw } finally { releaseConnection(); // might throw } } /** * Executes a statement that returns a count of the number of rows * that were changed. Use for UPDATE or DELETE SQL statements. * * @param sql The SQL statement to execute. * @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 cancellationSignal 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, CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { return 0; } acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { return mConnection.executeForChangedRowCount(sql, bindArgs, cancellationSignal); // might throw } finally { releaseConnection(); // might throw } } /** * Executes a statement that returns the row id of the last row inserted * by the statement. Use for INSERT SQL statements. * * @param sql The SQL statement to execute. * @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 cancellationSignal 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, CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { return 0; } acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { return mConnection.executeForLastInsertedRowId(sql, bindArgs, cancellationSignal); // might throw } finally { releaseConnection(); // might throw } } /** * Executes a statement and populates the specified {@link CursorWindow} * with a range of results. Returns the number of rows that were counted * during query execution. * * @param sql The SQL statement to execute. * @param bindArgs The arguments to bind, or null if none. * @param window The cursor window to clear and fill. * @param startPos The start position for filling the window. * @param requiredPos The position of a row that MUST be in the window. * If it won't fit, then the query should discard part of what it filled * so that it does. Must be greater than or equal to startPos. * @param countAllRows True to count all rows that the query would return * 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 cancellationSignal 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 countAllRows 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, CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } if (window == null) { throw new IllegalArgumentException("window must not be null."); } if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { window.clear(); return 0; } acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { return mConnection.executeForCursorWindow(sql, bindArgs, window, startPos, requiredPos, countAllRows, cancellationSignal); // might throw } finally { releaseConnection(); // might throw } } /** * Performs special reinterpretation of certain SQL statements such as "BEGIN", * "COMMIT" and "ROLLBACK" to ensure that transaction state invariants are * maintained. * * This function is mainly used to support legacy apps that perform their * own transactions by executing raw SQL rather than calling {@link #beginTransaction} * and the like. * * @param sql The SQL statement to execute. * @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 cancellationSignal 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, CancellationSignal cancellationSignal) { if (cancellationSignal != null) { cancellationSignal.throwIfCanceled(); } final int type = DatabaseUtils.getSqlStatementType(sql); switch (type) { case DatabaseUtils.STATEMENT_BEGIN: beginTransaction(TRANSACTION_MODE_EXCLUSIVE, null, connectionFlags, cancellationSignal); return true; case DatabaseUtils.STATEMENT_COMMIT: setTransactionSuccessful(); endTransaction(cancellationSignal); return true; case DatabaseUtils.STATEMENT_ABORT: endTransaction(cancellationSignal); return true; } return false; } private void acquireConnection(String sql, int connectionFlags, CancellationSignal cancellationSignal) { if (mConnection == null) { assert mConnectionUseCount == 0; mConnection = mConnectionPool.acquireConnection(sql, connectionFlags, cancellationSignal); // might throw mConnectionFlags = connectionFlags; } mConnectionUseCount += 1; } private void releaseConnection() { assert mConnection != null; assert mConnectionUseCount > 0; if (--mConnectionUseCount == 0) { try { mConnectionPool.releaseConnection(mConnection); // might throw } finally { mConnection = null; } } } private void throwIfNoTransaction() { if (mTransactionStack == null) { throw new IllegalStateException("Cannot perform this operation because " + "there is no current transaction."); } } private void throwIfTransactionMarkedSuccessful() { if (mTransactionStack != null && mTransactionStack.mMarkedSuccessful) { throw new IllegalStateException("Cannot perform this operation because " + "the transaction has already been marked successful. The only " + "thing you can do now is call endTransaction()."); } } private void throwIfNestedTransaction() { if (mTransactionStack == null && mTransactionStack.mParent != null) { throw new IllegalStateException("Cannot perform this operation because " + "a nested transaction is in progress."); } } private Transaction obtainTransaction(int mode, SQLiteTransactionListener listener) { Transaction transaction = mTransactionPool; if (transaction != null) { mTransactionPool = transaction.mParent; transaction.mParent = null; transaction.mMarkedSuccessful = false; transaction.mChildFailed = false; } else { transaction = new Transaction(); } transaction.mMode = mode; transaction.mListener = listener; return transaction; } private void recycleTransaction(Transaction transaction) { transaction.mParent = mTransactionPool; transaction.mListener = null; mTransactionPool = transaction; } private static final class Transaction { public Transaction mParent; public int mMode; public SQLiteTransactionListener mListener; public boolean mMarkedSuccessful; public boolean mChildFailed; } }