• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.database.sqlite;
18 
19 import android.database.CursorWindow;
20 import android.database.DatabaseUtils;
21 import android.os.CancellationSignal;
22 import android.os.OperationCanceledException;
23 import android.os.ParcelFileDescriptor;
24 
25 /**
26  * Provides a single client the ability to use a database.
27  *
28  * <h2>About database sessions</h2>
29  * <p>
30  * Database access is always performed using a session.  The session
31  * manages the lifecycle of transactions and database connections.
32  * </p><p>
33  * Sessions can be used to perform both read-only and read-write operations.
34  * There is some advantage to knowing when a session is being used for
35  * read-only purposes because the connection pool can optimize the use
36  * of the available connections to permit multiple read-only operations
37  * to execute in parallel whereas read-write operations may need to be serialized.
38  * </p><p>
39  * When <em>Write Ahead Logging (WAL)</em> is enabled, the database can
40  * execute simultaneous read-only and read-write transactions, provided that
41  * at most one read-write transaction is performed at a time.  When WAL is not
42  * enabled, read-only transactions can execute in parallel but read-write
43  * transactions are mutually exclusive.
44  * </p>
45  *
46  * <h2>Ownership and concurrency guarantees</h2>
47  * <p>
48  * Session objects are not thread-safe.  In fact, session objects are thread-bound.
49  * The {@link SQLiteDatabase} uses a thread-local variable to associate a session
50  * with each thread for the use of that thread alone.  Consequently, each thread
51  * has its own session object and therefore its own transaction state independent
52  * of other threads.
53  * </p><p>
54  * A thread has at most one session per database.  This constraint ensures that
55  * a thread can never use more than one database connection at a time for a
56  * given database.  As the number of available database connections is limited,
57  * if a single thread tried to acquire multiple connections for the same database
58  * at the same time, it might deadlock.  Therefore we allow there to be only
59  * one session (so, at most one connection) per thread per database.
60  * </p>
61  *
62  * <h2>Transactions</h2>
63  * <p>
64  * There are two kinds of transaction: implicit transactions and explicit
65  * transactions.
66  * </p><p>
67  * An implicit transaction is created whenever a database operation is requested
68  * and there is no explicit transaction currently in progress.  An implicit transaction
69  * only lasts for the duration of the database operation in question and then it
70  * is ended.  If the database operation was successful, then its changes are committed.
71  * </p><p>
72  * An explicit transaction is started by calling {@link #beginTransaction} and
73  * specifying the desired transaction mode.  Once an explicit transaction has begun,
74  * all subsequent database operations will be performed as part of that transaction.
75  * To end an explicit transaction, first call {@link #setTransactionSuccessful} if the
76  * transaction was successful, then call {@link #end}.  If the transaction was
77  * marked successful, its changes will be committed, otherwise they will be rolled back.
78  * </p><p>
79  * Explicit transactions can also be nested.  A nested explicit transaction is
80  * started with {@link #beginTransaction}, marked successful with
81  * {@link #setTransactionSuccessful}and ended with {@link #endTransaction}.
82  * If any nested transaction is not marked successful, then the entire transaction
83  * including all of its nested transactions will be rolled back
84  * when the outermost transaction is ended.
85  * </p><p>
86  * To improve concurrency, an explicit transaction can be yielded by calling
87  * {@link #yieldTransaction}.  If there is contention for use of the database,
88  * then yielding ends the current transaction, commits its changes, releases the
89  * database connection for use by another session for a little while, and starts a
90  * new transaction with the same properties as the original one.
91  * Changes committed by {@link #yieldTransaction} cannot be rolled back.
92  * </p><p>
93  * When a transaction is started, the client can provide a {@link SQLiteTransactionListener}
94  * to listen for notifications of transaction-related events.
95  * </p><p>
96  * Recommended usage:
97  * <code><pre>
98  * // First, begin the transaction.
99  * session.beginTransaction(SQLiteSession.TRANSACTION_MODE_DEFERRED, 0);
100  * try {
101  *     // Then do stuff...
102  *     session.execute("INSERT INTO ...", null, 0);
103  *
104  *     // As the very last step before ending the transaction, mark it successful.
105  *     session.setTransactionSuccessful();
106  * } finally {
107  *     // Finally, end the transaction.
108  *     // This statement will commit the transaction if it was marked successful or
109  *     // roll it back otherwise.
110  *     session.endTransaction();
111  * }
112  * </pre></code>
113  * </p>
114  *
115  * <h2>Database connections</h2>
116  * <p>
117  * A {@link SQLiteDatabase} can have multiple active sessions at the same
118  * time.  Each session acquires and releases connections to the database
119  * as needed to perform each requested database transaction.  If all connections
120  * are in use, then database transactions on some sessions will block until a
121  * connection becomes available.
122  * </p><p>
123  * The session acquires a single database connection only for the duration
124  * of a single (implicit or explicit) database transaction, then releases it.
125  * This characteristic allows a small pool of database connections to be shared
126  * efficiently by multiple sessions as long as they are not all trying to perform
127  * database transactions at the same time.
128  * </p>
129  *
130  * <h2>Responsiveness</h2>
131  * <p>
132  * Because there are a limited number of database connections and the session holds
133  * a database connection for the entire duration of a database transaction,
134  * it is important to keep transactions short.  This is especially important
135  * for read-write transactions since they may block other transactions
136  * from executing.  Consider calling {@link #yieldTransaction} periodically
137  * during long-running transactions.
138  * </p><p>
139  * Another important consideration is that transactions that take too long to
140  * run may cause the application UI to become unresponsive.  Even if the transaction
141  * is executed in a background thread, the user will get bored and
142  * frustrated if the application shows no data for several seconds while
143  * a transaction runs.
144  * </p><p>
145  * Guidelines:
146  * <ul>
147  * <li>Do not perform database transactions on the UI thread.</li>
148  * <li>Keep database transactions as short as possible.</li>
149  * <li>Simple queries often run faster than complex queries.</li>
150  * <li>Measure the performance of your database transactions.</li>
151  * <li>Consider what will happen when the size of the data set grows.
152  * A query that works well on 100 rows may struggle with 10,000.</li>
153  * </ul>
154  *
155  * <h2>Reentrance</h2>
156  * <p>
157  * This class must tolerate reentrant execution of SQLite operations because
158  * triggers may call custom SQLite functions that perform additional queries.
159  * </p>
160  *
161  * @hide
162  */
163 public final class SQLiteSession {
164     private final SQLiteConnectionPool mConnectionPool;
165 
166     private SQLiteConnection mConnection;
167     private int mConnectionFlags;
168     private int mConnectionUseCount;
169     private Transaction mTransactionPool;
170     private Transaction mTransactionStack;
171 
172     /**
173      * Transaction mode: Deferred.
174      * <p>
175      * In a deferred transaction, no locks are acquired on the database
176      * until the first operation is performed.  If the first operation is
177      * read-only, then a <code>SHARED</code> lock is acquired, otherwise
178      * a <code>RESERVED</code> lock is acquired.
179      * </p><p>
180      * While holding a <code>SHARED</code> lock, this session is only allowed to
181      * read but other sessions are allowed to read or write.
182      * While holding a <code>RESERVED</code> lock, this session is allowed to read
183      * or write but other sessions are only allowed to read.
184      * </p><p>
185      * Because the lock is only acquired when needed in a deferred transaction,
186      * it is possible for another session to write to the database first before
187      * this session has a chance to do anything.
188      * </p><p>
189      * Corresponds to the SQLite <code>BEGIN DEFERRED</code> transaction mode.
190      * </p>
191      */
192     public static final int TRANSACTION_MODE_DEFERRED = 0;
193 
194     /**
195      * Transaction mode: Immediate.
196      * <p>
197      * When an immediate transaction begins, the session acquires a
198      * <code>RESERVED</code> lock.
199      * </p><p>
200      * While holding a <code>RESERVED</code> lock, this session is allowed to read
201      * or write but other sessions are only allowed to read.
202      * </p><p>
203      * Corresponds to the SQLite <code>BEGIN IMMEDIATE</code> transaction mode.
204      * </p>
205      */
206     public static final int TRANSACTION_MODE_IMMEDIATE = 1;
207 
208     /**
209      * Transaction mode: Exclusive.
210      * <p>
211      * When an exclusive transaction begins, the session acquires an
212      * <code>EXCLUSIVE</code> lock.
213      * </p><p>
214      * While holding an <code>EXCLUSIVE</code> lock, this session is allowed to read
215      * or write but no other sessions are allowed to access the database.
216      * </p><p>
217      * Corresponds to the SQLite <code>BEGIN EXCLUSIVE</code> transaction mode.
218      * </p>
219      */
220     public static final int TRANSACTION_MODE_EXCLUSIVE = 2;
221 
222     /**
223      * Creates a session bound to the specified connection pool.
224      *
225      * @param connectionPool The connection pool.
226      */
SQLiteSession(SQLiteConnectionPool connectionPool)227     public SQLiteSession(SQLiteConnectionPool connectionPool) {
228         if (connectionPool == null) {
229             throw new IllegalArgumentException("connectionPool must not be null");
230         }
231 
232         mConnectionPool = connectionPool;
233     }
234 
235     /**
236      * Returns true if the session has a transaction in progress.
237      *
238      * @return True if the session has a transaction in progress.
239      */
hasTransaction()240     public boolean hasTransaction() {
241         return mTransactionStack != null;
242     }
243 
244     /**
245      * Returns true if the session has a nested transaction in progress.
246      *
247      * @return True if the session has a nested transaction in progress.
248      */
hasNestedTransaction()249     public boolean hasNestedTransaction() {
250         return mTransactionStack != null && mTransactionStack.mParent != null;
251     }
252 
253     /**
254      * Returns true if the session has an active database connection.
255      *
256      * @return True if the session has an active database connection.
257      */
hasConnection()258     public boolean hasConnection() {
259         return mConnection != null;
260     }
261 
262     /**
263      * Begins a transaction.
264      * <p>
265      * Transactions may nest.  If the transaction is not in progress,
266      * then a database connection is obtained and a new transaction is started.
267      * Otherwise, a nested transaction is started.
268      * </p><p>
269      * Each call to {@link #beginTransaction} must be matched exactly by a call
270      * to {@link #endTransaction}.  To mark a transaction as successful,
271      * call {@link #setTransactionSuccessful} before calling {@link #endTransaction}.
272      * If the transaction is not successful, or if any of its nested
273      * transactions were not successful, then the entire transaction will
274      * be rolled back when the outermost transaction is ended.
275      * </p>
276      *
277      * @param transactionMode The transaction mode.  One of: {@link #TRANSACTION_MODE_DEFERRED},
278      * {@link #TRANSACTION_MODE_IMMEDIATE}, or {@link #TRANSACTION_MODE_EXCLUSIVE}.
279      * Ignored when creating a nested transaction.
280      * @param transactionListener The transaction listener, or null if none.
281      * @param connectionFlags The connection flags to use if a connection must be
282      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
283      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
284      *
285      * @throws IllegalStateException if {@link #setTransactionSuccessful} has already been
286      * called for the current transaction.
287      * @throws SQLiteException if an error occurs.
288      * @throws OperationCanceledException if the operation was canceled.
289      *
290      * @see #setTransactionSuccessful
291      * @see #yieldTransaction
292      * @see #endTransaction
293      */
beginTransaction(int transactionMode, SQLiteTransactionListener transactionListener, int connectionFlags, CancellationSignal cancellationSignal)294     public void beginTransaction(int transactionMode,
295             SQLiteTransactionListener transactionListener, int connectionFlags,
296             CancellationSignal cancellationSignal) {
297         throwIfTransactionMarkedSuccessful();
298         beginTransactionUnchecked(transactionMode, transactionListener, connectionFlags,
299                 cancellationSignal);
300     }
301 
beginTransactionUnchecked(int transactionMode, SQLiteTransactionListener transactionListener, int connectionFlags, CancellationSignal cancellationSignal)302     private void beginTransactionUnchecked(int transactionMode,
303             SQLiteTransactionListener transactionListener, int connectionFlags,
304             CancellationSignal cancellationSignal) {
305         if (cancellationSignal != null) {
306             cancellationSignal.throwIfCanceled();
307         }
308 
309         if (mTransactionStack == null) {
310             acquireConnection(null, connectionFlags, cancellationSignal); // might throw
311         }
312         try {
313             // Set up the transaction such that we can back out safely
314             // in case we fail part way.
315             if (mTransactionStack == null) {
316                 // Execute SQL might throw a runtime exception.
317                 switch (transactionMode) {
318                     case TRANSACTION_MODE_IMMEDIATE:
319                         mConnection.execute("BEGIN IMMEDIATE;", null,
320                                 cancellationSignal); // might throw
321                         break;
322                     case TRANSACTION_MODE_EXCLUSIVE:
323                         mConnection.execute("BEGIN EXCLUSIVE;", null,
324                                 cancellationSignal); // might throw
325                         break;
326                     default:
327                         mConnection.execute("BEGIN;", null, cancellationSignal); // might throw
328                         break;
329                 }
330             }
331 
332             // Listener might throw a runtime exception.
333             if (transactionListener != null) {
334                 try {
335                     transactionListener.onBegin(); // might throw
336                 } catch (RuntimeException ex) {
337                     if (mTransactionStack == null) {
338                         mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
339                     }
340                     throw ex;
341                 }
342             }
343 
344             // Bookkeeping can't throw, except an OOM, which is just too bad...
345             Transaction transaction = obtainTransaction(transactionMode, transactionListener);
346             transaction.mParent = mTransactionStack;
347             mTransactionStack = transaction;
348         } finally {
349             if (mTransactionStack == null) {
350                 releaseConnection(); // might throw
351             }
352         }
353     }
354 
355     /**
356      * Marks the current transaction as having completed successfully.
357      * <p>
358      * This method can be called at most once between {@link #beginTransaction} and
359      * {@link #endTransaction} to indicate that the changes made by the transaction should be
360      * committed.  If this method is not called, the changes will be rolled back
361      * when the transaction is ended.
362      * </p>
363      *
364      * @throws IllegalStateException if there is no current transaction, or if
365      * {@link #setTransactionSuccessful} has already been called for the current transaction.
366      *
367      * @see #beginTransaction
368      * @see #endTransaction
369      */
setTransactionSuccessful()370     public void setTransactionSuccessful() {
371         throwIfNoTransaction();
372         throwIfTransactionMarkedSuccessful();
373 
374         mTransactionStack.mMarkedSuccessful = true;
375     }
376 
377     /**
378      * Ends the current transaction and commits or rolls back changes.
379      * <p>
380      * If this is the outermost transaction (not nested within any other
381      * transaction), then the changes are committed if {@link #setTransactionSuccessful}
382      * was called or rolled back otherwise.
383      * </p><p>
384      * This method must be called exactly once for each call to {@link #beginTransaction}.
385      * </p>
386      *
387      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
388      *
389      * @throws IllegalStateException if there is no current transaction.
390      * @throws SQLiteException if an error occurs.
391      * @throws OperationCanceledException if the operation was canceled.
392      *
393      * @see #beginTransaction
394      * @see #setTransactionSuccessful
395      * @see #yieldTransaction
396      */
endTransaction(CancellationSignal cancellationSignal)397     public void endTransaction(CancellationSignal cancellationSignal) {
398         throwIfNoTransaction();
399         assert mConnection != null;
400 
401         endTransactionUnchecked(cancellationSignal, false);
402     }
403 
endTransactionUnchecked(CancellationSignal cancellationSignal, boolean yielding)404     private void endTransactionUnchecked(CancellationSignal cancellationSignal, boolean yielding) {
405         if (cancellationSignal != null) {
406             cancellationSignal.throwIfCanceled();
407         }
408 
409         final Transaction top = mTransactionStack;
410         boolean successful = (top.mMarkedSuccessful || yielding) && !top.mChildFailed;
411 
412         RuntimeException listenerException = null;
413         final SQLiteTransactionListener listener = top.mListener;
414         if (listener != null) {
415             try {
416                 if (successful) {
417                     listener.onCommit(); // might throw
418                 } else {
419                     listener.onRollback(); // might throw
420                 }
421             } catch (RuntimeException ex) {
422                 listenerException = ex;
423                 successful = false;
424             }
425         }
426 
427         mTransactionStack = top.mParent;
428         recycleTransaction(top);
429 
430         if (mTransactionStack != null) {
431             if (!successful) {
432                 mTransactionStack.mChildFailed = true;
433             }
434         } else {
435             try {
436                 if (successful) {
437                     mConnection.execute("COMMIT;", null, cancellationSignal); // might throw
438                 } else {
439                     mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
440                 }
441             } finally {
442                 releaseConnection(); // might throw
443             }
444         }
445 
446         if (listenerException != null) {
447             throw listenerException;
448         }
449     }
450 
451     /**
452      * Temporarily ends a transaction to let other threads have use of
453      * the database.  Begins a new transaction after a specified delay.
454      * <p>
455      * If there are other threads waiting to acquire connections,
456      * then the current transaction is committed and the database
457      * connection is released.  After a short delay, a new transaction
458      * is started.
459      * </p><p>
460      * The transaction is assumed to be successful so far.  Do not call
461      * {@link #setTransactionSuccessful()} before calling this method.
462      * This method will fail if the transaction has already been marked
463      * successful.
464      * </p><p>
465      * The changes that were committed by a yield cannot be rolled back later.
466      * </p><p>
467      * Before this method was called, there must already have been
468      * a transaction in progress.  When this method returns, there will
469      * still be a transaction in progress, either the same one as before
470      * or a new one if the transaction was actually yielded.
471      * </p><p>
472      * This method should not be called when there is a nested transaction
473      * in progress because it is not possible to yield a nested transaction.
474      * If <code>throwIfNested</code> is true, then attempting to yield
475      * a nested transaction will throw {@link IllegalStateException}, otherwise
476      * the method will return <code>false</code> in that case.
477      * </p><p>
478      * If there is no nested transaction in progress but a previous nested
479      * transaction failed, then the transaction is not yielded (because it
480      * must be rolled back) and this method returns <code>false</code>.
481      * </p>
482      *
483      * @param sleepAfterYieldDelayMillis A delay time to wait after yielding
484      * the database connection to allow other threads some time to run.
485      * If the value is less than or equal to zero, there will be no additional
486      * delay beyond the time it will take to begin a new transaction.
487      * @param throwIfUnsafe If true, then instead of returning false when no
488      * transaction is in progress, a nested transaction is in progress, or when
489      * the transaction has already been marked successful, throws {@link IllegalStateException}.
490      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
491      * @return True if the transaction was actually yielded.
492      *
493      * @throws IllegalStateException if <code>throwIfNested</code> is true and
494      * there is no current transaction, there is a nested transaction in progress or
495      * if {@link #setTransactionSuccessful} has already been called for the current transaction.
496      * @throws SQLiteException if an error occurs.
497      * @throws OperationCanceledException if the operation was canceled.
498      *
499      * @see #beginTransaction
500      * @see #endTransaction
501      */
yieldTransaction(long sleepAfterYieldDelayMillis, boolean throwIfUnsafe, CancellationSignal cancellationSignal)502     public boolean yieldTransaction(long sleepAfterYieldDelayMillis, boolean throwIfUnsafe,
503             CancellationSignal cancellationSignal) {
504         if (throwIfUnsafe) {
505             throwIfNoTransaction();
506             throwIfTransactionMarkedSuccessful();
507             throwIfNestedTransaction();
508         } else {
509             if (mTransactionStack == null || mTransactionStack.mMarkedSuccessful
510                     || mTransactionStack.mParent != null) {
511                 return false;
512             }
513         }
514         assert mConnection != null;
515 
516         if (mTransactionStack.mChildFailed) {
517             return false;
518         }
519 
520         return yieldTransactionUnchecked(sleepAfterYieldDelayMillis,
521                 cancellationSignal); // might throw
522     }
523 
yieldTransactionUnchecked(long sleepAfterYieldDelayMillis, CancellationSignal cancellationSignal)524     private boolean yieldTransactionUnchecked(long sleepAfterYieldDelayMillis,
525             CancellationSignal cancellationSignal) {
526         if (cancellationSignal != null) {
527             cancellationSignal.throwIfCanceled();
528         }
529 
530         if (!mConnectionPool.shouldYieldConnection(mConnection, mConnectionFlags)) {
531             return false;
532         }
533 
534         final int transactionMode = mTransactionStack.mMode;
535         final SQLiteTransactionListener listener = mTransactionStack.mListener;
536         final int connectionFlags = mConnectionFlags;
537         endTransactionUnchecked(cancellationSignal, true); // might throw
538 
539         if (sleepAfterYieldDelayMillis > 0) {
540             try {
541                 Thread.sleep(sleepAfterYieldDelayMillis);
542             } catch (InterruptedException ex) {
543                 // we have been interrupted, that's all we need to do
544             }
545         }
546 
547         beginTransactionUnchecked(transactionMode, listener, connectionFlags,
548                 cancellationSignal); // might throw
549         return true;
550     }
551 
552     /**
553      * Prepares a statement for execution but does not bind its parameters or execute it.
554      * <p>
555      * This method can be used to check for syntax errors during compilation
556      * prior to execution of the statement.  If the {@code outStatementInfo} argument
557      * is not null, the provided {@link SQLiteStatementInfo} object is populated
558      * with information about the statement.
559      * </p><p>
560      * A prepared statement makes no reference to the arguments that may eventually
561      * be bound to it, consequently it it possible to cache certain prepared statements
562      * such as SELECT or INSERT/UPDATE statements.  If the statement is cacheable,
563      * then it will be stored in the cache for later and reused if possible.
564      * </p>
565      *
566      * @param sql The SQL statement to prepare.
567      * @param connectionFlags The connection flags to use if a connection must be
568      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
569      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
570      * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate
571      * with information about the statement, or null if none.
572      *
573      * @throws SQLiteException if an error occurs, such as a syntax error.
574      * @throws OperationCanceledException if the operation was canceled.
575      */
prepare(String sql, int connectionFlags, CancellationSignal cancellationSignal, SQLiteStatementInfo outStatementInfo)576     public void prepare(String sql, int connectionFlags, CancellationSignal cancellationSignal,
577             SQLiteStatementInfo outStatementInfo) {
578         if (sql == null) {
579             throw new IllegalArgumentException("sql must not be null.");
580         }
581 
582         if (cancellationSignal != null) {
583             cancellationSignal.throwIfCanceled();
584         }
585 
586         acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
587         try {
588             mConnection.prepare(sql, outStatementInfo); // might throw
589         } finally {
590             releaseConnection(); // might throw
591         }
592     }
593 
594     /**
595      * Executes a statement that does not return a result.
596      *
597      * @param sql The SQL statement to execute.
598      * @param bindArgs The arguments to bind, or null if none.
599      * @param connectionFlags The connection flags to use if a connection must be
600      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
601      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
602      *
603      * @throws SQLiteException if an error occurs, such as a syntax error
604      * or invalid number of bind arguments.
605      * @throws OperationCanceledException if the operation was canceled.
606      */
execute(String sql, Object[] bindArgs, int connectionFlags, CancellationSignal cancellationSignal)607     public void execute(String sql, Object[] bindArgs, int connectionFlags,
608             CancellationSignal cancellationSignal) {
609         if (sql == null) {
610             throw new IllegalArgumentException("sql must not be null.");
611         }
612 
613         if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
614             return;
615         }
616 
617         acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
618         try {
619             mConnection.execute(sql, bindArgs, cancellationSignal); // might throw
620         } finally {
621             releaseConnection(); // might throw
622         }
623     }
624 
625     /**
626      * Executes a statement that returns a single <code>long</code> result.
627      *
628      * @param sql The SQL statement to execute.
629      * @param bindArgs The arguments to bind, or null if none.
630      * @param connectionFlags The connection flags to use if a connection must be
631      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
632      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
633      * @return The value of the first column in the first row of the result set
634      * as a <code>long</code>, or zero if none.
635      *
636      * @throws SQLiteException if an error occurs, such as a syntax error
637      * or invalid number of bind arguments.
638      * @throws OperationCanceledException if the operation was canceled.
639      */
executeForLong(String sql, Object[] bindArgs, int connectionFlags, CancellationSignal cancellationSignal)640     public long executeForLong(String sql, Object[] bindArgs, int connectionFlags,
641             CancellationSignal cancellationSignal) {
642         if (sql == null) {
643             throw new IllegalArgumentException("sql must not be null.");
644         }
645 
646         if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
647             return 0;
648         }
649 
650         acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
651         try {
652             return mConnection.executeForLong(sql, bindArgs, cancellationSignal); // might throw
653         } finally {
654             releaseConnection(); // might throw
655         }
656     }
657 
658     /**
659      * Executes a statement that returns a single {@link String} result.
660      *
661      * @param sql The SQL statement to execute.
662      * @param bindArgs The arguments to bind, or null if none.
663      * @param connectionFlags The connection flags to use if a connection must be
664      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
665      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
666      * @return The value of the first column in the first row of the result set
667      * as a <code>String</code>, or null if none.
668      *
669      * @throws SQLiteException if an error occurs, such as a syntax error
670      * or invalid number of bind arguments.
671      * @throws OperationCanceledException if the operation was canceled.
672      */
executeForString(String sql, Object[] bindArgs, int connectionFlags, CancellationSignal cancellationSignal)673     public String executeForString(String sql, Object[] bindArgs, int connectionFlags,
674             CancellationSignal cancellationSignal) {
675         if (sql == null) {
676             throw new IllegalArgumentException("sql must not be null.");
677         }
678 
679         if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
680             return null;
681         }
682 
683         acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
684         try {
685             return mConnection.executeForString(sql, bindArgs, cancellationSignal); // might throw
686         } finally {
687             releaseConnection(); // might throw
688         }
689     }
690 
691     /**
692      * Executes a statement that returns a single BLOB result as a
693      * file descriptor to a shared memory region.
694      *
695      * @param sql The SQL statement to execute.
696      * @param bindArgs The arguments to bind, or null if none.
697      * @param connectionFlags The connection flags to use if a connection must be
698      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
699      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
700      * @return The file descriptor for a shared memory region that contains
701      * the value of the first column in the first row of the result set as a BLOB,
702      * or null if none.
703      *
704      * @throws SQLiteException if an error occurs, such as a syntax error
705      * or invalid number of bind arguments.
706      * @throws OperationCanceledException if the operation was canceled.
707      */
executeForBlobFileDescriptor(String sql, Object[] bindArgs, int connectionFlags, CancellationSignal cancellationSignal)708     public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs,
709             int connectionFlags, CancellationSignal cancellationSignal) {
710         if (sql == null) {
711             throw new IllegalArgumentException("sql must not be null.");
712         }
713 
714         if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
715             return null;
716         }
717 
718         acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
719         try {
720             return mConnection.executeForBlobFileDescriptor(sql, bindArgs,
721                     cancellationSignal); // might throw
722         } finally {
723             releaseConnection(); // might throw
724         }
725     }
726 
727     /**
728      * Executes a statement that returns a count of the number of rows
729      * that were changed.  Use for UPDATE or DELETE SQL statements.
730      *
731      * @param sql The SQL statement to execute.
732      * @param bindArgs The arguments to bind, or null if none.
733      * @param connectionFlags The connection flags to use if a connection must be
734      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
735      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
736      * @return The number of rows that were changed.
737      *
738      * @throws SQLiteException if an error occurs, such as a syntax error
739      * or invalid number of bind arguments.
740      * @throws OperationCanceledException if the operation was canceled.
741      */
executeForChangedRowCount(String sql, Object[] bindArgs, int connectionFlags, CancellationSignal cancellationSignal)742     public int executeForChangedRowCount(String sql, Object[] bindArgs, int connectionFlags,
743             CancellationSignal cancellationSignal) {
744         if (sql == null) {
745             throw new IllegalArgumentException("sql must not be null.");
746         }
747 
748         if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
749             return 0;
750         }
751 
752         acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
753         try {
754             return mConnection.executeForChangedRowCount(sql, bindArgs,
755                     cancellationSignal); // might throw
756         } finally {
757             releaseConnection(); // might throw
758         }
759     }
760 
761     /**
762      * Executes a statement that returns the row id of the last row inserted
763      * by the statement.  Use for INSERT SQL statements.
764      *
765      * @param sql The SQL statement to execute.
766      * @param bindArgs The arguments to bind, or null if none.
767      * @param connectionFlags The connection flags to use if a connection must be
768      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
769      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
770      * @return The row id of the last row that was inserted, or 0 if none.
771      *
772      * @throws SQLiteException if an error occurs, such as a syntax error
773      * or invalid number of bind arguments.
774      * @throws OperationCanceledException if the operation was canceled.
775      */
executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags, CancellationSignal cancellationSignal)776     public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags,
777             CancellationSignal cancellationSignal) {
778         if (sql == null) {
779             throw new IllegalArgumentException("sql must not be null.");
780         }
781 
782         if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
783             return 0;
784         }
785 
786         acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
787         try {
788             return mConnection.executeForLastInsertedRowId(sql, bindArgs,
789                     cancellationSignal); // might throw
790         } finally {
791             releaseConnection(); // might throw
792         }
793     }
794 
795     /**
796      * Executes a statement and populates the specified {@link CursorWindow}
797      * with a range of results.  Returns the number of rows that were counted
798      * during query execution.
799      *
800      * @param sql The SQL statement to execute.
801      * @param bindArgs The arguments to bind, or null if none.
802      * @param window The cursor window to clear and fill.
803      * @param startPos The start position for filling the window.
804      * @param requiredPos The position of a row that MUST be in the window.
805      * If it won't fit, then the query should discard part of what it filled
806      * so that it does.  Must be greater than or equal to <code>startPos</code>.
807      * @param countAllRows True to count all rows that the query would return
808      * regagless of whether they fit in the window.
809      * @param connectionFlags The connection flags to use if a connection must be
810      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
811      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
812      * @return The number of rows that were counted during query execution.  Might
813      * not be all rows in the result set unless <code>countAllRows</code> is true.
814      *
815      * @throws SQLiteException if an error occurs, such as a syntax error
816      * or invalid number of bind arguments.
817      * @throws OperationCanceledException if the operation was canceled.
818      */
executeForCursorWindow(String sql, Object[] bindArgs, CursorWindow window, int startPos, int requiredPos, boolean countAllRows, int connectionFlags, CancellationSignal cancellationSignal)819     public int executeForCursorWindow(String sql, Object[] bindArgs,
820             CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
821             int connectionFlags, CancellationSignal cancellationSignal) {
822         if (sql == null) {
823             throw new IllegalArgumentException("sql must not be null.");
824         }
825         if (window == null) {
826             throw new IllegalArgumentException("window must not be null.");
827         }
828 
829         if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
830             window.clear();
831             return 0;
832         }
833 
834         acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
835         try {
836             return mConnection.executeForCursorWindow(sql, bindArgs,
837                     window, startPos, requiredPos, countAllRows,
838                     cancellationSignal); // might throw
839         } finally {
840             releaseConnection(); // might throw
841         }
842     }
843 
844     /**
845      * Performs special reinterpretation of certain SQL statements such as "BEGIN",
846      * "COMMIT" and "ROLLBACK" to ensure that transaction state invariants are
847      * maintained.
848      *
849      * This function is mainly used to support legacy apps that perform their
850      * own transactions by executing raw SQL rather than calling {@link #beginTransaction}
851      * and the like.
852      *
853      * @param sql The SQL statement to execute.
854      * @param bindArgs The arguments to bind, or null if none.
855      * @param connectionFlags The connection flags to use if a connection must be
856      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
857      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
858      * @return True if the statement was of a special form that was handled here,
859      * false otherwise.
860      *
861      * @throws SQLiteException if an error occurs, such as a syntax error
862      * or invalid number of bind arguments.
863      * @throws OperationCanceledException if the operation was canceled.
864      */
executeSpecial(String sql, Object[] bindArgs, int connectionFlags, CancellationSignal cancellationSignal)865     private boolean executeSpecial(String sql, Object[] bindArgs, int connectionFlags,
866             CancellationSignal cancellationSignal) {
867         if (cancellationSignal != null) {
868             cancellationSignal.throwIfCanceled();
869         }
870 
871         final int type = DatabaseUtils.getSqlStatementType(sql);
872         switch (type) {
873             case DatabaseUtils.STATEMENT_BEGIN:
874                 beginTransaction(TRANSACTION_MODE_EXCLUSIVE, null, connectionFlags,
875                         cancellationSignal);
876                 return true;
877 
878             case DatabaseUtils.STATEMENT_COMMIT:
879                 setTransactionSuccessful();
880                 endTransaction(cancellationSignal);
881                 return true;
882 
883             case DatabaseUtils.STATEMENT_ABORT:
884                 endTransaction(cancellationSignal);
885                 return true;
886         }
887         return false;
888     }
889 
acquireConnection(String sql, int connectionFlags, CancellationSignal cancellationSignal)890     private void acquireConnection(String sql, int connectionFlags,
891             CancellationSignal cancellationSignal) {
892         if (mConnection == null) {
893             assert mConnectionUseCount == 0;
894             mConnection = mConnectionPool.acquireConnection(sql, connectionFlags,
895                     cancellationSignal); // might throw
896             mConnectionFlags = connectionFlags;
897         }
898         mConnectionUseCount += 1;
899     }
900 
releaseConnection()901     private void releaseConnection() {
902         assert mConnection != null;
903         assert mConnectionUseCount > 0;
904         if (--mConnectionUseCount == 0) {
905             try {
906                 mConnectionPool.releaseConnection(mConnection); // might throw
907             } finally {
908                 mConnection = null;
909             }
910         }
911     }
912 
throwIfNoTransaction()913     private void throwIfNoTransaction() {
914         if (mTransactionStack == null) {
915             throw new IllegalStateException("Cannot perform this operation because "
916                     + "there is no current transaction.");
917         }
918     }
919 
throwIfTransactionMarkedSuccessful()920     private void throwIfTransactionMarkedSuccessful() {
921         if (mTransactionStack != null && mTransactionStack.mMarkedSuccessful) {
922             throw new IllegalStateException("Cannot perform this operation because "
923                     + "the transaction has already been marked successful.  The only "
924                     + "thing you can do now is call endTransaction().");
925         }
926     }
927 
throwIfNestedTransaction()928     private void throwIfNestedTransaction() {
929         if (hasNestedTransaction()) {
930             throw new IllegalStateException("Cannot perform this operation because "
931                     + "a nested transaction is in progress.");
932         }
933     }
934 
obtainTransaction(int mode, SQLiteTransactionListener listener)935     private Transaction obtainTransaction(int mode, SQLiteTransactionListener listener) {
936         Transaction transaction = mTransactionPool;
937         if (transaction != null) {
938             mTransactionPool = transaction.mParent;
939             transaction.mParent = null;
940             transaction.mMarkedSuccessful = false;
941             transaction.mChildFailed = false;
942         } else {
943             transaction = new Transaction();
944         }
945         transaction.mMode = mode;
946         transaction.mListener = listener;
947         return transaction;
948     }
949 
recycleTransaction(Transaction transaction)950     private void recycleTransaction(Transaction transaction) {
951         transaction.mParent = mTransactionPool;
952         transaction.mListener = null;
953         mTransactionPool = transaction;
954     }
955 
956     private static final class Transaction {
957         public Transaction mParent;
958         public int mMode;
959         public SQLiteTransactionListener mListener;
960         public boolean mMarkedSuccessful;
961         public boolean mChildFailed;
962     }
963 }
964