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