diff --git a/core/api/current.txt b/core/api/current.txt index 921214349255b..396d8a6677c67 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -14261,11 +14261,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 { diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index 7e61b48b25470..328858b260acb 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 003d5da860762..d3ad6bb27b3cf 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 b6597ca798bdb..0d0615a28af3d 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. + * + *

See here for more + * details.

+ */ + 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. + * + *

See here for more + * details.

+ */ + 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. + * + *

See here for more + * details.

+ */ + 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. + * + *

See here for more + * details.

+ */ + 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. + * + *

See here for more + * details.

+ */ + 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. + * + *

See here for more + * details.

+ */ + 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. + * + *

See here for more + * details.

+ */ + @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. + * + *

See here for more + * details.

+ */ + 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. + * + *

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.

+ * + *

See here for more + * details.

+ */ + 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}. + * + *

See here for more + * details.

+ */ + 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 getActiveDatabasePools() { ArrayList connectionPools = new ArrayList(); synchronized (sActiveDatabases) { @@ -2638,9 +2806,7 @@ public final class SQLiteDatabase extends SQLiteClosable { /** * Returns journal mode. - * 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 journal mode - * to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag is not set. + * to use. + * + *

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 synchronous mode - * . - * @return + * + *

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 21c21c902fed4..8305843140399 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. *

Default is returned by {@link SQLiteGlobal#getDefaultJournalMode()} */ - public String journalMode; + public @SQLiteDatabase.JournalMode String journalMode; /** * Synchronous mode to use. *

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 5e2875d02d90e..28964fdd5bf61 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) {