Merge changes from topic "database-improvements"
* changes: Make slow queries logs show only when log tag enabled Remove old and unnecessary log Add some tests for database operations Add support for normal sync mode and propagate journalMode and syncMode on database open Add total execution time and statements executed to dumpsys dbinfo
This commit is contained in:
committed by
Android (Google) Code Review
commit
eafe89b200
@@ -24,19 +24,17 @@ import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.perftests.utils.BenchmarkState;
|
||||
import android.perftests.utils.PerfStatusReporter;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Random;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Performance tests for typical CRUD operations and loading rows into the Cursor
|
||||
*
|
||||
@@ -58,12 +56,9 @@ public class SQLiteDatabasePerfTest {
|
||||
public void setUp() {
|
||||
mContext = InstrumentationRegistry.getTargetContext();
|
||||
mContext.deleteDatabase(DB_NAME);
|
||||
mDatabase = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
|
||||
mDatabase.execSQL("CREATE TABLE T1 "
|
||||
+ "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)");
|
||||
mDatabase.execSQL("CREATE TABLE T2 ("
|
||||
+ "_ID INTEGER PRIMARY KEY, COL_A VARCHAR(100), T1_ID INTEGER,"
|
||||
+ "FOREIGN KEY(T1_ID) REFERENCES T1 (_ID))");
|
||||
|
||||
createOrOpenTestDatabase(
|
||||
SQLiteDatabase.JOURNAL_MODE_TRUNCATE, SQLiteDatabase.SYNC_MODE_FULL);
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -72,6 +67,25 @@ public class SQLiteDatabasePerfTest {
|
||||
mContext.deleteDatabase(DB_NAME);
|
||||
}
|
||||
|
||||
private void createOrOpenTestDatabase(String journalMode, String syncMode) {
|
||||
SQLiteDatabase.OpenParams.Builder paramsBuilder = new SQLiteDatabase.OpenParams.Builder();
|
||||
File dbFile = mContext.getDatabasePath(DB_NAME);
|
||||
if (journalMode != null) {
|
||||
paramsBuilder.setJournalMode(journalMode);
|
||||
}
|
||||
if (syncMode != null) {
|
||||
paramsBuilder.setSynchronousMode(syncMode);
|
||||
}
|
||||
paramsBuilder.addOpenFlags(SQLiteDatabase.CREATE_IF_NECESSARY);
|
||||
|
||||
mDatabase = SQLiteDatabase.openDatabase(dbFile, paramsBuilder.build());
|
||||
mDatabase.execSQL("CREATE TABLE T1 "
|
||||
+ "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)");
|
||||
mDatabase.execSQL("CREATE TABLE T2 ("
|
||||
+ "_ID INTEGER PRIMARY KEY, COL_A VARCHAR(100), T1_ID INTEGER,"
|
||||
+ "FOREIGN KEY(T1_ID) REFERENCES T1 (_ID))");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelect() {
|
||||
insertT1TestDataSet();
|
||||
@@ -192,22 +206,114 @@ public class SQLiteDatabasePerfTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This test measures the insertion of a single row into a database using DELETE journal and
|
||||
* synchronous modes.
|
||||
*/
|
||||
@Test
|
||||
public void testInsert() {
|
||||
insertT1TestDataSet();
|
||||
|
||||
testInsertInternal("testInsert");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertWithPersistFull() {
|
||||
recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_PERSIST, SQLiteDatabase.SYNC_MODE_FULL);
|
||||
insertT1TestDataSet();
|
||||
testInsertInternal("testInsertWithPersistFull");
|
||||
}
|
||||
|
||||
private void testInsertInternal(String traceTag) {
|
||||
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put("_ID", DEFAULT_DATASET_SIZE);
|
||||
cv.put("COL_B", "NewValue");
|
||||
cv.put("COL_C", 1.1);
|
||||
String[] deleteArgs = new String[]{String.valueOf(DEFAULT_DATASET_SIZE)};
|
||||
String[] deleteArgs = new String[] {String.valueOf(DEFAULT_DATASET_SIZE)};
|
||||
|
||||
while (state.keepRunning()) {
|
||||
android.os.Trace.beginSection(traceTag);
|
||||
assertEquals(DEFAULT_DATASET_SIZE, mDatabase.insert("T1", null, cv));
|
||||
state.pauseTiming();
|
||||
assertEquals(1, mDatabase.delete("T1", "_ID=?", deleteArgs));
|
||||
state.resumeTiming();
|
||||
android.os.Trace.endSection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This test measures the insertion of a single row into a database using WAL journal mode and
|
||||
* NORMAL synchronous mode.
|
||||
*/
|
||||
@Test
|
||||
public void testInsertWithWalNormalMode() {
|
||||
recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_NORMAL);
|
||||
insertT1TestDataSet();
|
||||
|
||||
testInsertInternal("testInsertWithWalNormalMode");
|
||||
}
|
||||
|
||||
/**
|
||||
* This test measures the insertion of a single row into a database using WAL journal mode and
|
||||
* FULL synchronous mode. The goal is to see the difference between NORMAL vs FULL sync modes.
|
||||
*/
|
||||
@Test
|
||||
public void testInsertWithWalFullMode() {
|
||||
recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_FULL);
|
||||
|
||||
insertT1TestDataSet();
|
||||
|
||||
testInsertInternal("testInsertWithWalFullMode");
|
||||
}
|
||||
|
||||
/**
|
||||
* This test measures the insertion of a multiple rows in a single transaction using WAL journal
|
||||
* mode and NORMAL synchronous mode.
|
||||
*/
|
||||
@Test
|
||||
public void testBulkInsertWithWalNormalMode() {
|
||||
recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_NORMAL);
|
||||
testBulkInsertInternal("testBulkInsertWithWalNormalMode");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBulkInsertWithPersistFull() {
|
||||
recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_PERSIST, SQLiteDatabase.SYNC_MODE_FULL);
|
||||
testBulkInsertInternal("testBulkInsertWithPersistFull");
|
||||
}
|
||||
|
||||
/**
|
||||
* This test measures the insertion of a multiple rows in a single transaction using TRUNCATE
|
||||
* journal mode and FULL synchronous mode.
|
||||
*/
|
||||
@Test
|
||||
public void testBulkInsert() {
|
||||
testBulkInsertInternal("testBulkInsert");
|
||||
}
|
||||
|
||||
private void testBulkInsertInternal(String traceTag) {
|
||||
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
|
||||
String[] statements = new String[DEFAULT_DATASET_SIZE];
|
||||
for (int i = 0; i < DEFAULT_DATASET_SIZE; ++i) {
|
||||
statements[i] = "INSERT INTO T1 VALUES (?,?,?,?)";
|
||||
}
|
||||
|
||||
while (state.keepRunning()) {
|
||||
android.os.Trace.beginSection(traceTag);
|
||||
mDatabase.beginTransaction();
|
||||
for (int i = 0; i < DEFAULT_DATASET_SIZE; ++i) {
|
||||
mDatabase.execSQL(statements[i], new Object[] {i, i, "T1Value" + i, i * 1.1});
|
||||
}
|
||||
mDatabase.setTransactionSuccessful();
|
||||
mDatabase.endTransaction();
|
||||
android.os.Trace.endSection();
|
||||
|
||||
state.pauseTiming();
|
||||
mDatabase.execSQL("DELETE FROM T1");
|
||||
state.resumeTiming();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,9 +333,30 @@ public class SQLiteDatabasePerfTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This test measures the update of a random row in a database.
|
||||
*/
|
||||
@Test
|
||||
public void testUpdateWithWalNormalMode() {
|
||||
recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_NORMAL);
|
||||
insertT1TestDataSet();
|
||||
testUpdateInternal("testUpdateWithWalNormalMode");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateWithPersistFull() {
|
||||
recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_PERSIST, SQLiteDatabase.SYNC_MODE_FULL);
|
||||
insertT1TestDataSet();
|
||||
testUpdateInternal("testUpdateWithPersistFull");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdate() {
|
||||
insertT1TestDataSet();
|
||||
testUpdateInternal("testUpdate");
|
||||
}
|
||||
|
||||
private void testUpdateInternal(String traceTag) {
|
||||
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
|
||||
Random rnd = new Random(0);
|
||||
@@ -237,6 +364,7 @@ public class SQLiteDatabasePerfTest {
|
||||
ContentValues cv = new ContentValues();
|
||||
String[] argArray = new String[1];
|
||||
while (state.keepRunning()) {
|
||||
android.os.Trace.beginSection(traceTag);
|
||||
int id = rnd.nextInt(DEFAULT_DATASET_SIZE);
|
||||
cv.put("COL_A", i);
|
||||
cv.put("COL_B", "UpdatedValue");
|
||||
@@ -244,6 +372,109 @@ public class SQLiteDatabasePerfTest {
|
||||
argArray[0] = String.valueOf(id);
|
||||
assertEquals(1, mDatabase.update("T1", cv, "_ID=?", argArray));
|
||||
i++;
|
||||
android.os.Trace.endSection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This test measures a multi-threaded read-write environment where there are 2 readers and
|
||||
* 1 writer in the database using TRUNCATE journal mode and FULL syncMode.
|
||||
*/
|
||||
@Test
|
||||
public void testMultithreadedReadWrite() {
|
||||
insertT1TestDataSet();
|
||||
performMultithreadedReadWriteTest();
|
||||
}
|
||||
|
||||
private void doReadLoop(int totalIterations) {
|
||||
Random rnd = new Random(0);
|
||||
int currentIteration = 0;
|
||||
while (currentIteration < totalIterations) {
|
||||
android.os.Trace.beginSection("ReadDatabase");
|
||||
int index = rnd.nextInt(DEFAULT_DATASET_SIZE);
|
||||
try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 "
|
||||
+ "WHERE _ID=?",
|
||||
new String[] {String.valueOf(index)})) {
|
||||
cursor.moveToNext();
|
||||
cursor.getInt(0);
|
||||
cursor.getInt(1);
|
||||
cursor.getString(2);
|
||||
cursor.getDouble(3);
|
||||
}
|
||||
++currentIteration;
|
||||
android.os.Trace.endSection();
|
||||
}
|
||||
}
|
||||
|
||||
private void doReadLoop(BenchmarkState state) {
|
||||
Random rnd = new Random(0);
|
||||
while (state.keepRunning()) {
|
||||
android.os.Trace.beginSection("ReadDatabase");
|
||||
int index = rnd.nextInt(DEFAULT_DATASET_SIZE);
|
||||
try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 "
|
||||
+ "WHERE _ID=?",
|
||||
new String[] {String.valueOf(index)})) {
|
||||
cursor.moveToNext();
|
||||
cursor.getInt(0);
|
||||
cursor.getInt(1);
|
||||
cursor.getString(2);
|
||||
cursor.getDouble(3);
|
||||
}
|
||||
android.os.Trace.endSection();
|
||||
}
|
||||
}
|
||||
|
||||
private void doUpdateLoop(int totalIterations) {
|
||||
SQLiteDatabase db = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
|
||||
Random rnd = new Random(0);
|
||||
int i = 0;
|
||||
ContentValues cv = new ContentValues();
|
||||
String[] argArray = new String[1];
|
||||
|
||||
while (i < totalIterations) {
|
||||
android.os.Trace.beginSection("UpdateDatabase");
|
||||
int id = rnd.nextInt(DEFAULT_DATASET_SIZE);
|
||||
cv.put("COL_A", i);
|
||||
cv.put("COL_B", "UpdatedValue");
|
||||
cv.put("COL_C", i);
|
||||
argArray[0] = String.valueOf(id);
|
||||
db.update("T1", cv, "_ID=?", argArray);
|
||||
i++;
|
||||
android.os.Trace.endSection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This test measures a multi-threaded read-write environment where there are 2 readers and
|
||||
* 1 writer in the database using WAL journal mode and NORMAL syncMode.
|
||||
*/
|
||||
@Test
|
||||
public void testMultithreadedReadWriteWithWalNormal() {
|
||||
recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_NORMAL);
|
||||
insertT1TestDataSet();
|
||||
|
||||
performMultithreadedReadWriteTest();
|
||||
}
|
||||
|
||||
private void performMultithreadedReadWriteTest() {
|
||||
int totalBGIterations = 10000;
|
||||
// Writer - Fixed iterations to avoid consuming cycles from mainloop benchmark iterations
|
||||
Thread updateThread = new Thread(() -> { doUpdateLoop(totalBGIterations); });
|
||||
|
||||
// Reader 1 - Fixed iterations to avoid consuming cycles from mainloop benchmark iterations
|
||||
Thread readerThread = new Thread(() -> { doReadLoop(totalBGIterations); });
|
||||
|
||||
updateThread.start();
|
||||
readerThread.start();
|
||||
|
||||
// Reader 2
|
||||
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
doReadLoop(state);
|
||||
|
||||
try {
|
||||
updateThread.join();
|
||||
readerThread.join();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,5 +501,11 @@ public class SQLiteDatabasePerfTest {
|
||||
mDatabase.setTransactionSuccessful();
|
||||
mDatabase.endTransaction();
|
||||
}
|
||||
|
||||
private void recreateTestDatabase(String journalMode, String syncMode) {
|
||||
mDatabase.close();
|
||||
mContext.deleteDatabase(DB_NAME);
|
||||
createOrOpenTestDatabase(journalMode, syncMode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14376,11 +14376,21 @@ package android.database.sqlite {
|
||||
field public static final int CONFLICT_ROLLBACK = 1; // 0x1
|
||||
field public static final int CREATE_IF_NECESSARY = 268435456; // 0x10000000
|
||||
field public static final int ENABLE_WRITE_AHEAD_LOGGING = 536870912; // 0x20000000
|
||||
field public static final String JOURNAL_MODE_DELETE = "DELETE";
|
||||
field public static final String JOURNAL_MODE_MEMORY = "MEMORY";
|
||||
field public static final String JOURNAL_MODE_OFF = "OFF";
|
||||
field public static final String JOURNAL_MODE_PERSIST = "PERSIST";
|
||||
field public static final String JOURNAL_MODE_TRUNCATE = "TRUNCATE";
|
||||
field public static final String JOURNAL_MODE_WAL = "WAL";
|
||||
field public static final int MAX_SQL_CACHE_SIZE = 100; // 0x64
|
||||
field public static final int NO_LOCALIZED_COLLATORS = 16; // 0x10
|
||||
field public static final int OPEN_READONLY = 1; // 0x1
|
||||
field public static final int OPEN_READWRITE = 0; // 0x0
|
||||
field public static final int SQLITE_MAX_LIKE_PATTERN_LENGTH = 50000; // 0xc350
|
||||
field public static final String SYNC_MODE_EXTRA = "EXTRA";
|
||||
field public static final String SYNC_MODE_FULL = "FULL";
|
||||
field public static final String SYNC_MODE_NORMAL = "NORMAL";
|
||||
field public static final String SYNC_MODE_OFF = "OFF";
|
||||
}
|
||||
|
||||
public static interface SQLiteDatabase.CursorFactory {
|
||||
|
||||
@@ -143,7 +143,6 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
|
||||
throw new AssertionError(); // Not possible, the native code won't return it.
|
||||
}
|
||||
mCloseGuard.open("close");
|
||||
recordNewWindow(Binder.getCallingPid(), mWindowPtr);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,7 +190,6 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
|
||||
mCloseGuard.close();
|
||||
}
|
||||
if (mWindowPtr != 0) {
|
||||
recordClosingOfWindow(mWindowPtr);
|
||||
nativeDispose(mWindowPtr);
|
||||
mWindowPtr = 0;
|
||||
}
|
||||
@@ -746,64 +744,6 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
|
||||
dispose();
|
||||
}
|
||||
|
||||
@UnsupportedAppUsage
|
||||
private static final LongSparseArray<Integer> sWindowToPidMap = new LongSparseArray<Integer>();
|
||||
|
||||
private void recordNewWindow(int pid, long window) {
|
||||
synchronized (sWindowToPidMap) {
|
||||
sWindowToPidMap.put(window, pid);
|
||||
if (Log.isLoggable(STATS_TAG, Log.VERBOSE)) {
|
||||
Log.i(STATS_TAG, "Created a new Cursor. " + printStats());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void recordClosingOfWindow(long window) {
|
||||
synchronized (sWindowToPidMap) {
|
||||
if (sWindowToPidMap.size() == 0) {
|
||||
// this means we are not in the ContentProvider.
|
||||
return;
|
||||
}
|
||||
sWindowToPidMap.delete(window);
|
||||
}
|
||||
}
|
||||
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
private String printStats() {
|
||||
StringBuilder buff = new StringBuilder();
|
||||
int myPid = Process.myPid();
|
||||
int total = 0;
|
||||
SparseIntArray pidCounts = new SparseIntArray();
|
||||
synchronized (sWindowToPidMap) {
|
||||
int size = sWindowToPidMap.size();
|
||||
if (size == 0) {
|
||||
// this means we are not in the ContentProvider.
|
||||
return "";
|
||||
}
|
||||
for (int indx = 0; indx < size; indx++) {
|
||||
int pid = sWindowToPidMap.valueAt(indx);
|
||||
int value = pidCounts.get(pid);
|
||||
pidCounts.put(pid, ++value);
|
||||
}
|
||||
}
|
||||
int numPids = pidCounts.size();
|
||||
for (int i = 0; i < numPids;i++) {
|
||||
buff.append(" (# cursors opened by ");
|
||||
int pid = pidCounts.keyAt(i);
|
||||
if (pid == myPid) {
|
||||
buff.append("this proc=");
|
||||
} else {
|
||||
buff.append("pid ").append(pid).append('=');
|
||||
}
|
||||
int num = pidCounts.get(pid);
|
||||
buff.append(num).append(')');
|
||||
total += num;
|
||||
}
|
||||
// limit the returned string size to 1000
|
||||
String s = (buff.length() > 980) ? buff.substring(0, 980) : buff.toString();
|
||||
return "# Open Cursors=" + total + s;
|
||||
}
|
||||
|
||||
private static int getCursorWindowSize() {
|
||||
if (sCursorWindowSize < 0) {
|
||||
// The cursor window size. resource xml file specifies the value in kB.
|
||||
|
||||
@@ -26,14 +26,13 @@ import android.os.OperationCanceledException;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.SystemClock;
|
||||
import android.os.Trace;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.LruCache;
|
||||
import android.util.Pair;
|
||||
import android.util.Printer;
|
||||
|
||||
import dalvik.system.BlockGuard;
|
||||
import dalvik.system.CloseGuard;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystems;
|
||||
@@ -177,7 +176,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
|
||||
mConfiguration = new SQLiteDatabaseConfiguration(configuration);
|
||||
mConnectionId = connectionId;
|
||||
mIsPrimaryConnection = primaryConnection;
|
||||
mIsReadOnlyConnection = (configuration.openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
|
||||
mIsReadOnlyConnection = mConfiguration.isReadOnlyDatabase();
|
||||
mPreparedStatementCache = new PreparedStatementCache(
|
||||
mConfiguration.maxSqlCacheSize);
|
||||
mCloseGuard.open("close");
|
||||
@@ -266,7 +265,8 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
|
||||
}
|
||||
setPageSize();
|
||||
setForeignKeyModeFromConfiguration();
|
||||
setWalModeFromConfiguration();
|
||||
setJournalFromConfiguration();
|
||||
setSyncModeFromConfiguration();
|
||||
setJournalSizeLimit();
|
||||
setAutoCheckpointInterval();
|
||||
setLocaleFromConfiguration();
|
||||
@@ -334,30 +334,19 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
|
||||
}
|
||||
}
|
||||
|
||||
private void setWalModeFromConfiguration() {
|
||||
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
|
||||
final boolean walEnabled =
|
||||
(mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
|
||||
// Use compatibility WAL unless an app explicitly set journal/synchronous mode
|
||||
// or DISABLE_COMPATIBILITY_WAL flag is set
|
||||
final boolean isCompatibilityWalEnabled =
|
||||
mConfiguration.isLegacyCompatibilityWalEnabled();
|
||||
if (walEnabled || isCompatibilityWalEnabled) {
|
||||
setJournalMode("WAL");
|
||||
if (mConfiguration.syncMode != null) {
|
||||
setSyncMode(mConfiguration.syncMode);
|
||||
} else if (isCompatibilityWalEnabled) {
|
||||
setSyncMode(SQLiteCompatibilityWalFlags.getWALSyncMode());
|
||||
} else {
|
||||
setSyncMode(SQLiteGlobal.getWALSyncMode());
|
||||
}
|
||||
maybeTruncateWalFile();
|
||||
} else {
|
||||
setJournalMode(mConfiguration.journalMode == null
|
||||
? SQLiteGlobal.getDefaultJournalMode() : mConfiguration.journalMode);
|
||||
setSyncMode(mConfiguration.syncMode == null
|
||||
? SQLiteGlobal.getDefaultSyncMode() : mConfiguration.syncMode);
|
||||
}
|
||||
private void setJournalFromConfiguration() {
|
||||
if (!mIsReadOnlyConnection) {
|
||||
setJournalMode(mConfiguration.resolveJournalMode());
|
||||
maybeTruncateWalFile();
|
||||
} else {
|
||||
// No need to truncate for read only databases.
|
||||
mConfiguration.shouldTruncateWalFile = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void setSyncModeFromConfiguration() {
|
||||
if (!mIsReadOnlyConnection) {
|
||||
setSyncMode(mConfiguration.resolveSyncMode());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,6 +355,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
|
||||
* PRAGMA wal_checkpoint.
|
||||
*/
|
||||
private void maybeTruncateWalFile() {
|
||||
if (!mConfiguration.shouldTruncateWalFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
final long threshold = SQLiteGlobal.getWALTruncateSize();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Truncate threshold=" + threshold);
|
||||
@@ -390,12 +383,17 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
|
||||
+ threshold + "; truncating");
|
||||
try {
|
||||
executeForString("PRAGMA wal_checkpoint(TRUNCATE)", null, null);
|
||||
mConfiguration.shouldTruncateWalFile = false;
|
||||
} catch (SQLiteException e) {
|
||||
Log.w(TAG, "Failed to truncate the -wal file", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSyncMode(String newValue) {
|
||||
private void setSyncMode(@SQLiteDatabase.SyncMode String newValue) {
|
||||
if (TextUtils.isEmpty(newValue)) {
|
||||
// No change to the sync mode is intended
|
||||
return;
|
||||
}
|
||||
String value = executeForString("PRAGMA synchronous", null, null);
|
||||
if (!canonicalizeSyncMode(value).equalsIgnoreCase(
|
||||
canonicalizeSyncMode(newValue))) {
|
||||
@@ -403,16 +401,21 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
|
||||
}
|
||||
}
|
||||
|
||||
private static String canonicalizeSyncMode(String value) {
|
||||
private static @SQLiteDatabase.SyncMode String canonicalizeSyncMode(String value) {
|
||||
switch (value) {
|
||||
case "0": return "OFF";
|
||||
case "1": return "NORMAL";
|
||||
case "2": return "FULL";
|
||||
case "0": return SQLiteDatabase.SYNC_MODE_OFF;
|
||||
case "1": return SQLiteDatabase.SYNC_MODE_NORMAL;
|
||||
case "2": return SQLiteDatabase.SYNC_MODE_FULL;
|
||||
case "3": return SQLiteDatabase.SYNC_MODE_EXTRA;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private void setJournalMode(String newValue) {
|
||||
private void setJournalMode(@SQLiteDatabase.JournalMode String newValue) {
|
||||
if (TextUtils.isEmpty(newValue)) {
|
||||
// No change to the journal mode is intended
|
||||
return;
|
||||
}
|
||||
String value = executeForString("PRAGMA journal_mode", null, null);
|
||||
if (!value.equalsIgnoreCase(newValue)) {
|
||||
try {
|
||||
@@ -565,9 +568,6 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
|
||||
// Remember what changed.
|
||||
boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
|
||||
!= mConfiguration.foreignKeyConstraintsEnabled;
|
||||
boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
|
||||
& (SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING
|
||||
| SQLiteDatabase.ENABLE_LEGACY_COMPATIBILITY_WAL)) != 0;
|
||||
boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
|
||||
boolean customScalarFunctionsChanged = !configuration.customScalarFunctions
|
||||
.equals(mConfiguration.customScalarFunctions);
|
||||
@@ -586,9 +586,19 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
|
||||
if (foreignKeyModeChanged) {
|
||||
setForeignKeyModeFromConfiguration();
|
||||
}
|
||||
if (walModeChanged) {
|
||||
setWalModeFromConfiguration();
|
||||
|
||||
boolean journalModeChanged = !configuration.resolveJournalMode().equalsIgnoreCase(
|
||||
mConfiguration.resolveJournalMode());
|
||||
if (journalModeChanged) {
|
||||
setJournalFromConfiguration();
|
||||
}
|
||||
|
||||
boolean syncModeChanged =
|
||||
!configuration.resolveSyncMode().equalsIgnoreCase(mConfiguration.resolveSyncMode());
|
||||
if (syncModeChanged) {
|
||||
setSyncModeFromConfiguration();
|
||||
}
|
||||
|
||||
if (localeChanged) {
|
||||
setLocaleFromConfiguration();
|
||||
}
|
||||
|
||||
@@ -106,7 +106,11 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
@GuardedBy("mLock")
|
||||
private IdleConnectionHandler mIdleConnectionHandler;
|
||||
|
||||
private final AtomicLong mTotalExecutionTimeCounter = new AtomicLong(0);
|
||||
// whole execution time for this connection in milliseconds.
|
||||
private final AtomicLong mTotalStatementsTime = new AtomicLong(0);
|
||||
|
||||
// total statements executed by this connection
|
||||
private final AtomicLong mTotalStatementsCount = new AtomicLong(0);
|
||||
|
||||
// Describes what should happen to an acquired connection when it is returned to the pool.
|
||||
enum AcquiredConnectionStatus {
|
||||
@@ -286,8 +290,11 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
synchronized (mLock) {
|
||||
throwIfClosedLocked();
|
||||
|
||||
boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
|
||||
& SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
|
||||
boolean isWalCurrentMode = mConfiguration.resolveJournalMode().equalsIgnoreCase(
|
||||
SQLiteDatabase.JOURNAL_MODE_WAL);
|
||||
boolean isWalNewMode = configuration.resolveJournalMode().equalsIgnoreCase(
|
||||
SQLiteDatabase.JOURNAL_MODE_WAL);
|
||||
boolean walModeChanged = isWalCurrentMode ^ isWalNewMode;
|
||||
if (walModeChanged) {
|
||||
// WAL mode can only be changed if there are no acquired connections
|
||||
// because we need to close all but the primary connection first.
|
||||
@@ -536,7 +543,8 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
}
|
||||
|
||||
void onStatementExecuted(long executionTimeMs) {
|
||||
mTotalExecutionTimeCounter.addAndGet(executionTimeMs);
|
||||
mTotalStatementsTime.addAndGet(executionTimeMs);
|
||||
mTotalStatementsCount.incrementAndGet();
|
||||
}
|
||||
|
||||
// Can't throw.
|
||||
@@ -1037,8 +1045,7 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
}
|
||||
|
||||
private void setMaxConnectionPoolSizeLocked() {
|
||||
if (!mConfiguration.isInMemoryDb()
|
||||
&& (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) {
|
||||
if (mConfiguration.resolveJournalMode().equalsIgnoreCase(SQLiteDatabase.JOURNAL_MODE_WAL)) {
|
||||
mMaxConnectionPoolSize = SQLiteGlobal.getWALConnectionPoolSize();
|
||||
} else {
|
||||
// We don't actually need to always restrict the connection pool size to 1
|
||||
@@ -1117,11 +1124,18 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
printer.println("Connection pool for " + mConfiguration.path + ":");
|
||||
printer.println(" Open: " + mIsOpen);
|
||||
printer.println(" Max connections: " + mMaxConnectionPoolSize);
|
||||
printer.println(" Total execution time: " + mTotalExecutionTimeCounter);
|
||||
printer.println(" Total execution time (ms): " + mTotalStatementsTime);
|
||||
printer.println(" Total statements executed: " + mTotalStatementsCount);
|
||||
if (mTotalStatementsCount.get() > 0) {
|
||||
// Avoid division by 0 by filtering out logs where there are no statements executed.
|
||||
printer.println(" Average time per statement (ms): "
|
||||
+ mTotalStatementsTime.get() / mTotalStatementsCount.get());
|
||||
}
|
||||
printer.println(" Configuration: openFlags=" + mConfiguration.openFlags
|
||||
+ ", isLegacyCompatibilityWalEnabled=" + isCompatibilityWalEnabled
|
||||
+ ", journalMode=" + TextUtils.emptyIfNull(mConfiguration.journalMode)
|
||||
+ ", syncMode=" + TextUtils.emptyIfNull(mConfiguration.syncMode));
|
||||
+ ", journalMode=" + TextUtils.emptyIfNull(mConfiguration.resolveJournalMode())
|
||||
+ ", syncMode=" + TextUtils.emptyIfNull(mConfiguration.resolveSyncMode()));
|
||||
printer.println(" IsReadOnlyDatabase=" + mConfiguration.isReadOnlyDatabase());
|
||||
|
||||
if (isCompatibilityWalEnabled) {
|
||||
printer.println(" Compatibility WAL enabled: wal_syncmode="
|
||||
@@ -1182,6 +1196,14 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
public long getTotalStatementsTime() {
|
||||
return mTotalStatementsTime.get();
|
||||
}
|
||||
|
||||
public long getTotalStatementsCount() {
|
||||
return mTotalStatementsCount.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SQLiteConnectionPool: " + mConfiguration.path;
|
||||
|
||||
@@ -20,6 +20,8 @@ import android.annotation.IntDef;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.StringDef;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityThread;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
@@ -42,11 +44,8 @@ import android.util.EventLog;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.Printer;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import dalvik.system.CloseGuard;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
@@ -290,15 +289,182 @@ public final class SQLiteDatabase extends SQLiteClosable {
|
||||
*/
|
||||
public static final int MAX_SQL_CACHE_SIZE = 100;
|
||||
|
||||
private SQLiteDatabase(final String path, final int openFlags,
|
||||
CursorFactory cursorFactory, DatabaseErrorHandler errorHandler,
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@StringDef(prefix = {"JOURNAL_MODE_"},
|
||||
value =
|
||||
{
|
||||
JOURNAL_MODE_WAL,
|
||||
JOURNAL_MODE_PERSIST,
|
||||
JOURNAL_MODE_TRUNCATE,
|
||||
JOURNAL_MODE_MEMORY,
|
||||
JOURNAL_MODE_DELETE,
|
||||
JOURNAL_MODE_OFF,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface JournalMode {}
|
||||
|
||||
/**
|
||||
* The {@code WAL} journaling mode uses a write-ahead log instead of a rollback journal to
|
||||
* implement transactions. The WAL journaling mode is persistent; after being set it stays
|
||||
* in effect across multiple database connections and after closing and reopening the database.
|
||||
*
|
||||
* Performance Considerations:
|
||||
* This mode is recommended when the goal is to improve write performance or parallel read/write
|
||||
* performance. However, it is important to note that WAL introduces checkpoints which commit
|
||||
* all transactions that have not been synced to the database thus to maximize read performance
|
||||
* and lower checkpointing cost a small journal size is recommended. However, other modes such
|
||||
* as {@code DELETE} will not perform checkpoints, so it is a trade off that needs to be
|
||||
* considered as part of the decision of which journal mode to use.
|
||||
*
|
||||
* <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
|
||||
* details.</p>
|
||||
*/
|
||||
public static final String JOURNAL_MODE_WAL = "WAL";
|
||||
|
||||
/**
|
||||
* The {@code PERSIST} journaling mode prevents the rollback journal from being deleted at the
|
||||
* end of each transaction. Instead, the header of the journal is overwritten with zeros.
|
||||
* This will prevent other database connections from rolling the journal back.
|
||||
*
|
||||
* This mode is useful as an optimization on platforms where deleting or truncating a file is
|
||||
* much more expensive than overwriting the first block of a file with zeros.
|
||||
*
|
||||
* <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
|
||||
* details.</p>
|
||||
*/
|
||||
public static final String JOURNAL_MODE_PERSIST = "PERSIST";
|
||||
|
||||
/**
|
||||
* The {@code TRUNCATE} journaling mode commits transactions by truncating the rollback journal
|
||||
* to zero-length instead of deleting it. On many systems, truncating a file is much faster than
|
||||
* deleting the file since the containing directory does not need to be changed.
|
||||
*
|
||||
* <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
|
||||
* details.</p>
|
||||
*/
|
||||
public static final String JOURNAL_MODE_TRUNCATE = "TRUNCATE";
|
||||
|
||||
/**
|
||||
* The {@code MEMORY} journaling mode stores the rollback journal in volatile RAM.
|
||||
* This saves disk I/O but at the expense of database safety and integrity. If the application
|
||||
* using SQLite crashes in the middle of a transaction when the MEMORY journaling mode is set,
|
||||
* then the database file will very likely go corrupt.
|
||||
*
|
||||
* <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
|
||||
* details.</p>
|
||||
*/
|
||||
public static final String JOURNAL_MODE_MEMORY = "MEMORY";
|
||||
|
||||
/**
|
||||
* The {@code DELETE} journaling mode is the normal behavior. In the DELETE mode, the rollback
|
||||
* journal is deleted at the conclusion of each transaction.
|
||||
*
|
||||
* <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
|
||||
* details.</p>
|
||||
*/
|
||||
public static final String JOURNAL_MODE_DELETE = "DELETE";
|
||||
|
||||
/**
|
||||
* The {@code OFF} journaling mode disables the rollback journal completely. No rollback journal
|
||||
* is ever created and hence there is never a rollback journal to delete. The OFF journaling
|
||||
* mode disables the atomic commit and rollback capabilities of SQLite. The ROLLBACK command
|
||||
* behaves in an undefined way thus applications must avoid using the ROLLBACK command.
|
||||
* If the application crashes in the middle of a transaction, then the database file will very
|
||||
* likely go corrupt.
|
||||
*
|
||||
* <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
|
||||
* details.</p>
|
||||
*/
|
||||
public static final String JOURNAL_MODE_OFF = "OFF";
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@StringDef(prefix = {"SYNC_MODE_"},
|
||||
value =
|
||||
{
|
||||
SYNC_MODE_EXTRA,
|
||||
SYNC_MODE_FULL,
|
||||
SYNC_MODE_NORMAL,
|
||||
SYNC_MODE_OFF,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface SyncMode {}
|
||||
|
||||
/**
|
||||
* The {@code EXTRA} sync mode is like {@code FULL} sync mode with the addition that the
|
||||
* directory containing a rollback journal is synced after that journal is unlinked to commit a
|
||||
* transaction in {@code DELETE} journal mode.
|
||||
*
|
||||
* {@code EXTRA} provides additional durability if the commit is followed closely by a
|
||||
* power loss.
|
||||
*
|
||||
* <p> See <a href=https://www.sqlite.org/pragma.html#pragma_synchronous>here</a> for more
|
||||
* details.</p>
|
||||
*/
|
||||
@SuppressLint("IntentName") public static final String SYNC_MODE_EXTRA = "EXTRA";
|
||||
|
||||
/**
|
||||
* In {@code FULL} sync mode the SQLite database engine will use the xSync method of the VFS
|
||||
* to ensure that all content is safely written to the disk surface prior to continuing.
|
||||
* This ensures that an operating system crash or power failure will not corrupt the database.
|
||||
* {@code FULL} is very safe, but it is also slower.
|
||||
*
|
||||
* {@code FULL} is the most commonly used synchronous setting when not in WAL mode.
|
||||
*
|
||||
* <p> See <a href=https://www.sqlite.org/pragma.html#pragma_synchronous>here</a> for more
|
||||
* details.</p>
|
||||
*/
|
||||
public static final String SYNC_MODE_FULL = "FULL";
|
||||
|
||||
/**
|
||||
* The {@code NORMAL} sync mode, the SQLite database engine will still sync at the most critical
|
||||
* moments, but less often than in {@code FULL} mode. There is a very small chance that a
|
||||
* power failure at the wrong time could corrupt the database in {@code DELETE} journal mode on
|
||||
* an older filesystem.
|
||||
*
|
||||
* {@code WAL} journal mode is safe from corruption with {@code NORMAL} sync mode, and probably
|
||||
* {@code DELETE} sync mode is safe too on modern filesystems. WAL mode is always consistent
|
||||
* with {@code NORMAL} sync mode, but WAL mode does lose durability. A transaction committed in
|
||||
* WAL mode with {@code NORMAL} might roll back following a power loss or system crash.
|
||||
* Transactions are durable across application crashes regardless of the synchronous setting
|
||||
* or journal mode.
|
||||
*
|
||||
* The {@code NORMAL} sync mode is a good choice for most applications running in WAL mode.
|
||||
*
|
||||
* <p>Caveat: Even though this sync mode is safe Be careful when using {@code NORMAL} sync mode
|
||||
* when dealing with data dependencies between multiple databases, unless those databases use
|
||||
* the same durability or are somehow synced, there could be corruption.</p>
|
||||
*
|
||||
* <p> See <a href=https://www.sqlite.org/pragma.html#pragma_synchronous>here</a> for more
|
||||
* details.</p>
|
||||
*/
|
||||
public static final String SYNC_MODE_NORMAL = "NORMAL";
|
||||
|
||||
/**
|
||||
* In {@code OFF} sync mode SQLite continues without syncing as soon as it has handed data off
|
||||
* to the operating system. If the application running SQLite crashes, the data will be safe,
|
||||
* but the database might become corrupted if the operating system crashes or the computer loses
|
||||
* power before that data has been written to the disk surface. On the other hand, commits can
|
||||
* be orders of magnitude faster with synchronous {@code OFF}.
|
||||
*
|
||||
* <p> See <a href=https://www.sqlite.org/pragma.html#pragma_synchronous>here</a> for more
|
||||
* details.</p>
|
||||
*/
|
||||
public static final String SYNC_MODE_OFF = "OFF";
|
||||
|
||||
private SQLiteDatabase(@Nullable final String path, @Nullable final int openFlags,
|
||||
@Nullable CursorFactory cursorFactory, @Nullable DatabaseErrorHandler errorHandler,
|
||||
int lookasideSlotSize, int lookasideSlotCount, long idleConnectionTimeoutMs,
|
||||
String journalMode, String syncMode) {
|
||||
@Nullable String journalMode, @Nullable String syncMode) {
|
||||
mCursorFactory = cursorFactory;
|
||||
mErrorHandler = errorHandler != null ? errorHandler : new DefaultDatabaseErrorHandler();
|
||||
mConfigurationLocked = new SQLiteDatabaseConfiguration(path, openFlags);
|
||||
mConfigurationLocked.lookasideSlotSize = lookasideSlotSize;
|
||||
mConfigurationLocked.lookasideSlotCount = lookasideSlotCount;
|
||||
|
||||
// Disable lookaside allocator on low-RAM devices
|
||||
if (ActivityManager.isLowRamDeviceStatic()) {
|
||||
mConfigurationLocked.lookasideSlotCount = 0;
|
||||
@@ -316,11 +482,11 @@ public final class SQLiteDatabase extends SQLiteClosable {
|
||||
}
|
||||
}
|
||||
mConfigurationLocked.idleConnectionTimeoutMs = effectiveTimeoutMs;
|
||||
mConfigurationLocked.journalMode = journalMode;
|
||||
mConfigurationLocked.syncMode = syncMode;
|
||||
if (SQLiteCompatibilityWalFlags.isLegacyCompatibilityWalEnabled()) {
|
||||
mConfigurationLocked.openFlags |= ENABLE_LEGACY_COMPATIBILITY_WAL;
|
||||
}
|
||||
mConfigurationLocked.journalMode = journalMode;
|
||||
mConfigurationLocked.syncMode = syncMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2191,7 +2357,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
|
||||
synchronized (mLock) {
|
||||
throwIfNotOpenLocked();
|
||||
|
||||
if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0) {
|
||||
if (mConfigurationLocked.resolveJournalMode().equalsIgnoreCase(
|
||||
SQLiteDatabase.JOURNAL_MODE_WAL)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2241,11 +2408,9 @@ public final class SQLiteDatabase extends SQLiteClosable {
|
||||
throwIfNotOpenLocked();
|
||||
|
||||
final int oldFlags = mConfigurationLocked.openFlags;
|
||||
final boolean walEnabled = (oldFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0;
|
||||
final boolean compatibilityWalEnabled =
|
||||
(oldFlags & ENABLE_LEGACY_COMPATIBILITY_WAL) != 0;
|
||||
// WAL was never enabled for this database, so there's nothing left to do.
|
||||
if (!walEnabled && !compatibilityWalEnabled) {
|
||||
if (!mConfigurationLocked.resolveJournalMode().equalsIgnoreCase(
|
||||
SQLiteDatabase.JOURNAL_MODE_WAL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2275,7 +2440,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
|
||||
synchronized (mLock) {
|
||||
throwIfNotOpenLocked();
|
||||
|
||||
return (mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0;
|
||||
return mConfigurationLocked.resolveJournalMode().equalsIgnoreCase(
|
||||
SQLiteDatabase.JOURNAL_MODE_WAL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2309,6 +2475,20 @@ public final class SQLiteDatabase extends SQLiteClosable {
|
||||
return databases;
|
||||
}
|
||||
|
||||
private static ArrayList<SQLiteConnectionPool> getActiveDatabasePools() {
|
||||
ArrayList<SQLiteConnectionPool> connectionPools = new ArrayList<SQLiteConnectionPool>();
|
||||
synchronized (sActiveDatabases) {
|
||||
for (SQLiteDatabase db : sActiveDatabases.keySet()) {
|
||||
synchronized (db.mLock) {
|
||||
if (db.mConnectionPoolLocked != null) {
|
||||
connectionPools.add(db.mConnectionPoolLocked);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return connectionPools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump detailed information about all open databases in the current process.
|
||||
* Used by bug report.
|
||||
@@ -2317,8 +2497,45 @@ public final class SQLiteDatabase extends SQLiteClosable {
|
||||
// Use this ArraySet to collect file paths.
|
||||
final ArraySet<String> directories = new ArraySet<>();
|
||||
|
||||
for (SQLiteDatabase db : getActiveDatabases()) {
|
||||
db.dump(printer, verbose, isSystem, directories);
|
||||
// Accounting across all databases
|
||||
long totalStatementsTimeInMs = 0;
|
||||
long totalStatementsCount = 0;
|
||||
|
||||
ArrayList<SQLiteConnectionPool> activeConnectionPools = getActiveDatabasePools();
|
||||
|
||||
activeConnectionPools.sort(
|
||||
(a, b) -> Long.compare(b.getTotalStatementsCount(), a.getTotalStatementsCount()));
|
||||
for (SQLiteConnectionPool dbPool : activeConnectionPools) {
|
||||
dbPool.dump(printer, verbose, directories);
|
||||
totalStatementsTimeInMs += dbPool.getTotalStatementsTime();
|
||||
totalStatementsCount += dbPool.getTotalStatementsCount();
|
||||
}
|
||||
|
||||
if (totalStatementsCount > 0) {
|
||||
// Only print when there is information available
|
||||
|
||||
// Sorted statements per database
|
||||
printer.println("Statements Executed per Database");
|
||||
for (SQLiteConnectionPool dbPool : activeConnectionPools) {
|
||||
printer.println(
|
||||
" " + dbPool.getPath() + " : " + dbPool.getTotalStatementsCount());
|
||||
}
|
||||
printer.println("");
|
||||
printer.println(
|
||||
"Total Statements Executed for all Active Databases: " + totalStatementsCount);
|
||||
|
||||
// Sorted execution time per database
|
||||
activeConnectionPools.sort(
|
||||
(a, b) -> Long.compare(b.getTotalStatementsTime(), a.getTotalStatementsTime()));
|
||||
printer.println("");
|
||||
printer.println("");
|
||||
printer.println("Statement Time per Database (ms)");
|
||||
for (SQLiteConnectionPool dbPool : activeConnectionPools) {
|
||||
printer.println(
|
||||
" " + dbPool.getPath() + " : " + dbPool.getTotalStatementsTime());
|
||||
}
|
||||
printer.println("Total Statements Time for all Active Databases (ms): "
|
||||
+ totalStatementsTimeInMs);
|
||||
}
|
||||
|
||||
// Dump DB files in the directories.
|
||||
@@ -2331,15 +2548,6 @@ public final class SQLiteDatabase extends SQLiteClosable {
|
||||
}
|
||||
}
|
||||
|
||||
private void dump(Printer printer, boolean verbose, boolean isSystem, ArraySet directories) {
|
||||
synchronized (mLock) {
|
||||
if (mConnectionPoolLocked != null) {
|
||||
printer.println("");
|
||||
mConnectionPoolLocked.dump(printer, verbose, directories);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpDatabaseDirectory(Printer pw, File dir, boolean isSystem) {
|
||||
pw.println("");
|
||||
pw.println("Database files in " + dir.getAbsolutePath() + ":");
|
||||
@@ -2598,9 +2806,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
|
||||
|
||||
/**
|
||||
* Returns <a href="https://sqlite.org/pragma.html#pragma_journal_mode">journal mode</a>.
|
||||
* This journal mode will only be used if {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING}
|
||||
* flag is not set, otherwise a platform will use "WAL" journal mode.
|
||||
* @see Builder#setJournalMode(String)
|
||||
* set via {@link Builder#setJournalMode(String)}.
|
||||
*/
|
||||
@Nullable
|
||||
public String getJournalMode() {
|
||||
@@ -2799,25 +3005,28 @@ public final class SQLiteDatabase extends SQLiteClosable {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets <a href="https://sqlite.org/pragma.html#pragma_journal_mode">journal mode</a>
|
||||
* to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag is not set.
|
||||
* to use.
|
||||
*
|
||||
* <p>Note: If journal mode is not set, the platform will use a manufactured-specified
|
||||
* default which can vary across devices.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setJournalMode(@NonNull String journalMode) {
|
||||
public Builder setJournalMode(@JournalMode @NonNull String journalMode) {
|
||||
Objects.requireNonNull(journalMode);
|
||||
mJournalMode = journalMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**w
|
||||
/**
|
||||
* Sets <a href="https://sqlite.org/pragma.html#pragma_synchronous">synchronous mode</a>
|
||||
* .
|
||||
* @return
|
||||
*
|
||||
* <p>Note: If sync mode is not set, the platform will use a manufactured-specified
|
||||
* default which can vary across devices.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setSynchronousMode(@NonNull String syncMode) {
|
||||
public Builder setSynchronousMode(@SyncMode @NonNull String syncMode) {
|
||||
Objects.requireNonNull(syncMode);
|
||||
mSyncMode = syncMode;
|
||||
return this;
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
package android.database.sqlite;
|
||||
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import java.util.function.BinaryOperator;
|
||||
@@ -132,14 +132,16 @@ public final class SQLiteDatabaseConfiguration {
|
||||
* Journal mode to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} is not set.
|
||||
* <p>Default is returned by {@link SQLiteGlobal#getDefaultJournalMode()}
|
||||
*/
|
||||
public String journalMode;
|
||||
public @SQLiteDatabase.JournalMode String journalMode;
|
||||
|
||||
/**
|
||||
* Synchronous mode to use.
|
||||
* <p>Default is returned by {@link SQLiteGlobal#getDefaultSyncMode()}
|
||||
* or {@link SQLiteGlobal#getWALSyncMode()} depending on journal mode
|
||||
*/
|
||||
public String syncMode;
|
||||
public @SQLiteDatabase.SyncMode String syncMode;
|
||||
|
||||
public boolean shouldTruncateWalFile;
|
||||
|
||||
/**
|
||||
* Creates a database configuration with the required parameters for opening a
|
||||
@@ -217,6 +219,10 @@ public final class SQLiteDatabaseConfiguration {
|
||||
return path.equalsIgnoreCase(MEMORY_DB_PATH);
|
||||
}
|
||||
|
||||
public boolean isReadOnlyDatabase() {
|
||||
return (openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
|
||||
}
|
||||
|
||||
boolean isLegacyCompatibilityWalEnabled() {
|
||||
return journalMode == null && syncMode == null
|
||||
&& (openFlags & SQLiteDatabase.ENABLE_LEGACY_COMPATIBILITY_WAL) != 0;
|
||||
@@ -232,4 +238,81 @@ public final class SQLiteDatabaseConfiguration {
|
||||
boolean isLookasideConfigSet() {
|
||||
return lookasideSlotCount >= 0 && lookasideSlotSize >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the journal mode that should be used when opening a connection to the database.
|
||||
*
|
||||
* Note: assumes openFlags have already been set.
|
||||
*
|
||||
* @return Resolved journal mode that should be used for this database connection or an empty
|
||||
* string if no journal mode should be set.
|
||||
*/
|
||||
public @SQLiteDatabase.JournalMode String resolveJournalMode() {
|
||||
if (isReadOnlyDatabase()) {
|
||||
// No need to specify a journal mode when only reading.
|
||||
return "";
|
||||
}
|
||||
|
||||
if (isInMemoryDb()) {
|
||||
if (journalMode != null
|
||||
&& journalMode.equalsIgnoreCase(SQLiteDatabase.JOURNAL_MODE_OFF)) {
|
||||
return SQLiteDatabase.JOURNAL_MODE_OFF;
|
||||
}
|
||||
return SQLiteDatabase.JOURNAL_MODE_MEMORY;
|
||||
}
|
||||
|
||||
shouldTruncateWalFile = false;
|
||||
|
||||
if (isWalEnabledInternal()) {
|
||||
shouldTruncateWalFile = true;
|
||||
return SQLiteDatabase.JOURNAL_MODE_WAL;
|
||||
} else {
|
||||
// WAL is not explicitly set so use requested journal mode or platform default
|
||||
return this.journalMode != null ? this.journalMode
|
||||
: SQLiteGlobal.getDefaultJournalMode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the sync mode that should be used when opening a connection to the database.
|
||||
*
|
||||
* Note: assumes openFlags have already been set.
|
||||
* @return Resolved journal mode that should be used for this database connection or null
|
||||
* if no journal mode should be set.
|
||||
*/
|
||||
public @SQLiteDatabase.SyncMode String resolveSyncMode() {
|
||||
if (isReadOnlyDatabase()) {
|
||||
// No sync mode will be used since database will be only used for reading.
|
||||
return "";
|
||||
}
|
||||
|
||||
if (isInMemoryDb()) {
|
||||
// No sync mode will be used since database will be in volatile memory
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(syncMode)) {
|
||||
return syncMode;
|
||||
}
|
||||
|
||||
if (isWalEnabledInternal()) {
|
||||
if (isLegacyCompatibilityWalEnabled()) {
|
||||
return SQLiteCompatibilityWalFlags.getWALSyncMode();
|
||||
} else {
|
||||
return SQLiteGlobal.getDefaultSyncMode();
|
||||
}
|
||||
} else {
|
||||
return SQLiteGlobal.getDefaultSyncMode();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isWalEnabledInternal() {
|
||||
final boolean walEnabled = (openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
|
||||
// Use compatibility WAL unless an app explicitly set journal/synchronous mode
|
||||
// or DISABLE_COMPATIBILITY_WAL flag is set
|
||||
final boolean isCompatibilityWalEnabled = isLegacyCompatibilityWalEnabled();
|
||||
return walEnabled || isCompatibilityWalEnabled
|
||||
|| (journalMode != null
|
||||
&& journalMode.equalsIgnoreCase(SQLiteDatabase.JOURNAL_MODE_WAL));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@ public final class SQLiteDebug {
|
||||
/**
|
||||
* True to enable database performance testing instrumentation.
|
||||
*/
|
||||
public static final boolean DEBUG_LOG_SLOW_QUERIES = Build.IS_DEBUGGABLE;
|
||||
public static final boolean DEBUG_LOG_SLOW_QUERIES =
|
||||
Log.isLoggable("SQLiteSlowQueries", Log.VERBOSE);
|
||||
|
||||
private static final String SLOW_QUERY_THRESHOLD_PROP = "db.log.slow_query_threshold";
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ public final class SQLiteGlobal {
|
||||
/**
|
||||
* Gets the default journal mode when WAL is not in use.
|
||||
*/
|
||||
public static String getDefaultJournalMode() {
|
||||
public static @SQLiteDatabase.JournalMode String getDefaultJournalMode() {
|
||||
return SystemProperties.get("debug.sqlite.journalmode",
|
||||
Resources.getSystem().getString(
|
||||
com.android.internal.R.string.db_default_journal_mode));
|
||||
@@ -102,7 +102,7 @@ public final class SQLiteGlobal {
|
||||
/**
|
||||
* Gets the default database synchronization mode when WAL is not in use.
|
||||
*/
|
||||
public static String getDefaultSyncMode() {
|
||||
public static @SQLiteDatabase.SyncMode String getDefaultSyncMode() {
|
||||
// Use the FULL synchronous mode for system processes by default.
|
||||
String defaultMode = sDefaultSyncMode;
|
||||
if (defaultMode != null) {
|
||||
@@ -116,7 +116,7 @@ public final class SQLiteGlobal {
|
||||
/**
|
||||
* Gets the database synchronization mode when in WAL mode.
|
||||
*/
|
||||
public static String getWALSyncMode() {
|
||||
public static @SQLiteDatabase.SyncMode String getWALSyncMode() {
|
||||
// Use the FULL synchronous mode for system processes by default.
|
||||
String defaultMode = sDefaultSyncMode;
|
||||
if (defaultMode != null) {
|
||||
|
||||
Reference in New Issue
Block a user