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