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 
17 package android.database.sqlite;
18 
19 import android.database.Cursor;
20 import android.database.CursorWindow;
21 import android.database.DatabaseUtils;
22 import android.database.sqlite.SQLiteDebug.DbStats;
23 import android.database.sqlite.SQLiteDebug.NoPreloadHolder;
24 import android.os.CancellationSignal;
25 import android.os.OperationCanceledException;
26 import android.os.ParcelFileDescriptor;
27 import android.os.SystemClock;
28 import android.os.Trace;
29 import android.text.TextUtils;
30 import android.util.Log;
31 import android.util.LruCache;
32 import android.util.Pair;
33 import android.util.Printer;
34 import dalvik.system.BlockGuard;
35 import dalvik.system.CloseGuard;
36 import java.io.File;
37 import java.io.IOException;
38 import java.nio.file.FileSystems;
39 import java.nio.file.Files;
40 import java.nio.file.Path;
41 import java.text.SimpleDateFormat;
42 import java.util.ArrayList;
43 import java.util.Date;
44 import java.util.Map;
45 import java.util.function.BinaryOperator;
46 import java.util.function.UnaryOperator;
47 
48 /**
49  * Represents a SQLite database connection.
50  * Each connection wraps an instance of a native <code>sqlite3</code> object.
51  * <p>
52  * When database connection pooling is enabled, there can be multiple active
53  * connections to the same database.  Otherwise there is typically only one
54  * connection per database.
55  * </p><p>
56  * When the SQLite WAL feature is enabled, multiple readers and one writer
57  * can concurrently access the database.  Without WAL, readers and writers
58  * are mutually exclusive.
59  * </p>
60  *
61  * <h2>Ownership and concurrency guarantees</h2>
62  * <p>
63  * Connection objects are not thread-safe.  They are acquired as needed to
64  * perform a database operation and are then returned to the pool.  At any
65  * given time, a connection is either owned and used by a {@link SQLiteSession}
66  * object or the {@link SQLiteConnectionPool}.  Those classes are
67  * responsible for serializing operations to guard against concurrent
68  * use of a connection.
69  * </p><p>
70  * The guarantee of having a single owner allows this class to be implemented
71  * without locks and greatly simplifies resource management.
72  * </p>
73  *
74  * <h2>Encapsulation guarantees</h2>
75  * <p>
76  * The connection object object owns *all* of the SQLite related native
77  * objects that are associated with the connection.  What's more, there are
78  * no other objects in the system that are capable of obtaining handles to
79  * those native objects.  Consequently, when the connection is closed, we do
80  * not have to worry about what other components might have references to
81  * its associated SQLite state -- there are none.
82  * </p><p>
83  * Encapsulation is what ensures that the connection object's
84  * lifecycle does not become a tortured mess of finalizers and reference
85  * queues.
86  * </p>
87  *
88  * <h2>Reentrance</h2>
89  * <p>
90  * This class must tolerate reentrant execution of SQLite operations because
91  * triggers may call custom SQLite functions that perform additional queries.
92  * </p>
93  *
94  * @hide
95  */
96 public final class SQLiteConnection implements CancellationSignal.OnCancelListener {
97     private static final String TAG = "SQLiteConnection";
98     private static final boolean DEBUG = false;
99 
100     private static final String[] EMPTY_STRING_ARRAY = new String[0];
101     private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
102 
103     private final CloseGuard mCloseGuard = CloseGuard.get();
104 
105     private final SQLiteConnectionPool mPool;
106     private final SQLiteDatabaseConfiguration mConfiguration;
107     private final int mConnectionId;
108     private final boolean mIsPrimaryConnection;
109     private final boolean mIsReadOnlyConnection;
110     private final PreparedStatementCache mPreparedStatementCache;
111     private PreparedStatement mPreparedStatementPool;
112 
113     // The recent operations log.
114     private final OperationLog mRecentOperations;
115 
116     // The native SQLiteConnection pointer.  (FOR INTERNAL USE ONLY)
117     private long mConnectionPtr;
118 
119     private boolean mOnlyAllowReadOnlyOperations;
120 
121     // The number of times attachCancellationSignal has been called.
122     // Because SQLite statement execution can be reentrant, we keep track of how many
123     // times we have attempted to attach a cancellation signal to the connection so that
124     // we can ensure that we detach the signal at the right time.
125     private int mCancellationSignalAttachCount;
126 
nativeOpen(String path, int openFlags, String label, boolean enableTrace, boolean enableProfile, int lookasideSlotSize, int lookasideSlotCount)127     private static native long nativeOpen(String path, int openFlags, String label,
128             boolean enableTrace, boolean enableProfile, int lookasideSlotSize,
129             int lookasideSlotCount);
nativeClose(long connectionPtr)130     private static native void nativeClose(long connectionPtr);
nativeRegisterCustomScalarFunction(long connectionPtr, String name, UnaryOperator<String> function)131     private static native void nativeRegisterCustomScalarFunction(long connectionPtr,
132             String name, UnaryOperator<String> function);
nativeRegisterCustomAggregateFunction(long connectionPtr, String name, BinaryOperator<String> function)133     private static native void nativeRegisterCustomAggregateFunction(long connectionPtr,
134             String name, BinaryOperator<String> function);
nativeRegisterLocalizedCollators(long connectionPtr, String locale)135     private static native void nativeRegisterLocalizedCollators(long connectionPtr, String locale);
nativePrepareStatement(long connectionPtr, String sql)136     private static native long nativePrepareStatement(long connectionPtr, String sql);
nativeFinalizeStatement(long connectionPtr, long statementPtr)137     private static native void nativeFinalizeStatement(long connectionPtr, long statementPtr);
nativeGetParameterCount(long connectionPtr, long statementPtr)138     private static native int nativeGetParameterCount(long connectionPtr, long statementPtr);
nativeIsReadOnly(long connectionPtr, long statementPtr)139     private static native boolean nativeIsReadOnly(long connectionPtr, long statementPtr);
nativeGetColumnCount(long connectionPtr, long statementPtr)140     private static native int nativeGetColumnCount(long connectionPtr, long statementPtr);
nativeGetColumnName(long connectionPtr, long statementPtr, int index)141     private static native String nativeGetColumnName(long connectionPtr, long statementPtr,
142             int index);
nativeBindNull(long connectionPtr, long statementPtr, int index)143     private static native void nativeBindNull(long connectionPtr, long statementPtr,
144             int index);
nativeBindLong(long connectionPtr, long statementPtr, int index, long value)145     private static native void nativeBindLong(long connectionPtr, long statementPtr,
146             int index, long value);
nativeBindDouble(long connectionPtr, long statementPtr, int index, double value)147     private static native void nativeBindDouble(long connectionPtr, long statementPtr,
148             int index, double value);
nativeBindString(long connectionPtr, long statementPtr, int index, String value)149     private static native void nativeBindString(long connectionPtr, long statementPtr,
150             int index, String value);
nativeBindBlob(long connectionPtr, long statementPtr, int index, byte[] value)151     private static native void nativeBindBlob(long connectionPtr, long statementPtr,
152             int index, byte[] value);
nativeResetStatementAndClearBindings( long connectionPtr, long statementPtr)153     private static native void nativeResetStatementAndClearBindings(
154             long connectionPtr, long statementPtr);
nativeExecute(long connectionPtr, long statementPtr, boolean isPragmaStmt)155     private static native void nativeExecute(long connectionPtr, long statementPtr,
156             boolean isPragmaStmt);
nativeExecuteForLong(long connectionPtr, long statementPtr)157     private static native long nativeExecuteForLong(long connectionPtr, long statementPtr);
nativeExecuteForString(long connectionPtr, long statementPtr)158     private static native String nativeExecuteForString(long connectionPtr, long statementPtr);
nativeExecuteForBlobFileDescriptor( long connectionPtr, long statementPtr)159     private static native int nativeExecuteForBlobFileDescriptor(
160             long connectionPtr, long statementPtr);
nativeExecuteForChangedRowCount(long connectionPtr, long statementPtr)161     private static native int nativeExecuteForChangedRowCount(long connectionPtr, long statementPtr);
nativeExecuteForLastInsertedRowId( long connectionPtr, long statementPtr)162     private static native long nativeExecuteForLastInsertedRowId(
163             long connectionPtr, long statementPtr);
nativeExecuteForCursorWindow( long connectionPtr, long statementPtr, long windowPtr, int startPos, int requiredPos, boolean countAllRows)164     private static native long nativeExecuteForCursorWindow(
165             long connectionPtr, long statementPtr, long windowPtr,
166             int startPos, int requiredPos, boolean countAllRows);
nativeGetDbLookaside(long connectionPtr)167     private static native int nativeGetDbLookaside(long connectionPtr);
nativeCancel(long connectionPtr)168     private static native void nativeCancel(long connectionPtr);
nativeResetCancel(long connectionPtr, boolean cancelable)169     private static native void nativeResetCancel(long connectionPtr, boolean cancelable);
170 
SQLiteConnection(SQLiteConnectionPool pool, SQLiteDatabaseConfiguration configuration, int connectionId, boolean primaryConnection)171     private SQLiteConnection(SQLiteConnectionPool pool,
172             SQLiteDatabaseConfiguration configuration,
173             int connectionId, boolean primaryConnection) {
174         mPool = pool;
175         mRecentOperations = new OperationLog(mPool);
176         mConfiguration = new SQLiteDatabaseConfiguration(configuration);
177         mConnectionId = connectionId;
178         mIsPrimaryConnection = primaryConnection;
179         mIsReadOnlyConnection = mConfiguration.isReadOnlyDatabase();
180         mPreparedStatementCache = new PreparedStatementCache(
181                 mConfiguration.maxSqlCacheSize);
182         mCloseGuard.open("SQLiteConnection.close");
183     }
184 
185     @Override
finalize()186     protected void finalize() throws Throwable {
187         try {
188             if (mPool != null && mConnectionPtr != 0) {
189                 mPool.onConnectionLeaked();
190             }
191 
192             dispose(true);
193         } finally {
194             super.finalize();
195         }
196     }
197 
198     // Called by SQLiteConnectionPool only.
open(SQLiteConnectionPool pool, SQLiteDatabaseConfiguration configuration, int connectionId, boolean primaryConnection)199     static SQLiteConnection open(SQLiteConnectionPool pool,
200             SQLiteDatabaseConfiguration configuration,
201             int connectionId, boolean primaryConnection) {
202         SQLiteConnection connection = new SQLiteConnection(pool, configuration,
203                 connectionId, primaryConnection);
204         try {
205             connection.open();
206             return connection;
207         } catch (SQLiteException ex) {
208             connection.dispose(false);
209             throw ex;
210         }
211     }
212 
213     // Called by SQLiteConnectionPool only.
214     // Closes the database closes and releases all of its associated resources.
215     // Do not call methods on the connection after it is closed.  It will probably crash.
close()216     void close() {
217         dispose(false);
218     }
219 
open()220     private void open() {
221         final String file = mConfiguration.path;
222         final int cookie = mRecentOperations.beginOperation("open", null, null);
223         try {
224             mConnectionPtr = nativeOpen(file, mConfiguration.openFlags,
225                     mConfiguration.label,
226                     NoPreloadHolder.DEBUG_SQL_STATEMENTS, NoPreloadHolder.DEBUG_SQL_TIME,
227                     mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount);
228         } catch (SQLiteCantOpenDatabaseException e) {
229             final StringBuilder message = new StringBuilder("Cannot open database '")
230                     .append(file).append('\'')
231                     .append(" with flags 0x")
232                     .append(Integer.toHexString(mConfiguration.openFlags));
233 
234             try {
235                 // Try to diagnose for common reasons. If something fails in here, that's fine;
236                 // just swallow the exception.
237 
238                 final Path path = FileSystems.getDefault().getPath(file);
239                 final Path dir = path.getParent();
240                 if (dir == null) {
241                     message.append(": Directory not specified in the file path");
242                 } else if (!Files.isDirectory(dir)) {
243                     message.append(": Directory ").append(dir).append(" doesn't exist");
244                 } else if (!Files.exists(path)) {
245                     message.append(": File ").append(path).append(
246                             " doesn't exist");
247                     if ((mConfiguration.openFlags & SQLiteDatabase.CREATE_IF_NECESSARY) != 0) {
248                         message.append(
249                                 " and CREATE_IF_NECESSARY is set, check directory permissions");
250                     }
251                 } else if (!Files.isReadable(path)) {
252                     message.append(": File ").append(path).append(" is not readable");
253                 } else if (Files.isDirectory(path)) {
254                     message.append(": Path ").append(path).append(" is a directory");
255                 } else {
256                     message.append(": Unable to deduct failure reason");
257                 }
258             } catch (Throwable th) {
259                 message.append(": Unable to deduct failure reason"
260                         + " because filesystem couldn't be examined: ").append(th.getMessage());
261             }
262             throw new SQLiteCantOpenDatabaseException(message.toString(), e);
263         } finally {
264             mRecentOperations.endOperation(cookie);
265         }
266         setPageSize();
267         setForeignKeyModeFromConfiguration();
268         setJournalFromConfiguration();
269         setSyncModeFromConfiguration();
270         setJournalSizeLimit();
271         setAutoCheckpointInterval();
272         setLocaleFromConfiguration();
273         setCustomFunctionsFromConfiguration();
274         executePerConnectionSqlFromConfiguration(0);
275     }
276 
dispose(boolean finalized)277     private void dispose(boolean finalized) {
278         if (mCloseGuard != null) {
279             if (finalized) {
280                 mCloseGuard.warnIfOpen();
281             }
282             mCloseGuard.close();
283         }
284 
285         if (mConnectionPtr != 0) {
286             final int cookie = mRecentOperations.beginOperation("close", null, null);
287             try {
288                 mPreparedStatementCache.evictAll();
289                 nativeClose(mConnectionPtr);
290                 mConnectionPtr = 0;
291             } finally {
292                 mRecentOperations.endOperation(cookie);
293             }
294         }
295     }
296 
setPageSize()297     private void setPageSize() {
298         if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
299             final long newValue = SQLiteGlobal.getDefaultPageSize();
300             long value = executeForLong("PRAGMA page_size", null, null);
301             if (value != newValue) {
302                 execute("PRAGMA page_size=" + newValue, null, null);
303             }
304         }
305     }
306 
setAutoCheckpointInterval()307     private void setAutoCheckpointInterval() {
308         if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
309             final long newValue = SQLiteGlobal.getWALAutoCheckpoint();
310             long value = executeForLong("PRAGMA wal_autocheckpoint", null, null);
311             if (value != newValue) {
312                 executeForLong("PRAGMA wal_autocheckpoint=" + newValue, null, null);
313             }
314         }
315     }
316 
setJournalSizeLimit()317     private void setJournalSizeLimit() {
318         if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
319             final long newValue = SQLiteGlobal.getJournalSizeLimit();
320             long value = executeForLong("PRAGMA journal_size_limit", null, null);
321             if (value != newValue) {
322                 executeForLong("PRAGMA journal_size_limit=" + newValue, null, null);
323             }
324         }
325     }
326 
setForeignKeyModeFromConfiguration()327     private void setForeignKeyModeFromConfiguration() {
328         if (!mIsReadOnlyConnection) {
329             final long newValue = mConfiguration.foreignKeyConstraintsEnabled ? 1 : 0;
330             long value = executeForLong("PRAGMA foreign_keys", null, null);
331             if (value != newValue) {
332                 execute("PRAGMA foreign_keys=" + newValue, null, null);
333             }
334         }
335     }
336 
setJournalFromConfiguration()337     private void setJournalFromConfiguration() {
338         if (!mIsReadOnlyConnection) {
339             setJournalMode(mConfiguration.resolveJournalMode());
340             maybeTruncateWalFile();
341         } else {
342             // No need to truncate for read only databases.
343             mConfiguration.shouldTruncateWalFile = false;
344         }
345     }
346 
setSyncModeFromConfiguration()347     private void setSyncModeFromConfiguration() {
348         if (!mIsReadOnlyConnection) {
349             setSyncMode(mConfiguration.resolveSyncMode());
350         }
351     }
352 
353     /**
354      * If the WAL file exists and larger than a threshold, truncate it by executing
355      * PRAGMA wal_checkpoint.
356      */
maybeTruncateWalFile()357     private void maybeTruncateWalFile() {
358         if (!mConfiguration.shouldTruncateWalFile) {
359             return;
360         }
361 
362         final long threshold = SQLiteGlobal.getWALTruncateSize();
363         if (DEBUG) {
364             Log.d(TAG, "Truncate threshold=" + threshold);
365         }
366         if (threshold == 0) {
367             return;
368         }
369 
370         final File walFile = new File(mConfiguration.path + "-wal");
371         if (!walFile.isFile()) {
372             return;
373         }
374         final long size = walFile.length();
375         if (size < threshold) {
376             if (DEBUG) {
377                 Log.d(TAG, walFile.getAbsolutePath() + " " + size + " bytes: No need to truncate");
378             }
379             return;
380         }
381 
382         Log.i(TAG, walFile.getAbsolutePath() + " " + size + " bytes: Bigger than "
383                 + threshold + "; truncating");
384         try {
385             executeForString("PRAGMA wal_checkpoint(TRUNCATE)", null, null);
386             mConfiguration.shouldTruncateWalFile = false;
387         } catch (SQLiteException e) {
388             Log.w(TAG, "Failed to truncate the -wal file", e);
389         }
390     }
391 
setSyncMode(@QLiteDatabase.SyncMode String newValue)392     private void setSyncMode(@SQLiteDatabase.SyncMode String newValue) {
393         if (TextUtils.isEmpty(newValue)) {
394             // No change to the sync mode is intended
395             return;
396         }
397         String value = executeForString("PRAGMA synchronous", null, null);
398         if (!canonicalizeSyncMode(value).equalsIgnoreCase(
399                 canonicalizeSyncMode(newValue))) {
400             execute("PRAGMA synchronous=" + newValue, null, null);
401         }
402     }
403 
canonicalizeSyncMode(String value)404     private static @SQLiteDatabase.SyncMode String canonicalizeSyncMode(String value) {
405         switch (value) {
406             case "0": return SQLiteDatabase.SYNC_MODE_OFF;
407             case "1": return SQLiteDatabase.SYNC_MODE_NORMAL;
408             case "2": return SQLiteDatabase.SYNC_MODE_FULL;
409             case "3": return SQLiteDatabase.SYNC_MODE_EXTRA;
410         }
411         return value;
412     }
413 
setJournalMode(@QLiteDatabase.JournalMode String newValue)414     private void setJournalMode(@SQLiteDatabase.JournalMode String newValue) {
415         if (TextUtils.isEmpty(newValue)) {
416             // No change to the journal mode is intended
417             return;
418         }
419         String value = executeForString("PRAGMA journal_mode", null, null);
420         if (!value.equalsIgnoreCase(newValue)) {
421             try {
422                 String result = executeForString("PRAGMA journal_mode=" + newValue, null, null);
423                 if (result.equalsIgnoreCase(newValue)) {
424                     return;
425                 }
426                 // PRAGMA journal_mode silently fails and returns the original journal
427                 // mode in some cases if the journal mode could not be changed.
428             } catch (SQLiteDatabaseLockedException ex) {
429                 // This error (SQLITE_BUSY) occurs if one connection has the database
430                 // open in WAL mode and another tries to change it to non-WAL.
431             }
432             // Because we always disable WAL mode when a database is first opened
433             // (even if we intend to re-enable it), we can encounter problems if
434             // there is another open connection to the database somewhere.
435             // This can happen for a variety of reasons such as an application opening
436             // the same database in multiple processes at the same time or if there is a
437             // crashing content provider service that the ActivityManager has
438             // removed from its registry but whose process hasn't quite died yet
439             // by the time it is restarted in a new process.
440             //
441             // If we don't change the journal mode, nothing really bad happens.
442             // In the worst case, an application that enables WAL might not actually
443             // get it, although it can still use connection pooling.
444             Log.w(TAG, "Could not change the database journal mode of '"
445                     + mConfiguration.label + "' from '" + value + "' to '" + newValue
446                     + "' because the database is locked.  This usually means that "
447                     + "there are other open connections to the database which prevents "
448                     + "the database from enabling or disabling write-ahead logging mode.  "
449                     + "Proceeding without changing the journal mode.");
450         }
451     }
452 
setLocaleFromConfiguration()453     private void setLocaleFromConfiguration() {
454         if ((mConfiguration.openFlags & SQLiteDatabase.NO_LOCALIZED_COLLATORS) != 0) {
455             return;
456         }
457 
458         // Register the localized collators.
459         final String newLocale = mConfiguration.locale.toString();
460         nativeRegisterLocalizedCollators(mConnectionPtr, newLocale);
461 
462         if (!mConfiguration.isInMemoryDb()) {
463             checkDatabaseWiped();
464         }
465 
466         // If the database is read-only, we cannot modify the android metadata table
467         // or existing indexes.
468         if (mIsReadOnlyConnection) {
469             return;
470         }
471 
472         try {
473             // Ensure the android metadata table exists.
474             execute("CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null);
475 
476             // Check whether the locale was actually changed.
477             final String oldLocale = executeForString("SELECT locale FROM android_metadata "
478                     + "UNION SELECT NULL ORDER BY locale DESC LIMIT 1", null, null);
479             if (oldLocale != null && oldLocale.equals(newLocale)) {
480                 return;
481             }
482 
483             // Go ahead and update the indexes using the new locale.
484             execute("BEGIN", null, null);
485             boolean success = false;
486             try {
487                 execute("DELETE FROM android_metadata", null, null);
488                 execute("INSERT INTO android_metadata (locale) VALUES(?)",
489                         new Object[] { newLocale }, null);
490                 execute("REINDEX LOCALIZED", null, null);
491                 success = true;
492             } finally {
493                 execute(success ? "COMMIT" : "ROLLBACK", null, null);
494             }
495         } catch (SQLiteException ex) {
496             throw ex;
497         } catch (RuntimeException ex) {
498             throw new SQLiteException("Failed to change locale for db '" + mConfiguration.label
499                     + "' to '" + newLocale + "'.", ex);
500         }
501     }
502 
setCustomFunctionsFromConfiguration()503     private void setCustomFunctionsFromConfiguration() {
504         for (int i = 0; i < mConfiguration.customScalarFunctions.size(); i++) {
505             nativeRegisterCustomScalarFunction(mConnectionPtr,
506                     mConfiguration.customScalarFunctions.keyAt(i),
507                     mConfiguration.customScalarFunctions.valueAt(i));
508         }
509         for (int i = 0; i < mConfiguration.customAggregateFunctions.size(); i++) {
510             nativeRegisterCustomAggregateFunction(mConnectionPtr,
511                     mConfiguration.customAggregateFunctions.keyAt(i),
512                     mConfiguration.customAggregateFunctions.valueAt(i));
513         }
514     }
515 
executePerConnectionSqlFromConfiguration(int startIndex)516     private void executePerConnectionSqlFromConfiguration(int startIndex) {
517         for (int i = startIndex; i < mConfiguration.perConnectionSql.size(); i++) {
518             final Pair<String, Object[]> statement = mConfiguration.perConnectionSql.get(i);
519             final int type = DatabaseUtils.getSqlStatementType(statement.first);
520             switch (type) {
521                 case DatabaseUtils.STATEMENT_SELECT:
522                     executeForString(statement.first, statement.second, null);
523                     break;
524                 case DatabaseUtils.STATEMENT_PRAGMA:
525                     execute(statement.first, statement.second, null);
526                     break;
527                 default:
528                     throw new IllegalArgumentException(
529                             "Unsupported configuration statement: " + statement);
530             }
531         }
532     }
533 
checkDatabaseWiped()534     private void checkDatabaseWiped() {
535         if (!SQLiteGlobal.checkDbWipe()) {
536             return;
537         }
538         try {
539             final File checkFile = new File(mConfiguration.path
540                     + SQLiteGlobal.WIPE_CHECK_FILE_SUFFIX);
541 
542             final boolean hasMetadataTable = executeForLong(
543                     "SELECT count(*) FROM sqlite_master"
544                             + " WHERE type='table' AND name='android_metadata'", null, null) > 0;
545             final boolean hasCheckFile = checkFile.exists();
546 
547             if (!mIsReadOnlyConnection && !hasCheckFile) {
548                 // Create the check file, unless it's a readonly connection,
549                 // in which case we can't create the metadata table anyway.
550                 checkFile.createNewFile();
551             }
552 
553             if (!hasMetadataTable && hasCheckFile) {
554                 // Bad. The DB is gone unexpectedly.
555                 SQLiteDatabase.wipeDetected(mConfiguration.path, "unknown");
556             }
557 
558         } catch (RuntimeException | IOException ex) {
559             SQLiteDatabase.wtfAsSystemServer(TAG,
560                     "Unexpected exception while checking for wipe", ex);
561         }
562     }
563 
564     // Called by SQLiteConnectionPool only.
reconfigure(SQLiteDatabaseConfiguration configuration)565     void reconfigure(SQLiteDatabaseConfiguration configuration) {
566         mOnlyAllowReadOnlyOperations = false;
567 
568         // Remember what changed.
569         boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
570                 != mConfiguration.foreignKeyConstraintsEnabled;
571         boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
572         boolean customScalarFunctionsChanged = !configuration.customScalarFunctions
573                 .equals(mConfiguration.customScalarFunctions);
574         boolean customAggregateFunctionsChanged = !configuration.customAggregateFunctions
575                 .equals(mConfiguration.customAggregateFunctions);
576         final int oldSize = mConfiguration.perConnectionSql.size();
577         final int newSize = configuration.perConnectionSql.size();
578         boolean perConnectionSqlChanged = newSize > oldSize;
579 
580         // Update configuration parameters.
581         mConfiguration.updateParametersFrom(configuration);
582 
583         // Update prepared statement cache size.
584         mPreparedStatementCache.resize(configuration.maxSqlCacheSize);
585 
586         if (foreignKeyModeChanged) {
587             setForeignKeyModeFromConfiguration();
588         }
589 
590         boolean journalModeChanged = !configuration.resolveJournalMode().equalsIgnoreCase(
591                 mConfiguration.resolveJournalMode());
592         if (journalModeChanged) {
593             setJournalFromConfiguration();
594         }
595 
596         boolean syncModeChanged =
597                 !configuration.resolveSyncMode().equalsIgnoreCase(mConfiguration.resolveSyncMode());
598         if (syncModeChanged) {
599             setSyncModeFromConfiguration();
600         }
601 
602         if (localeChanged) {
603             setLocaleFromConfiguration();
604         }
605         if (customScalarFunctionsChanged || customAggregateFunctionsChanged) {
606             setCustomFunctionsFromConfiguration();
607         }
608         if (perConnectionSqlChanged) {
609             executePerConnectionSqlFromConfiguration(oldSize);
610         }
611     }
612 
613     // Called by SQLiteConnectionPool only.
614     // When set to true, executing write operations will throw SQLiteException.
615     // Preparing statements that might write is ok, just don't execute them.
setOnlyAllowReadOnlyOperations(boolean readOnly)616     void setOnlyAllowReadOnlyOperations(boolean readOnly) {
617         mOnlyAllowReadOnlyOperations = readOnly;
618     }
619 
620     // Called by SQLiteConnectionPool only.
621     // Returns true if the prepared statement cache contains the specified SQL.
isPreparedStatementInCache(String sql)622     boolean isPreparedStatementInCache(String sql) {
623         return mPreparedStatementCache.get(sql) != null;
624     }
625 
626     /**
627      * Gets the unique id of this connection.
628      * @return The connection id.
629      */
getConnectionId()630     public int getConnectionId() {
631         return mConnectionId;
632     }
633 
634     /**
635      * Returns true if this is the primary database connection.
636      * @return True if this is the primary database connection.
637      */
isPrimaryConnection()638     public boolean isPrimaryConnection() {
639         return mIsPrimaryConnection;
640     }
641 
642     /**
643      * Prepares a statement for execution but does not bind its parameters or execute it.
644      * <p>
645      * This method can be used to check for syntax errors during compilation
646      * prior to execution of the statement.  If the {@code outStatementInfo} argument
647      * is not null, the provided {@link SQLiteStatementInfo} object is populated
648      * with information about the statement.
649      * </p><p>
650      * A prepared statement makes no reference to the arguments that may eventually
651      * be bound to it, consequently it it possible to cache certain prepared statements
652      * such as SELECT or INSERT/UPDATE statements.  If the statement is cacheable,
653      * then it will be stored in the cache for later.
654      * </p><p>
655      * To take advantage of this behavior as an optimization, the connection pool
656      * provides a method to acquire a connection that already has a given SQL statement
657      * in its prepared statement cache so that it is ready for execution.
658      * </p>
659      *
660      * @param sql The SQL statement to prepare.
661      * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate
662      * with information about the statement, or null if none.
663      *
664      * @throws SQLiteException if an error occurs, such as a syntax error.
665      */
prepare(String sql, SQLiteStatementInfo outStatementInfo)666     public void prepare(String sql, SQLiteStatementInfo outStatementInfo) {
667         if (sql == null) {
668             throw new IllegalArgumentException("sql must not be null.");
669         }
670 
671         final int cookie = mRecentOperations.beginOperation("prepare", sql, null);
672         try {
673             final PreparedStatement statement = acquirePreparedStatement(sql);
674             try {
675                 if (outStatementInfo != null) {
676                     outStatementInfo.numParameters = statement.mNumParameters;
677                     outStatementInfo.readOnly = statement.mReadOnly;
678 
679                     final int columnCount = nativeGetColumnCount(
680                             mConnectionPtr, statement.mStatementPtr);
681                     if (columnCount == 0) {
682                         outStatementInfo.columnNames = EMPTY_STRING_ARRAY;
683                     } else {
684                         outStatementInfo.columnNames = new String[columnCount];
685                         for (int i = 0; i < columnCount; i++) {
686                             outStatementInfo.columnNames[i] = nativeGetColumnName(
687                                     mConnectionPtr, statement.mStatementPtr, i);
688                         }
689                     }
690                 }
691             } finally {
692                 releasePreparedStatement(statement);
693             }
694         } catch (RuntimeException ex) {
695             mRecentOperations.failOperation(cookie, ex);
696             throw ex;
697         } finally {
698             mRecentOperations.endOperation(cookie);
699         }
700     }
701 
702     /**
703      * Executes a statement that does not return a result.
704      *
705      * @param sql The SQL statement to execute.
706      * @param bindArgs The arguments to bind, or null if none.
707      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
708      *
709      * @throws SQLiteException if an error occurs, such as a syntax error
710      * or invalid number of bind arguments.
711      * @throws OperationCanceledException if the operation was canceled.
712      */
execute(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)713     public void execute(String sql, Object[] bindArgs,
714             CancellationSignal cancellationSignal) {
715         if (sql == null) {
716             throw new IllegalArgumentException("sql must not be null.");
717         }
718 
719         final int cookie = mRecentOperations.beginOperation("execute", sql, bindArgs);
720         try {
721             final boolean isPragmaStmt =
722                 DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_PRAGMA;
723             final PreparedStatement statement = acquirePreparedStatement(sql);
724             try {
725                 throwIfStatementForbidden(statement);
726                 bindArguments(statement, bindArgs);
727                 applyBlockGuardPolicy(statement);
728                 attachCancellationSignal(cancellationSignal);
729                 try {
730                     nativeExecute(mConnectionPtr, statement.mStatementPtr, isPragmaStmt);
731                 } finally {
732                     detachCancellationSignal(cancellationSignal);
733                 }
734             } finally {
735                 releasePreparedStatement(statement);
736             }
737         } catch (RuntimeException ex) {
738             mRecentOperations.failOperation(cookie, ex);
739             throw ex;
740         } finally {
741             mRecentOperations.endOperation(cookie);
742         }
743     }
744 
745     /**
746      * Executes a statement that returns a single <code>long</code> result.
747      *
748      * @param sql The SQL statement to execute.
749      * @param bindArgs The arguments to bind, or null if none.
750      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
751      * @return The value of the first column in the first row of the result set
752      * as a <code>long</code>, or zero if none.
753      *
754      * @throws SQLiteException if an error occurs, such as a syntax error
755      * or invalid number of bind arguments.
756      * @throws OperationCanceledException if the operation was canceled.
757      */
executeForLong(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)758     public long executeForLong(String sql, Object[] bindArgs,
759             CancellationSignal cancellationSignal) {
760         if (sql == null) {
761             throw new IllegalArgumentException("sql must not be null.");
762         }
763 
764         final int cookie = mRecentOperations.beginOperation("executeForLong", sql, bindArgs);
765         try {
766             final PreparedStatement statement = acquirePreparedStatement(sql);
767             try {
768                 throwIfStatementForbidden(statement);
769                 bindArguments(statement, bindArgs);
770                 applyBlockGuardPolicy(statement);
771                 attachCancellationSignal(cancellationSignal);
772                 try {
773                     long ret = nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr);
774                     mRecentOperations.setResult(ret);
775                     return ret;
776                 } finally {
777                     detachCancellationSignal(cancellationSignal);
778                 }
779             } finally {
780                 releasePreparedStatement(statement);
781             }
782         } catch (RuntimeException ex) {
783             mRecentOperations.failOperation(cookie, ex);
784             throw ex;
785         } finally {
786             mRecentOperations.endOperation(cookie);
787         }
788     }
789 
790     /**
791      * Executes a statement that returns a single {@link String} result.
792      *
793      * @param sql The SQL statement to execute.
794      * @param bindArgs The arguments to bind, or null if none.
795      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
796      * @return The value of the first column in the first row of the result set
797      * as a <code>String</code>, or null if none.
798      *
799      * @throws SQLiteException if an error occurs, such as a syntax error
800      * or invalid number of bind arguments.
801      * @throws OperationCanceledException if the operation was canceled.
802      */
executeForString(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)803     public String executeForString(String sql, Object[] bindArgs,
804             CancellationSignal cancellationSignal) {
805         if (sql == null) {
806             throw new IllegalArgumentException("sql must not be null.");
807         }
808 
809         final int cookie = mRecentOperations.beginOperation("executeForString", sql, bindArgs);
810         try {
811             final PreparedStatement statement = acquirePreparedStatement(sql);
812             try {
813                 throwIfStatementForbidden(statement);
814                 bindArguments(statement, bindArgs);
815                 applyBlockGuardPolicy(statement);
816                 attachCancellationSignal(cancellationSignal);
817                 try {
818                     String ret = nativeExecuteForString(mConnectionPtr, statement.mStatementPtr);
819                     mRecentOperations.setResult(ret);
820                     return ret;
821                 } finally {
822                     detachCancellationSignal(cancellationSignal);
823                 }
824             } finally {
825                 releasePreparedStatement(statement);
826             }
827         } catch (RuntimeException ex) {
828             mRecentOperations.failOperation(cookie, ex);
829             throw ex;
830         } finally {
831             mRecentOperations.endOperation(cookie);
832         }
833     }
834 
835     /**
836      * Executes a statement that returns a single BLOB result as a
837      * file descriptor to a shared memory region.
838      *
839      * @param sql The SQL statement to execute.
840      * @param bindArgs The arguments to bind, or null if none.
841      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
842      * @return The file descriptor for a shared memory region that contains
843      * the value of the first column in the first row of the result set as a BLOB,
844      * or null if none.
845      *
846      * @throws SQLiteException if an error occurs, such as a syntax error
847      * or invalid number of bind arguments.
848      * @throws OperationCanceledException if the operation was canceled.
849      */
executeForBlobFileDescriptor(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)850     public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs,
851             CancellationSignal cancellationSignal) {
852         if (sql == null) {
853             throw new IllegalArgumentException("sql must not be null.");
854         }
855 
856         final int cookie = mRecentOperations.beginOperation("executeForBlobFileDescriptor",
857                 sql, bindArgs);
858         try {
859             final PreparedStatement statement = acquirePreparedStatement(sql);
860             try {
861                 throwIfStatementForbidden(statement);
862                 bindArguments(statement, bindArgs);
863                 applyBlockGuardPolicy(statement);
864                 attachCancellationSignal(cancellationSignal);
865                 try {
866                     int fd = nativeExecuteForBlobFileDescriptor(
867                             mConnectionPtr, statement.mStatementPtr);
868                     return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null;
869                 } finally {
870                     detachCancellationSignal(cancellationSignal);
871                 }
872             } finally {
873                 releasePreparedStatement(statement);
874             }
875         } catch (RuntimeException ex) {
876             mRecentOperations.failOperation(cookie, ex);
877             throw ex;
878         } finally {
879             mRecentOperations.endOperation(cookie);
880         }
881     }
882 
883     /**
884      * Executes a statement that returns a count of the number of rows
885      * that were changed.  Use for UPDATE or DELETE SQL statements.
886      *
887      * @param sql The SQL statement to execute.
888      * @param bindArgs The arguments to bind, or null if none.
889      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
890      * @return The number of rows that were changed.
891      *
892      * @throws SQLiteException if an error occurs, such as a syntax error
893      * or invalid number of bind arguments.
894      * @throws OperationCanceledException if the operation was canceled.
895      */
executeForChangedRowCount(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)896     public int executeForChangedRowCount(String sql, Object[] bindArgs,
897             CancellationSignal cancellationSignal) {
898         if (sql == null) {
899             throw new IllegalArgumentException("sql must not be null.");
900         }
901 
902         int changedRows = 0;
903         final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount",
904                 sql, bindArgs);
905         try {
906             final PreparedStatement statement = acquirePreparedStatement(sql);
907             try {
908                 throwIfStatementForbidden(statement);
909                 bindArguments(statement, bindArgs);
910                 applyBlockGuardPolicy(statement);
911                 attachCancellationSignal(cancellationSignal);
912                 try {
913                     changedRows = nativeExecuteForChangedRowCount(
914                             mConnectionPtr, statement.mStatementPtr);
915                     return changedRows;
916                 } finally {
917                     detachCancellationSignal(cancellationSignal);
918                 }
919             } finally {
920                 releasePreparedStatement(statement);
921             }
922         } catch (RuntimeException ex) {
923             mRecentOperations.failOperation(cookie, ex);
924             throw ex;
925         } finally {
926             if (mRecentOperations.endOperationDeferLog(cookie)) {
927                 mRecentOperations.logOperation(cookie, "changedRows=" + changedRows);
928             }
929         }
930     }
931 
932     /**
933      * Executes a statement that returns the row id of the last row inserted
934      * by the statement.  Use for INSERT SQL statements.
935      *
936      * @param sql The SQL statement to execute.
937      * @param bindArgs The arguments to bind, or null if none.
938      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
939      * @return The row id of the last row that was inserted, or 0 if none.
940      *
941      * @throws SQLiteException if an error occurs, such as a syntax error
942      * or invalid number of bind arguments.
943      * @throws OperationCanceledException if the operation was canceled.
944      */
executeForLastInsertedRowId(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)945     public long executeForLastInsertedRowId(String sql, Object[] bindArgs,
946             CancellationSignal cancellationSignal) {
947         if (sql == null) {
948             throw new IllegalArgumentException("sql must not be null.");
949         }
950 
951         final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId",
952                 sql, bindArgs);
953         try {
954             final PreparedStatement statement = acquirePreparedStatement(sql);
955             try {
956                 throwIfStatementForbidden(statement);
957                 bindArguments(statement, bindArgs);
958                 applyBlockGuardPolicy(statement);
959                 attachCancellationSignal(cancellationSignal);
960                 try {
961                     return nativeExecuteForLastInsertedRowId(
962                             mConnectionPtr, statement.mStatementPtr);
963                 } finally {
964                     detachCancellationSignal(cancellationSignal);
965                 }
966             } finally {
967                 releasePreparedStatement(statement);
968             }
969         } catch (RuntimeException ex) {
970             mRecentOperations.failOperation(cookie, ex);
971             throw ex;
972         } finally {
973             mRecentOperations.endOperation(cookie);
974         }
975     }
976 
977     /**
978      * Executes a statement and populates the specified {@link CursorWindow}
979      * with a range of results.  Returns the number of rows that were counted
980      * during query execution.
981      *
982      * @param sql The SQL statement to execute.
983      * @param bindArgs The arguments to bind, or null if none.
984      * @param window The cursor window to clear and fill.
985      * @param startPos The start position for filling the window.
986      * @param requiredPos The position of a row that MUST be in the window.
987      * If it won't fit, then the query should discard part of what it filled
988      * so that it does.  Must be greater than or equal to <code>startPos</code>.
989      * @param countAllRows True to count all rows that the query would return
990      * regagless of whether they fit in the window.
991      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
992      * @return The number of rows that were counted during query execution.  Might
993      * not be all rows in the result set unless <code>countAllRows</code> is true.
994      *
995      * @throws SQLiteException if an error occurs, such as a syntax error
996      * or invalid number of bind arguments.
997      * @throws OperationCanceledException if the operation was canceled.
998      */
executeForCursorWindow(String sql, Object[] bindArgs, CursorWindow window, int startPos, int requiredPos, boolean countAllRows, CancellationSignal cancellationSignal)999     public int executeForCursorWindow(String sql, Object[] bindArgs,
1000             CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
1001             CancellationSignal cancellationSignal) {
1002         if (sql == null) {
1003             throw new IllegalArgumentException("sql must not be null.");
1004         }
1005         if (window == null) {
1006             throw new IllegalArgumentException("window must not be null.");
1007         }
1008 
1009         window.acquireReference();
1010         try {
1011             int actualPos = -1;
1012             int countedRows = -1;
1013             int filledRows = -1;
1014             final int cookie = mRecentOperations.beginOperation("executeForCursorWindow",
1015                     sql, bindArgs);
1016             try {
1017                 final PreparedStatement statement = acquirePreparedStatement(sql);
1018                 try {
1019                     throwIfStatementForbidden(statement);
1020                     bindArguments(statement, bindArgs);
1021                     applyBlockGuardPolicy(statement);
1022                     attachCancellationSignal(cancellationSignal);
1023                     try {
1024                         final long result = nativeExecuteForCursorWindow(
1025                                 mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
1026                                 startPos, requiredPos, countAllRows);
1027                         actualPos = (int)(result >> 32);
1028                         countedRows = (int)result;
1029                         filledRows = window.getNumRows();
1030                         window.setStartPosition(actualPos);
1031                         return countedRows;
1032                     } finally {
1033                         detachCancellationSignal(cancellationSignal);
1034                     }
1035                 } finally {
1036                     releasePreparedStatement(statement);
1037                 }
1038             } catch (RuntimeException ex) {
1039                 mRecentOperations.failOperation(cookie, ex);
1040                 throw ex;
1041             } finally {
1042                 if (mRecentOperations.endOperationDeferLog(cookie)) {
1043                     mRecentOperations.logOperation(cookie, "window='" + window
1044                             + "', startPos=" + startPos
1045                             + ", actualPos=" + actualPos
1046                             + ", filledRows=" + filledRows
1047                             + ", countedRows=" + countedRows);
1048                 }
1049             }
1050         } finally {
1051             window.releaseReference();
1052         }
1053     }
1054 
acquirePreparedStatement(String sql)1055     private PreparedStatement acquirePreparedStatement(String sql) {
1056         ++mPool.mTotalPrepareStatements;
1057         PreparedStatement statement = mPreparedStatementCache.get(sql);
1058         boolean skipCache = false;
1059         if (statement != null) {
1060             if (!statement.mInUse) {
1061                 return statement;
1062             }
1063             // The statement is already in the cache but is in use (this statement appears
1064             // to be not only re-entrant but recursive!).  So prepare a new copy of the
1065             // statement but do not cache it.
1066             skipCache = true;
1067         }
1068         ++mPool.mTotalPrepareStatementCacheMiss;
1069         final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
1070         try {
1071             final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
1072             final int type = DatabaseUtils.getSqlStatementType(sql);
1073             final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
1074             statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly);
1075             if (!skipCache && isCacheable(type)) {
1076                 mPreparedStatementCache.put(sql, statement);
1077                 statement.mInCache = true;
1078             }
1079         } catch (RuntimeException ex) {
1080             // Finalize the statement if an exception occurred and we did not add
1081             // it to the cache.  If it is already in the cache, then leave it there.
1082             if (statement == null || !statement.mInCache) {
1083                 nativeFinalizeStatement(mConnectionPtr, statementPtr);
1084             }
1085             throw ex;
1086         }
1087         statement.mInUse = true;
1088         return statement;
1089     }
1090 
releasePreparedStatement(PreparedStatement statement)1091     private void releasePreparedStatement(PreparedStatement statement) {
1092         statement.mInUse = false;
1093         if (statement.mInCache) {
1094             try {
1095                 nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr);
1096             } catch (SQLiteException ex) {
1097                 // The statement could not be reset due to an error.  Remove it from the cache.
1098                 // When remove() is called, the cache will invoke its entryRemoved() callback,
1099                 // which will in turn call finalizePreparedStatement() to finalize and
1100                 // recycle the statement.
1101                 if (DEBUG) {
1102                     Log.d(TAG, "Could not reset prepared statement due to an exception.  "
1103                             + "Removing it from the cache.  SQL: "
1104                             + trimSqlForDisplay(statement.mSql), ex);
1105                 }
1106 
1107                 mPreparedStatementCache.remove(statement.mSql);
1108             }
1109         } else {
1110             finalizePreparedStatement(statement);
1111         }
1112     }
1113 
finalizePreparedStatement(PreparedStatement statement)1114     private void finalizePreparedStatement(PreparedStatement statement) {
1115         nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr);
1116         recyclePreparedStatement(statement);
1117     }
1118 
attachCancellationSignal(CancellationSignal cancellationSignal)1119     private void attachCancellationSignal(CancellationSignal cancellationSignal) {
1120         if (cancellationSignal != null) {
1121             cancellationSignal.throwIfCanceled();
1122 
1123             mCancellationSignalAttachCount += 1;
1124             if (mCancellationSignalAttachCount == 1) {
1125                 // Reset cancellation flag before executing the statement.
1126                 nativeResetCancel(mConnectionPtr, true /*cancelable*/);
1127 
1128                 // After this point, onCancel() may be called concurrently.
1129                 cancellationSignal.setOnCancelListener(this);
1130             }
1131         }
1132     }
1133 
detachCancellationSignal(CancellationSignal cancellationSignal)1134     private void detachCancellationSignal(CancellationSignal cancellationSignal) {
1135         if (cancellationSignal != null) {
1136             assert mCancellationSignalAttachCount > 0;
1137 
1138             mCancellationSignalAttachCount -= 1;
1139             if (mCancellationSignalAttachCount == 0) {
1140                 // After this point, onCancel() cannot be called concurrently.
1141                 cancellationSignal.setOnCancelListener(null);
1142 
1143                 // Reset cancellation flag after executing the statement.
1144                 nativeResetCancel(mConnectionPtr, false /*cancelable*/);
1145             }
1146         }
1147     }
1148 
1149     // CancellationSignal.OnCancelListener callback.
1150     // This method may be called on a different thread than the executing statement.
1151     // However, it will only be called between calls to attachCancellationSignal and
1152     // detachCancellationSignal, while a statement is executing.  We can safely assume
1153     // that the SQLite connection is still alive.
1154     @Override
onCancel()1155     public void onCancel() {
1156         nativeCancel(mConnectionPtr);
1157     }
1158 
bindArguments(PreparedStatement statement, Object[] bindArgs)1159     private void bindArguments(PreparedStatement statement, Object[] bindArgs) {
1160         final int count = bindArgs != null ? bindArgs.length : 0;
1161         if (count != statement.mNumParameters) {
1162             throw new SQLiteBindOrColumnIndexOutOfRangeException(
1163                     "Expected " + statement.mNumParameters + " bind arguments but "
1164                     + count + " were provided.");
1165         }
1166         if (count == 0) {
1167             return;
1168         }
1169 
1170         final long statementPtr = statement.mStatementPtr;
1171         for (int i = 0; i < count; i++) {
1172             final Object arg = bindArgs[i];
1173             switch (DatabaseUtils.getTypeOfObject(arg)) {
1174                 case Cursor.FIELD_TYPE_NULL:
1175                     nativeBindNull(mConnectionPtr, statementPtr, i + 1);
1176                     break;
1177                 case Cursor.FIELD_TYPE_INTEGER:
1178                     nativeBindLong(mConnectionPtr, statementPtr, i + 1,
1179                             ((Number)arg).longValue());
1180                     break;
1181                 case Cursor.FIELD_TYPE_FLOAT:
1182                     nativeBindDouble(mConnectionPtr, statementPtr, i + 1,
1183                             ((Number)arg).doubleValue());
1184                     break;
1185                 case Cursor.FIELD_TYPE_BLOB:
1186                     nativeBindBlob(mConnectionPtr, statementPtr, i + 1, (byte[])arg);
1187                     break;
1188                 case Cursor.FIELD_TYPE_STRING:
1189                 default:
1190                     if (arg instanceof Boolean) {
1191                         // Provide compatibility with legacy applications which may pass
1192                         // Boolean values in bind args.
1193                         nativeBindLong(mConnectionPtr, statementPtr, i + 1,
1194                                 ((Boolean)arg).booleanValue() ? 1 : 0);
1195                     } else {
1196                         nativeBindString(mConnectionPtr, statementPtr, i + 1, arg.toString());
1197                     }
1198                     break;
1199             }
1200         }
1201     }
1202 
throwIfStatementForbidden(PreparedStatement statement)1203     private void throwIfStatementForbidden(PreparedStatement statement) {
1204         if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) {
1205             throw new SQLiteException("Cannot execute this statement because it "
1206                     + "might modify the database but the connection is read-only.");
1207         }
1208     }
1209 
isCacheable(int statementType)1210     private static boolean isCacheable(int statementType) {
1211         if (statementType == DatabaseUtils.STATEMENT_UPDATE
1212                 || statementType == DatabaseUtils.STATEMENT_SELECT) {
1213             return true;
1214         }
1215         return false;
1216     }
1217 
applyBlockGuardPolicy(PreparedStatement statement)1218     private void applyBlockGuardPolicy(PreparedStatement statement) {
1219         if (!mConfiguration.isInMemoryDb()) {
1220             if (statement.mReadOnly) {
1221                 BlockGuard.getThreadPolicy().onReadFromDisk();
1222             } else {
1223                 BlockGuard.getThreadPolicy().onWriteToDisk();
1224             }
1225         }
1226     }
1227 
1228     /**
1229      * Dumps debugging information about this connection.
1230      *
1231      * @param printer The printer to receive the dump, not null.
1232      * @param verbose True to dump more verbose information.
1233      */
dump(Printer printer, boolean verbose)1234     public void dump(Printer printer, boolean verbose) {
1235         dumpUnsafe(printer, verbose);
1236     }
1237 
1238     /**
1239      * Dumps debugging information about this connection, in the case where the
1240      * caller might not actually own the connection.
1241      *
1242      * This function is written so that it may be called by a thread that does not
1243      * own the connection.  We need to be very careful because the connection state is
1244      * not synchronized.
1245      *
1246      * At worst, the method may return stale or slightly wrong data, however
1247      * it should not crash.  This is ok as it is only used for diagnostic purposes.
1248      *
1249      * @param printer The printer to receive the dump, not null.
1250      * @param verbose True to dump more verbose information.
1251      */
dumpUnsafe(Printer printer, boolean verbose)1252     void dumpUnsafe(Printer printer, boolean verbose) {
1253         printer.println("Connection #" + mConnectionId + ":");
1254         if (verbose) {
1255             printer.println("  connectionPtr: 0x" + Long.toHexString(mConnectionPtr));
1256         }
1257         printer.println("  isPrimaryConnection: " + mIsPrimaryConnection);
1258         printer.println("  onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
1259 
1260         mRecentOperations.dump(printer);
1261 
1262         if (verbose) {
1263             mPreparedStatementCache.dump(printer);
1264         }
1265     }
1266 
1267     /**
1268      * Describes the currently executing operation, in the case where the
1269      * caller might not actually own the connection.
1270      *
1271      * This function is written so that it may be called by a thread that does not
1272      * own the connection.  We need to be very careful because the connection state is
1273      * not synchronized.
1274      *
1275      * At worst, the method may return stale or slightly wrong data, however
1276      * it should not crash.  This is ok as it is only used for diagnostic purposes.
1277      *
1278      * @return A description of the current operation including how long it has been running,
1279      * or null if none.
1280      */
describeCurrentOperationUnsafe()1281     String describeCurrentOperationUnsafe() {
1282         return mRecentOperations.describeCurrentOperation();
1283     }
1284 
1285     /**
1286      * Collects statistics about database connection memory usage.
1287      *
1288      * @param dbStatsList The list to populate.
1289      */
collectDbStats(ArrayList<DbStats> dbStatsList)1290     void collectDbStats(ArrayList<DbStats> dbStatsList) {
1291         // Get information about the main database.
1292         int lookaside = nativeGetDbLookaside(mConnectionPtr);
1293         long pageCount = 0;
1294         long pageSize = 0;
1295         try {
1296             pageCount = executeForLong("PRAGMA page_count;", null, null);
1297             pageSize = executeForLong("PRAGMA page_size;", null, null);
1298         } catch (SQLiteException ex) {
1299             // Ignore.
1300         }
1301         dbStatsList.add(getMainDbStatsUnsafe(lookaside, pageCount, pageSize));
1302 
1303         // Get information about attached databases.
1304         // We ignore the first row in the database list because it corresponds to
1305         // the main database which we have already described.
1306         CursorWindow window = new CursorWindow("collectDbStats");
1307         try {
1308             executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null);
1309             for (int i = 1; i < window.getNumRows(); i++) {
1310                 String name = window.getString(i, 1);
1311                 String path = window.getString(i, 2);
1312                 pageCount = 0;
1313                 pageSize = 0;
1314                 try {
1315                     pageCount = executeForLong("PRAGMA " + name + ".page_count;", null, null);
1316                     pageSize = executeForLong("PRAGMA " + name + ".page_size;", null, null);
1317                 } catch (SQLiteException ex) {
1318                     // Ignore.
1319                 }
1320                 StringBuilder label = new StringBuilder("  (attached) ").append(name);
1321                 if (!path.isEmpty()) {
1322                     label.append(": ").append(path);
1323                 }
1324                 dbStatsList.add(
1325                         new DbStats(label.toString(), pageCount, pageSize, 0, 0, 0, 0, false));
1326             }
1327         } catch (SQLiteException ex) {
1328             // Ignore.
1329         } finally {
1330             window.close();
1331         }
1332     }
1333 
1334     /**
1335      * Collects statistics about database connection memory usage, in the case where the
1336      * caller might not actually own the connection.
1337      *
1338      * @return The statistics object, never null.
1339      */
collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList)1340     void collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList) {
1341         dbStatsList.add(getMainDbStatsUnsafe(0, 0, 0));
1342     }
1343 
getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize)1344     private DbStats getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize) {
1345         // The prepared statement cache is thread-safe so we can access its statistics
1346         // even if we do not own the database connection.
1347         String label;
1348         if (mIsPrimaryConnection) {
1349             label = mConfiguration.path;
1350         } else {
1351             label = mConfiguration.path + " (" + mConnectionId + ")";
1352         }
1353         return new DbStats(label, pageCount, pageSize, lookaside,
1354                 mPreparedStatementCache.hitCount(), mPreparedStatementCache.missCount(),
1355                 mPreparedStatementCache.size(), false);
1356     }
1357 
1358     @Override
toString()1359     public String toString() {
1360         return "SQLiteConnection: " + mConfiguration.path + " (" + mConnectionId + ")";
1361     }
1362 
obtainPreparedStatement(String sql, long statementPtr, int numParameters, int type, boolean readOnly)1363     private PreparedStatement obtainPreparedStatement(String sql, long statementPtr,
1364             int numParameters, int type, boolean readOnly) {
1365         PreparedStatement statement = mPreparedStatementPool;
1366         if (statement != null) {
1367             mPreparedStatementPool = statement.mPoolNext;
1368             statement.mPoolNext = null;
1369             statement.mInCache = false;
1370         } else {
1371             statement = new PreparedStatement();
1372         }
1373         statement.mSql = sql;
1374         statement.mStatementPtr = statementPtr;
1375         statement.mNumParameters = numParameters;
1376         statement.mType = type;
1377         statement.mReadOnly = readOnly;
1378         return statement;
1379     }
1380 
recyclePreparedStatement(PreparedStatement statement)1381     private void recyclePreparedStatement(PreparedStatement statement) {
1382         statement.mSql = null;
1383         statement.mPoolNext = mPreparedStatementPool;
1384         mPreparedStatementPool = statement;
1385     }
1386 
trimSqlForDisplay(String sql)1387     private static String trimSqlForDisplay(String sql) {
1388         // Note: Creating and caching a regular expression is expensive at preload-time
1389         //       and stops compile-time initialization. This pattern is only used when
1390         //       dumping the connection, which is a rare (mainly error) case. So:
1391         //       DO NOT CACHE.
1392         return sql.replaceAll("[\\s]*\\n+[\\s]*", " ");
1393     }
1394 
1395     /**
1396      * Holder type for a prepared statement.
1397      *
1398      * Although this object holds a pointer to a native statement object, it
1399      * does not have a finalizer.  This is deliberate.  The {@link SQLiteConnection}
1400      * owns the statement object and will take care of freeing it when needed.
1401      * In particular, closing the connection requires a guarantee of deterministic
1402      * resource disposal because all native statement objects must be freed before
1403      * the native database object can be closed.  So no finalizers here.
1404      */
1405     private static final class PreparedStatement {
1406         // Next item in pool.
1407         public PreparedStatement mPoolNext;
1408 
1409         // The SQL from which the statement was prepared.
1410         public String mSql;
1411 
1412         // The native sqlite3_stmt object pointer.
1413         // Lifetime is managed explicitly by the connection.
1414         public long mStatementPtr;
1415 
1416         // The number of parameters that the prepared statement has.
1417         public int mNumParameters;
1418 
1419         // The statement type.
1420         public int mType;
1421 
1422         // True if the statement is read-only.
1423         public boolean mReadOnly;
1424 
1425         // True if the statement is in the cache.
1426         public boolean mInCache;
1427 
1428         // True if the statement is in use (currently executing).
1429         // We need this flag because due to the use of custom functions in triggers, it's
1430         // possible for SQLite calls to be re-entrant.  Consequently we need to prevent
1431         // in use statements from being finalized until they are no longer in use.
1432         public boolean mInUse;
1433     }
1434 
1435     private final class PreparedStatementCache
1436             extends LruCache<String, PreparedStatement> {
PreparedStatementCache(int size)1437         public PreparedStatementCache(int size) {
1438             super(size);
1439         }
1440 
1441         @Override
entryRemoved(boolean evicted, String key, PreparedStatement oldValue, PreparedStatement newValue)1442         protected void entryRemoved(boolean evicted, String key,
1443                 PreparedStatement oldValue, PreparedStatement newValue) {
1444             oldValue.mInCache = false;
1445             if (!oldValue.mInUse) {
1446                 finalizePreparedStatement(oldValue);
1447             }
1448         }
1449 
dump(Printer printer)1450         public void dump(Printer printer) {
1451             printer.println("  Prepared statement cache:");
1452             Map<String, PreparedStatement> cache = snapshot();
1453             if (!cache.isEmpty()) {
1454                 int i = 0;
1455                 for (Map.Entry<String, PreparedStatement> entry : cache.entrySet()) {
1456                     PreparedStatement statement = entry.getValue();
1457                     if (statement.mInCache) { // might be false due to a race with entryRemoved
1458                         String sql = entry.getKey();
1459                         printer.println("    " + i + ": statementPtr=0x"
1460                                 + Long.toHexString(statement.mStatementPtr)
1461                                 + ", numParameters=" + statement.mNumParameters
1462                                 + ", type=" + statement.mType
1463                                 + ", readOnly=" + statement.mReadOnly
1464                                 + ", sql=\"" + trimSqlForDisplay(sql) + "\"");
1465                     }
1466                     i += 1;
1467                 }
1468             } else {
1469                 printer.println("    <none>");
1470             }
1471         }
1472     }
1473 
1474     private static final class OperationLog {
1475         private static final int MAX_RECENT_OPERATIONS = 20;
1476         private static final int COOKIE_GENERATION_SHIFT = 8;
1477         private static final int COOKIE_INDEX_MASK = 0xff;
1478 
1479         private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
1480         private int mIndex;
1481         private int mGeneration;
1482         private final SQLiteConnectionPool mPool;
1483         private long mResultLong = Long.MIN_VALUE;
1484         private String mResultString;
1485 
OperationLog(SQLiteConnectionPool pool)1486         OperationLog(SQLiteConnectionPool pool) {
1487             mPool = pool;
1488         }
1489 
beginOperation(String kind, String sql, Object[] bindArgs)1490         public int beginOperation(String kind, String sql, Object[] bindArgs) {
1491             mResultLong = Long.MIN_VALUE;
1492             mResultString = null;
1493 
1494             synchronized (mOperations) {
1495                 final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
1496                 Operation operation = mOperations[index];
1497                 if (operation == null) {
1498                     operation = new Operation();
1499                     mOperations[index] = operation;
1500                 } else {
1501                     operation.mFinished = false;
1502                     operation.mException = null;
1503                     if (operation.mBindArgs != null) {
1504                         operation.mBindArgs.clear();
1505                     }
1506                 }
1507                 operation.mStartWallTime = System.currentTimeMillis();
1508                 operation.mStartTime = SystemClock.uptimeMillis();
1509                 operation.mKind = kind;
1510                 operation.mSql = sql;
1511                 operation.mPath = mPool.getPath();
1512                 operation.mResultLong = Long.MIN_VALUE;
1513                 operation.mResultString = null;
1514                 if (bindArgs != null) {
1515                     if (operation.mBindArgs == null) {
1516                         operation.mBindArgs = new ArrayList<Object>();
1517                     } else {
1518                         operation.mBindArgs.clear();
1519                     }
1520                     for (int i = 0; i < bindArgs.length; i++) {
1521                         final Object arg = bindArgs[i];
1522                         if (arg != null && arg instanceof byte[]) {
1523                             // Don't hold onto the real byte array longer than necessary.
1524                             operation.mBindArgs.add(EMPTY_BYTE_ARRAY);
1525                         } else {
1526                             operation.mBindArgs.add(arg);
1527                         }
1528                     }
1529                 }
1530                 operation.mCookie = newOperationCookieLocked(index);
1531                 if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
1532                     Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
1533                             operation.mCookie);
1534                 }
1535                 mIndex = index;
1536                 return operation.mCookie;
1537             }
1538         }
1539 
failOperation(int cookie, Exception ex)1540         public void failOperation(int cookie, Exception ex) {
1541             synchronized (mOperations) {
1542                 final Operation operation = getOperationLocked(cookie);
1543                 if (operation != null) {
1544                     operation.mException = ex;
1545                 }
1546             }
1547         }
1548 
endOperation(int cookie)1549         public void endOperation(int cookie) {
1550             synchronized (mOperations) {
1551                 if (endOperationDeferLogLocked(cookie)) {
1552                     logOperationLocked(cookie, null);
1553                 }
1554             }
1555         }
1556 
endOperationDeferLog(int cookie)1557         public boolean endOperationDeferLog(int cookie) {
1558             synchronized (mOperations) {
1559                 return endOperationDeferLogLocked(cookie);
1560             }
1561         }
1562 
logOperation(int cookie, String detail)1563         public void logOperation(int cookie, String detail) {
1564             synchronized (mOperations) {
1565                 logOperationLocked(cookie, detail);
1566             }
1567         }
1568 
setResult(long longResult)1569         public void setResult(long longResult) {
1570             mResultLong = longResult;
1571         }
1572 
setResult(String stringResult)1573         public void setResult(String stringResult) {
1574             mResultString = stringResult;
1575         }
1576 
endOperationDeferLogLocked(int cookie)1577         private boolean endOperationDeferLogLocked(int cookie) {
1578             final Operation operation = getOperationLocked(cookie);
1579             if (operation != null) {
1580                 if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
1581                     Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
1582                             operation.mCookie);
1583                 }
1584                 operation.mEndTime = SystemClock.uptimeMillis();
1585                 operation.mFinished = true;
1586                 final long execTime = operation.mEndTime - operation.mStartTime;
1587                 mPool.onStatementExecuted(execTime);
1588                 return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
1589                         execTime);
1590             }
1591             return false;
1592         }
1593 
logOperationLocked(int cookie, String detail)1594         private void logOperationLocked(int cookie, String detail) {
1595             final Operation operation = getOperationLocked(cookie);
1596             operation.mResultLong = mResultLong;
1597             operation.mResultString = mResultString;
1598             StringBuilder msg = new StringBuilder();
1599             operation.describe(msg, true);
1600             if (detail != null) {
1601                 msg.append(", ").append(detail);
1602             }
1603             Log.d(TAG, msg.toString());
1604         }
1605 
newOperationCookieLocked(int index)1606         private int newOperationCookieLocked(int index) {
1607             final int generation = mGeneration++;
1608             return generation << COOKIE_GENERATION_SHIFT | index;
1609         }
1610 
getOperationLocked(int cookie)1611         private Operation getOperationLocked(int cookie) {
1612             final int index = cookie & COOKIE_INDEX_MASK;
1613             final Operation operation = mOperations[index];
1614             return operation.mCookie == cookie ? operation : null;
1615         }
1616 
describeCurrentOperation()1617         public String describeCurrentOperation() {
1618             synchronized (mOperations) {
1619                 final Operation operation = mOperations[mIndex];
1620                 if (operation != null && !operation.mFinished) {
1621                     StringBuilder msg = new StringBuilder();
1622                     operation.describe(msg, false);
1623                     return msg.toString();
1624                 }
1625                 return null;
1626             }
1627         }
1628 
dump(Printer printer)1629         public void dump(Printer printer) {
1630             synchronized (mOperations) {
1631                 printer.println("  Most recently executed operations:");
1632                 int index = mIndex;
1633                 Operation operation = mOperations[index];
1634                 if (operation != null) {
1635                     // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created,
1636                     // and is relatively expensive to create during preloading. This method is only
1637                     // used when dumping a connection, which is a rare (mainly error) case.
1638                     SimpleDateFormat opDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
1639                     int n = 0;
1640                     do {
1641                         StringBuilder msg = new StringBuilder();
1642                         msg.append("    ").append(n).append(": [");
1643                         String formattedStartTime = opDF.format(new Date(operation.mStartWallTime));
1644                         msg.append(formattedStartTime);
1645                         msg.append("] ");
1646                         operation.describe(msg, false); // Never dump bingargs in a bugreport
1647                         printer.println(msg.toString());
1648 
1649                         if (index > 0) {
1650                             index -= 1;
1651                         } else {
1652                             index = MAX_RECENT_OPERATIONS - 1;
1653                         }
1654                         n += 1;
1655                         operation = mOperations[index];
1656                     } while (operation != null && n < MAX_RECENT_OPERATIONS);
1657                 } else {
1658                     printer.println("    <none>");
1659                 }
1660             }
1661         }
1662     }
1663 
1664     private static final class Operation {
1665         // Trim all SQL statements to 256 characters inside the trace marker.
1666         // This limit gives plenty of context while leaving space for other
1667         // entries in the trace buffer (and ensures atrace doesn't truncate the
1668         // marker for us, potentially losing metadata in the process).
1669         private static final int MAX_TRACE_METHOD_NAME_LEN = 256;
1670 
1671         public long mStartWallTime; // in System.currentTimeMillis()
1672         public long mStartTime; // in SystemClock.uptimeMillis();
1673         public long mEndTime; // in SystemClock.uptimeMillis();
1674         public String mKind;
1675         public String mSql;
1676         public ArrayList<Object> mBindArgs;
1677         public boolean mFinished;
1678         public Exception mException;
1679         public int mCookie;
1680         public String mPath;
1681         public long mResultLong; // MIN_VALUE means "value not set".
1682         public String mResultString;
1683 
describe(StringBuilder msg, boolean allowDetailedLog)1684         public void describe(StringBuilder msg, boolean allowDetailedLog) {
1685             msg.append(mKind);
1686             if (mFinished) {
1687                 msg.append(" took ").append(mEndTime - mStartTime).append("ms");
1688             } else {
1689                 msg.append(" started ").append(System.currentTimeMillis() - mStartWallTime)
1690                         .append("ms ago");
1691             }
1692             msg.append(" - ").append(getStatus());
1693             if (mSql != null) {
1694                 msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\"");
1695             }
1696             final boolean dumpDetails = allowDetailedLog && NoPreloadHolder.DEBUG_LOG_DETAILED
1697                     && mBindArgs != null && mBindArgs.size() != 0;
1698             if (dumpDetails) {
1699                 msg.append(", bindArgs=[");
1700                 final int count = mBindArgs.size();
1701                 for (int i = 0; i < count; i++) {
1702                     final Object arg = mBindArgs.get(i);
1703                     if (i != 0) {
1704                         msg.append(", ");
1705                     }
1706                     if (arg == null) {
1707                         msg.append("null");
1708                     } else if (arg instanceof byte[]) {
1709                         msg.append("<byte[]>");
1710                     } else if (arg instanceof String) {
1711                         msg.append("\"").append((String)arg).append("\"");
1712                     } else {
1713                         msg.append(arg);
1714                     }
1715                 }
1716                 msg.append("]");
1717             }
1718             msg.append(", path=").append(mPath);
1719             if (mException != null) {
1720                 msg.append(", exception=\"").append(mException.getMessage()).append("\"");
1721             }
1722             if (mResultLong != Long.MIN_VALUE) {
1723                 msg.append(", result=").append(mResultLong);
1724             }
1725             if (mResultString != null) {
1726                 msg.append(", result=\"").append(mResultString).append("\"");
1727             }
1728         }
1729 
getStatus()1730         private String getStatus() {
1731             if (!mFinished) {
1732                 return "running";
1733             }
1734             return mException != null ? "failed" : "succeeded";
1735         }
1736 
getTraceMethodName()1737         private String getTraceMethodName() {
1738             String methodName = mKind + " " + mSql;
1739             if (methodName.length() > MAX_TRACE_METHOD_NAME_LEN)
1740                 return methodName.substring(0, MAX_TRACE_METHOD_NAME_LEN);
1741             return methodName;
1742         }
1743 
1744     }
1745 }
1746