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