SQLiteConnection.java revision 4c1241df8f8b7fd5ec3dff6c7e0f66271248e76e
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 17package android.database.sqlite; 18 19import dalvik.system.BlockGuard; 20import dalvik.system.CloseGuard; 21 22import android.content.CancellationSignal; 23import android.content.OperationCanceledException; 24import android.database.Cursor; 25import android.database.CursorWindow; 26import android.database.DatabaseUtils; 27import android.database.sqlite.SQLiteDebug.DbStats; 28import android.os.ParcelFileDescriptor; 29import android.util.Log; 30import android.util.LruCache; 31import android.util.Printer; 32 33import java.sql.Date; 34import java.text.SimpleDateFormat; 35import java.util.ArrayList; 36import java.util.Map; 37import java.util.regex.Pattern; 38 39/** 40 * Represents a SQLite database connection. 41 * Each connection wraps an instance of a native <code>sqlite3</code> object. 42 * <p> 43 * When database connection pooling is enabled, there can be multiple active 44 * connections to the same database. Otherwise there is typically only one 45 * connection per database. 46 * </p><p> 47 * When the SQLite WAL feature is enabled, multiple readers and one writer 48 * can concurrently access the database. Without WAL, readers and writers 49 * are mutually exclusive. 50 * </p> 51 * 52 * <h2>Ownership and concurrency guarantees</h2> 53 * <p> 54 * Connection objects are not thread-safe. They are acquired as needed to 55 * perform a database operation and are then returned to the pool. At any 56 * given time, a connection is either owned and used by a {@link SQLiteSession} 57 * object or the {@link SQLiteConnectionPool}. Those classes are 58 * responsible for serializing operations to guard against concurrent 59 * use of a connection. 60 * </p><p> 61 * The guarantee of having a single owner allows this class to be implemented 62 * without locks and greatly simplifies resource management. 63 * </p> 64 * 65 * <h2>Encapsulation guarantees</h2> 66 * <p> 67 * The connection object object owns *all* of the SQLite related native 68 * objects that are associated with the connection. What's more, there are 69 * no other objects in the system that are capable of obtaining handles to 70 * those native objects. Consequently, when the connection is closed, we do 71 * not have to worry about what other components might have references to 72 * its associated SQLite state -- there are none. 73 * </p><p> 74 * Encapsulation is what ensures that the connection object's 75 * lifecycle does not become a tortured mess of finalizers and reference 76 * queues. 77 * </p> 78 * 79 * <h2>Reentrance</h2> 80 * <p> 81 * This class must tolerate reentrant execution of SQLite operations because 82 * triggers may call custom SQLite functions that perform additional queries. 83 * </p> 84 * 85 * @hide 86 */ 87public final class SQLiteConnection implements CancellationSignal.OnCancelListener { 88 private static final String TAG = "SQLiteConnection"; 89 private static final boolean DEBUG = false; 90 91 private static final String[] EMPTY_STRING_ARRAY = new String[0]; 92 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 93 94 private static final Pattern TRIM_SQL_PATTERN = Pattern.compile("[\\s]*\\n+[\\s]*"); 95 96 private final CloseGuard mCloseGuard = CloseGuard.get(); 97 98 private final SQLiteConnectionPool mPool; 99 private final SQLiteDatabaseConfiguration mConfiguration; 100 private final int mConnectionId; 101 private final boolean mIsPrimaryConnection; 102 private final PreparedStatementCache mPreparedStatementCache; 103 private PreparedStatement mPreparedStatementPool; 104 105 // The recent operations log. 106 private final OperationLog mRecentOperations = new OperationLog(); 107 108 // The native SQLiteConnection pointer. (FOR INTERNAL USE ONLY) 109 private int mConnectionPtr; 110 111 private boolean mOnlyAllowReadOnlyOperations; 112 113 // The number of times attachCancellationSignal has been called. 114 // Because SQLite statement execution can be re-entrant, we keep track of how many 115 // times we have attempted to attach a cancellation signal to the connection so that 116 // we can ensure that we detach the signal at the right time. 117 private int mCancellationSignalAttachCount; 118 119 private static native int nativeOpen(String path, int openFlags, String label, 120 boolean enableTrace, boolean enableProfile); 121 private static native void nativeClose(int connectionPtr); 122 private static native void nativeRegisterCustomFunction(int connectionPtr, 123 SQLiteCustomFunction function); 124 private static native void nativeSetLocale(int connectionPtr, String locale); 125 private static native int nativePrepareStatement(int connectionPtr, String sql); 126 private static native void nativeFinalizeStatement(int connectionPtr, int statementPtr); 127 private static native int nativeGetParameterCount(int connectionPtr, int statementPtr); 128 private static native boolean nativeIsReadOnly(int connectionPtr, int statementPtr); 129 private static native int nativeGetColumnCount(int connectionPtr, int statementPtr); 130 private static native String nativeGetColumnName(int connectionPtr, int statementPtr, 131 int index); 132 private static native void nativeBindNull(int connectionPtr, int statementPtr, 133 int index); 134 private static native void nativeBindLong(int connectionPtr, int statementPtr, 135 int index, long value); 136 private static native void nativeBindDouble(int connectionPtr, int statementPtr, 137 int index, double value); 138 private static native void nativeBindString(int connectionPtr, int statementPtr, 139 int index, String value); 140 private static native void nativeBindBlob(int connectionPtr, int statementPtr, 141 int index, byte[] value); 142 private static native void nativeResetStatementAndClearBindings( 143 int connectionPtr, int statementPtr); 144 private static native void nativeExecute(int connectionPtr, int statementPtr); 145 private static native long nativeExecuteForLong(int connectionPtr, int statementPtr); 146 private static native String nativeExecuteForString(int connectionPtr, int statementPtr); 147 private static native int nativeExecuteForBlobFileDescriptor( 148 int connectionPtr, int statementPtr); 149 private static native int nativeExecuteForChangedRowCount(int connectionPtr, int statementPtr); 150 private static native long nativeExecuteForLastInsertedRowId( 151 int connectionPtr, int statementPtr); 152 private static native long nativeExecuteForCursorWindow( 153 int connectionPtr, int statementPtr, int windowPtr, 154 int startPos, int requiredPos, boolean countAllRows); 155 private static native int nativeGetDbLookaside(int connectionPtr); 156 private static native void nativeCancel(int connectionPtr); 157 private static native void nativeResetCancel(int connectionPtr, boolean cancelable); 158 159 private SQLiteConnection(SQLiteConnectionPool pool, 160 SQLiteDatabaseConfiguration configuration, 161 int connectionId, boolean primaryConnection) { 162 mPool = pool; 163 mConfiguration = new SQLiteDatabaseConfiguration(configuration); 164 mConnectionId = connectionId; 165 mIsPrimaryConnection = primaryConnection; 166 mPreparedStatementCache = new PreparedStatementCache( 167 mConfiguration.maxSqlCacheSize); 168 mCloseGuard.open("close"); 169 } 170 171 @Override 172 protected void finalize() throws Throwable { 173 try { 174 if (mPool != null && mConnectionPtr != 0) { 175 mPool.onConnectionLeaked(); 176 } 177 178 dispose(true); 179 } finally { 180 super.finalize(); 181 } 182 } 183 184 // Called by SQLiteConnectionPool only. 185 static SQLiteConnection open(SQLiteConnectionPool pool, 186 SQLiteDatabaseConfiguration configuration, 187 int connectionId, boolean primaryConnection) { 188 SQLiteConnection connection = new SQLiteConnection(pool, configuration, 189 connectionId, primaryConnection); 190 try { 191 connection.open(); 192 return connection; 193 } catch (SQLiteException ex) { 194 connection.dispose(false); 195 throw ex; 196 } 197 } 198 199 // Called by SQLiteConnectionPool only. 200 // Closes the database closes and releases all of its associated resources. 201 // Do not call methods on the connection after it is closed. It will probably crash. 202 void close() { 203 dispose(false); 204 } 205 206 private void open() { 207 mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags, 208 mConfiguration.label, 209 SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME); 210 211 setLocaleFromConfiguration(); 212 } 213 214 private void dispose(boolean finalized) { 215 if (mCloseGuard != null) { 216 if (finalized) { 217 mCloseGuard.warnIfOpen(); 218 } 219 mCloseGuard.close(); 220 } 221 222 if (mConnectionPtr != 0) { 223 final int cookie = mRecentOperations.beginOperation("close", null, null); 224 try { 225 mPreparedStatementCache.evictAll(); 226 nativeClose(mConnectionPtr); 227 mConnectionPtr = 0; 228 } finally { 229 mRecentOperations.endOperation(cookie); 230 } 231 } 232 } 233 234 private void setLocaleFromConfiguration() { 235 nativeSetLocale(mConnectionPtr, mConfiguration.locale.toString()); 236 } 237 238 // Called by SQLiteConnectionPool only. 239 void reconfigure(SQLiteDatabaseConfiguration configuration) { 240 // Register custom functions. 241 final int functionCount = configuration.customFunctions.size(); 242 for (int i = 0; i < functionCount; i++) { 243 SQLiteCustomFunction function = configuration.customFunctions.get(i); 244 if (!mConfiguration.customFunctions.contains(function)) { 245 nativeRegisterCustomFunction(mConnectionPtr, function); 246 } 247 } 248 249 // Remember whether locale has changed. 250 boolean localeChanged = !configuration.locale.equals(mConfiguration.locale); 251 252 // Update configuration parameters. 253 mConfiguration.updateParametersFrom(configuration); 254 255 // Update prepared statement cache size. 256 mPreparedStatementCache.resize(configuration.maxSqlCacheSize); 257 258 // Update locale. 259 if (localeChanged) { 260 setLocaleFromConfiguration(); 261 } 262 } 263 264 // Called by SQLiteConnectionPool only. 265 // When set to true, executing write operations will throw SQLiteException. 266 // Preparing statements that might write is ok, just don't execute them. 267 void setOnlyAllowReadOnlyOperations(boolean readOnly) { 268 mOnlyAllowReadOnlyOperations = readOnly; 269 } 270 271 // Called by SQLiteConnectionPool only. 272 // Returns true if the prepared statement cache contains the specified SQL. 273 boolean isPreparedStatementInCache(String sql) { 274 return mPreparedStatementCache.get(sql) != null; 275 } 276 277 /** 278 * Gets the unique id of this connection. 279 * @return The connection id. 280 */ 281 public int getConnectionId() { 282 return mConnectionId; 283 } 284 285 /** 286 * Returns true if this is the primary database connection. 287 * @return True if this is the primary database connection. 288 */ 289 public boolean isPrimaryConnection() { 290 return mIsPrimaryConnection; 291 } 292 293 /** 294 * Prepares a statement for execution but does not bind its parameters or execute it. 295 * <p> 296 * This method can be used to check for syntax errors during compilation 297 * prior to execution of the statement. If the {@code outStatementInfo} argument 298 * is not null, the provided {@link SQLiteStatementInfo} object is populated 299 * with information about the statement. 300 * </p><p> 301 * A prepared statement makes no reference to the arguments that may eventually 302 * be bound to it, consequently it it possible to cache certain prepared statements 303 * such as SELECT or INSERT/UPDATE statements. If the statement is cacheable, 304 * then it will be stored in the cache for later. 305 * </p><p> 306 * To take advantage of this behavior as an optimization, the connection pool 307 * provides a method to acquire a connection that already has a given SQL statement 308 * in its prepared statement cache so that it is ready for execution. 309 * </p> 310 * 311 * @param sql The SQL statement to prepare. 312 * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate 313 * with information about the statement, or null if none. 314 * 315 * @throws SQLiteException if an error occurs, such as a syntax error. 316 */ 317 public void prepare(String sql, SQLiteStatementInfo outStatementInfo) { 318 if (sql == null) { 319 throw new IllegalArgumentException("sql must not be null."); 320 } 321 322 final int cookie = mRecentOperations.beginOperation("prepare", sql, null); 323 try { 324 final PreparedStatement statement = acquirePreparedStatement(sql); 325 try { 326 if (outStatementInfo != null) { 327 outStatementInfo.numParameters = statement.mNumParameters; 328 outStatementInfo.readOnly = statement.mReadOnly; 329 330 final int columnCount = nativeGetColumnCount( 331 mConnectionPtr, statement.mStatementPtr); 332 if (columnCount == 0) { 333 outStatementInfo.columnNames = EMPTY_STRING_ARRAY; 334 } else { 335 outStatementInfo.columnNames = new String[columnCount]; 336 for (int i = 0; i < columnCount; i++) { 337 outStatementInfo.columnNames[i] = nativeGetColumnName( 338 mConnectionPtr, statement.mStatementPtr, i); 339 } 340 } 341 } 342 } finally { 343 releasePreparedStatement(statement); 344 } 345 } catch (RuntimeException ex) { 346 mRecentOperations.failOperation(cookie, ex); 347 throw ex; 348 } finally { 349 mRecentOperations.endOperation(cookie); 350 } 351 } 352 353 /** 354 * Executes a statement that does not return a result. 355 * 356 * @param sql The SQL statement to execute. 357 * @param bindArgs The arguments to bind, or null if none. 358 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 359 * 360 * @throws SQLiteException if an error occurs, such as a syntax error 361 * or invalid number of bind arguments. 362 * @throws OperationCanceledException if the operation was canceled. 363 */ 364 public void execute(String sql, Object[] bindArgs, 365 CancellationSignal cancellationSignal) { 366 if (sql == null) { 367 throw new IllegalArgumentException("sql must not be null."); 368 } 369 370 final int cookie = mRecentOperations.beginOperation("execute", sql, bindArgs); 371 try { 372 final PreparedStatement statement = acquirePreparedStatement(sql); 373 try { 374 throwIfStatementForbidden(statement); 375 bindArguments(statement, bindArgs); 376 applyBlockGuardPolicy(statement); 377 attachCancellationSignal(cancellationSignal); 378 try { 379 nativeExecute(mConnectionPtr, statement.mStatementPtr); 380 } finally { 381 detachCancellationSignal(cancellationSignal); 382 } 383 } finally { 384 releasePreparedStatement(statement); 385 } 386 } catch (RuntimeException ex) { 387 mRecentOperations.failOperation(cookie, ex); 388 throw ex; 389 } finally { 390 mRecentOperations.endOperation(cookie); 391 } 392 } 393 394 /** 395 * Executes a statement that returns a single <code>long</code> result. 396 * 397 * @param sql The SQL statement to execute. 398 * @param bindArgs The arguments to bind, or null if none. 399 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 400 * @return The value of the first column in the first row of the result set 401 * as a <code>long</code>, or zero if none. 402 * 403 * @throws SQLiteException if an error occurs, such as a syntax error 404 * or invalid number of bind arguments. 405 * @throws OperationCanceledException if the operation was canceled. 406 */ 407 public long executeForLong(String sql, Object[] bindArgs, 408 CancellationSignal cancellationSignal) { 409 if (sql == null) { 410 throw new IllegalArgumentException("sql must not be null."); 411 } 412 413 final int cookie = mRecentOperations.beginOperation("executeForLong", sql, bindArgs); 414 try { 415 final PreparedStatement statement = acquirePreparedStatement(sql); 416 try { 417 throwIfStatementForbidden(statement); 418 bindArguments(statement, bindArgs); 419 applyBlockGuardPolicy(statement); 420 attachCancellationSignal(cancellationSignal); 421 try { 422 return nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr); 423 } finally { 424 detachCancellationSignal(cancellationSignal); 425 } 426 } finally { 427 releasePreparedStatement(statement); 428 } 429 } catch (RuntimeException ex) { 430 mRecentOperations.failOperation(cookie, ex); 431 throw ex; 432 } finally { 433 mRecentOperations.endOperation(cookie); 434 } 435 } 436 437 /** 438 * Executes a statement that returns a single {@link String} result. 439 * 440 * @param sql The SQL statement to execute. 441 * @param bindArgs The arguments to bind, or null if none. 442 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 443 * @return The value of the first column in the first row of the result set 444 * as a <code>String</code>, or null if none. 445 * 446 * @throws SQLiteException if an error occurs, such as a syntax error 447 * or invalid number of bind arguments. 448 * @throws OperationCanceledException if the operation was canceled. 449 */ 450 public String executeForString(String sql, Object[] bindArgs, 451 CancellationSignal cancellationSignal) { 452 if (sql == null) { 453 throw new IllegalArgumentException("sql must not be null."); 454 } 455 456 final int cookie = mRecentOperations.beginOperation("executeForString", sql, bindArgs); 457 try { 458 final PreparedStatement statement = acquirePreparedStatement(sql); 459 try { 460 throwIfStatementForbidden(statement); 461 bindArguments(statement, bindArgs); 462 applyBlockGuardPolicy(statement); 463 attachCancellationSignal(cancellationSignal); 464 try { 465 return nativeExecuteForString(mConnectionPtr, statement.mStatementPtr); 466 } finally { 467 detachCancellationSignal(cancellationSignal); 468 } 469 } finally { 470 releasePreparedStatement(statement); 471 } 472 } catch (RuntimeException ex) { 473 mRecentOperations.failOperation(cookie, ex); 474 throw ex; 475 } finally { 476 mRecentOperations.endOperation(cookie); 477 } 478 } 479 480 /** 481 * Executes a statement that returns a single BLOB result as a 482 * file descriptor to a shared memory region. 483 * 484 * @param sql The SQL statement to execute. 485 * @param bindArgs The arguments to bind, or null if none. 486 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 487 * @return The file descriptor for a shared memory region that contains 488 * the value of the first column in the first row of the result set as a BLOB, 489 * or null if none. 490 * 491 * @throws SQLiteException if an error occurs, such as a syntax error 492 * or invalid number of bind arguments. 493 * @throws OperationCanceledException if the operation was canceled. 494 */ 495 public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs, 496 CancellationSignal cancellationSignal) { 497 if (sql == null) { 498 throw new IllegalArgumentException("sql must not be null."); 499 } 500 501 final int cookie = mRecentOperations.beginOperation("executeForBlobFileDescriptor", 502 sql, bindArgs); 503 try { 504 final PreparedStatement statement = acquirePreparedStatement(sql); 505 try { 506 throwIfStatementForbidden(statement); 507 bindArguments(statement, bindArgs); 508 applyBlockGuardPolicy(statement); 509 attachCancellationSignal(cancellationSignal); 510 try { 511 int fd = nativeExecuteForBlobFileDescriptor( 512 mConnectionPtr, statement.mStatementPtr); 513 return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null; 514 } finally { 515 detachCancellationSignal(cancellationSignal); 516 } 517 } finally { 518 releasePreparedStatement(statement); 519 } 520 } catch (RuntimeException ex) { 521 mRecentOperations.failOperation(cookie, ex); 522 throw ex; 523 } finally { 524 mRecentOperations.endOperation(cookie); 525 } 526 } 527 528 /** 529 * Executes a statement that returns a count of the number of rows 530 * that were changed. Use for UPDATE or DELETE SQL statements. 531 * 532 * @param sql The SQL statement to execute. 533 * @param bindArgs The arguments to bind, or null if none. 534 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 535 * @return The number of rows that were changed. 536 * 537 * @throws SQLiteException if an error occurs, such as a syntax error 538 * or invalid number of bind arguments. 539 * @throws OperationCanceledException if the operation was canceled. 540 */ 541 public int executeForChangedRowCount(String sql, Object[] bindArgs, 542 CancellationSignal cancellationSignal) { 543 if (sql == null) { 544 throw new IllegalArgumentException("sql must not be null."); 545 } 546 547 final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount", 548 sql, bindArgs); 549 try { 550 final PreparedStatement statement = acquirePreparedStatement(sql); 551 try { 552 throwIfStatementForbidden(statement); 553 bindArguments(statement, bindArgs); 554 applyBlockGuardPolicy(statement); 555 attachCancellationSignal(cancellationSignal); 556 try { 557 return nativeExecuteForChangedRowCount( 558 mConnectionPtr, statement.mStatementPtr); 559 } finally { 560 detachCancellationSignal(cancellationSignal); 561 } 562 } finally { 563 releasePreparedStatement(statement); 564 } 565 } catch (RuntimeException ex) { 566 mRecentOperations.failOperation(cookie, ex); 567 throw ex; 568 } finally { 569 mRecentOperations.endOperation(cookie); 570 } 571 } 572 573 /** 574 * Executes a statement that returns the row id of the last row inserted 575 * by the statement. Use for INSERT SQL statements. 576 * 577 * @param sql The SQL statement to execute. 578 * @param bindArgs The arguments to bind, or null if none. 579 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 580 * @return The row id of the last row that was inserted, or 0 if none. 581 * 582 * @throws SQLiteException if an error occurs, such as a syntax error 583 * or invalid number of bind arguments. 584 * @throws OperationCanceledException if the operation was canceled. 585 */ 586 public long executeForLastInsertedRowId(String sql, Object[] bindArgs, 587 CancellationSignal cancellationSignal) { 588 if (sql == null) { 589 throw new IllegalArgumentException("sql must not be null."); 590 } 591 592 final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId", 593 sql, bindArgs); 594 try { 595 final PreparedStatement statement = acquirePreparedStatement(sql); 596 try { 597 throwIfStatementForbidden(statement); 598 bindArguments(statement, bindArgs); 599 applyBlockGuardPolicy(statement); 600 attachCancellationSignal(cancellationSignal); 601 try { 602 return nativeExecuteForLastInsertedRowId( 603 mConnectionPtr, statement.mStatementPtr); 604 } finally { 605 detachCancellationSignal(cancellationSignal); 606 } 607 } finally { 608 releasePreparedStatement(statement); 609 } 610 } catch (RuntimeException ex) { 611 mRecentOperations.failOperation(cookie, ex); 612 throw ex; 613 } finally { 614 mRecentOperations.endOperation(cookie); 615 } 616 } 617 618 /** 619 * Executes a statement and populates the specified {@link CursorWindow} 620 * with a range of results. Returns the number of rows that were counted 621 * during query execution. 622 * 623 * @param sql The SQL statement to execute. 624 * @param bindArgs The arguments to bind, or null if none. 625 * @param window The cursor window to clear and fill. 626 * @param startPos The start position for filling the window. 627 * @param requiredPos The position of a row that MUST be in the window. 628 * If it won't fit, then the query should discard part of what it filled 629 * so that it does. Must be greater than or equal to <code>startPos</code>. 630 * @param countAllRows True to count all rows that the query would return 631 * regagless of whether they fit in the window. 632 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 633 * @return The number of rows that were counted during query execution. Might 634 * not be all rows in the result set unless <code>countAllRows</code> is true. 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 */ 640 public int executeForCursorWindow(String sql, Object[] bindArgs, 641 CursorWindow window, int startPos, int requiredPos, boolean countAllRows, 642 CancellationSignal cancellationSignal) { 643 if (sql == null) { 644 throw new IllegalArgumentException("sql must not be null."); 645 } 646 if (window == null) { 647 throw new IllegalArgumentException("window must not be null."); 648 } 649 650 int actualPos = -1; 651 int countedRows = -1; 652 int filledRows = -1; 653 final int cookie = mRecentOperations.beginOperation("executeForCursorWindow", 654 sql, bindArgs); 655 try { 656 final PreparedStatement statement = acquirePreparedStatement(sql); 657 try { 658 throwIfStatementForbidden(statement); 659 bindArguments(statement, bindArgs); 660 applyBlockGuardPolicy(statement); 661 attachCancellationSignal(cancellationSignal); 662 try { 663 final long result = nativeExecuteForCursorWindow( 664 mConnectionPtr, statement.mStatementPtr, window.mWindowPtr, 665 startPos, requiredPos, countAllRows); 666 actualPos = (int)(result >> 32); 667 countedRows = (int)result; 668 filledRows = window.getNumRows(); 669 window.setStartPosition(actualPos); 670 return countedRows; 671 } finally { 672 detachCancellationSignal(cancellationSignal); 673 } 674 } finally { 675 releasePreparedStatement(statement); 676 } 677 } catch (RuntimeException ex) { 678 mRecentOperations.failOperation(cookie, ex); 679 throw ex; 680 } finally { 681 if (mRecentOperations.endOperationDeferLog(cookie)) { 682 mRecentOperations.logOperation(cookie, "window='" + window 683 + "', startPos=" + startPos 684 + ", actualPos=" + actualPos 685 + ", filledRows=" + filledRows 686 + ", countedRows=" + countedRows); 687 } 688 } 689 } 690 691 private PreparedStatement acquirePreparedStatement(String sql) { 692 PreparedStatement statement = mPreparedStatementCache.get(sql); 693 boolean skipCache = false; 694 if (statement != null) { 695 if (!statement.mInUse) { 696 return statement; 697 } 698 // The statement is already in the cache but is in use (this statement appears 699 // to be not only re-entrant but recursive!). So prepare a new copy of the 700 // statement but do not cache it. 701 skipCache = true; 702 } 703 704 final int statementPtr = nativePrepareStatement(mConnectionPtr, sql); 705 try { 706 final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr); 707 final int type = DatabaseUtils.getSqlStatementType(sql); 708 final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr); 709 statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly); 710 if (!skipCache && isCacheable(type)) { 711 mPreparedStatementCache.put(sql, statement); 712 statement.mInCache = true; 713 } 714 } catch (RuntimeException ex) { 715 // Finalize the statement if an exception occurred and we did not add 716 // it to the cache. If it is already in the cache, then leave it there. 717 if (statement == null || !statement.mInCache) { 718 nativeFinalizeStatement(mConnectionPtr, statementPtr); 719 } 720 throw ex; 721 } 722 statement.mInUse = true; 723 return statement; 724 } 725 726 private void releasePreparedStatement(PreparedStatement statement) { 727 statement.mInUse = false; 728 if (statement.mInCache) { 729 try { 730 nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr); 731 } catch (SQLiteException ex) { 732 // The statement could not be reset due to an error. Remove it from the cache. 733 // When remove() is called, the cache will invoke its entryRemoved() callback, 734 // which will in turn call finalizePreparedStatement() to finalize and 735 // recycle the statement. 736 if (DEBUG) { 737 Log.d(TAG, "Could not reset prepared statement due to an exception. " 738 + "Removing it from the cache. SQL: " 739 + trimSqlForDisplay(statement.mSql), ex); 740 } 741 742 mPreparedStatementCache.remove(statement.mSql); 743 } 744 } else { 745 finalizePreparedStatement(statement); 746 } 747 } 748 749 private void finalizePreparedStatement(PreparedStatement statement) { 750 nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr); 751 recyclePreparedStatement(statement); 752 } 753 754 private void attachCancellationSignal(CancellationSignal cancellationSignal) { 755 if (cancellationSignal != null) { 756 cancellationSignal.throwIfCanceled(); 757 758 mCancellationSignalAttachCount += 1; 759 if (mCancellationSignalAttachCount == 1) { 760 // Reset cancellation flag before executing the statement. 761 nativeResetCancel(mConnectionPtr, true /*cancelable*/); 762 763 // After this point, onCancel() may be called concurrently. 764 cancellationSignal.setOnCancelListener(this); 765 } 766 } 767 } 768 769 private void detachCancellationSignal(CancellationSignal cancellationSignal) { 770 if (cancellationSignal != null) { 771 assert mCancellationSignalAttachCount > 0; 772 773 mCancellationSignalAttachCount -= 1; 774 if (mCancellationSignalAttachCount == 0) { 775 // After this point, onCancel() cannot be called concurrently. 776 cancellationSignal.setOnCancelListener(null); 777 778 // Reset cancellation flag after executing the statement. 779 nativeResetCancel(mConnectionPtr, false /*cancelable*/); 780 } 781 } 782 } 783 784 // CancellationSignal.OnCancelListener callback. 785 // This method may be called on a different thread than the executing statement. 786 // However, it will only be called between calls to attachCancellationSignal and 787 // detachCancellationSignal, while a statement is executing. We can safely assume 788 // that the SQLite connection is still alive. 789 @Override 790 public void onCancel() { 791 nativeCancel(mConnectionPtr); 792 } 793 794 private void bindArguments(PreparedStatement statement, Object[] bindArgs) { 795 final int count = bindArgs != null ? bindArgs.length : 0; 796 if (count != statement.mNumParameters) { 797 throw new SQLiteBindOrColumnIndexOutOfRangeException( 798 "Expected " + statement.mNumParameters + " bind arguments but " 799 + bindArgs.length + " were provided."); 800 } 801 if (count == 0) { 802 return; 803 } 804 805 final int statementPtr = statement.mStatementPtr; 806 for (int i = 0; i < count; i++) { 807 final Object arg = bindArgs[i]; 808 switch (DatabaseUtils.getTypeOfObject(arg)) { 809 case Cursor.FIELD_TYPE_NULL: 810 nativeBindNull(mConnectionPtr, statementPtr, i + 1); 811 break; 812 case Cursor.FIELD_TYPE_INTEGER: 813 nativeBindLong(mConnectionPtr, statementPtr, i + 1, 814 ((Number)arg).longValue()); 815 break; 816 case Cursor.FIELD_TYPE_FLOAT: 817 nativeBindDouble(mConnectionPtr, statementPtr, i + 1, 818 ((Number)arg).doubleValue()); 819 break; 820 case Cursor.FIELD_TYPE_BLOB: 821 nativeBindBlob(mConnectionPtr, statementPtr, i + 1, (byte[])arg); 822 break; 823 case Cursor.FIELD_TYPE_STRING: 824 default: 825 if (arg instanceof Boolean) { 826 // Provide compatibility with legacy applications which may pass 827 // Boolean values in bind args. 828 nativeBindLong(mConnectionPtr, statementPtr, i + 1, 829 ((Boolean)arg).booleanValue() ? 1 : 0); 830 } else { 831 nativeBindString(mConnectionPtr, statementPtr, i + 1, arg.toString()); 832 } 833 break; 834 } 835 } 836 } 837 838 private void throwIfStatementForbidden(PreparedStatement statement) { 839 if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) { 840 throw new SQLiteException("Cannot execute this statement because it " 841 + "might modify the database but the connection is read-only."); 842 } 843 } 844 845 private static boolean isCacheable(int statementType) { 846 if (statementType == DatabaseUtils.STATEMENT_UPDATE 847 || statementType == DatabaseUtils.STATEMENT_SELECT) { 848 return true; 849 } 850 return false; 851 } 852 853 private void applyBlockGuardPolicy(PreparedStatement statement) { 854 if (!mConfiguration.isInMemoryDb()) { 855 if (statement.mReadOnly) { 856 BlockGuard.getThreadPolicy().onReadFromDisk(); 857 } else { 858 BlockGuard.getThreadPolicy().onWriteToDisk(); 859 } 860 } 861 } 862 863 /** 864 * Dumps debugging information about this connection. 865 * 866 * @param printer The printer to receive the dump, not null. 867 * @param verbose True to dump more verbose information. 868 */ 869 public void dump(Printer printer, boolean verbose) { 870 dumpUnsafe(printer, verbose); 871 } 872 873 /** 874 * Dumps debugging information about this connection, in the case where the 875 * caller might not actually own the connection. 876 * 877 * This function is written so that it may be called by a thread that does not 878 * own the connection. We need to be very careful because the connection state is 879 * not synchronized. 880 * 881 * At worst, the method may return stale or slightly wrong data, however 882 * it should not crash. This is ok as it is only used for diagnostic purposes. 883 * 884 * @param printer The printer to receive the dump, not null. 885 * @param verbose True to dump more verbose information. 886 */ 887 void dumpUnsafe(Printer printer, boolean verbose) { 888 printer.println("Connection #" + mConnectionId + ":"); 889 if (verbose) { 890 printer.println(" connectionPtr: 0x" + Integer.toHexString(mConnectionPtr)); 891 } 892 printer.println(" isPrimaryConnection: " + mIsPrimaryConnection); 893 printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations); 894 895 mRecentOperations.dump(printer); 896 897 if (verbose) { 898 mPreparedStatementCache.dump(printer); 899 } 900 } 901 902 /** 903 * Describes the currently executing operation, in the case where the 904 * caller might not actually own the connection. 905 * 906 * This function is written so that it may be called by a thread that does not 907 * own the connection. We need to be very careful because the connection state is 908 * not synchronized. 909 * 910 * At worst, the method may return stale or slightly wrong data, however 911 * it should not crash. This is ok as it is only used for diagnostic purposes. 912 * 913 * @return A description of the current operation including how long it has been running, 914 * or null if none. 915 */ 916 String describeCurrentOperationUnsafe() { 917 return mRecentOperations.describeCurrentOperation(); 918 } 919 920 /** 921 * Collects statistics about database connection memory usage. 922 * 923 * @param dbStatsList The list to populate. 924 */ 925 void collectDbStats(ArrayList<DbStats> dbStatsList) { 926 // Get information about the main database. 927 int lookaside = nativeGetDbLookaside(mConnectionPtr); 928 long pageCount = 0; 929 long pageSize = 0; 930 try { 931 pageCount = executeForLong("PRAGMA page_count;", null, null); 932 pageSize = executeForLong("PRAGMA page_size;", null, null); 933 } catch (SQLiteException ex) { 934 // Ignore. 935 } 936 dbStatsList.add(getMainDbStatsUnsafe(lookaside, pageCount, pageSize)); 937 938 // Get information about attached databases. 939 // We ignore the first row in the database list because it corresponds to 940 // the main database which we have already described. 941 CursorWindow window = new CursorWindow("collectDbStats"); 942 try { 943 executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null); 944 for (int i = 1; i < window.getNumRows(); i++) { 945 String name = window.getString(i, 1); 946 String path = window.getString(i, 2); 947 pageCount = 0; 948 pageSize = 0; 949 try { 950 pageCount = executeForLong("PRAGMA " + name + ".page_count;", null, null); 951 pageSize = executeForLong("PRAGMA " + name + ".page_size;", null, null); 952 } catch (SQLiteException ex) { 953 // Ignore. 954 } 955 String label = " (attached) " + name; 956 if (!path.isEmpty()) { 957 label += ": " + path; 958 } 959 dbStatsList.add(new DbStats(label, pageCount, pageSize, 0, 0, 0, 0)); 960 } 961 } catch (SQLiteException ex) { 962 // Ignore. 963 } finally { 964 window.close(); 965 } 966 } 967 968 /** 969 * Collects statistics about database connection memory usage, in the case where the 970 * caller might not actually own the connection. 971 * 972 * @return The statistics object, never null. 973 */ 974 void collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList) { 975 dbStatsList.add(getMainDbStatsUnsafe(0, 0, 0)); 976 } 977 978 private DbStats getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize) { 979 // The prepared statement cache is thread-safe so we can access its statistics 980 // even if we do not own the database connection. 981 String label = mConfiguration.path; 982 if (!mIsPrimaryConnection) { 983 label += " (" + mConnectionId + ")"; 984 } 985 return new DbStats(label, pageCount, pageSize, lookaside, 986 mPreparedStatementCache.hitCount(), 987 mPreparedStatementCache.missCount(), 988 mPreparedStatementCache.size()); 989 } 990 991 @Override 992 public String toString() { 993 return "SQLiteConnection: " + mConfiguration.path + " (" + mConnectionId + ")"; 994 } 995 996 private PreparedStatement obtainPreparedStatement(String sql, int statementPtr, 997 int numParameters, int type, boolean readOnly) { 998 PreparedStatement statement = mPreparedStatementPool; 999 if (statement != null) { 1000 mPreparedStatementPool = statement.mPoolNext; 1001 statement.mPoolNext = null; 1002 statement.mInCache = false; 1003 } else { 1004 statement = new PreparedStatement(); 1005 } 1006 statement.mSql = sql; 1007 statement.mStatementPtr = statementPtr; 1008 statement.mNumParameters = numParameters; 1009 statement.mType = type; 1010 statement.mReadOnly = readOnly; 1011 return statement; 1012 } 1013 1014 private void recyclePreparedStatement(PreparedStatement statement) { 1015 statement.mSql = null; 1016 statement.mPoolNext = mPreparedStatementPool; 1017 mPreparedStatementPool = statement; 1018 } 1019 1020 private static String trimSqlForDisplay(String sql) { 1021 return TRIM_SQL_PATTERN.matcher(sql).replaceAll(" "); 1022 } 1023 1024 /** 1025 * Holder type for a prepared statement. 1026 * 1027 * Although this object holds a pointer to a native statement object, it 1028 * does not have a finalizer. This is deliberate. The {@link SQLiteConnection} 1029 * owns the statement object and will take care of freeing it when needed. 1030 * In particular, closing the connection requires a guarantee of deterministic 1031 * resource disposal because all native statement objects must be freed before 1032 * the native database object can be closed. So no finalizers here. 1033 */ 1034 private static final class PreparedStatement { 1035 // Next item in pool. 1036 public PreparedStatement mPoolNext; 1037 1038 // The SQL from which the statement was prepared. 1039 public String mSql; 1040 1041 // The native sqlite3_stmt object pointer. 1042 // Lifetime is managed explicitly by the connection. 1043 public int mStatementPtr; 1044 1045 // The number of parameters that the prepared statement has. 1046 public int mNumParameters; 1047 1048 // The statement type. 1049 public int mType; 1050 1051 // True if the statement is read-only. 1052 public boolean mReadOnly; 1053 1054 // True if the statement is in the cache. 1055 public boolean mInCache; 1056 1057 // True if the statement is in use (currently executing). 1058 // We need this flag because due to the use of custom functions in triggers, it's 1059 // possible for SQLite calls to be re-entrant. Consequently we need to prevent 1060 // in use statements from being finalized until they are no longer in use. 1061 public boolean mInUse; 1062 } 1063 1064 private final class PreparedStatementCache 1065 extends LruCache<String, PreparedStatement> { 1066 public PreparedStatementCache(int size) { 1067 super(size); 1068 } 1069 1070 @Override 1071 protected void entryRemoved(boolean evicted, String key, 1072 PreparedStatement oldValue, PreparedStatement newValue) { 1073 oldValue.mInCache = false; 1074 if (!oldValue.mInUse) { 1075 finalizePreparedStatement(oldValue); 1076 } 1077 } 1078 1079 public void dump(Printer printer) { 1080 printer.println(" Prepared statement cache:"); 1081 Map<String, PreparedStatement> cache = snapshot(); 1082 if (!cache.isEmpty()) { 1083 int i = 0; 1084 for (Map.Entry<String, PreparedStatement> entry : cache.entrySet()) { 1085 PreparedStatement statement = entry.getValue(); 1086 if (statement.mInCache) { // might be false due to a race with entryRemoved 1087 String sql = entry.getKey(); 1088 printer.println(" " + i + ": statementPtr=0x" 1089 + Integer.toHexString(statement.mStatementPtr) 1090 + ", numParameters=" + statement.mNumParameters 1091 + ", type=" + statement.mType 1092 + ", readOnly=" + statement.mReadOnly 1093 + ", sql=\"" + trimSqlForDisplay(sql) + "\""); 1094 } 1095 i += 1; 1096 } 1097 } else { 1098 printer.println(" <none>"); 1099 } 1100 } 1101 } 1102 1103 private static final class OperationLog { 1104 private static final int MAX_RECENT_OPERATIONS = 20; 1105 private static final int COOKIE_GENERATION_SHIFT = 8; 1106 private static final int COOKIE_INDEX_MASK = 0xff; 1107 1108 private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS]; 1109 private int mIndex; 1110 private int mGeneration; 1111 1112 public int beginOperation(String kind, String sql, Object[] bindArgs) { 1113 synchronized (mOperations) { 1114 final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS; 1115 Operation operation = mOperations[index]; 1116 if (operation == null) { 1117 operation = new Operation(); 1118 mOperations[index] = operation; 1119 } else { 1120 operation.mFinished = false; 1121 operation.mException = null; 1122 if (operation.mBindArgs != null) { 1123 operation.mBindArgs.clear(); 1124 } 1125 } 1126 operation.mStartTime = System.currentTimeMillis(); 1127 operation.mKind = kind; 1128 operation.mSql = sql; 1129 if (bindArgs != null) { 1130 if (operation.mBindArgs == null) { 1131 operation.mBindArgs = new ArrayList<Object>(); 1132 } else { 1133 operation.mBindArgs.clear(); 1134 } 1135 for (int i = 0; i < bindArgs.length; i++) { 1136 final Object arg = bindArgs[i]; 1137 if (arg != null && arg instanceof byte[]) { 1138 // Don't hold onto the real byte array longer than necessary. 1139 operation.mBindArgs.add(EMPTY_BYTE_ARRAY); 1140 } else { 1141 operation.mBindArgs.add(arg); 1142 } 1143 } 1144 } 1145 operation.mCookie = newOperationCookieLocked(index); 1146 mIndex = index; 1147 return operation.mCookie; 1148 } 1149 } 1150 1151 public void failOperation(int cookie, Exception ex) { 1152 synchronized (mOperations) { 1153 final Operation operation = getOperationLocked(cookie); 1154 if (operation != null) { 1155 operation.mException = ex; 1156 } 1157 } 1158 } 1159 1160 public void endOperation(int cookie) { 1161 synchronized (mOperations) { 1162 if (endOperationDeferLogLocked(cookie)) { 1163 logOperationLocked(cookie, null); 1164 } 1165 } 1166 } 1167 1168 public boolean endOperationDeferLog(int cookie) { 1169 synchronized (mOperations) { 1170 return endOperationDeferLogLocked(cookie); 1171 } 1172 } 1173 1174 public void logOperation(int cookie, String detail) { 1175 synchronized (mOperations) { 1176 logOperationLocked(cookie, detail); 1177 } 1178 } 1179 1180 private boolean endOperationDeferLogLocked(int cookie) { 1181 final Operation operation = getOperationLocked(cookie); 1182 if (operation != null) { 1183 operation.mEndTime = System.currentTimeMillis(); 1184 operation.mFinished = true; 1185 return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery( 1186 operation.mEndTime - operation.mStartTime); 1187 } 1188 return false; 1189 } 1190 1191 private void logOperationLocked(int cookie, String detail) { 1192 final Operation operation = getOperationLocked(cookie); 1193 StringBuilder msg = new StringBuilder(); 1194 operation.describe(msg); 1195 if (detail != null) { 1196 msg.append(", ").append(detail); 1197 } 1198 Log.d(TAG, msg.toString()); 1199 } 1200 1201 private int newOperationCookieLocked(int index) { 1202 final int generation = mGeneration++; 1203 return generation << COOKIE_GENERATION_SHIFT | index; 1204 } 1205 1206 private Operation getOperationLocked(int cookie) { 1207 final int index = cookie & COOKIE_INDEX_MASK; 1208 final Operation operation = mOperations[index]; 1209 return operation.mCookie == cookie ? operation : null; 1210 } 1211 1212 public String describeCurrentOperation() { 1213 synchronized (mOperations) { 1214 final Operation operation = mOperations[mIndex]; 1215 if (operation != null && !operation.mFinished) { 1216 StringBuilder msg = new StringBuilder(); 1217 operation.describe(msg); 1218 return msg.toString(); 1219 } 1220 return null; 1221 } 1222 } 1223 1224 public void dump(Printer printer) { 1225 synchronized (mOperations) { 1226 printer.println(" Most recently executed operations:"); 1227 int index = mIndex; 1228 Operation operation = mOperations[index]; 1229 if (operation != null) { 1230 int n = 0; 1231 do { 1232 StringBuilder msg = new StringBuilder(); 1233 msg.append(" ").append(n).append(": ["); 1234 msg.append(operation.getFormattedStartTime()); 1235 msg.append("] "); 1236 operation.describe(msg); 1237 printer.println(msg.toString()); 1238 1239 if (index > 0) { 1240 index -= 1; 1241 } else { 1242 index = MAX_RECENT_OPERATIONS - 1; 1243 } 1244 n += 1; 1245 operation = mOperations[index]; 1246 } while (operation != null && n < MAX_RECENT_OPERATIONS); 1247 } else { 1248 printer.println(" <none>"); 1249 } 1250 } 1251 } 1252 } 1253 1254 private static final class Operation { 1255 private static final SimpleDateFormat sDateFormat = 1256 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 1257 1258 public long mStartTime; 1259 public long mEndTime; 1260 public String mKind; 1261 public String mSql; 1262 public ArrayList<Object> mBindArgs; 1263 public boolean mFinished; 1264 public Exception mException; 1265 public int mCookie; 1266 1267 public void describe(StringBuilder msg) { 1268 msg.append(mKind); 1269 if (mFinished) { 1270 msg.append(" took ").append(mEndTime - mStartTime).append("ms"); 1271 } else { 1272 msg.append(" started ").append(System.currentTimeMillis() - mStartTime) 1273 .append("ms ago"); 1274 } 1275 msg.append(" - ").append(getStatus()); 1276 if (mSql != null) { 1277 msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\""); 1278 } 1279 if (mBindArgs != null && mBindArgs.size() != 0) { 1280 msg.append(", bindArgs=["); 1281 final int count = mBindArgs.size(); 1282 for (int i = 0; i < count; i++) { 1283 final Object arg = mBindArgs.get(i); 1284 if (i != 0) { 1285 msg.append(", "); 1286 } 1287 if (arg == null) { 1288 msg.append("null"); 1289 } else if (arg instanceof byte[]) { 1290 msg.append("<byte[]>"); 1291 } else if (arg instanceof String) { 1292 msg.append("\"").append((String)arg).append("\""); 1293 } else { 1294 msg.append(arg); 1295 } 1296 } 1297 msg.append("]"); 1298 } 1299 if (mException != null) { 1300 msg.append(", exception=\"").append(mException.getMessage()).append("\""); 1301 } 1302 } 1303 1304 private String getStatus() { 1305 if (!mFinished) { 1306 return "running"; 1307 } 1308 return mException != null ? "failed" : "succeeded"; 1309 } 1310 1311 private String getFormattedStartTime() { 1312 return sDateFormat.format(new Date(mStartTime)); 1313 } 1314 } 1315} 1316