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