summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
authorEdgar Arriaga <edgararriaga@google.com>2021-10-26 16:58:48 -0700
committerEdgar Arriaga <edgararriaga@google.com>2021-11-29 12:50:35 -0800
commit5eee0b59b7a9f22a59680e97d3440bef9965bbc9 (patch)
treefe7e8edefc49fbef3f10572400a6cec192597b5a /core/java/android
parent5e28d4b59dc6287107c11c2daedad42c98239fa8 (diff)
Add support for normal sync mode and propagate journalMode and syncMode on database open
This fixes dumpsys dbinfo not reporting the journalMode and syncMode as well and made flags and configuration converge to the same value which is less bug prone. Test: dumpsys dbinfo Bug: 193925357 Change-Id: I9a30b64b06a94fa9bd5589eba391a561ddcfaaf0
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/database/sqlite/SQLiteConnection.java88
-rw-r--r--core/java/android/database/sqlite/SQLiteConnectionPool.java18
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java213
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java89
-rw-r--r--core/java/android/database/sqlite/SQLiteGlobal.java6
5 files changed, 338 insertions, 76 deletions
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 7e61b48b2547..328858b260ac 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -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();
}
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 003d5da86076..d3ad6bb27b3c 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -290,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.
@@ -1042,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
@@ -1131,11 +1133,9 @@ public final class SQLiteConnectionPool implements Closeable {
}
printer.println(" Configuration: openFlags=" + mConfiguration.openFlags
+ ", isLegacyCompatibilityWalEnabled=" + isCompatibilityWalEnabled
- + ", journalMode=" + TextUtils.emptyIfNull(mConfiguration.journalMode)
- + ", syncMode=" + TextUtils.emptyIfNull(mConfiguration.syncMode));
- boolean isReadOnlyDatabase =
- (mConfiguration.openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
- printer.println(" IsReadOnlyDatabase=" + isReadOnlyDatabase);
+ + ", journalMode=" + TextUtils.emptyIfNull(mConfiguration.resolveJournalMode())
+ + ", syncMode=" + TextUtils.emptyIfNull(mConfiguration.resolveSyncMode()));
+ printer.println(" IsReadOnlyDatabase=" + mConfiguration.isReadOnlyDatabase());
if (isCompatibilityWalEnabled) {
printer.println(" Compatibility WAL enabled: wal_syncmode="
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index b6597ca798bd..0d0615a28af3 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -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;
@@ -287,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;
@@ -313,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
@@ -2188,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;
}
@@ -2238,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;
}
@@ -2272,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);
}
}
@@ -2306,7 +2475,6 @@ public final class SQLiteDatabase extends SQLiteClosable {
return databases;
}
- @UnsupportedAppUsage
private static ArrayList<SQLiteConnectionPool> getActiveDatabasePools() {
ArrayList<SQLiteConnectionPool> connectionPools = new ArrayList<SQLiteConnectionPool>();
synchronized (sActiveDatabases) {
@@ -2638,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() {
@@ -2839,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;
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
index 21c21c902fed..830584314039 100644
--- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -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));
+ }
}
diff --git a/core/java/android/database/sqlite/SQLiteGlobal.java b/core/java/android/database/sqlite/SQLiteGlobal.java
index 5e2875d02d90..28964fdd5bf6 100644
--- a/core/java/android/database/sqlite/SQLiteGlobal.java
+++ b/core/java/android/database/sqlite/SQLiteGlobal.java
@@ -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) {