• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         PreparedStatement statement = mPreparedStatementCache.get(sql);
1057         boolean skipCache = false;
1058         if (statement != null) {
1059             if (!statement.mInUse) {
1060                 return statement;
1061             }
1062             // The statement is already in the cache but is in use (this statement appears
1063             // to be not only re-entrant but recursive!).  So prepare a new copy of the
1064             // statement but do not cache it.
1065             skipCache = true;
1066         }
1067 
1068         final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
1069         try {
1070             final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
1071             final int type = DatabaseUtils.getSqlStatementType(sql);
1072             final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
1073             statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly);
1074             if (!skipCache && isCacheable(type)) {
1075                 mPreparedStatementCache.put(sql, statement);
1076                 statement.mInCache = true;
1077             }
1078         } catch (RuntimeException ex) {
1079             // Finalize the statement if an exception occurred and we did not add
1080             // it to the cache.  If it is already in the cache, then leave it there.
1081             if (statement == null || !statement.mInCache) {
1082                 nativeFinalizeStatement(mConnectionPtr, statementPtr);
1083             }
1084             throw ex;
1085         }
1086         statement.mInUse = true;
1087         return statement;
1088     }
1089 
releasePreparedStatement(PreparedStatement statement)1090     private void releasePreparedStatement(PreparedStatement statement) {
1091         statement.mInUse = false;
1092         if (statement.mInCache) {
1093             try {
1094                 nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr);
1095             } catch (SQLiteException ex) {
1096                 // The statement could not be reset due to an error.  Remove it from the cache.
1097                 // When remove() is called, the cache will invoke its entryRemoved() callback,
1098                 // which will in turn call finalizePreparedStatement() to finalize and
1099                 // recycle the statement.
1100                 if (DEBUG) {
1101                     Log.d(TAG, "Could not reset prepared statement due to an exception.  "
1102                             + "Removing it from the cache.  SQL: "
1103                             + trimSqlForDisplay(statement.mSql), ex);
1104                 }
1105 
1106                 mPreparedStatementCache.remove(statement.mSql);
1107             }
1108         } else {
1109             finalizePreparedStatement(statement);
1110         }
1111     }
1112 
finalizePreparedStatement(PreparedStatement statement)1113     private void finalizePreparedStatement(PreparedStatement statement) {
1114         nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr);
1115         recyclePreparedStatement(statement);
1116     }
1117 
attachCancellationSignal(CancellationSignal cancellationSignal)1118     private void attachCancellationSignal(CancellationSignal cancellationSignal) {
1119         if (cancellationSignal != null) {
1120             cancellationSignal.throwIfCanceled();
1121 
1122             mCancellationSignalAttachCount += 1;
1123             if (mCancellationSignalAttachCount == 1) {
1124                 // Reset cancellation flag before executing the statement.
1125                 nativeResetCancel(mConnectionPtr, true /*cancelable*/);
1126 
1127                 // After this point, onCancel() may be called concurrently.
1128                 cancellationSignal.setOnCancelListener(this);
1129             }
1130         }
1131     }
1132 
detachCancellationSignal(CancellationSignal cancellationSignal)1133     private void detachCancellationSignal(CancellationSignal cancellationSignal) {
1134         if (cancellationSignal != null) {
1135             assert mCancellationSignalAttachCount > 0;
1136 
1137             mCancellationSignalAttachCount -= 1;
1138             if (mCancellationSignalAttachCount == 0) {
1139                 // After this point, onCancel() cannot be called concurrently.
1140                 cancellationSignal.setOnCancelListener(null);
1141 
1142                 // Reset cancellation flag after executing the statement.
1143                 nativeResetCancel(mConnectionPtr, false /*cancelable*/);
1144             }
1145         }
1146     }
1147 
1148     // CancellationSignal.OnCancelListener callback.
1149     // This method may be called on a different thread than the executing statement.
1150     // However, it will only be called between calls to attachCancellationSignal and
1151     // detachCancellationSignal, while a statement is executing.  We can safely assume
1152     // that the SQLite connection is still alive.
1153     @Override
onCancel()1154     public void onCancel() {
1155         nativeCancel(mConnectionPtr);
1156     }
1157 
bindArguments(PreparedStatement statement, Object[] bindArgs)1158     private void bindArguments(PreparedStatement statement, Object[] bindArgs) {
1159         final int count = bindArgs != null ? bindArgs.length : 0;
1160         if (count != statement.mNumParameters) {
1161             throw new SQLiteBindOrColumnIndexOutOfRangeException(
1162                     "Expected " + statement.mNumParameters + " bind arguments but "
1163                     + count + " were provided.");
1164         }
1165         if (count == 0) {
1166             return;
1167         }
1168 
1169         final long statementPtr = statement.mStatementPtr;
1170         for (int i = 0; i < count; i++) {
1171             final Object arg = bindArgs[i];
1172             switch (DatabaseUtils.getTypeOfObject(arg)) {
1173                 case Cursor.FIELD_TYPE_NULL:
1174                     nativeBindNull(mConnectionPtr, statementPtr, i + 1);
1175                     break;
1176                 case Cursor.FIELD_TYPE_INTEGER:
1177                     nativeBindLong(mConnectionPtr, statementPtr, i + 1,
1178                             ((Number)arg).longValue());
1179                     break;
1180                 case Cursor.FIELD_TYPE_FLOAT:
1181                     nativeBindDouble(mConnectionPtr, statementPtr, i + 1,
1182                             ((Number)arg).doubleValue());
1183                     break;
1184                 case Cursor.FIELD_TYPE_BLOB:
1185                     nativeBindBlob(mConnectionPtr, statementPtr, i + 1, (byte[])arg);
1186                     break;
1187                 case Cursor.FIELD_TYPE_STRING:
1188                 default:
1189                     if (arg instanceof Boolean) {
1190                         // Provide compatibility with legacy applications which may pass
1191                         // Boolean values in bind args.
1192                         nativeBindLong(mConnectionPtr, statementPtr, i + 1,
1193                                 ((Boolean)arg).booleanValue() ? 1 : 0);
1194                     } else {
1195                         nativeBindString(mConnectionPtr, statementPtr, i + 1, arg.toString());
1196                     }
1197                     break;
1198             }
1199         }
1200     }
1201 
throwIfStatementForbidden(PreparedStatement statement)1202     private void throwIfStatementForbidden(PreparedStatement statement) {
1203         if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) {
1204             throw new SQLiteException("Cannot execute this statement because it "
1205                     + "might modify the database but the connection is read-only.");
1206         }
1207     }
1208 
isCacheable(int statementType)1209     private static boolean isCacheable(int statementType) {
1210         if (statementType == DatabaseUtils.STATEMENT_UPDATE
1211                 || statementType == DatabaseUtils.STATEMENT_SELECT) {
1212             return true;
1213         }
1214         return false;
1215     }
1216 
applyBlockGuardPolicy(PreparedStatement statement)1217     private void applyBlockGuardPolicy(PreparedStatement statement) {
1218         if (!mConfiguration.isInMemoryDb()) {
1219             if (statement.mReadOnly) {
1220                 BlockGuard.getThreadPolicy().onReadFromDisk();
1221             } else {
1222                 BlockGuard.getThreadPolicy().onWriteToDisk();
1223             }
1224         }
1225     }
1226 
1227     /**
1228      * Dumps debugging information about this connection.
1229      *
1230      * @param printer The printer to receive the dump, not null.
1231      * @param verbose True to dump more verbose information.
1232      */
dump(Printer printer, boolean verbose)1233     public void dump(Printer printer, boolean verbose) {
1234         dumpUnsafe(printer, verbose);
1235     }
1236 
1237     /**
1238      * Dumps debugging information about this connection, in the case where the
1239      * caller might not actually own the connection.
1240      *
1241      * This function is written so that it may be called by a thread that does not
1242      * own the connection.  We need to be very careful because the connection state is
1243      * not synchronized.
1244      *
1245      * At worst, the method may return stale or slightly wrong data, however
1246      * it should not crash.  This is ok as it is only used for diagnostic purposes.
1247      *
1248      * @param printer The printer to receive the dump, not null.
1249      * @param verbose True to dump more verbose information.
1250      */
dumpUnsafe(Printer printer, boolean verbose)1251     void dumpUnsafe(Printer printer, boolean verbose) {
1252         printer.println("Connection #" + mConnectionId + ":");
1253         if (verbose) {
1254             printer.println("  connectionPtr: 0x" + Long.toHexString(mConnectionPtr));
1255         }
1256         printer.println("  isPrimaryConnection: " + mIsPrimaryConnection);
1257         printer.println("  onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
1258 
1259         mRecentOperations.dump(printer);
1260 
1261         if (verbose) {
1262             mPreparedStatementCache.dump(printer);
1263         }
1264     }
1265 
1266     /**
1267      * Describes the currently executing operation, in the case where the
1268      * caller might not actually own the connection.
1269      *
1270      * This function is written so that it may be called by a thread that does not
1271      * own the connection.  We need to be very careful because the connection state is
1272      * not synchronized.
1273      *
1274      * At worst, the method may return stale or slightly wrong data, however
1275      * it should not crash.  This is ok as it is only used for diagnostic purposes.
1276      *
1277      * @return A description of the current operation including how long it has been running,
1278      * or null if none.
1279      */
describeCurrentOperationUnsafe()1280     String describeCurrentOperationUnsafe() {
1281         return mRecentOperations.describeCurrentOperation();
1282     }
1283 
1284     /**
1285      * Collects statistics about database connection memory usage.
1286      *
1287      * @param dbStatsList The list to populate.
1288      */
collectDbStats(ArrayList<DbStats> dbStatsList)1289     void collectDbStats(ArrayList<DbStats> dbStatsList) {
1290         // Get information about the main database.
1291         int lookaside = nativeGetDbLookaside(mConnectionPtr);
1292         long pageCount = 0;
1293         long pageSize = 0;
1294         try {
1295             pageCount = executeForLong("PRAGMA page_count;", null, null);
1296             pageSize = executeForLong("PRAGMA page_size;", null, null);
1297         } catch (SQLiteException ex) {
1298             // Ignore.
1299         }
1300         dbStatsList.add(getMainDbStatsUnsafe(lookaside, pageCount, pageSize));
1301 
1302         // Get information about attached databases.
1303         // We ignore the first row in the database list because it corresponds to
1304         // the main database which we have already described.
1305         CursorWindow window = new CursorWindow("collectDbStats");
1306         try {
1307             executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null);
1308             for (int i = 1; i < window.getNumRows(); i++) {
1309                 String name = window.getString(i, 1);
1310                 String path = window.getString(i, 2);
1311                 pageCount = 0;
1312                 pageSize = 0;
1313                 try {
1314                     pageCount = executeForLong("PRAGMA " + name + ".page_count;", null, null);
1315                     pageSize = executeForLong("PRAGMA " + name + ".page_size;", null, null);
1316                 } catch (SQLiteException ex) {
1317                     // Ignore.
1318                 }
1319                 StringBuilder label = new StringBuilder("  (attached) ").append(name);
1320                 if (!path.isEmpty()) {
1321                     label.append(": ").append(path);
1322                 }
1323                 dbStatsList.add(new DbStats(label.toString(), pageCount, pageSize, 0, 0, 0, 0));
1324             }
1325         } catch (SQLiteException ex) {
1326             // Ignore.
1327         } finally {
1328             window.close();
1329         }
1330     }
1331 
1332     /**
1333      * Collects statistics about database connection memory usage, in the case where the
1334      * caller might not actually own the connection.
1335      *
1336      * @return The statistics object, never null.
1337      */
collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList)1338     void collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList) {
1339         dbStatsList.add(getMainDbStatsUnsafe(0, 0, 0));
1340     }
1341 
getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize)1342     private DbStats getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize) {
1343         // The prepared statement cache is thread-safe so we can access its statistics
1344         // even if we do not own the database connection.
1345         String label;
1346         if (mIsPrimaryConnection) {
1347             label = mConfiguration.path;
1348         } else {
1349             label = mConfiguration.path + " (" + mConnectionId + ")";
1350         }
1351         return new DbStats(label, pageCount, pageSize, lookaside,
1352                 mPreparedStatementCache.hitCount(),
1353                 mPreparedStatementCache.missCount(),
1354                 mPreparedStatementCache.size());
1355     }
1356 
1357     @Override
toString()1358     public String toString() {
1359         return "SQLiteConnection: " + mConfiguration.path + " (" + mConnectionId + ")";
1360     }
1361 
obtainPreparedStatement(String sql, long statementPtr, int numParameters, int type, boolean readOnly)1362     private PreparedStatement obtainPreparedStatement(String sql, long statementPtr,
1363             int numParameters, int type, boolean readOnly) {
1364         PreparedStatement statement = mPreparedStatementPool;
1365         if (statement != null) {
1366             mPreparedStatementPool = statement.mPoolNext;
1367             statement.mPoolNext = null;
1368             statement.mInCache = false;
1369         } else {
1370             statement = new PreparedStatement();
1371         }
1372         statement.mSql = sql;
1373         statement.mStatementPtr = statementPtr;
1374         statement.mNumParameters = numParameters;
1375         statement.mType = type;
1376         statement.mReadOnly = readOnly;
1377         return statement;
1378     }
1379 
recyclePreparedStatement(PreparedStatement statement)1380     private void recyclePreparedStatement(PreparedStatement statement) {
1381         statement.mSql = null;
1382         statement.mPoolNext = mPreparedStatementPool;
1383         mPreparedStatementPool = statement;
1384     }
1385 
trimSqlForDisplay(String sql)1386     private static String trimSqlForDisplay(String sql) {
1387         // Note: Creating and caching a regular expression is expensive at preload-time
1388         //       and stops compile-time initialization. This pattern is only used when
1389         //       dumping the connection, which is a rare (mainly error) case. So:
1390         //       DO NOT CACHE.
1391         return sql.replaceAll("[\\s]*\\n+[\\s]*", " ");
1392     }
1393 
1394     /**
1395      * Holder type for a prepared statement.
1396      *
1397      * Although this object holds a pointer to a native statement object, it
1398      * does not have a finalizer.  This is deliberate.  The {@link SQLiteConnection}
1399      * owns the statement object and will take care of freeing it when needed.
1400      * In particular, closing the connection requires a guarantee of deterministic
1401      * resource disposal because all native statement objects must be freed before
1402      * the native database object can be closed.  So no finalizers here.
1403      */
1404     private static final class PreparedStatement {
1405         // Next item in pool.
1406         public PreparedStatement mPoolNext;
1407 
1408         // The SQL from which the statement was prepared.
1409         public String mSql;
1410 
1411         // The native sqlite3_stmt object pointer.
1412         // Lifetime is managed explicitly by the connection.
1413         public long mStatementPtr;
1414 
1415         // The number of parameters that the prepared statement has.
1416         public int mNumParameters;
1417 
1418         // The statement type.
1419         public int mType;
1420 
1421         // True if the statement is read-only.
1422         public boolean mReadOnly;
1423 
1424         // True if the statement is in the cache.
1425         public boolean mInCache;
1426 
1427         // True if the statement is in use (currently executing).
1428         // We need this flag because due to the use of custom functions in triggers, it's
1429         // possible for SQLite calls to be re-entrant.  Consequently we need to prevent
1430         // in use statements from being finalized until they are no longer in use.
1431         public boolean mInUse;
1432     }
1433 
1434     private final class PreparedStatementCache
1435             extends LruCache<String, PreparedStatement> {
PreparedStatementCache(int size)1436         public PreparedStatementCache(int size) {
1437             super(size);
1438         }
1439 
1440         @Override
entryRemoved(boolean evicted, String key, PreparedStatement oldValue, PreparedStatement newValue)1441         protected void entryRemoved(boolean evicted, String key,
1442                 PreparedStatement oldValue, PreparedStatement newValue) {
1443             oldValue.mInCache = false;
1444             if (!oldValue.mInUse) {
1445                 finalizePreparedStatement(oldValue);
1446             }
1447         }
1448 
dump(Printer printer)1449         public void dump(Printer printer) {
1450             printer.println("  Prepared statement cache:");
1451             Map<String, PreparedStatement> cache = snapshot();
1452             if (!cache.isEmpty()) {
1453                 int i = 0;
1454                 for (Map.Entry<String, PreparedStatement> entry : cache.entrySet()) {
1455                     PreparedStatement statement = entry.getValue();
1456                     if (statement.mInCache) { // might be false due to a race with entryRemoved
1457                         String sql = entry.getKey();
1458                         printer.println("    " + i + ": statementPtr=0x"
1459                                 + Long.toHexString(statement.mStatementPtr)
1460                                 + ", numParameters=" + statement.mNumParameters
1461                                 + ", type=" + statement.mType
1462                                 + ", readOnly=" + statement.mReadOnly
1463                                 + ", sql=\"" + trimSqlForDisplay(sql) + "\"");
1464                     }
1465                     i += 1;
1466                 }
1467             } else {
1468                 printer.println("    <none>");
1469             }
1470         }
1471     }
1472 
1473     private static final class OperationLog {
1474         private static final int MAX_RECENT_OPERATIONS = 20;
1475         private static final int COOKIE_GENERATION_SHIFT = 8;
1476         private static final int COOKIE_INDEX_MASK = 0xff;
1477 
1478         private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
1479         private int mIndex;
1480         private int mGeneration;
1481         private final SQLiteConnectionPool mPool;
1482         private long mResultLong = Long.MIN_VALUE;
1483         private String mResultString;
1484 
OperationLog(SQLiteConnectionPool pool)1485         OperationLog(SQLiteConnectionPool pool) {
1486             mPool = pool;
1487         }
1488 
beginOperation(String kind, String sql, Object[] bindArgs)1489         public int beginOperation(String kind, String sql, Object[] bindArgs) {
1490             mResultLong = Long.MIN_VALUE;
1491             mResultString = null;
1492 
1493             synchronized (mOperations) {
1494                 final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
1495                 Operation operation = mOperations[index];
1496                 if (operation == null) {
1497                     operation = new Operation();
1498                     mOperations[index] = operation;
1499                 } else {
1500                     operation.mFinished = false;
1501                     operation.mException = null;
1502                     if (operation.mBindArgs != null) {
1503                         operation.mBindArgs.clear();
1504                     }
1505                 }
1506                 operation.mStartWallTime = System.currentTimeMillis();
1507                 operation.mStartTime = SystemClock.uptimeMillis();
1508                 operation.mKind = kind;
1509                 operation.mSql = sql;
1510                 operation.mPath = mPool.getPath();
1511                 operation.mResultLong = Long.MIN_VALUE;
1512                 operation.mResultString = null;
1513                 if (bindArgs != null) {
1514                     if (operation.mBindArgs == null) {
1515                         operation.mBindArgs = new ArrayList<Object>();
1516                     } else {
1517                         operation.mBindArgs.clear();
1518                     }
1519                     for (int i = 0; i < bindArgs.length; i++) {
1520                         final Object arg = bindArgs[i];
1521                         if (arg != null && arg instanceof byte[]) {
1522                             // Don't hold onto the real byte array longer than necessary.
1523                             operation.mBindArgs.add(EMPTY_BYTE_ARRAY);
1524                         } else {
1525                             operation.mBindArgs.add(arg);
1526                         }
1527                     }
1528                 }
1529                 operation.mCookie = newOperationCookieLocked(index);
1530                 if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
1531                     Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
1532                             operation.mCookie);
1533                 }
1534                 mIndex = index;
1535                 return operation.mCookie;
1536             }
1537         }
1538 
failOperation(int cookie, Exception ex)1539         public void failOperation(int cookie, Exception ex) {
1540             synchronized (mOperations) {
1541                 final Operation operation = getOperationLocked(cookie);
1542                 if (operation != null) {
1543                     operation.mException = ex;
1544                 }
1545             }
1546         }
1547 
endOperation(int cookie)1548         public void endOperation(int cookie) {
1549             synchronized (mOperations) {
1550                 if (endOperationDeferLogLocked(cookie)) {
1551                     logOperationLocked(cookie, null);
1552                 }
1553             }
1554         }
1555 
endOperationDeferLog(int cookie)1556         public boolean endOperationDeferLog(int cookie) {
1557             synchronized (mOperations) {
1558                 return endOperationDeferLogLocked(cookie);
1559             }
1560         }
1561 
logOperation(int cookie, String detail)1562         public void logOperation(int cookie, String detail) {
1563             synchronized (mOperations) {
1564                 logOperationLocked(cookie, detail);
1565             }
1566         }
1567 
setResult(long longResult)1568         public void setResult(long longResult) {
1569             mResultLong = longResult;
1570         }
1571 
setResult(String stringResult)1572         public void setResult(String stringResult) {
1573             mResultString = stringResult;
1574         }
1575 
endOperationDeferLogLocked(int cookie)1576         private boolean endOperationDeferLogLocked(int cookie) {
1577             final Operation operation = getOperationLocked(cookie);
1578             if (operation != null) {
1579                 if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
1580                     Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
1581                             operation.mCookie);
1582                 }
1583                 operation.mEndTime = SystemClock.uptimeMillis();
1584                 operation.mFinished = true;
1585                 final long execTime = operation.mEndTime - operation.mStartTime;
1586                 mPool.onStatementExecuted(execTime);
1587                 return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
1588                         execTime);
1589             }
1590             return false;
1591         }
1592 
logOperationLocked(int cookie, String detail)1593         private void logOperationLocked(int cookie, String detail) {
1594             final Operation operation = getOperationLocked(cookie);
1595             operation.mResultLong = mResultLong;
1596             operation.mResultString = mResultString;
1597             StringBuilder msg = new StringBuilder();
1598             operation.describe(msg, true);
1599             if (detail != null) {
1600                 msg.append(", ").append(detail);
1601             }
1602             Log.d(TAG, msg.toString());
1603         }
1604 
newOperationCookieLocked(int index)1605         private int newOperationCookieLocked(int index) {
1606             final int generation = mGeneration++;
1607             return generation << COOKIE_GENERATION_SHIFT | index;
1608         }
1609 
getOperationLocked(int cookie)1610         private Operation getOperationLocked(int cookie) {
1611             final int index = cookie & COOKIE_INDEX_MASK;
1612             final Operation operation = mOperations[index];
1613             return operation.mCookie == cookie ? operation : null;
1614         }
1615 
describeCurrentOperation()1616         public String describeCurrentOperation() {
1617             synchronized (mOperations) {
1618                 final Operation operation = mOperations[mIndex];
1619                 if (operation != null && !operation.mFinished) {
1620                     StringBuilder msg = new StringBuilder();
1621                     operation.describe(msg, false);
1622                     return msg.toString();
1623                 }
1624                 return null;
1625             }
1626         }
1627 
dump(Printer printer)1628         public void dump(Printer printer) {
1629             synchronized (mOperations) {
1630                 printer.println("  Most recently executed operations:");
1631                 int index = mIndex;
1632                 Operation operation = mOperations[index];
1633                 if (operation != null) {
1634                     // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created,
1635                     // and is relatively expensive to create during preloading. This method is only
1636                     // used when dumping a connection, which is a rare (mainly error) case.
1637                     SimpleDateFormat opDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
1638                     int n = 0;
1639                     do {
1640                         StringBuilder msg = new StringBuilder();
1641                         msg.append("    ").append(n).append(": [");
1642                         String formattedStartTime = opDF.format(new Date(operation.mStartWallTime));
1643                         msg.append(formattedStartTime);
1644                         msg.append("] ");
1645                         operation.describe(msg, false); // Never dump bingargs in a bugreport
1646                         printer.println(msg.toString());
1647 
1648                         if (index > 0) {
1649                             index -= 1;
1650                         } else {
1651                             index = MAX_RECENT_OPERATIONS - 1;
1652                         }
1653                         n += 1;
1654                         operation = mOperations[index];
1655                     } while (operation != null && n < MAX_RECENT_OPERATIONS);
1656                 } else {
1657                     printer.println("    <none>");
1658                 }
1659             }
1660         }
1661     }
1662 
1663     private static final class Operation {
1664         // Trim all SQL statements to 256 characters inside the trace marker.
1665         // This limit gives plenty of context while leaving space for other
1666         // entries in the trace buffer (and ensures atrace doesn't truncate the
1667         // marker for us, potentially losing metadata in the process).
1668         private static final int MAX_TRACE_METHOD_NAME_LEN = 256;
1669 
1670         public long mStartWallTime; // in System.currentTimeMillis()
1671         public long mStartTime; // in SystemClock.uptimeMillis();
1672         public long mEndTime; // in SystemClock.uptimeMillis();
1673         public String mKind;
1674         public String mSql;
1675         public ArrayList<Object> mBindArgs;
1676         public boolean mFinished;
1677         public Exception mException;
1678         public int mCookie;
1679         public String mPath;
1680         public long mResultLong; // MIN_VALUE means "value not set".
1681         public String mResultString;
1682 
describe(StringBuilder msg, boolean allowDetailedLog)1683         public void describe(StringBuilder msg, boolean allowDetailedLog) {
1684             msg.append(mKind);
1685             if (mFinished) {
1686                 msg.append(" took ").append(mEndTime - mStartTime).append("ms");
1687             } else {
1688                 msg.append(" started ").append(System.currentTimeMillis() - mStartWallTime)
1689                         .append("ms ago");
1690             }
1691             msg.append(" - ").append(getStatus());
1692             if (mSql != null) {
1693                 msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\"");
1694             }
1695             final boolean dumpDetails = allowDetailedLog && NoPreloadHolder.DEBUG_LOG_DETAILED
1696                     && mBindArgs != null && mBindArgs.size() != 0;
1697             if (dumpDetails) {
1698                 msg.append(", bindArgs=[");
1699                 final int count = mBindArgs.size();
1700                 for (int i = 0; i < count; i++) {
1701                     final Object arg = mBindArgs.get(i);
1702                     if (i != 0) {
1703                         msg.append(", ");
1704                     }
1705                     if (arg == null) {
1706                         msg.append("null");
1707                     } else if (arg instanceof byte[]) {
1708                         msg.append("<byte[]>");
1709                     } else if (arg instanceof String) {
1710                         msg.append("\"").append((String)arg).append("\"");
1711                     } else {
1712                         msg.append(arg);
1713                     }
1714                 }
1715                 msg.append("]");
1716             }
1717             msg.append(", path=").append(mPath);
1718             if (mException != null) {
1719                 msg.append(", exception=\"").append(mException.getMessage()).append("\"");
1720             }
1721             if (mResultLong != Long.MIN_VALUE) {
1722                 msg.append(", result=").append(mResultLong);
1723             }
1724             if (mResultString != null) {
1725                 msg.append(", result=\"").append(mResultString).append("\"");
1726             }
1727         }
1728 
getStatus()1729         private String getStatus() {
1730             if (!mFinished) {
1731                 return "running";
1732             }
1733             return mException != null ? "failed" : "succeeded";
1734         }
1735 
getTraceMethodName()1736         private String getTraceMethodName() {
1737             String methodName = mKind + " " + mSql;
1738             if (methodName.length() > MAX_TRACE_METHOD_NAME_LEN)
1739                 return methodName.substring(0, MAX_TRACE_METHOD_NAME_LEN);
1740             return methodName;
1741         }
1742 
1743     }
1744 }
1745